@openapi-typescript-infra/service 5.4.3 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +1 -1
- package/.prettierrc.cjs +1 -1
- package/Makefile +2 -2
- package/build/bin/start-service.js +3 -0
- package/build/bin/start-service.js.map +1 -1
- package/build/config/shortstops.js +3 -3
- package/build/config/shortstops.js.map +1 -1
- package/build/config/validation.js +2 -1
- package/build/config/validation.js.map +1 -1
- package/build/express-app/app.js +17 -1
- package/build/express-app/app.js.map +1 -1
- package/build/telemetry/hook-modules.d.ts +1 -0
- package/build/telemetry/hook-modules.js +18 -0
- package/build/telemetry/hook-modules.js.map +1 -0
- package/build/telemetry/index.js +42 -16
- package/build/telemetry/index.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +22 -20
- package/src/bin/start-service.ts +4 -0
- package/src/config/shortstops.ts +3 -3
- package/src/config/validation.ts +2 -1
- package/src/express-app/app.ts +19 -1
- package/src/telemetry/hook-modules.ts +18 -0
- package/src/telemetry/index.ts +52 -16
- package/vitest.config.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openapi-typescript-infra/service",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "An opinionated framework for building configuration driven services - web, api, or ob. Uses OpenAPI, pino logging, express, confit, Typescript and vitest.",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -72,20 +72,21 @@
|
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@godaddy/terminus": "^4.12.1",
|
|
74
74
|
"@opentelemetry/api": "^1.9.0",
|
|
75
|
-
"@opentelemetry/
|
|
76
|
-
"@opentelemetry/
|
|
77
|
-
"@opentelemetry/instrumentation-
|
|
78
|
-
"@opentelemetry/instrumentation-
|
|
79
|
-
"@opentelemetry/instrumentation-
|
|
80
|
-
"@opentelemetry/instrumentation-
|
|
81
|
-
"@opentelemetry/instrumentation-
|
|
82
|
-
"@opentelemetry/instrumentation-
|
|
83
|
-
"@opentelemetry/instrumentation-
|
|
84
|
-
"@opentelemetry/instrumentation-
|
|
85
|
-
"@opentelemetry/instrumentation-
|
|
86
|
-
"@opentelemetry/
|
|
87
|
-
"@opentelemetry/resource-detector-
|
|
88
|
-
"@opentelemetry/
|
|
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",
|
|
89
90
|
"@opentelemetry/semantic-conventions": "^1.27.0",
|
|
90
91
|
"@sesamecare-oss/confit": "^2.2.1",
|
|
91
92
|
"@sesamecare-oss/opentelemetry-node-metrics": "^1.1.0",
|
|
@@ -94,8 +95,9 @@
|
|
|
94
95
|
"cookie-parser": "^1.4.7",
|
|
95
96
|
"dotenv": "^16.4.5",
|
|
96
97
|
"express": "^5.0.1",
|
|
97
|
-
"express-openapi-validator": "^5.3.
|
|
98
|
+
"express-openapi-validator": "^5.3.8",
|
|
98
99
|
"glob": "^11.0.0",
|
|
100
|
+
"import-in-the-middle": "^1.11.2",
|
|
99
101
|
"minimist": "^1.2.8",
|
|
100
102
|
"moderndash": "^3.12.0",
|
|
101
103
|
"pino": "^9.5.0",
|
|
@@ -113,12 +115,12 @@
|
|
|
113
115
|
"@types/cookie-parser": "^1.4.7",
|
|
114
116
|
"@types/express": "^5.0.0",
|
|
115
117
|
"@types/minimist": "^1.2.5",
|
|
116
|
-
"@types/node": "^22.
|
|
118
|
+
"@types/node": "^22.8.1",
|
|
117
119
|
"@types/request-ip": "^0.0.41",
|
|
118
120
|
"@types/supertest": "^6.0.2",
|
|
119
121
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
120
122
|
"@typescript-eslint/parser": "^7.18.0",
|
|
121
|
-
"coconfig": "^1.6.
|
|
123
|
+
"coconfig": "^1.6.1",
|
|
122
124
|
"eslint": "^8.57.1",
|
|
123
125
|
"eslint-config-prettier": "^9.1.0",
|
|
124
126
|
"eslint-import-resolver-typescript": "^3.6.3",
|
|
@@ -127,9 +129,9 @@
|
|
|
127
129
|
"pinst": "^3.0.0",
|
|
128
130
|
"supertest": "^7.0.0",
|
|
129
131
|
"tsconfig-paths": "^4.2.0",
|
|
130
|
-
"tsx": "^4.19.
|
|
132
|
+
"tsx": "^4.19.2",
|
|
131
133
|
"typescript": "^5.6.3",
|
|
132
|
-
"vitest": "^2.1.
|
|
134
|
+
"vitest": "^2.1.4"
|
|
133
135
|
},
|
|
134
136
|
"resolutions": {
|
|
135
137
|
"qs": "^6.11.0"
|
package/src/bin/start-service.ts
CHANGED
|
@@ -15,6 +15,10 @@ const argv = minimist(process.argv.slice(2), {
|
|
|
15
15
|
boolean: ['built', 'repl', 'telemetry', 'nobind'],
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
if (argv.telemetry) {
|
|
19
|
+
await import('../telemetry/hook-modules.js');
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
const noTelemetry = (argv.repl || isDev()) && !argv.telemetry;
|
|
19
23
|
bootstrap({
|
|
20
24
|
...argv,
|
package/src/config/shortstops.ts
CHANGED
|
@@ -84,7 +84,7 @@ function serviceTypeFactory(name: string) {
|
|
|
84
84
|
return function serviceType(v: string) {
|
|
85
85
|
let checkValue = v;
|
|
86
86
|
let matchIsGood = true;
|
|
87
|
-
if (checkValue
|
|
87
|
+
if (checkValue.startsWith('!')) {
|
|
88
88
|
matchIsGood = false;
|
|
89
89
|
checkValue = checkValue.substring(1);
|
|
90
90
|
}
|
|
@@ -114,7 +114,7 @@ export function shortstops(service: { name: string }, sourcedir: string) {
|
|
|
114
114
|
env,
|
|
115
115
|
// A version of env that can default to false
|
|
116
116
|
env_switch(v: string) {
|
|
117
|
-
if (v && v
|
|
117
|
+
if (v && v.startsWith('!')) {
|
|
118
118
|
const bval = env(`${v.substring(1)}|b`);
|
|
119
119
|
return !bval;
|
|
120
120
|
}
|
|
@@ -122,7 +122,7 @@ export function shortstops(service: { name: string }, sourcedir: string) {
|
|
|
122
122
|
},
|
|
123
123
|
base64: base64Handler(),
|
|
124
124
|
regex(v: string) {
|
|
125
|
-
const [, pattern, flags] =
|
|
125
|
+
const [, pattern, flags] = /^\/(.*)\/([a-z]*)$/.exec(v) || [];
|
|
126
126
|
if (pattern === undefined) {
|
|
127
127
|
throw new Error(`Invalid regular expression in configuration ${v}`);
|
|
128
128
|
}
|
package/src/config/validation.ts
CHANGED
|
@@ -16,7 +16,8 @@ export function validateConfiguration<Config extends ConfigurationSchema>(
|
|
|
16
16
|
) {
|
|
17
17
|
const result = validator(config);
|
|
18
18
|
if (!result.success) {
|
|
19
|
+
const errorMessages = result.errors.map((e) => ` - ${e.path}: ${e.message}`).join('\n');
|
|
19
20
|
throw new Error(`Configuration validation failed:
|
|
20
|
-
${
|
|
21
|
+
${errorMessages}`);
|
|
21
22
|
}
|
|
22
23
|
}
|
package/src/express-app/app.ts
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
|
|
6
6
|
import { pino } from 'pino';
|
|
7
7
|
import cookieParser from 'cookie-parser';
|
|
8
|
-
import { metrics } from '@opentelemetry/api';
|
|
8
|
+
import { context, metrics, trace } from '@opentelemetry/api';
|
|
9
9
|
import { setupNodeMetrics } from '@sesamecare-oss/opentelemetry-node-metrics';
|
|
10
10
|
import { createTerminus } from '@godaddy/terminus';
|
|
11
11
|
import type { RequestHandler, Response } from 'express';
|
|
@@ -52,6 +52,22 @@ export async function startApp<
|
|
|
52
52
|
dest: process.env.LOG_TO_FILE || process.stdout.fd,
|
|
53
53
|
minLength: process.env.LOG_BUFFER ? Number(process.env.LOG_BUFFER) : undefined,
|
|
54
54
|
});
|
|
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
|
+
|
|
55
71
|
const logger = shouldPrettyPrint
|
|
56
72
|
? pino(
|
|
57
73
|
{
|
|
@@ -61,6 +77,7 @@ export async function startApp<
|
|
|
61
77
|
colorize: true,
|
|
62
78
|
},
|
|
63
79
|
},
|
|
80
|
+
mixin: poorMansOtlp,
|
|
64
81
|
},
|
|
65
82
|
destination,
|
|
66
83
|
)
|
|
@@ -71,6 +88,7 @@ export async function startApp<
|
|
|
71
88
|
return { level: label };
|
|
72
89
|
},
|
|
73
90
|
},
|
|
91
|
+
mixin: poorMansOtlp,
|
|
74
92
|
},
|
|
75
93
|
destination,
|
|
76
94
|
);
|
|
@@ -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
|
+
});
|
package/src/telemetry/index.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-proto';
|
|
1
2
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
|
|
2
3
|
import {
|
|
4
|
+
Detector,
|
|
5
|
+
DetectorSync,
|
|
3
6
|
envDetectorSync,
|
|
4
7
|
hostDetectorSync,
|
|
8
|
+
IResource,
|
|
5
9
|
osDetectorSync,
|
|
6
10
|
processDetectorSync,
|
|
11
|
+
ResourceDetectionConfig,
|
|
7
12
|
} from '@opentelemetry/resources';
|
|
8
13
|
import { containerDetector } from '@opentelemetry/resource-detector-container';
|
|
9
14
|
import { gcpDetector } from '@opentelemetry/resource-detector-gcp';
|
|
@@ -23,14 +28,18 @@ import type { ConfigurationSchema } from '../config/schema.js';
|
|
|
23
28
|
import { getAutoInstrumentations } from './instrumentations.js';
|
|
24
29
|
import { DummySpanExporter } from './DummyExporter.js';
|
|
25
30
|
|
|
26
|
-
|
|
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() {
|
|
27
36
|
if (
|
|
28
37
|
!process.env.DISABLE_OLTP_EXPORTER &&
|
|
29
38
|
(['production', 'staging'].includes(process.env.APP_ENV || process.env.NODE_ENV || '') ||
|
|
30
39
|
process.env.OTLP_EXPORTER)
|
|
31
40
|
) {
|
|
32
41
|
return new OTLPTraceExporter({
|
|
33
|
-
url: process.env.OTLP_EXPORTER ||
|
|
42
|
+
url: process.env.OTLP_EXPORTER || `${baseDefaultOtlpUrl}/traces`,
|
|
34
43
|
});
|
|
35
44
|
}
|
|
36
45
|
if (process.env.ENABLE_CONSOLE_OLTP_EXPORTER) {
|
|
@@ -39,9 +48,36 @@ function getExporter() {
|
|
|
39
48
|
return new DummySpanExporter();
|
|
40
49
|
}
|
|
41
50
|
|
|
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
|
+
|
|
42
67
|
let prometheusExporter: PrometheusExporter | undefined;
|
|
43
68
|
let telemetrySdk: opentelemetry.NodeSDK | undefined;
|
|
44
69
|
|
|
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
|
+
|
|
45
81
|
/**
|
|
46
82
|
* OpenTelemetry is not friendly to the idea of stopping
|
|
47
83
|
* and starting itself, it seems. So we can only keep a global
|
|
@@ -52,31 +88,31 @@ let telemetrySdk: opentelemetry.NodeSDK | undefined;
|
|
|
52
88
|
*/
|
|
53
89
|
export async function startGlobalTelemetry(serviceName: string) {
|
|
54
90
|
if (!prometheusExporter) {
|
|
55
|
-
|
|
56
|
-
opentelemetry.api.diag.setLogger(new (opentelemetry.api.DiagConsoleLogger)(), opentelemetry.api.DiagLogLevel.INFO);
|
|
91
|
+
const { metrics, logs, NodeSDK } = opentelemetry;
|
|
57
92
|
|
|
58
93
|
prometheusExporter = new PrometheusExporter({ preventServerStart: true });
|
|
59
94
|
const instrumentations = getAutoInstrumentations();
|
|
60
|
-
|
|
95
|
+
const logExporter = getLogExporter();
|
|
96
|
+
telemetrySdk = new NodeSDK({
|
|
61
97
|
serviceName,
|
|
62
98
|
autoDetectResources: false,
|
|
63
|
-
traceExporter: getExporter(),
|
|
64
99
|
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),
|
|
71
106
|
],
|
|
107
|
+
traceExporter: getSpanExporter(),
|
|
72
108
|
metricReader: prometheusExporter,
|
|
73
109
|
instrumentations,
|
|
74
|
-
logRecordProcessors: [],
|
|
110
|
+
logRecordProcessors: logExporter ? [new logs.BatchLogRecordProcessor(logExporter)] : [],
|
|
75
111
|
views: [
|
|
76
|
-
new
|
|
112
|
+
new metrics.View({
|
|
77
113
|
instrumentName: 'http_request_duration_seconds',
|
|
78
|
-
instrumentType:
|
|
79
|
-
aggregation: new
|
|
114
|
+
instrumentType: metrics.InstrumentType.HISTOGRAM,
|
|
115
|
+
aggregation: new metrics.ExplicitBucketHistogramAggregation(
|
|
80
116
|
[0.003, 0.03, 0.1, 0.3, 1.5, 10],
|
|
81
117
|
true,
|
|
82
118
|
),
|
package/vitest.config.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Instead, edit the coconfig.js or coconfig.ts file in your project root.
|
|
4
4
|
*
|
|
5
5
|
* See https://github.com/gas-buddy/coconfig for more information.
|
|
6
|
-
* @version coconfig@1.6.
|
|
6
|
+
* @version coconfig@1.6.1
|
|
7
7
|
*/
|
|
8
8
|
import cjs from '@openapi-typescript-infra/coconfig';
|
|
9
9
|
import * as esmToCjs from '@openapi-typescript-infra/coconfig';
|