@openapi-typescript-infra/service 5.4.4 → 5.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openapi-typescript-infra/service",
3
- "version": "5.4.4",
3
+ "version": "5.5.1",
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/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",
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.7",
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.7.7",
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.0",
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.1",
132
+ "tsx": "^4.19.2",
131
133
  "typescript": "^5.6.3",
132
- "vitest": "^2.1.3"
134
+ "vitest": "^2.1.4"
133
135
  },
134
136
  "resolutions": {
135
137
  "qs": "^6.11.0"
@@ -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,
@@ -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[0] === '!') {
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[0] === '!') {
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] = v.match(/^\/(.*)\/([a-z]*)$/) || [];
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
  }
@@ -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
- ${result.errors.map((e) => ` - ${e.path}: ${e.message}`).join('\n')}`);
21
+ ${errorMessages}`);
21
22
  }
22
23
  }
@@ -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
+ });
@@ -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
- 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() {
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 || 'http://otlp-exporter:4318/v1/traces',
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
- // 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;
57
92
 
58
93
  prometheusExporter = new PrometheusExporter({ preventServerStart: true });
59
94
  const instrumentations = getAutoInstrumentations();
60
- telemetrySdk = new opentelemetry.NodeSDK({
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 opentelemetry.metrics.View({
112
+ new metrics.View({
77
113
  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(
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.0
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';