nugi-ts-logs-sdk 1.0.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/README.md +218 -0
- package/dist/adapters/common.d.ts +43 -0
- package/dist/adapters/common.d.ts.map +1 -0
- package/dist/adapters/common.js +153 -0
- package/dist/adapters/common.js.map +1 -0
- package/dist/adapters/express.d.ts +29 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +62 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +18 -0
- package/dist/adapters/fastify.d.ts.map +1 -0
- package/dist/adapters/fastify.js +78 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/adapters/nestjs.d.ts +93 -0
- package/dist/adapters/nestjs.d.ts.map +1 -0
- package/dist/adapters/nestjs.js +310 -0
- package/dist/adapters/nestjs.js.map +1 -0
- package/dist/client.d.ts +66 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +299 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +116 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/db-error.d.ts +27 -0
- package/dist/instrumentation/db-error.d.ts.map +1 -0
- package/dist/instrumentation/db-error.js +153 -0
- package/dist/instrumentation/db-error.js.map +1 -0
- package/dist/instrumentation/query-capture.d.ts +39 -0
- package/dist/instrumentation/query-capture.d.ts.map +1 -0
- package/dist/instrumentation/query-capture.js +359 -0
- package/dist/instrumentation/query-capture.js.map +1 -0
- package/dist/internal/breadcrumbs.d.ts +32 -0
- package/dist/internal/breadcrumbs.d.ts.map +1 -0
- package/dist/internal/breadcrumbs.js +31 -0
- package/dist/internal/breadcrumbs.js.map +1 -0
- package/dist/internal/console-capture.d.ts +31 -0
- package/dist/internal/console-capture.d.ts.map +1 -0
- package/dist/internal/console-capture.js +113 -0
- package/dist/internal/console-capture.js.map +1 -0
- package/dist/internal/context.d.ts +21 -0
- package/dist/internal/context.d.ts.map +1 -0
- package/dist/internal/context.js +42 -0
- package/dist/internal/context.js.map +1 -0
- package/dist/internal/diagnostics.d.ts +9 -0
- package/dist/internal/diagnostics.d.ts.map +1 -0
- package/dist/internal/diagnostics.js +43 -0
- package/dist/internal/diagnostics.js.map +1 -0
- package/dist/internal/http-utils.d.ts +16 -0
- package/dist/internal/http-utils.d.ts.map +1 -0
- package/dist/internal/http-utils.js +81 -0
- package/dist/internal/http-utils.js.map +1 -0
- package/dist/internal/ids.d.ts +9 -0
- package/dist/internal/ids.d.ts.map +1 -0
- package/dist/internal/ids.js +34 -0
- package/dist/internal/ids.js.map +1 -0
- package/dist/internal/metadata.d.ts +4 -0
- package/dist/internal/metadata.d.ts.map +1 -0
- package/dist/internal/metadata.js +151 -0
- package/dist/internal/metadata.js.map +1 -0
- package/dist/internal/propagation.d.ts +30 -0
- package/dist/internal/propagation.d.ts.map +1 -0
- package/dist/internal/propagation.js +99 -0
- package/dist/internal/propagation.js.map +1 -0
- package/dist/internal/sanitize.d.ts +11 -0
- package/dist/internal/sanitize.d.ts.map +1 -0
- package/dist/internal/sanitize.js +77 -0
- package/dist/internal/sanitize.js.map +1 -0
- package/dist/internal/stacktrace.d.ts +29 -0
- package/dist/internal/stacktrace.d.ts.map +1 -0
- package/dist/internal/stacktrace.js +182 -0
- package/dist/internal/stacktrace.js.map +1 -0
- package/dist/signals/checkins.d.ts +28 -0
- package/dist/signals/checkins.d.ts.map +1 -0
- package/dist/signals/checkins.js +76 -0
- package/dist/signals/checkins.js.map +1 -0
- package/dist/signals/logger.d.ts +26 -0
- package/dist/signals/logger.d.ts.map +1 -0
- package/dist/signals/logger.js +63 -0
- package/dist/signals/logger.js.map +1 -0
- package/dist/signals/metrics.d.ts +58 -0
- package/dist/signals/metrics.d.ts.map +1 -0
- package/dist/signals/metrics.js +166 -0
- package/dist/signals/metrics.js.map +1 -0
- package/dist/signals/tracer.d.ts +77 -0
- package/dist/signals/tracer.d.ts.map +1 -0
- package/dist/signals/tracer.js +210 -0
- package/dist/signals/tracer.js.map +1 -0
- package/dist/transport/batcher.d.ts +31 -0
- package/dist/transport/batcher.d.ts.map +1 -0
- package/dist/transport/batcher.js +129 -0
- package/dist/transport/batcher.js.map +1 -0
- package/dist/transport/legacy-logs.d.ts +21 -0
- package/dist/transport/legacy-logs.d.ts.map +1 -0
- package/dist/transport/legacy-logs.js +61 -0
- package/dist/transport/legacy-logs.js.map +1 -0
- package/dist/transport/retry.d.ts +16 -0
- package/dist/transport/retry.d.ts.map +1 -0
- package/dist/transport/retry.js +43 -0
- package/dist/transport/retry.js.map +1 -0
- package/dist/transport/transport.d.ts +14 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +70 -0
- package/dist/transport/transport.js.map +1 -0
- package/dist/types.d.ts +136 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# nugi-ts-logs-sdk
|
|
2
|
+
|
|
3
|
+
Framework-agnostic TypeScript observability SDK. One client covering the same
|
|
4
|
+
surface as `nugi-logs-sdk` (Nest) — **logs, metrics, traces, errors, DB query
|
|
5
|
+
capture, and cron check-ins** — with **distributed tracing**, **request
|
|
6
|
+
tracking**, and a delivery pipeline that does **batching, retries and bounded
|
|
7
|
+
buffering**. Ships adapters for **Express**, **Fastify**, and **NestJS**, and
|
|
8
|
+
works with plain Node / JavaScript via CommonJS `require()`.
|
|
9
|
+
|
|
10
|
+
Designed to never crash or block the host app: every collection and delivery
|
|
11
|
+
path is fault-tolerant and non-blocking.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install nugi-ts-logs-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node 16+ (uses the built-in `fetch` on Node 18+; on Node 16 provide a
|
|
20
|
+
global `fetch` polyfill). No mandatory runtime dependencies. `@nestjs/common`
|
|
21
|
+
and `rxjs` are optional peers, only needed if you use the NestJS adapter.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createClient } from 'nugi-ts-logs-sdk';
|
|
27
|
+
|
|
28
|
+
export const nugi = createClient({
|
|
29
|
+
apiKey: process.env.NUGI_API_KEY!,
|
|
30
|
+
appId: process.env.NUGI_APP_ID!,
|
|
31
|
+
endpoint: process.env.NUGI_ENDPOINT, // https://logs.example.com
|
|
32
|
+
environment: process.env.NUGI_ENVIRONMENT || 'production',
|
|
33
|
+
serviceName: process.env.NUGI_SERVICE_NAME || 'api',
|
|
34
|
+
release: process.env.NUGI_RELEASE, // optional deploy version
|
|
35
|
+
captureConsole: true,
|
|
36
|
+
captureQueries: true, // pg / mysql2 / sequelize / typeorm
|
|
37
|
+
reportQueryErrors: true,
|
|
38
|
+
slowQueryThresholdMs: 500,
|
|
39
|
+
captureUncaught: true,
|
|
40
|
+
// batching / reliability (defaults shown)
|
|
41
|
+
batchSize: 100,
|
|
42
|
+
flushIntervalMs: 5000,
|
|
43
|
+
maxBufferSize: 10_000,
|
|
44
|
+
maxRetries: 3,
|
|
45
|
+
sampleRate: 1,
|
|
46
|
+
redact: ['ssn', 'creditCard'],
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Logs
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
nugi.logger.info('order placed', { orderId, amount });
|
|
54
|
+
nugi.logger.error('payment failed', { orderId, reason });
|
|
55
|
+
|
|
56
|
+
const scoped = nugi.logger.child({ component: 'billing' });
|
|
57
|
+
scoped.warn('retrying charge');
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Metrics
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
nugi.metrics.counter('orders.placed').add(1, { channel: 'web' });
|
|
64
|
+
nugi.metrics.gauge('queue.depth').set(42);
|
|
65
|
+
nugi.metrics.histogram('db.query.duration', 'ms').record(12.5);
|
|
66
|
+
|
|
67
|
+
// Nest-SDK-compatible helpers
|
|
68
|
+
nugi.metrics.count('orders.placed');
|
|
69
|
+
nugi.metrics.distribution('latency', 12.5, { unit: 'ms' });
|
|
70
|
+
nugi.metrics.setAttributes({ region: 'eu-west' });
|
|
71
|
+
nugi.metrics.withScope({ tenant: 'acme' }, () => {
|
|
72
|
+
nugi.metrics.count('checkout.step');
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Traces & distributed tracing
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
await nugi.tracer.startActiveSpan('charge-card', { kind: 'client' }, async (span) => {
|
|
80
|
+
span.setAttribute('gateway', 'stripe');
|
|
81
|
+
const headers = {};
|
|
82
|
+
span.inject(headers); // W3C traceparent
|
|
83
|
+
await fetch(url, { headers });
|
|
84
|
+
nugi.logger.info('charging');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Continue an inbound trace (W3C + x-trace-id / B3 / ALB fallbacks)
|
|
88
|
+
import { extractContext } from 'nugi-ts-logs-sdk';
|
|
89
|
+
const parent = extractContext(req.headers);
|
|
90
|
+
nugi.tracer.startActiveSpan('handle', { kind: 'server', parent }, () => { /* ... */ });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Error tracking
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
try { /* ... */ } catch (err) {
|
|
97
|
+
nugi.trackError(err, { handled: true, attributes: { orderId } });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
createClient({ /* ... */, captureUncaught: true });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Errors include Sentry-style stack frames with source context when available.
|
|
104
|
+
|
|
105
|
+
### Database query capture
|
|
106
|
+
|
|
107
|
+
Auto-patches **pg**, **mysql2**, **sequelize**, and **typeorm** when installed.
|
|
108
|
+
Prisma needs an explicit wrap:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const prisma = nugi.wrapPrisma(new PrismaClient());
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Manual:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
nugi.logQuery('SELECT 1', 12, { orm: 'pg', success: true });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Failed queries can auto-`trackError` (`reportQueryErrors`, default on). Slow
|
|
121
|
+
queries (≥ `slowQueryThresholdMs`) emit `db.query.slow` metrics and land in the
|
|
122
|
+
Queries dashboard (dual-written to `/api/v1/logs` + batch `kind: 'query'`).
|
|
123
|
+
|
|
124
|
+
### Cron check-ins
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
nugi.checkins.ok('nightly-report');
|
|
128
|
+
nugi.checkins.error('nightly-report', 'DB timeout');
|
|
129
|
+
nugi.checkins.capture('nightly-report', 'in_progress');
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Express
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { nugiExpress, nugiExpressErrorHandler } from 'nugi-ts-logs-sdk';
|
|
136
|
+
|
|
137
|
+
app.use(nugiExpress(nugi)); // early
|
|
138
|
+
// ... routes ...
|
|
139
|
+
app.use(nugiExpressErrorHandler(nugi)); // last
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
HTTP instrumentation records access logs, server spans, RED metrics
|
|
143
|
+
(`http.server.requests` / `duration` / `errors` / `client_errors`), real client
|
|
144
|
+
IP (X-Forwarded-For), and a `requestId`.
|
|
145
|
+
|
|
146
|
+
### Express (JavaScript / CommonJS)
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const { createClient, nugiExpress, nugiExpressErrorHandler } = require('nugi-ts-logs-sdk');
|
|
150
|
+
const nugi = createClient({ /* ... */ });
|
|
151
|
+
app.use(nugiExpress(nugi));
|
|
152
|
+
app.use(nugiExpressErrorHandler(nugi));
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Fastify
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { nugiFastify } from 'nugi-ts-logs-sdk';
|
|
159
|
+
fastify.register(nugiFastify(nugi));
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### NestJS
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { NugiModule, NUGI_CLIENT } from 'nugi-ts-logs-sdk';
|
|
166
|
+
|
|
167
|
+
@Module({
|
|
168
|
+
imports: [
|
|
169
|
+
NugiModule.forRoot({
|
|
170
|
+
apiKey: process.env.NUGI_API_KEY!,
|
|
171
|
+
appId: process.env.NUGI_APP_ID!,
|
|
172
|
+
endpoint: process.env.NUGI_ENDPOINT,
|
|
173
|
+
captureQueries: true,
|
|
174
|
+
http: { captureRequestBody: false },
|
|
175
|
+
}),
|
|
176
|
+
],
|
|
177
|
+
})
|
|
178
|
+
export class AppModule {}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Inject the client anywhere:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
constructor(@Inject(NUGI_CLIENT) private readonly nugi: NugiClient) {}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Delivery guarantees
|
|
188
|
+
|
|
189
|
+
- **Batching** — flush at `batchSize` or every `flushIntervalMs`.
|
|
190
|
+
- **Buffering** — bounded at `maxBufferSize`; oldest dropped on overload.
|
|
191
|
+
- **Retries** — exponential backoff + jitter on transient failures.
|
|
192
|
+
- **Graceful shutdown** — `await nugi.shutdown()` (also wired to process signals when `flushOnExit` is on).
|
|
193
|
+
- **Legacy dual-write** — DB queries also POST to `/api/v1/logs` so the Queries UI stays populated alongside the Nest SDK.
|
|
194
|
+
|
|
195
|
+
## Wire format
|
|
196
|
+
|
|
197
|
+
Each flush POSTs an `EventEnvelope` to `endpoint + ingestPath`
|
|
198
|
+
(default `/api/v1/telemetry/batch`) with an `X-API-Key` header:
|
|
199
|
+
|
|
200
|
+
```jsonc
|
|
201
|
+
{
|
|
202
|
+
"resource": { "appId": "...", "serviceName": "...", "environment": "...",
|
|
203
|
+
"release": "...",
|
|
204
|
+
"host": { "hostname": "...", "serverIp": "...", "serverIps": { "ipv4": [], "ipv6": [] },
|
|
205
|
+
"sdkVersion": "...", "nodeVersion": "...", "os": "...", "pid": 123 } },
|
|
206
|
+
"events": [ /* log | metric | span | error | query */ ],
|
|
207
|
+
"sentAt": "2026-07-05T20:00:00.000Z"
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## API surface
|
|
212
|
+
|
|
213
|
+
`createClient(config)` / `new NugiClient(config)` →
|
|
214
|
+
`{ logger, metrics, tracer, checkins, trackError, logQuery, wrapPrisma, flush, shutdown, resource, bufferSize }`.
|
|
215
|
+
|
|
216
|
+
Also exported: query capture helpers (`startQueryCapture`, `instrumentPrisma`,
|
|
217
|
+
`normalizeDbError`), stack frames (`buildStackFrames`), W3C + vendor
|
|
218
|
+
propagation, sanitize helpers, and framework adapters.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { NugiClient } from '../client';
|
|
2
|
+
import { Span } from '../signals/tracer';
|
|
3
|
+
export interface HttpAdapterOptions {
|
|
4
|
+
/** Capture (sanitized) request headers. Default true. */
|
|
5
|
+
captureHeaders?: boolean;
|
|
6
|
+
/** Capture (sanitized) request body. Default false. */
|
|
7
|
+
captureRequestBody?: boolean;
|
|
8
|
+
/** Capture (sanitized) response body. Default false. */
|
|
9
|
+
captureResponseBody?: boolean;
|
|
10
|
+
/** Return true to skip instrumenting a request (e.g. health checks). */
|
|
11
|
+
ignore?: (info: NormalizedRequest) => boolean;
|
|
12
|
+
/** Override the span/transaction name. Default `HTTP <METHOD> <route|path>`. */
|
|
13
|
+
spanName?: (info: NormalizedRequest) => string;
|
|
14
|
+
/** Extra field names to redact, merged with the client's `redact` config. */
|
|
15
|
+
redact?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface NormalizedRequest {
|
|
18
|
+
method: string;
|
|
19
|
+
url: string;
|
|
20
|
+
path: string;
|
|
21
|
+
route?: string;
|
|
22
|
+
headers: Record<string, unknown>;
|
|
23
|
+
ip?: string;
|
|
24
|
+
userAgent?: string;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
userId?: string;
|
|
27
|
+
/** Correlation id for this request (generated if inbound header absent). */
|
|
28
|
+
requestId?: string;
|
|
29
|
+
raw: unknown;
|
|
30
|
+
}
|
|
31
|
+
export interface NormalizedResponse {
|
|
32
|
+
statusCode: number;
|
|
33
|
+
headers?: Record<string, unknown>;
|
|
34
|
+
body?: unknown;
|
|
35
|
+
}
|
|
36
|
+
export declare function spanNameFor(info: NormalizedRequest, options: HttpAdapterOptions): string;
|
|
37
|
+
/** Begin a server span for an inbound request, continuing any inbound trace. */
|
|
38
|
+
export declare function startServerSpan(client: NugiClient, info: NormalizedRequest, options: HttpAdapterOptions): Span;
|
|
39
|
+
/** Finalize the span and emit the correlated access log + duration metric. */
|
|
40
|
+
export declare function completeServerSpan(client: NugiClient, span: Span, info: NormalizedRequest, response: NormalizedResponse, startedAtMs: number, options: HttpAdapterOptions): void;
|
|
41
|
+
/** Normalize a variety of request shapes (Express/Fastify/Nest) into one model. */
|
|
42
|
+
export declare function normalizeRequest(req: unknown): NormalizedRequest;
|
|
43
|
+
//# sourceMappingURL=common.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAYzC,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC9C,gFAAgF;IAChF,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,MAAM,CAAC;IAC/C,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAOD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,kBAAkB,GAC1B,MAAM,CASR;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,iBAAiB,EACvB,OAAO,EAAE,kBAAkB,GAC1B,IAAI,CAQN;AA8BD,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,iBAAiB,EACvB,QAAQ,EAAE,kBAAkB,EAC5B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,kBAAkB,GAC1B,IAAI,CAqDN;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAwBhE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Framework-agnostic HTTP instrumentation shared by the Express / Fastify /
|
|
3
|
+
// NestJS adapters. Handles: distributed-trace continuation, server span
|
|
4
|
+
// creation, request/response attribute capture, and the correlated log + metric
|
|
5
|
+
// emitted when a request completes. Every function is defensive.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.spanNameFor = spanNameFor;
|
|
8
|
+
exports.startServerSpan = startServerSpan;
|
|
9
|
+
exports.completeServerSpan = completeServerSpan;
|
|
10
|
+
exports.normalizeRequest = normalizeRequest;
|
|
11
|
+
const propagation_1 = require("../internal/propagation");
|
|
12
|
+
const sanitize_1 = require("../internal/sanitize");
|
|
13
|
+
const http_utils_1 = require("../internal/http-utils");
|
|
14
|
+
const ids_1 = require("../internal/ids");
|
|
15
|
+
/** Combine the client-wide redact list with any per-adapter override. */
|
|
16
|
+
function mergedRedact(client, options) {
|
|
17
|
+
return [...(client.config.redact || []), ...(options.redact || [])];
|
|
18
|
+
}
|
|
19
|
+
function spanNameFor(info, options) {
|
|
20
|
+
if (options.spanName) {
|
|
21
|
+
try {
|
|
22
|
+
return options.spanName(info);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
/* fall through to default */
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return `HTTP ${info.method} ${info.route || info.path}`;
|
|
29
|
+
}
|
|
30
|
+
/** Begin a server span for an inbound request, continuing any inbound trace. */
|
|
31
|
+
function startServerSpan(client, info, options) {
|
|
32
|
+
const parent = (0, propagation_1.extractContext)(info.headers) ?? null;
|
|
33
|
+
const span = client.tracer.startSpan(spanNameFor(info, options), {
|
|
34
|
+
kind: 'server',
|
|
35
|
+
parent,
|
|
36
|
+
attributes: requestAttributes(info, options, mergedRedact(client, options)),
|
|
37
|
+
});
|
|
38
|
+
return span;
|
|
39
|
+
}
|
|
40
|
+
function requestAttributes(info, options, redact) {
|
|
41
|
+
const attributes = {
|
|
42
|
+
'http.method': info.method,
|
|
43
|
+
'http.url': info.url,
|
|
44
|
+
'http.target': info.path,
|
|
45
|
+
'http.route': info.route,
|
|
46
|
+
'http.user_agent': info.userAgent,
|
|
47
|
+
'net.peer.ip': info.ip,
|
|
48
|
+
'enduser.id': info.userId,
|
|
49
|
+
'http.request_id': info.requestId,
|
|
50
|
+
'http.request_content_length': (0, http_utils_1.byteLength)(info.headers['content-length'], info.body),
|
|
51
|
+
};
|
|
52
|
+
if (options.captureHeaders !== false) {
|
|
53
|
+
attributes['http.request.headers'] = (0, sanitize_1.sanitizeHeaders)(info.headers, redact);
|
|
54
|
+
}
|
|
55
|
+
if (options.captureRequestBody && info.body !== undefined) {
|
|
56
|
+
attributes['http.request.body'] = (0, sanitize_1.sanitizeBody)(info.body, redact);
|
|
57
|
+
}
|
|
58
|
+
return attributes;
|
|
59
|
+
}
|
|
60
|
+
/** Finalize the span and emit the correlated access log + duration metric. */
|
|
61
|
+
function completeServerSpan(client, span, info, response, startedAtMs, options) {
|
|
62
|
+
try {
|
|
63
|
+
const durationMs = Math.max(0, Date.now() - startedAtMs);
|
|
64
|
+
const status = response.statusCode;
|
|
65
|
+
const isError = status >= 500;
|
|
66
|
+
const isClientError = status >= 400 && status < 500;
|
|
67
|
+
span.setAttributes({
|
|
68
|
+
'http.status_code': status,
|
|
69
|
+
'http.request_id': info.requestId,
|
|
70
|
+
'http.response_content_length': (0, http_utils_1.byteLength)(response.headers?.['content-length'], response.body),
|
|
71
|
+
});
|
|
72
|
+
if (options.captureResponseBody && response.body !== undefined) {
|
|
73
|
+
span.setAttribute('http.response.body', (0, sanitize_1.sanitizeBody)(response.body, mergedRedact(client, options)));
|
|
74
|
+
}
|
|
75
|
+
span.end({ status: isError ? 'error' : 'ok' });
|
|
76
|
+
const ctx = span.context();
|
|
77
|
+
client.logger.log(isError ? 'error' : isClientError ? 'warn' : 'info', 'http_request', {
|
|
78
|
+
'http.method': info.method,
|
|
79
|
+
'http.target': info.path,
|
|
80
|
+
'http.status_code': status,
|
|
81
|
+
duration_ms: durationMs,
|
|
82
|
+
'enduser.id': info.userId,
|
|
83
|
+
'http.request_id': info.requestId,
|
|
84
|
+
'net.peer.ip': info.ip,
|
|
85
|
+
trace_id: ctx.traceId,
|
|
86
|
+
span_id: ctx.spanId,
|
|
87
|
+
});
|
|
88
|
+
const tags = {
|
|
89
|
+
method: info.method,
|
|
90
|
+
route: info.route || info.path,
|
|
91
|
+
status_code: status,
|
|
92
|
+
};
|
|
93
|
+
client.metrics.counter('http.server.requests').add(1, tags);
|
|
94
|
+
client.metrics
|
|
95
|
+
.histogram('http.server.duration', 'ms')
|
|
96
|
+
.record(durationMs, tags);
|
|
97
|
+
if (isError) {
|
|
98
|
+
client.metrics.counter('http.server.errors').add(1, tags);
|
|
99
|
+
}
|
|
100
|
+
else if (isClientError) {
|
|
101
|
+
client.metrics.counter('http.server.client_errors').add(1, tags);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* instrumentation must never break the response */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Normalize a variety of request shapes (Express/Fastify/Nest) into one model. */
|
|
109
|
+
function normalizeRequest(req) {
|
|
110
|
+
const r = (req || {});
|
|
111
|
+
const headers = (0, http_utils_1.normalizeHeaders)(r.headers);
|
|
112
|
+
const method = String(r.method || 'GET').toUpperCase();
|
|
113
|
+
const url = String(r.originalUrl || r.url || '');
|
|
114
|
+
const path = safePath(url);
|
|
115
|
+
const route = extractRoute(r);
|
|
116
|
+
const inboundId = (typeof headers['x-request-id'] === 'string' && headers['x-request-id']) ||
|
|
117
|
+
(typeof headers['x-correlation-id'] === 'string' && headers['x-correlation-id']) ||
|
|
118
|
+
undefined;
|
|
119
|
+
return {
|
|
120
|
+
method,
|
|
121
|
+
url,
|
|
122
|
+
path,
|
|
123
|
+
route,
|
|
124
|
+
headers,
|
|
125
|
+
ip: (0, http_utils_1.resolveClientIp)(req) || undefined,
|
|
126
|
+
userAgent: headers['user-agent'] || undefined,
|
|
127
|
+
body: r.body,
|
|
128
|
+
userId: (0, http_utils_1.extractUserId)(req),
|
|
129
|
+
requestId: inboundId || (0, ids_1.generateSpanId)(),
|
|
130
|
+
raw: req,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function safePath(url) {
|
|
134
|
+
try {
|
|
135
|
+
return new URL(url, 'http://localhost').pathname;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
const q = url.indexOf('?');
|
|
139
|
+
return q >= 0 ? url.slice(0, q) : url;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function extractRoute(r) {
|
|
143
|
+
const route = r.route;
|
|
144
|
+
if (route?.path)
|
|
145
|
+
return String(route.path);
|
|
146
|
+
const routeOptions = r.routeOptions;
|
|
147
|
+
if (routeOptions?.url)
|
|
148
|
+
return String(routeOptions.url);
|
|
149
|
+
if (typeof r.routerPath === 'string')
|
|
150
|
+
return r.routerPath;
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NugiClient } from '../client';
|
|
2
|
+
import { HttpAdapterOptions } from './common';
|
|
3
|
+
interface ExpressRequest {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
interface ExpressResponse {
|
|
7
|
+
statusCode: number;
|
|
8
|
+
getHeader(name: string): unknown;
|
|
9
|
+
on(event: string, listener: () => void): unknown;
|
|
10
|
+
}
|
|
11
|
+
type NextFunction = (err?: unknown) => void;
|
|
12
|
+
type ExpressMiddleware = (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
|
|
13
|
+
type ExpressErrorMiddleware = (err: unknown, req: ExpressRequest, res: ExpressResponse, next: NextFunction) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Request-tracking middleware. Continues inbound distributed traces, opens a
|
|
16
|
+
* server span for the request, runs the handler chain inside that trace context
|
|
17
|
+
* (so logs/spans correlate), and records the access log + metrics on finish.
|
|
18
|
+
*
|
|
19
|
+
* Mount early: `app.use(nugiExpress(client))`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function nugiExpress(client: NugiClient, options?: HttpAdapterOptions): ExpressMiddleware;
|
|
22
|
+
/**
|
|
23
|
+
* Error-tracking middleware. Mount after your routes:
|
|
24
|
+
* `app.use(nugiExpressErrorHandler(client))`. It records the error and re-throws
|
|
25
|
+
* it into the next handler so your own error handling still runs.
|
|
26
|
+
*/
|
|
27
|
+
export declare function nugiExpressErrorHandler(client: NugiClient): ExpressErrorMiddleware;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAEL,kBAAkB,EAGnB,MAAM,UAAU,CAAC;AAElB,UAAU,cAAc;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AACD,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAClD;AACD,KAAK,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAC5C,KAAK,iBAAiB,GAAG,CACvB,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,KACf,IAAI,CAAC;AACV,KAAK,sBAAsB,GAAG,CAC5B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,cAAc,EACnB,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,KACf,IAAI,CAAC;AAEV;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAsCnB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,UAAU,GACjB,sBAAsB,CAQxB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Express instrumentation. Zero hard dependency on express — typed against the
|
|
3
|
+
// minimal structural shape it needs, so it works across express 4/5 and
|
|
4
|
+
// compatible routers (connect, etc.).
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.nugiExpress = nugiExpress;
|
|
7
|
+
exports.nugiExpressErrorHandler = nugiExpressErrorHandler;
|
|
8
|
+
const context_1 = require("../internal/context");
|
|
9
|
+
const common_1 = require("./common");
|
|
10
|
+
/**
|
|
11
|
+
* Request-tracking middleware. Continues inbound distributed traces, opens a
|
|
12
|
+
* server span for the request, runs the handler chain inside that trace context
|
|
13
|
+
* (so logs/spans correlate), and records the access log + metrics on finish.
|
|
14
|
+
*
|
|
15
|
+
* Mount early: `app.use(nugiExpress(client))`.
|
|
16
|
+
*/
|
|
17
|
+
function nugiExpress(client, options = {}) {
|
|
18
|
+
return function nugiExpressMiddleware(req, res, next) {
|
|
19
|
+
let ran = false;
|
|
20
|
+
try {
|
|
21
|
+
const info = (0, common_1.normalizeRequest)(req);
|
|
22
|
+
if (options.ignore?.(info)) {
|
|
23
|
+
next();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const startedAtMs = Date.now();
|
|
27
|
+
const span = (0, common_1.startServerSpan)(client, info, options);
|
|
28
|
+
res.on('finish', () => {
|
|
29
|
+
// req.user is typically populated by auth middleware after we start, so
|
|
30
|
+
// re-normalize to pick up the authenticated user id at completion.
|
|
31
|
+
const finalInfo = (0, common_1.normalizeRequest)(req);
|
|
32
|
+
(0, common_1.completeServerSpan)(client, span, finalInfo, {
|
|
33
|
+
statusCode: res.statusCode,
|
|
34
|
+
headers: { 'content-length': res.getHeader('content-length') },
|
|
35
|
+
}, startedAtMs, options);
|
|
36
|
+
});
|
|
37
|
+
ran = true;
|
|
38
|
+
// Propagate the trace context to the rest of the request lifecycle.
|
|
39
|
+
(0, context_1.runWithContext)({ span: span.context() }, () => next());
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
client.trackError(error, { attributes: { source: 'nugiExpress' } });
|
|
43
|
+
if (!ran)
|
|
44
|
+
next();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Error-tracking middleware. Mount after your routes:
|
|
50
|
+
* `app.use(nugiExpressErrorHandler(client))`. It records the error and re-throws
|
|
51
|
+
* it into the next handler so your own error handling still runs.
|
|
52
|
+
*/
|
|
53
|
+
function nugiExpressErrorHandler(client) {
|
|
54
|
+
return function nugiExpressError(err, _req, _res, next) {
|
|
55
|
+
client.trackError(err, {
|
|
56
|
+
handled: false,
|
|
57
|
+
attributes: { source: 'express.error_handler' },
|
|
58
|
+
});
|
|
59
|
+
next(err);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../src/adapters/express.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,wEAAwE;AACxE,sCAAsC;;AAuCtC,kCAyCC;AAOD,0DAUC;AA9FD,iDAAqD;AACrD,qCAKkB;AAuBlB;;;;;;GAMG;AACH,SAAgB,WAAW,CACzB,MAAkB,EAClB,UAA8B,EAAE;IAEhC,OAAO,SAAS,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI;QAClD,IAAI,GAAG,GAAG,KAAK,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAA,yBAAgB,EAAC,GAAG,CAAC,CAAC;YACnC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAA,wBAAe,EAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAEpD,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACpB,wEAAwE;gBACxE,mEAAmE;gBACnE,MAAM,SAAS,GAAG,IAAA,yBAAgB,EAAC,GAAG,CAAC,CAAC;gBACxC,IAAA,2BAAkB,EAChB,MAAM,EACN,IAAI,EACJ,SAAS,EACT;oBACE,UAAU,EAAE,GAAG,CAAC,UAAU;oBAC1B,OAAO,EAAE,EAAE,gBAAgB,EAAE,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,EAAE;iBAC/D,EACD,WAAW,EACX,OAAO,CACR,CAAC;YACJ,CAAC,CAAC,CAAC;YAEH,GAAG,GAAG,IAAI,CAAC;YACX,oEAAoE;YACpE,IAAA,wBAAc,EAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,GAAG;gBAAE,IAAI,EAAE,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,uBAAuB,CACrC,MAAkB;IAElB,OAAO,SAAS,gBAAgB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;QACpD,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;YACrB,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,EAAE,MAAM,EAAE,uBAAuB,EAAE;SAChD,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NugiClient } from '../client';
|
|
2
|
+
import { HttpAdapterOptions } from './common';
|
|
3
|
+
interface FastifyInstance {
|
|
4
|
+
addHook(name: string, handler: (...args: any[]) => unknown): unknown;
|
|
5
|
+
}
|
|
6
|
+
type DoneFn = (err?: unknown) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Fastify plugin providing request tracking, distributed tracing, error
|
|
9
|
+
* tracking and RED metrics.
|
|
10
|
+
*
|
|
11
|
+
* fastify.register(nugiFastify(client));
|
|
12
|
+
*
|
|
13
|
+
* Hooks: onRequest (start span + enter trace context), onResponse (finish +
|
|
14
|
+
* access log/metrics), onError (record exception).
|
|
15
|
+
*/
|
|
16
|
+
export declare function nugiFastify(client: NugiClient, options?: HttpAdapterOptions): (fastify: FastifyInstance, _opts: unknown, done: DoneFn) => void;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=fastify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EAEL,kBAAkB,EAGnB,MAAM,UAAU,CAAC;AAElB,UAAU,eAAe;IAGvB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,GAAG,OAAO,CAAC;CACtE;AACD,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAStC;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,GAAE,kBAAuB,aAEnE,eAAe,SACjB,OAAO,QACR,MAAM,KACX,IAAI,CAwER"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fastify instrumentation. Zero hard dependency on fastify — typed against the
|
|
3
|
+
// minimal structural shape it needs. Registered as a plugin whose hooks apply
|
|
4
|
+
// application-wide (encapsulation is skipped).
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.nugiFastify = nugiFastify;
|
|
7
|
+
const context_1 = require("../internal/context");
|
|
8
|
+
const common_1 = require("./common");
|
|
9
|
+
const STATE = Symbol('nugi.request.state');
|
|
10
|
+
/**
|
|
11
|
+
* Fastify plugin providing request tracking, distributed tracing, error
|
|
12
|
+
* tracking and RED metrics.
|
|
13
|
+
*
|
|
14
|
+
* fastify.register(nugiFastify(client));
|
|
15
|
+
*
|
|
16
|
+
* Hooks: onRequest (start span + enter trace context), onResponse (finish +
|
|
17
|
+
* access log/metrics), onError (record exception).
|
|
18
|
+
*/
|
|
19
|
+
function nugiFastify(client, options = {}) {
|
|
20
|
+
const plugin = function nugiFastifyPlugin(fastify, _opts, done) {
|
|
21
|
+
fastify.addHook('onRequest', (request, _reply, hookDone) => {
|
|
22
|
+
try {
|
|
23
|
+
const info = (0, common_1.normalizeRequest)(request);
|
|
24
|
+
if (options.ignore?.(info)) {
|
|
25
|
+
hookDone();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const span = (0, common_1.startServerSpan)(client, info, options);
|
|
29
|
+
request[STATE] = {
|
|
30
|
+
span,
|
|
31
|
+
startedAtMs: Date.now(),
|
|
32
|
+
};
|
|
33
|
+
// Make the span the active context for the rest of the request.
|
|
34
|
+
(0, context_1.enterContext)({ span: span.context() });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
client.trackError(error, { attributes: { source: 'fastify.onRequest' } });
|
|
38
|
+
}
|
|
39
|
+
hookDone();
|
|
40
|
+
});
|
|
41
|
+
fastify.addHook('onResponse', (request, reply, hookDone) => {
|
|
42
|
+
try {
|
|
43
|
+
const state = request[STATE];
|
|
44
|
+
if (state) {
|
|
45
|
+
const info = (0, common_1.normalizeRequest)(request);
|
|
46
|
+
const r = reply;
|
|
47
|
+
(0, common_1.completeServerSpan)(client, state.span, info, {
|
|
48
|
+
statusCode: r.statusCode,
|
|
49
|
+
headers: { 'content-length': r.getHeader?.('content-length') },
|
|
50
|
+
}, state.startedAtMs, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
client.trackError(error, { attributes: { source: 'fastify.onResponse' } });
|
|
55
|
+
}
|
|
56
|
+
hookDone();
|
|
57
|
+
});
|
|
58
|
+
fastify.addHook('onError', (request, _reply, error, hookDone) => {
|
|
59
|
+
try {
|
|
60
|
+
const state = request[STATE];
|
|
61
|
+
state?.span.recordException(error);
|
|
62
|
+
client.trackError(error, {
|
|
63
|
+
handled: false,
|
|
64
|
+
attributes: { source: 'fastify.onError' },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
/* never break the error path */
|
|
69
|
+
}
|
|
70
|
+
hookDone();
|
|
71
|
+
});
|
|
72
|
+
done();
|
|
73
|
+
};
|
|
74
|
+
// Break Fastify encapsulation so the hooks apply to the whole application.
|
|
75
|
+
plugin[Symbol.for('skip-override')] = true;
|
|
76
|
+
return plugin;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=fastify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../src/adapters/fastify.ts"],"names":[],"mappings":"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"}
|