@openapi-typescript-infra/service 4.5.4 → 4.6.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": "4.5.4",
3
+ "version": "4.6.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
  "main": "build/index.js",
6
6
  "scripts": {
@@ -118,7 +118,11 @@ export async function startApp<
118
118
  app.set('trust proxy', config.trustProxy);
119
119
  }
120
120
 
121
- app.use(loggerMiddleware(app, logging?.logRequestBody, logging?.logResponseBody));
121
+ const histogram = app.locals.meter.createHistogram('http_request_duration_seconds', {
122
+ description: 'Duration of HTTP requests in seconds',
123
+ });
124
+
125
+ app.use(loggerMiddleware(app, histogram, logging?.logRequestBody, logging?.logResponseBody));
122
126
 
123
127
  // Allow the service to add locals, etc. We put this before the body parsers
124
128
  // so that the req can decide whether to save the raw request body or not.
@@ -236,7 +240,7 @@ export async function startApp<
236
240
  app.use(notFoundMiddleware());
237
241
  }
238
242
  if (errors?.enabled) {
239
- app.use(errorHandlerMiddleware(app, errors?.unnest, errors?.render));
243
+ app.use(errorHandlerMiddleware(app, histogram, errors?.unnest, errors?.render));
240
244
  }
241
245
 
242
246
  return app;
@@ -55,6 +55,16 @@ export function startGlobalTelemetry(serviceName: string) {
55
55
  resourceDetectors: getResourceDetectors(),
56
56
  metricReader: prometheusExporter,
57
57
  instrumentations: [getAutoInstrumentations()],
58
+ views: [
59
+ new opentelemetry.metrics.View({
60
+ instrumentName: 'http_request_duration_seconds',
61
+ instrumentType: opentelemetry.metrics.InstrumentType.HISTOGRAM,
62
+ aggregation: new opentelemetry.metrics.ExplicitBucketHistogramAggregation(
63
+ [0.003, 0.03, 0.1, 0.3, 1.5, 10],
64
+ true,
65
+ ),
66
+ }),
67
+ ],
58
68
  });
59
69
  telemetrySdk.start();
60
70
  }
@@ -1,4 +1,5 @@
1
1
  import type { RequestHandler, Request, Response, ErrorRequestHandler } from 'express';
2
+ import { Histogram } from '@opentelemetry/api';
2
3
 
3
4
  import { ServiceError } from '../error';
4
5
  import type { AnyServiceLocals, RequestWithApp, ServiceExpress, ServiceLocals } from '../types';
@@ -44,6 +45,7 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
44
45
  error: Error | undefined,
45
46
  req: Request,
46
47
  res: Response,
48
+ histogram: Histogram,
47
49
  ) {
48
50
  const prefs = (res.locals as WithLogPrefs)[LOG_PREFS];
49
51
  if (prefs.logged) {
@@ -61,6 +63,14 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
61
63
  dur,
62
64
  };
63
65
 
66
+ const path = req.route ? { path: req.route.path } : undefined;
67
+ histogram.record(dur, {
68
+ status_code: endLog.s,
69
+ method: endLog.m,
70
+ ...path,
71
+ service: app.locals.name,
72
+ });
73
+
64
74
  if (res.locals.user?.id) {
65
75
  endLog.u = res.locals.user.id;
66
76
  }
@@ -95,7 +105,12 @@ function finishLog<SLocals extends AnyServiceLocals = ServiceLocals<Configuratio
95
105
 
96
106
  export function loggerMiddleware<
97
107
  SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
98
- >(app: ServiceExpress<SLocals>, logRequests?: boolean, logResponses?: boolean): RequestHandler {
108
+ >(
109
+ app: ServiceExpress<SLocals>,
110
+ histogram: Histogram,
111
+ logRequests?: boolean,
112
+ logResponses?: boolean,
113
+ ): RequestHandler {
99
114
  const { logger, service } = app.locals;
100
115
  return function gblogger(req, res, next) {
101
116
  const prefs: LogPrefs = {
@@ -135,7 +150,7 @@ export function loggerMiddleware<
135
150
  service.getLogFields?.(req as RequestWithApp<SLocals>, preLog);
136
151
  logger.info(preLog, 'pre');
137
152
 
138
- const logWriter = () => finishLog(app, undefined, req, res);
153
+ const logWriter = () => finishLog(app, undefined, req, res, histogram);
139
154
  res.on('finish', logWriter);
140
155
  next();
141
156
  };
@@ -143,7 +158,7 @@ export function loggerMiddleware<
143
158
 
144
159
  export function errorHandlerMiddleware<
145
160
  SLocals extends AnyServiceLocals = ServiceLocals<ConfigurationSchema>,
146
- >(app: ServiceExpress<SLocals>, unnest?: boolean, returnError?: boolean) {
161
+ >(app: ServiceExpress<SLocals>, histogram: Histogram, unnest?: boolean, returnError?: boolean) {
147
162
  const gbErrorHandler: ErrorRequestHandler = (error, req, res, next) => {
148
163
  let loggable: Partial<ServiceError> = error;
149
164
  const body = error.response?.body || error.body;
@@ -159,7 +174,7 @@ export function errorHandlerMiddleware<
159
174
  // Set the status to error, even if we aren't going to render the error.
160
175
  res.status(loggable.status || 500);
161
176
  if (returnError) {
162
- finishLog(app, error, req, res);
177
+ finishLog(app, error, req, res, histogram);
163
178
  const prefs = (res.locals as WithLogPrefs)[LOG_PREFS];
164
179
  prefs.logged = true;
165
180
  res.json({