/* eslint-disable camelcase,max-len */
'use strict';
/**
* Node.js Express application request Open Telemetry tracing.
*
* @module tracer
* @requires @google-cloud/opentelemetry-cloud-trace-exporter
* @requires @google-cloud/opentelemetry-cloud-trace-propagator
* @requires @opentelemetry/api
* @requires @opentelemetry/instrumentation
* @requires @opentelemetry/instrumentation-express
* @requires @opentelemetry/instrumentation-http
* @requires @opentelemetry/resources
* @requires @opentelemetry/sdk-trace-node
* @requires @opentelemetry/sdk-trace-base
* @requires @opentelemetry/semantic-conventions
* @see https://cloud.google.com/trace/docs/setup/nodejs-ot
*/
const path = require('path');
const common = require(path.join(__dirname, 'common'));
const opentelemetry = require('@opentelemetry/api');
// Not functionally required but gives some insight what happens behind the scenes
const {diag, DiagConsoleLogger, DiagLogLevel} = opentelemetry;
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO);
const {AlwaysOnSampler, SamplingDecision} = require('@opentelemetry/sdk-trace-base');
const {BatchSpanProcessor} = require('@opentelemetry/sdk-trace-base');
const {CloudPropagator} = require('@google-cloud/opentelemetry-cloud-trace-propagator');
const {ExpressInstrumentation} = require('@opentelemetry/instrumentation-express');
const {HttpInstrumentation} = require('@opentelemetry/instrumentation-http');
const {NodeTracerProvider} = require('@opentelemetry/sdk-trace-node');
const {registerInstrumentations} = require('@opentelemetry/instrumentation');
const {Resource} = require('@opentelemetry/resources');
const {SemanticAttributes, SemanticResourceAttributes} = require('@opentelemetry/semantic-conventions');
// const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base');
const {TraceExporter} = require('@google-cloud/opentelemetry-cloud-trace-exporter');
/**
* Node Open Telemetry trace provider.
*
* @param {string} serviceName - Name of the service to create traces for.
* @return {object} - Tracer object.
*/
module.exports = (serviceName) => {
const provider = new NodeTracerProvider({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: serviceName,
[SemanticResourceAttributes.SERVICE_VERSION]: common.version,
}),
sampler: filterSampler(ignoreHealthCheck, new AlwaysOnSampler()),
});
if (process.env.NODE_ENV === 'test') {
registerInstrumentations({
tracerProvider: provider,
instrumentations: [HttpInstrumentation],
});
} else {
registerInstrumentations({
tracerProvider: provider,
instrumentations: [
// Express instrumentation expects HTTP layers to be instrumented
HttpInstrumentation,
ExpressInstrumentation,
],
});
}
// Initialize the exporter. When your application is running on Google Cloud,
// you do not need to provide auth credentials or a project id.
const exporter = new TraceExporter();
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
// Initialize the OpenTelemetry APIs to use the NodeTracerProvider bindings
provider.register({
propagator: new CloudPropagator(),
});
['SIGINT', 'SIGTERM'].forEach((signal) => {
process.on(signal, () => provider.shutdown().catch(console.error));
});
// provider.register();
return opentelemetry.trace.getTracer(common.name);
};
/**
* Filter data returned to opentelemetry.
*
* @param {function} filterFn - Function to use to filter sample.
* @param {object} parent - Parent sampler object.
* @return {any} - Trace data filtered by the supplied function.
*/
function filterSampler(filterFn, parent) {
// noinspection JSUnusedGlobalSymbols
return {
shouldSample(ctx, tid, spanName, spanKind, attr, links) {
if (!filterFn(spanName, spanKind, attr)) {
return {decision: SamplingDecision.NOT_RECORD};
}
return parent.shouldSample(ctx, tid, spanName, spanKind, attr, links);
},
toString() {
return `FilterSampler(${parent.toString()})`;
},
};
}
/**
* Function used by opentelemetry to not trace health check endpoints.
*
* @param {string} spanName - Opentelemetry span name.
* @param {string} spanKind - Opentelemetry span kind.
* @param {object} attributes - Object attributes used to identify health check endpoints.
* @return {boolean} - true when health check should be ignored, false otherwise.
*/
function ignoreHealthCheck(spanName, spanKind, attributes) {
return spanKind !== opentelemetry.SpanKind.SERVER.toString() || attributes[SemanticAttributes.HTTP_ROUTE] !== '/health';
}