keymetrics/pm2-io-apm

View on GitHub
src/features/tracing.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { Feature } from '../featureManager'
import * as Debug from 'debug'
import Configuration from '../configuration'
import { IOConfig } from '../pmx'
import { resolve } from 'path'
import * as httpModule from 'http'
import { IgnoreMatcher } from '../census/plugins/http'
import * as core from '@opencensus/core'

export interface TracingConfig {
  /**
   * Enabled the distributed tracing feature.
   */
  enabled: boolean
  /**
   * If you want to report a specific service name
   * the default is the same as in apmOptions
   */
  serviceName?: string
  /**
   * Generate trace for outgoing request that aren't connected to a incoming one
   * default is false
   */
  outbound?: boolean
  /**
   * Determines the probability of a request to be traced. Ranges from 0.0 to 1.0
   * default is 0.5
   */
  samplingRate?: number,
  /**
   * Add details about databases calls (redis, mongodb)
   */
  detailedDatabasesCalls?: boolean,
  /**
   * Ignore specific incoming request depending on their path
   */
  ignoreIncomingPaths?: Array<IgnoreMatcher<httpModule.IncomingMessage>>
  /**
   * Ignore specific outgoing request depending on their url
   */
  ignoreOutgoingUrls?: Array<IgnoreMatcher<httpModule.ClientRequest>>
  /**
   * Set to true when wanting to create span for raw TCP connection
   * instead of new http request
   */
  createSpanWithNet?: boolean
}

const httpMethodToIgnore = [
  'options',
  'head'
]
const defaultTracingConfig: TracingConfig = {
  enabled: false,
  outbound: false,
  samplingRate: 0,
  ignoreIncomingPaths: [],
  ignoreOutgoingUrls: [],
  detailedDatabasesCalls: false,
  createSpanWithNet: false
}

const enabledTracingConfig: TracingConfig = {
  enabled: true,
  outbound: false,
  samplingRate: 0.5,
  ignoreIncomingPaths: [
    (url, request) => {
      const method = (request.method || 'GET').toLowerCase()
      return httpMethodToIgnore.indexOf(method) > -1
    },
    /(.*).js/,
    /(.*).css/,
    /(.*).png/,
    /(.*).ico/,
    /(.*).svg/,
    /webpack/
  ],
  ignoreOutgoingUrls: [],
  detailedDatabasesCalls: false,
  createSpanWithNet: false
}

export class TracingFeature implements Feature {
  private exporter: any
  private options: TracingConfig
  private tracer: core.Tracing
  private logger: Function = Debug('axm:tracing')

  init (config: IOConfig): void {
    this.logger('init tracing')

    if (config.tracing === undefined) {
      config.tracing = defaultTracingConfig
    } else if (config.tracing === true) {
      config.tracing = enabledTracingConfig
    } else if (config.tracing === false) {
      config.tracing = defaultTracingConfig
    }
    if (config.tracing.enabled === false) {
      return this.logger('tracing disabled')
    }

    this.options = Object.assign(enabledTracingConfig, config.tracing)
    // tslint:disable-next-line
    if (typeof config.apmOptions === 'object' && typeof config.apmOptions.appName === 'string') {
      this.options.serviceName = config.apmOptions.appName
    } else if (typeof process.env.name === 'string') {
      this.options.serviceName = process.env.name
    }
    if (config.tracing.ignoreOutgoingUrls === undefined) {
      config.tracing.ignoreOutgoingUrls = enabledTracingConfig.ignoreOutgoingUrls
    }
    if (config.tracing.ignoreIncomingPaths === undefined) {
      config.tracing.ignoreIncomingPaths = enabledTracingConfig.ignoreIncomingPaths
    }

    const B3Format = require('@opencensus/propagation-b3').B3Format
    const CustomCensusExporter = require('../census/exporter').CustomCensusExporter
    const Tracing = require('../census/tracer').Tracing

    this.exporter = new CustomCensusExporter(this.options)
    if (this.tracer && this.tracer.active) {
      throw new Error(`Tracing was already enabled`)
    }
    this.logger('start census tracer')
    const tracer = Tracing.instance
    const plugins = {
      'http': {
        module: resolve(__dirname, '../census/plugins/http'),
        config: config.tracing
      },
      'http2': resolve(__dirname, '../census/plugins/http2'),
      'https': resolve(__dirname, '../census/plugins/https'),
      'mongodb-core': {
        module: resolve(__dirname, '../census/plugins/mongodb'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'mysql': {
        module: resolve(__dirname, '../census/plugins/mysql'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'mysql2': {
        module: resolve(__dirname, '../census/plugins/mysql2'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'redis': {
        module: resolve(__dirname, '../census/plugins/redis'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'ioredis': {
        module: resolve(__dirname, '../census/plugins/ioredis'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'pg': {
        module: resolve(__dirname, '../census/plugins/pg'),
        config: { detailedCommands: config.tracing.detailedDatabasesCalls }
      },
      'vue-server-renderer': {
        module: resolve(__dirname, '../census/plugins/vue'),
        config: {}
      }
    }
    if (this.options.createSpanWithNet === true) {
      plugins['net'] = {
        module: resolve(__dirname, '../census/plugins/net')
      }
    }
    this.tracer = tracer.start({
      exporter: this.exporter,
      plugins,
      propagation: new B3Format(),
      samplingRate: this.options.samplingRate || 0.5,
      logLevel: this.isDebugEnabled() ? 4 : 1
    })
    Configuration.configureModule({
      census_tracing: true
    })
  }

  private isDebugEnabled () {
    return typeof process.env.DEBUG === 'string' &&
      (process.env.DEBUG.indexOf('axm:*') >= 0 || process.env.DEBUG.indexOf('axm:tracing') >= 0)
  }

  getTracer (): core.Tracer | undefined {
    return this.tracer ? this.tracer.tracer : undefined
  }

  destroy () {
    if (!this.tracer) return
    this.logger('stop census tracer')
    Configuration.configureModule({
      census_tracing: false
    })
    this.tracer.stop()
  }
}