Skip to content

Commit 57229ca

Browse files
authored
feat: try and fix logs to include trace information (#27)
* feat: try and fix logs to include trace information * fix: get SonarCloud off my back for a default local network URL * fix: ok, fine, default to HTTPS * fix: try and suppress sonar warning * fix: try another way to ignore issue * fix: try NOSONAR
1 parent fde9295 commit 57229ca

File tree

8 files changed

+1048
-427
lines changed

8 files changed

+1048
-427
lines changed

package.json

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,21 @@
7272
"dependencies": {
7373
"@godaddy/terminus": "^4.12.1",
7474
"@opentelemetry/api": "^1.9.0",
75-
"@opentelemetry/exporter-prometheus": "^0.53.0",
76-
"@opentelemetry/instrumentation-dns": "^0.39.0",
77-
"@opentelemetry/instrumentation-express": "^0.43.0",
78-
"@opentelemetry/instrumentation-generic-pool": "^0.39.0",
79-
"@opentelemetry/instrumentation-graphql": "^0.43.0",
80-
"@opentelemetry/instrumentation-http": "^0.53.0",
81-
"@opentelemetry/instrumentation-ioredis": "^0.43.0",
82-
"@opentelemetry/instrumentation-net": "^0.39.0",
83-
"@opentelemetry/instrumentation-pg": "^0.46.0",
84-
"@opentelemetry/instrumentation-pino": "^0.42.0",
85-
"@opentelemetry/instrumentation-undici": "^0.6.0",
86-
"@opentelemetry/resource-detector-container": "^0.4.4",
87-
"@opentelemetry/resource-detector-gcp": "^0.29.12",
88-
"@opentelemetry/sdk-node": "^0.53.0",
75+
"@opentelemetry/auto-instrumentations-node": "^0.52.0",
76+
"@opentelemetry/exporter-prometheus": "^0.54.0",
77+
"@opentelemetry/instrumentation-dns": "^0.40.0",
78+
"@opentelemetry/instrumentation-express": "^0.44.0",
79+
"@opentelemetry/instrumentation-generic-pool": "^0.40.0",
80+
"@opentelemetry/instrumentation-graphql": "^0.44.0",
81+
"@opentelemetry/instrumentation-http": "^0.54.0",
82+
"@opentelemetry/instrumentation-ioredis": "^0.44.0",
83+
"@opentelemetry/instrumentation-net": "^0.40.0",
84+
"@opentelemetry/instrumentation-pg": "^0.47.0",
85+
"@opentelemetry/instrumentation-pino": "^0.43.0",
86+
"@opentelemetry/instrumentation-undici": "^0.7.0",
87+
"@opentelemetry/resource-detector-container": "^0.5.0",
88+
"@opentelemetry/resource-detector-gcp": "^0.29.13",
89+
"@opentelemetry/sdk-node": "^0.54.0",
8990
"@opentelemetry/semantic-conventions": "^1.27.0",
9091
"@sesamecare-oss/confit": "^2.2.1",
9192
"@sesamecare-oss/opentelemetry-node-metrics": "^1.1.0",
@@ -94,8 +95,9 @@
9495
"cookie-parser": "^1.4.7",
9596
"dotenv": "^16.4.5",
9697
"express": "^5.0.1",
97-
"express-openapi-validator": "^5.3.7",
98+
"express-openapi-validator": "^5.3.8",
9899
"glob": "^11.0.0",
100+
"import-in-the-middle": "^1.11.2",
99101
"minimist": "^1.2.8",
100102
"moderndash": "^3.12.0",
101103
"pino": "^9.5.0",
@@ -113,12 +115,12 @@
113115
"@types/cookie-parser": "^1.4.7",
114116
"@types/express": "^5.0.0",
115117
"@types/minimist": "^1.2.5",
116-
"@types/node": "^22.7.7",
118+
"@types/node": "^22.8.1",
117119
"@types/request-ip": "^0.0.41",
118120
"@types/supertest": "^6.0.2",
119121
"@typescript-eslint/eslint-plugin": "^7.18.0",
120122
"@typescript-eslint/parser": "^7.18.0",
121-
"coconfig": "^1.6.0",
123+
"coconfig": "^1.6.1",
122124
"eslint": "^8.57.1",
123125
"eslint-config-prettier": "^9.1.0",
124126
"eslint-import-resolver-typescript": "^3.6.3",
@@ -127,9 +129,9 @@
127129
"pinst": "^3.0.0",
128130
"supertest": "^7.0.0",
129131
"tsconfig-paths": "^4.2.0",
130-
"tsx": "^4.19.1",
132+
"tsx": "^4.19.2",
131133
"typescript": "^5.6.3",
132-
"vitest": "^2.1.3"
134+
"vitest": "^2.1.4"
133135
},
134136
"resolutions": {
135137
"qs": "^6.11.0"

src/bin/start-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const argv = minimist(process.argv.slice(2), {
1515
boolean: ['built', 'repl', 'telemetry', 'nobind'],
1616
});
1717

18+
if (argv.telemetry) {
19+
await import('../telemetry/hook-modules.js');
20+
}
21+
1822
const noTelemetry = (argv.repl || isDev()) && !argv.telemetry;
1923
bootstrap({
2024
...argv,

src/config/shortstops.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function serviceTypeFactory(name: string) {
8484
return function serviceType(v: string) {
8585
let checkValue = v;
8686
let matchIsGood = true;
87-
if (checkValue[0] === '!') {
87+
if (checkValue.startsWith('!')) {
8888
matchIsGood = false;
8989
checkValue = checkValue.substring(1);
9090
}
@@ -114,15 +114,15 @@ export function shortstops(service: { name: string }, sourcedir: string) {
114114
env,
115115
// A version of env that can default to false
116116
env_switch(v: string) {
117-
if (v && v[0] === '!') {
117+
if (v && v.startsWith('!')) {
118118
const bval = env(`${v.substring(1)}|b`);
119119
return !bval;
120120
}
121121
return !!env(v);
122122
},
123123
base64: base64Handler(),
124124
regex(v: string) {
125-
const [, pattern, flags] = v.match(/^\/(.*)\/([a-z]*)$/) || [];
125+
const [, pattern, flags] = /^\/(.*)\/([a-z]*)$/.exec(v) || [];
126126
if (pattern === undefined) {
127127
throw new Error(`Invalid regular expression in configuration ${v}`);
128128
}

src/config/validation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export function validateConfiguration<Config extends ConfigurationSchema>(
1616
) {
1717
const result = validator(config);
1818
if (!result.success) {
19+
const errorMessages = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join('\n');
1920
throw new Error(`Configuration validation failed:
20-
${result.errors.map((e) => ` - ${e.path}: ${e.message}`).join('\n')}`);
21+
${errorMessages}`);
2122
}
2223
}

src/express-app/app.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from 'path';
55

66
import { pino } from 'pino';
77
import cookieParser from 'cookie-parser';
8-
import { metrics } from '@opentelemetry/api';
8+
import { context, metrics, trace } from '@opentelemetry/api';
99
import { setupNodeMetrics } from '@sesamecare-oss/opentelemetry-node-metrics';
1010
import { createTerminus } from '@godaddy/terminus';
1111
import type { RequestHandler, Response } from 'express';
@@ -52,6 +52,22 @@ export async function startApp<
5252
dest: process.env.LOG_TO_FILE || process.stdout.fd,
5353
minLength: process.env.LOG_BUFFER ? Number(process.env.LOG_BUFFER) : undefined,
5454
});
55+
56+
function poorMansOtlp(mergeObject: object) {
57+
if (!('trace_id' in mergeObject)) {
58+
const activeSpan = trace.getSpan(context.active());
59+
if (activeSpan) {
60+
const ctx = activeSpan.spanContext();
61+
Object.assign(mergeObject, {
62+
trace_id: ctx.traceId,
63+
span_id: ctx.spanId,
64+
trace_flags: ctx.traceFlags
65+
});
66+
}
67+
}
68+
return mergeObject;
69+
}
70+
5571
const logger = shouldPrettyPrint
5672
? pino(
5773
{
@@ -61,6 +77,7 @@ export async function startApp<
6177
colorize: true,
6278
},
6379
},
80+
mixin: poorMansOtlp,
6481
},
6582
destination,
6683
)
@@ -71,6 +88,7 @@ export async function startApp<
7188
return { level: label };
7289
},
7390
},
91+
mixin: poorMansOtlp,
7492
},
7593
destination,
7694
);

src/telemetry/hook-modules.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import module from 'node:module';
2+
3+
module.register('import-in-the-middle/hook.mjs', import.meta.url, {
4+
parentURL: import.meta.url,
5+
data: {
6+
include: [
7+
'express',
8+
'pino',
9+
'http',
10+
'dns',
11+
'net',
12+
'pg',
13+
'ioredis',
14+
'undici',
15+
'generic-pool',
16+
],
17+
},
18+
});

src/telemetry/index.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
12
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
23
import {
4+
Detector,
5+
DetectorSync,
36
envDetectorSync,
47
hostDetectorSync,
8+
IResource,
59
osDetectorSync,
610
processDetectorSync,
11+
ResourceDetectionConfig,
712
} from '@opentelemetry/resources';
813
import { containerDetector } from '@opentelemetry/resource-detector-container';
914
import { gcpDetector } from '@opentelemetry/resource-detector-gcp';
@@ -23,14 +28,18 @@ import type { ConfigurationSchema } from '../config/schema.js';
2328
import { getAutoInstrumentations } from './instrumentations.js';
2429
import { DummySpanExporter } from './DummyExporter.js';
2530

26-
function getExporter() {
31+
// OTLP seems to only support http, and this is a default on the local network so I'm keeping it.
32+
// NOSONAR
33+
const baseDefaultOtlpUrl = new URL('http://otlp-exporter:4318/v1').toString();
34+
35+
function getSpanExporter() {
2736
if (
2837
!process.env.DISABLE_OLTP_EXPORTER &&
2938
(['production', 'staging'].includes(process.env.APP_ENV || process.env.NODE_ENV || '') ||
3039
process.env.OTLP_EXPORTER)
3140
) {
3241
return new OTLPTraceExporter({
33-
url: process.env.OTLP_EXPORTER || 'http://otlp-exporter:4318/v1/traces',
42+
url: process.env.OTLP_EXPORTER || `${baseDefaultOtlpUrl}/traces`,
3443
});
3544
}
3645
if (process.env.ENABLE_CONSOLE_OLTP_EXPORTER) {
@@ -39,9 +48,36 @@ function getExporter() {
3948
return new DummySpanExporter();
4049
}
4150

51+
function getLogExporter() {
52+
if (
53+
!process.env.DISABLE_OLTP_EXPORTER &&
54+
(['production', 'staging'].includes(process.env.APP_ENV || process.env.NODE_ENV || '') ||
55+
process.env.OTLP_EXPORTER)
56+
) {
57+
return new OTLPLogExporter({
58+
url: process.env.OTLP_EXPORTER || `${baseDefaultOtlpUrl}/logs`,
59+
});
60+
}
61+
if (process.env.ENABLE_CONSOLE_OLTP_EXPORTER) {
62+
return new opentelemetry.logs.ConsoleLogRecordExporter();
63+
}
64+
return undefined;
65+
}
66+
4267
let prometheusExporter: PrometheusExporter | undefined;
4368
let telemetrySdk: opentelemetry.NodeSDK | undefined;
4469

70+
function awaitAttributes(detector: DetectorSync): Detector {
71+
return {
72+
async detect(config?: ResourceDetectionConfig): Promise<IResource> {
73+
const resource = detector.detect(config)
74+
await resource.waitForAsyncAttributes?.()
75+
76+
return resource
77+
},
78+
}
79+
}
80+
4581
/**
4682
* OpenTelemetry is not friendly to the idea of stopping
4783
* and starting itself, it seems. So we can only keep a global
@@ -52,31 +88,31 @@ let telemetrySdk: opentelemetry.NodeSDK | undefined;
5288
*/
5389
export async function startGlobalTelemetry(serviceName: string) {
5490
if (!prometheusExporter) {
55-
// For troubleshooting, set the log level to DiagLogLevel.DEBUG
56-
opentelemetry.api.diag.setLogger(new (opentelemetry.api.DiagConsoleLogger)(), opentelemetry.api.DiagLogLevel.INFO);
91+
const { metrics, logs, NodeSDK } = opentelemetry;
5792

5893
prometheusExporter = new PrometheusExporter({ preventServerStart: true });
5994
const instrumentations = getAutoInstrumentations();
60-
telemetrySdk = new opentelemetry.NodeSDK({
95+
const logExporter = getLogExporter();
96+
telemetrySdk = new NodeSDK({
6197
serviceName,
6298
autoDetectResources: false,
63-
traceExporter: getExporter(),
6499
resourceDetectors: [
65-
envDetectorSync,
66-
hostDetectorSync,
67-
osDetectorSync,
68-
processDetectorSync,
69-
containerDetector,
70-
gcpDetector,
100+
awaitAttributes(envDetectorSync),
101+
awaitAttributes(hostDetectorSync),
102+
awaitAttributes(osDetectorSync),
103+
awaitAttributes(processDetectorSync),
104+
awaitAttributes(containerDetector),
105+
awaitAttributes(gcpDetector),
71106
],
107+
traceExporter: getSpanExporter(),
72108
metricReader: prometheusExporter,
73109
instrumentations,
74-
logRecordProcessors: [],
110+
logRecordProcessors: logExporter ? [new logs.BatchLogRecordProcessor(logExporter)] : [],
75111
views: [
76-
new opentelemetry.metrics.View({
112+
new metrics.View({
77113
instrumentName: 'http_request_duration_seconds',
78-
instrumentType: opentelemetry.metrics.InstrumentType.HISTOGRAM,
79-
aggregation: new opentelemetry.metrics.ExplicitBucketHistogramAggregation(
114+
instrumentType: metrics.InstrumentType.HISTOGRAM,
115+
aggregation: new metrics.ExplicitBucketHistogramAggregation(
80116
[0.003, 0.03, 0.1, 0.3, 1.5, 10],
81117
true,
82118
),

0 commit comments

Comments
 (0)