diff --git a/package.json b/package.json index 0c01bb1..a48cc46 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "minimatch@<3.1.3": "3.1.3", "minimatch@>=5.0.0 <5.1.8": "5.1.8", "minimatch@>=9.0.0 <9.0.7": "9.0.7", - "tar": "^7.5.16", + "tar": "^7.5.21", "brace-expansion@>=2.0.0 <2.0.3": "2.0.3", "picomatch@^2": "2.3.2", "yaml": "^2.8.3", @@ -70,7 +70,9 @@ "sigstore": "^4.1.1", "@sigstore/core": "^3.2.1", "@opentelemetry/core": "^2.8.0", - "ws": "^8.21.0" + "ws": "^8.21.0", + "find-my-way": "^9.7.0", + "@fastify/static": "^10.1.2" } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79c1d7c..dbc553c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ overrides: minimatch@<3.1.3: 3.1.3 minimatch@>=5.0.0 <5.1.8: 5.1.8 minimatch@>=9.0.0 <9.0.7: 9.0.7 - tar: ^7.5.16 + tar: ^7.5.21 brace-expansion@>=2.0.0 <2.0.3: 2.0.3 picomatch@^2: 2.3.2 yaml: ^2.8.3 @@ -26,6 +26,8 @@ overrides: '@sigstore/core': ^3.2.1 '@opentelemetry/core': ^2.8.0 ws: ^8.21.0 + find-my-way: ^9.7.0 + '@fastify/static': ^10.1.2 importers: @@ -738,8 +740,8 @@ packages: '@fastify/send@4.1.0': resolution: {integrity: sha512-TMYeQLCBSy2TOFmV95hQWkiTYgC/SEx7vMdV+wnZVX4tt8VBLKzmH8vV9OzJehV0+XBfg+WxPMt5wp+JBUKsVw==} - '@fastify/static@9.3.0': - resolution: {integrity: sha512-9YMYRpCOtMBrqKYWcqiw7ykOrn4D0jogHpJrFS0KGeSuOwzKMM5/mjj7B0CFLVoQ6htqKYw//Zs7APn9DBq05w==} + '@fastify/static@10.1.2': + resolution: {integrity: sha512-G/g18cG9tLutT/OVyN1AIsHIl9L1UwmJ+S3dkyhVpplIx0nEMicd7RGQ+uJLyhKKF4a3tTcQydccn3Mop1fX+Q==} '@fastify/swagger-ui@5.2.6': resolution: {integrity: sha512-OMnms0O5s9wb6wis/K5nlrAMLsgUbr1GA8uphM41IasWe3AFdgxz6r/3bA9HTxlDNUYc2FGGKeqMp3ntxmSiNA==} @@ -1883,8 +1885,8 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} engines: {node: '>=18'} convert-source-map@2.0.0: @@ -2089,8 +2091,8 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-my-way@9.6.0: - resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} engines: {node: '>=20'} find-up@4.1.0: @@ -3133,8 +3135,8 @@ packages: engines: {node: 20 || >=22} hasBin: true - tar@7.5.20: - resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} + tar@7.5.22: + resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} engines: {node: '>=18'} tcompare@9.3.2: @@ -4272,18 +4274,19 @@ snapshots: http-errors: 2.0.1 mime: 3.0.0 - '@fastify/static@9.3.0': + '@fastify/static@10.1.2': dependencies: '@fastify/accept-negotiator': 2.0.1 + '@fastify/error': 4.2.0 '@fastify/send': 4.1.0 - content-disposition: 1.1.0 + content-disposition: 2.0.1 fastify-plugin: 6.0.0 fastq: 1.20.1 glob: 13.0.6 '@fastify/swagger-ui@5.2.6': dependencies: - '@fastify/static': 9.3.0 + '@fastify/static': 10.1.2 fastify-plugin: 5.1.0 openapi-types: 12.1.3 rfdc: 1.4.1 @@ -5779,7 +5782,7 @@ snapshots: color-name@1.1.4: {} - content-disposition@1.1.0: {} + content-disposition@2.0.1: {} convert-source-map@2.0.0: {} @@ -5935,7 +5938,7 @@ snapshots: abstract-logging: 2.0.1 avvio: 9.3.0 fast-json-stringify: 7.0.1 - find-my-way: 9.6.0 + find-my-way: 9.7.0 light-my-request: 6.6.0 pino: 9.14.0 process-warning: 5.0.0 @@ -5961,7 +5964,7 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-my-way@9.6.0: + find-my-way@9.7.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 @@ -6429,7 +6432,7 @@ snapshots: nopt: 9.0.0 proc-log: 6.1.0 semver: 7.8.5 - tar: 7.5.20 + tar: 7.5.22 tinyglobby: 0.2.17 undici: 6.27.0 which: 6.0.1 @@ -6565,7 +6568,7 @@ snapshots: proc-log: 6.1.0 sigstore: 4.1.1 ssri: 13.0.1 - tar: 7.5.20 + tar: 7.5.22 transitivePeerDependencies: - supports-color @@ -7060,7 +7063,7 @@ snapshots: - typescript - utf-8-validate - tar@7.5.20: + tar@7.5.22: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 diff --git a/src/app.ts b/src/app.ts index 36cdc0c..079ee8e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -16,6 +16,7 @@ import fastifySwagger from '@fastify/swagger'; import fastifySwaggerUI from '@fastify/swagger-ui'; import { Config } from './config'; +import { AppLogController } from './logging'; const { name: pkgName, version: pkgVersion } = JSON.parse( readFileSync(join(__dirname, '..', 'package.json'), 'utf8') @@ -33,6 +34,7 @@ export const initApp = async (config: Config, logger: pino.Logger) => { trustProxy: true, bodyLimit: 1024, genReqId: () => randomUUID(), + logController: new AppLogController(), }); app.setValidatorCompiler(validatorCompiler); app.setSerializerCompiler(serializerCompiler); diff --git a/src/logging.ts b/src/logging.ts index fd85d57..1ff651a 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,8 +1,71 @@ -import pino from "pino"; -import { Config } from "./config"; +import pino from 'pino'; +import { + FastifyReply, + FastifyRequest, + LogController, +} from 'fastify'; + +import { Config } from './config'; + +export const REQUEST_ID_LOG_LABEL = 'request_id'; +export const DURATION_MS_LOG_LABEL = 'duration_ms'; + +export const serializeRequest = (req: FastifyRequest) => ({ + method: req.method, + path: req.url, + route: req.routeOptions?.url, + host: req.host, + remote_address: req.ip, + remote_port: req.socket?.remotePort, + user_agent: req.headers['user-agent'], +}); + +export const serializeResponse = (reply: FastifyReply) => ({ + status_code: reply.statusCode, +}); + +export class AppLogController extends LogController { + constructor(options?: ConstructorParameters[0]) { + super({ + ...options, + requestIdLogLabel: REQUEST_ID_LOG_LABEL, + }); + } + + requestCompleted( + error: Error | null, + request: FastifyRequest, + reply: FastifyReply + ) { + if (this.isLogDisabled(request)) return; + + if (error) { + reply.log.error( + { + res: reply, + err: error, + [DURATION_MS_LOG_LABEL]: reply.elapsedTime, + }, + 'request errored' + ); + } else { + reply.log.info( + { + res: reply, + [DURATION_MS_LOG_LABEL]: reply.elapsedTime, + }, + 'request completed' + ); + } + } +} export const initLogging = async (config: Config): Promise => { return pino({ level: config.logLevel, + serializers: { + req: serializeRequest, + res: serializeResponse, + }, }); -} +};