pm2
Version:
Production process manager for Node.JS applications with a built-in load balancer.
175 lines (149 loc) • 4.58 kB
JavaScript
'use strict'
const Debug = require('debug')
const Configuration = require('../configuration')
const httpMethodToIgnore = [
'options',
'head'
]
const defaultTracingConfig = {
enabled: true,
outbound: false,
samplingRate: 0,
ignoreIncomingPaths: [],
ignoreOutgoingUrls: [],
createSpanWithNet: false
}
const enabledTracingConfig = {
enabled: true,
outbound: true,
samplingRate: 0.5,
ignoreIncomingPaths: [
(_url, request) => {
const method = (request.method || 'GET').toLowerCase()
return httpMethodToIgnore.indexOf(method) > -1
},
/(.*).js/,
/(.*).css/,
/(.*).png/,
/(.*).ico/,
/(.*).svg/,
/webpack/
],
ignoreOutgoingUrls: [],
createSpanWithNet: false
}
class TracingFeature {
constructor () {
this.options = null
this.logger = Debug('axm:tracing')
this.otel = undefined
}
init (config) {
this.logger('init tracing', config)
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) {
this.logger('tracing disabled')
return
} else {
this.logger('tracing enabled')
}
this.options = enabledTracingConfig
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
}
let NodeSDK, getNodeAutoInstrumentations, CustomZipkinExporter
try {
NodeSDK = require('@opentelemetry/sdk-node').NodeSDK
getNodeAutoInstrumentations = require('@opentelemetry/auto-instrumentations-node').getNodeAutoInstrumentations
CustomZipkinExporter = require('../otel/custom-zipkin-exporter/zipkin').CustomZipkinExporter
} catch(e) {
console.error('[PM2][ERROR] OpenTelemetry packages not installed. Tracing disabled.')
console.error('[PM2][ERROR] To enable tracing, run: pm2 install-otel')
return
}
const traceExporter = new CustomZipkinExporter()
const serviceName =
process.env.OTEL_SERVICE_NAME ||
this.options.serviceName
this.otel = new NodeSDK({
traceExporter,
serviceName,
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-dns': {
enabled: false
},
'@opentelemetry/instrumentation-fs': {
enabled: false
},
'@opentelemetry/instrumentation-net': {
enabled: this.options.createSpanWithNet
},
'@opentelemetry/instrumentation-http': {
ignoreIncomingRequestHook: (request) => {
if (!this.options.ignoreIncomingPaths) {
return false
}
return this.options.ignoreIncomingPaths.some((matcher) => applyMatcher(matcher, request))
},
ignoreOutgoingRequestHook: (request) => {
if (!this.options.ignoreOutgoingUrls) {
return false
}
return this.options.ignoreOutgoingUrls.some((matcher) => applyMatcher(matcher, request))
}
}
})
]
})
this.otel.start()
Configuration.configureModule({
otel_tracing: true
})
}
getTracer () {
if (!this.options.serviceName) {
throw new Error('serviceName is required')
}
const { trace } = require('@opentelemetry/api')
return trace.getTracer(this.options.serviceName)
}
destroy () {
if (!this.otel) return
this.logger('stop otel tracer')
this.otel.shutdown()
Configuration.configureModule({
otel_tracing: false
})
}
}
function applyMatcher (matcher, request) {
if (!matcher) {
return false
}
if (!request.url) {
return false
}
if (typeof matcher === 'string') {
return request.url.includes(matcher)
}
if (matcher instanceof RegExp) {
return matcher.test(request.url)
}
return matcher(request.url, request)
}
module.exports = { TracingFeature }