|
| 1 | +const { fork } = require('node:child_process'); |
| 2 | + |
| 3 | +function waitForBaseUrl(childProcess) { |
| 4 | + return new Promise((resolve) => { |
| 5 | + const handler = (message) => { |
| 6 | + if (message?.ready) { |
| 7 | + resolve(`http://localhost:${message.port}`); |
| 8 | + childProcess.off('message', handler); |
| 9 | + } |
| 10 | + }; |
| 11 | + childProcess.on('message', handler); |
| 12 | + }); |
| 13 | +} |
| 14 | + |
| 15 | +function stdoutToLogs(stdout) { |
| 16 | + return stdout.split('\n').map((logLine) => { |
| 17 | + try { |
| 18 | + return JSON.parse(logLine); |
| 19 | + } catch (_) { |
| 20 | + return logLine; |
| 21 | + } |
| 22 | + }); |
| 23 | +} |
| 24 | + |
| 25 | +describe('@dotcom-reliability-kit/opentelemetry end-to-end', () => { |
| 26 | + let collector; |
| 27 | + let collectorStdout = ''; |
| 28 | + let collectorBaseUrl; |
| 29 | + let exporter; |
| 30 | + let exporterStdout = ''; |
| 31 | + let exporterBaseUrl; |
| 32 | + |
| 33 | + beforeAll(async () => { |
| 34 | + // Set up a mock collector |
| 35 | + collector = fork(`${__dirname}/fixtures/collector.js`, { |
| 36 | + env: { |
| 37 | + ...process.env, |
| 38 | + NODE_ENV: 'production', |
| 39 | + SYSTEM_CODE: 'mock-system' |
| 40 | + }, |
| 41 | + stdio: 'pipe' |
| 42 | + }); |
| 43 | + collector.stdout.on('data', (chunk) => { |
| 44 | + collectorStdout += chunk.toString(); |
| 45 | + }); |
| 46 | + collector.stderr.on('data', (chunk) => { |
| 47 | + collectorStdout += chunk.toString(); |
| 48 | + }); |
| 49 | + collectorBaseUrl = await waitForBaseUrl(collector); |
| 50 | + |
| 51 | + // Set up a Node.js app that sends Opentelemetry metrics and traces |
| 52 | + exporter = fork(`${__dirname}/fixtures/app.js`, { |
| 53 | + env: { |
| 54 | + ...process.env, |
| 55 | + NODE_ENV: 'production', |
| 56 | + OPENTELEMETRY_METRICS_ENDPOINT: `${collectorBaseUrl}/metrics`, |
| 57 | + OPENTELEMETRY_TRACING_ENDPOINT: `${collectorBaseUrl}/traces`, |
| 58 | + OPENTELEMETRY_TRACING_SAMPLE_PERCENTAGE: '100', |
| 59 | + SYSTEM_CODE: 'mock-system' |
| 60 | + }, |
| 61 | + execArgv: ['--require', '@dotcom-reliability-kit/opentelemetry/setup'], |
| 62 | + stdio: 'pipe' |
| 63 | + }); |
| 64 | + exporter.stdout.on('data', (chunk) => { |
| 65 | + exporterStdout += chunk.toString(); |
| 66 | + }); |
| 67 | + exporter.stderr.on('data', (chunk) => { |
| 68 | + exporterStdout += chunk.toString(); |
| 69 | + }); |
| 70 | + exporterBaseUrl = await waitForBaseUrl(exporter); |
| 71 | + }); |
| 72 | + |
| 73 | + afterAll(() => { |
| 74 | + if (collector) { |
| 75 | + collector.kill('SIGINT'); |
| 76 | + } |
| 77 | + if (exporter) { |
| 78 | + exporter.kill('SIGINT'); |
| 79 | + } |
| 80 | + }); |
| 81 | + |
| 82 | + describe('sending an HTTP request to the exporting app', () => { |
| 83 | + let collectorLogs; |
| 84 | + let exporterLogs; |
| 85 | + |
| 86 | + beforeAll(async () => { |
| 87 | + try { |
| 88 | + await fetch(`${exporterBaseUrl}/example`); |
| 89 | + collectorLogs = stdoutToLogs(collectorStdout); |
| 90 | + exporterLogs = stdoutToLogs(exporterStdout); |
| 91 | + } catch (cause) { |
| 92 | + // eslint-disable-next-line no-console |
| 93 | + console.log('COLLECTOR:', stdoutToLogs(collectorStdout)); |
| 94 | + // eslint-disable-next-line no-console |
| 95 | + console.log('EXPORTER:', stdoutToLogs(exporterStdout)); |
| 96 | + throw new Error('Fetch failed, see logs'); |
| 97 | + } |
| 98 | + }); |
| 99 | + |
| 100 | + describe('exporter', () => { |
| 101 | + it('logs that OpenTelemetry metrics are enabled', () => { |
| 102 | + const log = exporterLogs.find( |
| 103 | + (log) => log?.event === 'OTEL_METRICS_STATUS' && log?.enabled === true |
| 104 | + ); |
| 105 | + expect(log).toBeDefined(); |
| 106 | + }); |
| 107 | + it('logs that OpenTelemetry tracing is enabled', () => { |
| 108 | + const log = exporterLogs.find( |
| 109 | + (log) => log?.event === 'OTEL_TRACE_STATUS' && log?.enabled === true |
| 110 | + ); |
| 111 | + expect(log).toBeDefined(); |
| 112 | + }); |
| 113 | + }); |
| 114 | + |
| 115 | + describe('collector', () => { |
| 116 | + it('receives metrics', () => { |
| 117 | + const log = collectorLogs.find( |
| 118 | + (log) => |
| 119 | + log?.event === 'INCOMING_REQUEST' && |
| 120 | + log?.method === 'POST' && |
| 121 | + log?.url === '/metrics' |
| 122 | + ); |
| 123 | + expect(log).toBeDefined(); |
| 124 | + expect(log.headers['content-type']).toBe('application/x-protobuf'); |
| 125 | + expect(log.body).toContain('mock-system'); |
| 126 | + }); |
| 127 | + it('receives traces', () => { |
| 128 | + const log = collectorLogs.find( |
| 129 | + (log) => |
| 130 | + log?.event === 'INCOMING_REQUEST' && |
| 131 | + log?.method === 'POST' && |
| 132 | + log?.url === '/traces' |
| 133 | + ); |
| 134 | + expect(log).toBeDefined(); |
| 135 | + expect(log.headers['content-type']).toBe('application/x-protobuf'); |
| 136 | + expect(log.body).toContain('mock-system'); |
| 137 | + }); |
| 138 | + }); |
| 139 | + }); |
| 140 | +}); |
0 commit comments