'use strict' ; import { BasePlugin , Span , SpanKind } from '@opencensus/core' ; import * as shimmer from 'shimmer' ; import * as semver from 'semver' ; import * as express from 'express' ; export const kMiddlewareStack = Symbol ( 'express-middleware-stack' ) ; /** express instrumentation plugin for Opencensus */ export class ExpressPlugin extends BasePlugin { private kPatched = Symbol ( 'express-layer-patched' ) ; /** Constructs a new instance. */ constructor ( moduleName ) { super ( moduleName ) ; } /**
     * Patches functions.
     */ protected applyPatch ( ) { this . logger . debug ( 'Patched express' ) ; if ( ! semver . satisfies ( this . version , '^4.0.0' ) ) { this . logger . debug ( 'express version %s not supported - aborting...' , this . version ) ; return this . moduleExports ; } if ( this . moduleExports ) { const routerProto = semver . satisfies ( this . version , '^5' ) ? ( this . moduleExports . Router && this . moduleExports . Router . prototype ) : this . moduleExports . Router ; const plugin = this ; this . logger . debug ( 'patching express.Router.prototype.route' ) ; shimmer . wrap ( routerProto , 'route' , ( original ) => { return function route_trace ( path ) { const route = original . apply ( this , arguments ) ; const layer = this . stack [ this . stack . length - 1 ] ; plugin . applyLayerPatch ( layer , path ) ; return route ; } ; } ) ; this . logger . debug ( 'patching express.Router.prototype.use' ) ; shimmer . wrap ( routerProto , 'use' , ( original ) => { return function use ( path ) { const route = original . apply ( this , arguments ) ; const layer = this . stack [ this . stack . length - 1 ] ; plugin . applyLayerPatch ( layer , path ) ; return route ; } ; } ) ; this . logger . debug ( 'patching express.Application.use' ) ; shimmer . wrap ( this . moduleExports . application , 'use' , ( original ) => { return function use ( path ) { const route = original . apply ( this , arguments ) ; const layer = this . _router . stack [ this . _router . stack . length - 1 ] ; plugin . applyLayerPatch ( layer , path ) ; return route ; } ; } ) ; } return this . moduleExports ; } /** Unpatches all patched functions. */ applyUnpatch ( ) { if ( ! semver . satisfies ( this . version , '^4.0.0' ) ) { return this . moduleExports ; } const routerProto = semver . satisfies ( this . version , '^5' ) ? ( this . moduleExports . Router && this . moduleExports . Router . prototype ) : this . moduleExports . Router ; shimmer . unwrap ( routerProto , 'use' ) ; shimmer . unwrap ( routerProto , 'route' ) ; shimmer . unwrap ( this . moduleExports . application , 'use' ) ; } private applyLayerPatch ( layer , layerPath ? ) { const plugin = this ; if ( layer [ this . kPatched ] === true ) return ; layer [ this . kPatched ] = true ; plugin . logger . debug ( 'patching express.Router.Layer.handle' ) ; shimmer . wrap ( layer , 'handle' , function ( original ) { if ( original . length !== 4 ) { return function ( req , res , next ) { plugin . safePush ( req , kMiddlewareStack , layerPath ) ; let spanName = `Middleware - ${ layer . name }` ; if ( layer . route ) { spanName = `Route - ${ layer . route . path }` ; } else if ( layer . name === 'router' ) { spanName = `Router - ${ layerPath }` ; } const span = plugin . tracer . startChildSpan ( spanName , SpanKind . CLIENT ) ; if ( span === null ) return original . apply ( this , arguments ) ; arguments [ 2 ] = function ( ) { if ( ! ( req . route && arguments [ 0 ] instanceof Error ) ) { req [ kMiddlewareStack ] . pop ( ) ; } return plugin . patchEnd ( span , next ) ( ) ; } ; return original . apply ( this , arguments ) ; } ; } return function ( _err , req , res , next ) { return original . apply ( this , arguments ) ; } ; } ) ; } private safePush ( obj , prop , value ) { if ( ! obj [ prop ] ) obj [ prop ] = [ ] ; obj [ prop ] . push ( value ) ; } param span private patchEnd ( span , callback ) { const plugin = this ; const patchedEnd = function ( err ? ) : void { if ( plugin . options . detailedCommands === true && err instanceof Error ) { span . addAttribute ( 'error' , err . message ) ; } if ( span . ended === false ) { span . end ( ) ; } return callback . apply ( this , arguments ) ; } ; return this . tracer . wrap ( patchedEnd ) ; } } const plugin = new ExpressPlugin ( 'express' ) ; export { plugin } ;