diff --git a/package-lock.json b/package-lock.json index d57a4320..e459259c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12801,6 +12801,11 @@ "@opentelemetry/sdk-node": "^0.53.0", "@opentelemetry/semantic-conventions": "^1.26.0" }, + "devDependencies": { + "@types/express": "^4.17.21", + "express": "^4.21.0", + "node-fetch": "^2.6.7" + }, "engines": { "node": "18.x || 20.x || 22.x", "npm": "8.x || 9.x || 10.x" diff --git a/packages/opentelemetry/lib/config/instrumentations.js b/packages/opentelemetry/lib/config/instrumentations.js index e1ec0bb6..9b6ef4fb 100644 --- a/packages/opentelemetry/lib/config/instrumentations.js +++ b/packages/opentelemetry/lib/config/instrumentations.js @@ -24,6 +24,21 @@ exports.createInstrumentationConfig = function createInstrumentationConfig() { return [ getNodeAutoInstrumentations({ '@opentelemetry/instrumentation-http': { + startOutgoingSpanHook(request) { + return { + ['net.host.name']: `set-by-instrumentation-http-start-outgoing-span-hook: ${request.path}`, + ['peer.service']: 'test-peer-service', + ['peer.name']: 'test-peer-name' + }; + }, + startIncomingSpanHook() { + return { + ['net.host.name']: + 'set-by-instrumentation-http-start-incoming-span-hook', + ['peer.service']: 'test-peer-service', + ['peer.name']: 'test-peer-name' + }; + }, ignoreIncomingRequestHook }, '@opentelemetry/instrumentation-fs': { @@ -31,9 +46,19 @@ exports.createInstrumentationConfig = function createInstrumentationConfig() { }, '@opentelemetry/instrumentation-pino': { enabled: false + }, + '@opentelemetry/instrumentation-undici': { + startSpanHook(request) { + return { + ['server.address']: `set-by-instrumentation-undici-start-span-hook: ${request.path}`, + ['peer.service']: 'test-peer-service', + ['peer.name']: 'test-peer-name' + }; + } } - }), - new RuntimeNodeInstrumentation() + }) + // Commented out to avoid a lot of spam when testing + // new RuntimeNodeInstrumentation() ]; }; diff --git a/packages/opentelemetry/lib/config/metrics.js b/packages/opentelemetry/lib/config/metrics.js index 2fc59793..da1be6e4 100644 --- a/packages/opentelemetry/lib/config/metrics.js +++ b/packages/opentelemetry/lib/config/metrics.js @@ -32,11 +32,27 @@ exports.createMetricsConfig = function createMetricsConfig(options) { headers['X-OTel-Key'] = options.apiGatewayKey; } config.metricReader = new PeriodicExportingMetricReader({ - exporter: new OTLPMetricExporter({ - url: options.endpoint, - compression: CompressionAlgorithm.GZIP, - headers - }) + exportIntervalMillis: 5000, + exporter: { + // Test exporter to log metric names and attributes + export(metrics, done) { + metrics.scopeMetrics + .flatMap(({ metrics }) => metrics) + .map((metr) => ({ + name: metr.descriptor.name, + attributes: metr.dataPoints.map((dp) => dp.attributes) + })) + .forEach((metric) => { + logger.info({ + message: 'Exporting OpenTelementry metric', + metric + }); + }); + done({ code: 0 }); + }, + async forceFlush() {}, + async shutdown() {} + } }); logger.info({ diff --git a/packages/opentelemetry/lib/index.js b/packages/opentelemetry/lib/index.js index 5718df9e..f95e6135 100644 --- a/packages/opentelemetry/lib/index.js +++ b/packages/opentelemetry/lib/index.js @@ -83,14 +83,15 @@ function setupOpenTelemetry({ }; instances.sdk.start(); - // Set up host metrics if we have a metrics endpoint - if (metricsOptions?.endpoint) { - const meterProvider = /** @type {MeterProvider} */ ( - opentelemetry.api.metrics.getMeterProvider() - ); - instances.hostMetrics = new HostMetrics({ meterProvider }); - instances.hostMetrics.start(); - } + // Commented out to avoid a lot of spam when testing + // // Set up host metrics if we have a metrics endpoint + // if (metricsOptions?.endpoint) { + // const meterProvider = /** @type {MeterProvider} */ ( + // opentelemetry.api.metrics.getMeterProvider() + // ); + // instances.hostMetrics = new HostMetrics({ meterProvider }); + // instances.hostMetrics.start(); + // } return instances; } diff --git a/packages/opentelemetry/package.json b/packages/opentelemetry/package.json index 38d6a36d..340aea41 100644 --- a/packages/opentelemetry/package.json +++ b/packages/opentelemetry/package.json @@ -28,5 +28,10 @@ "@opentelemetry/instrumentation-runtime-node": "^0.7.0", "@opentelemetry/sdk-node": "^0.53.0", "@opentelemetry/semantic-conventions": "^1.26.0" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "express": "^4.21.0", + "node-fetch": "^2.6.7" } } diff --git a/packages/opentelemetry/test/end-to-end/fixtures/app.js b/packages/opentelemetry/test/end-to-end/fixtures/app.js new file mode 100644 index 00000000..705879f7 --- /dev/null +++ b/packages/opentelemetry/test/end-to-end/fixtures/app.js @@ -0,0 +1,31 @@ +const express = require('express'); +const nodeFetch = require('node-fetch'); + +/** + * @returns {express.Application} + */ +exports.createTestApp = function createTestApp() { + const app = express(); + + app.get('/', async (request, response) => { + // Native fetch + const bulbasaur = await fetch( + 'https://pokeapi.co/api/v2/pokemon/bulbasaur' + ).then((response) => response.json()); + + // Node fetch + const squirtle = await nodeFetch( + 'https://pokeapi.co/api/v2/pokemon/squirtle' + ).then((response) => response.json()); + + response.send( + [ + `Bulbasaur has the types: ${bulbasaur.types.map(({ type }) => type.name).join(', ')}`, + `Squirtle has the types: ${squirtle.types.map(({ type }) => type.name).join(', ')}`, + '' + ].join('\n') + ); + }); + + return app; +}; diff --git a/packages/opentelemetry/test/end-to-end/scripts/run-test-app.js b/packages/opentelemetry/test/end-to-end/scripts/run-test-app.js new file mode 100644 index 00000000..d8b0814e --- /dev/null +++ b/packages/opentelemetry/test/end-to-end/scripts/run-test-app.js @@ -0,0 +1,12 @@ +const { createTestApp } = require('../fixtures/app'); +const logger = require('@dotcom-reliability-kit/logger'); + +const app = createTestApp(); + +const server = app.listen(4001, () => { + logger.info('Test app running on port 4001'); +}); + +process.on('beforeExit', () => { + server.close(); +}); diff --git a/packages/opentelemetry/test/end-to-end/scripts/run.js b/packages/opentelemetry/test/end-to-end/scripts/run.js new file mode 100644 index 00000000..9aa2b893 --- /dev/null +++ b/packages/opentelemetry/test/end-to-end/scripts/run.js @@ -0,0 +1,22 @@ +const { fork } = require('node:child_process'); +const { join: joinPath } = require('node:path'); + +const cwd = __dirname; +const env = { + HEROKU_RELEASE_CREATED_AT: 'mock-release-date', + HEROKU_SLUG_COMMIT: 'mock-commit-hash', + LOG_LEVEL: 'debug', + OPENTELEMETRY_METRICS_ENDPOINT: 'http://localhost:4318/v1/metrics', + REGION: 'mock-region', + SYSTEM_CODE: 'reliability-kit/opentelemetry' +}; + +const app = fork(joinPath(cwd, 'run-test-app.js'), { + cwd, + env, + execArgv: ['--require', '@dotcom-reliability-kit/opentelemetry/setup'] +}); + +app.on('close', (code) => { + process.exit(code); +});