trickle-observe 0.2.126 → 0.2.128
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/dist/fastify.d.ts +36 -0
- package/dist/fastify.js +398 -0
- package/dist/hono.d.ts +41 -0
- package/dist/hono.js +396 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +50 -8
- package/dist/koa.d.ts +45 -0
- package/dist/koa.js +369 -0
- package/dist/observe-register.js +142 -0
- package/package.json +2 -2
- package/src/fastify.ts +421 -0
- package/src/hono.ts +403 -0
- package/src/index.ts +48 -7
- package/src/koa.ts +387 -0
- package/src/observe-register.ts +133 -0
- package/dist/vite-plugin.test.d.ts +0 -1
- package/dist/vite-plugin.test.js +0 -160
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instrument a Fastify instance by monkey-patching route registration methods.
|
|
3
|
+
*
|
|
4
|
+
* Must be called BEFORE routes are defined:
|
|
5
|
+
*
|
|
6
|
+
* import Fastify from 'fastify';
|
|
7
|
+
* import { instrumentFastify } from 'trickle';
|
|
8
|
+
*
|
|
9
|
+
* const app = Fastify();
|
|
10
|
+
* instrumentFastify(app);
|
|
11
|
+
*
|
|
12
|
+
* app.get('/api/users', async (request, reply) => { ... });
|
|
13
|
+
*
|
|
14
|
+
* Each registered handler is wrapped to capture:
|
|
15
|
+
* - Input: `{ body, params, query }` from the request
|
|
16
|
+
* - Output: the data returned or passed to `reply.send()`
|
|
17
|
+
* - Errors: exceptions thrown in async handlers
|
|
18
|
+
*/
|
|
19
|
+
export declare function instrumentFastify(fastify: any, userOpts?: {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
environment?: string;
|
|
22
|
+
sampleRate?: number;
|
|
23
|
+
maxDepth?: number;
|
|
24
|
+
}): void;
|
|
25
|
+
/**
|
|
26
|
+
* Fastify plugin-style instrumentation using onRequest/onResponse hooks.
|
|
27
|
+
*
|
|
28
|
+
* Use this as an alternative to monkey-patching:
|
|
29
|
+
*
|
|
30
|
+
* import Fastify from 'fastify';
|
|
31
|
+
* import { tricklePlugin } from 'trickle';
|
|
32
|
+
*
|
|
33
|
+
* const app = Fastify();
|
|
34
|
+
* app.register(tricklePlugin);
|
|
35
|
+
*/
|
|
36
|
+
export declare function tricklePlugin(fastify: any, userOpts: any, done: () => void): void;
|
package/dist/fastify.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.instrumentFastify = instrumentFastify;
|
|
37
|
+
exports.tricklePlugin = tricklePlugin;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const pathMod = __importStar(require("path"));
|
|
40
|
+
const type_inference_1 = require("./type-inference");
|
|
41
|
+
const type_hash_1 = require("./type-hash");
|
|
42
|
+
const cache_1 = require("./cache");
|
|
43
|
+
const transport_1 = require("./transport");
|
|
44
|
+
const env_detect_1 = require("./env-detect");
|
|
45
|
+
const call_trace_1 = require("./call-trace");
|
|
46
|
+
const fastifyCache = new cache_1.TypeCache();
|
|
47
|
+
// ── Input extraction ──
|
|
48
|
+
function extractFastifyInput(request) {
|
|
49
|
+
const input = {};
|
|
50
|
+
try {
|
|
51
|
+
if (request.body !== undefined && request.body !== null && typeof request.body === 'object' && Object.keys(request.body).length > 0) {
|
|
52
|
+
input.body = request.body;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch { }
|
|
56
|
+
try {
|
|
57
|
+
if (request.params && typeof request.params === 'object' && Object.keys(request.params).length > 0) {
|
|
58
|
+
input.params = request.params;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch { }
|
|
62
|
+
try {
|
|
63
|
+
if (request.query && typeof request.query === 'object' && Object.keys(request.query).length > 0) {
|
|
64
|
+
input.query = request.query;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
return input;
|
|
69
|
+
}
|
|
70
|
+
// ── Sample sanitization (local copy) ──
|
|
71
|
+
function sanitizeSample(value, depth = 3) {
|
|
72
|
+
if (depth <= 0)
|
|
73
|
+
return '[truncated]';
|
|
74
|
+
if (value === null || value === undefined)
|
|
75
|
+
return value;
|
|
76
|
+
const t = typeof value;
|
|
77
|
+
if (t === 'string') {
|
|
78
|
+
const s = value;
|
|
79
|
+
return s.length > 200 ? s.substring(0, 200) + '...' : s;
|
|
80
|
+
}
|
|
81
|
+
if (t === 'number' || t === 'boolean')
|
|
82
|
+
return value;
|
|
83
|
+
if (t === 'bigint')
|
|
84
|
+
return String(value);
|
|
85
|
+
if (t === 'symbol')
|
|
86
|
+
return String(value);
|
|
87
|
+
if (t === 'function')
|
|
88
|
+
return `[Function: ${value.name || 'anonymous'}]`;
|
|
89
|
+
if (Array.isArray(value)) {
|
|
90
|
+
return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
|
|
91
|
+
}
|
|
92
|
+
if (t === 'object') {
|
|
93
|
+
if (value instanceof Date)
|
|
94
|
+
return value.toISOString();
|
|
95
|
+
if (value instanceof RegExp)
|
|
96
|
+
return String(value);
|
|
97
|
+
if (value instanceof Error)
|
|
98
|
+
return { error: value.message };
|
|
99
|
+
if (value instanceof Map)
|
|
100
|
+
return `[Map: ${value.size} entries]`;
|
|
101
|
+
if (value instanceof Set)
|
|
102
|
+
return `[Set: ${value.size} items]`;
|
|
103
|
+
const obj = value;
|
|
104
|
+
const result = {};
|
|
105
|
+
const keys = Object.keys(obj).slice(0, 20);
|
|
106
|
+
for (const key of keys) {
|
|
107
|
+
try {
|
|
108
|
+
result[key] = sanitizeSample(obj[key], depth - 1);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
result[key] = '[unreadable]';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
return String(value);
|
|
117
|
+
}
|
|
118
|
+
// ── Error file writing ──
|
|
119
|
+
function writeErrorToFile(error, input, routeName) {
|
|
120
|
+
try {
|
|
121
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
122
|
+
const isLambda = !!process.env.AWS_LAMBDA_FUNCTION_NAME;
|
|
123
|
+
const defaultDir = isLambda ? '/tmp/.trickle' : pathMod.join(process.cwd(), '.trickle');
|
|
124
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || defaultDir;
|
|
125
|
+
try {
|
|
126
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
catch { }
|
|
129
|
+
const stackLines = (err.stack || '').split('\n');
|
|
130
|
+
let errorFile;
|
|
131
|
+
let errorLine;
|
|
132
|
+
for (const sl of stackLines.slice(1)) {
|
|
133
|
+
const m = sl.match(/\((.+):(\d+):\d+\)/) || sl.match(/at (.+):(\d+):\d+/);
|
|
134
|
+
if (m && !m[1].includes('node_modules') && !m[1].includes('node:') && !m[1].includes('trickle-observe')) {
|
|
135
|
+
errorFile = m[1];
|
|
136
|
+
errorLine = parseInt(m[2]);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const record = {
|
|
141
|
+
kind: 'error',
|
|
142
|
+
error: err.message,
|
|
143
|
+
type: err.constructor?.name || 'Error',
|
|
144
|
+
message: err.message,
|
|
145
|
+
file: errorFile,
|
|
146
|
+
line: errorLine,
|
|
147
|
+
stack: stackLines.slice(0, 6).join('\n'),
|
|
148
|
+
route: routeName,
|
|
149
|
+
request: input,
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
};
|
|
152
|
+
const errorsFile = pathMod.join(dir, 'errors.jsonl');
|
|
153
|
+
fs.appendFileSync(errorsFile, JSON.stringify(record) + '\n');
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Never crash the user's app
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// ── Payload emission ──
|
|
160
|
+
function emitFastifyPayload(functionName, environment, maxDepth, input, output, error, durationMs) {
|
|
161
|
+
try {
|
|
162
|
+
const functionKey = `fastify::${functionName}`;
|
|
163
|
+
const argsType = (0, type_inference_1.inferType)(input, maxDepth);
|
|
164
|
+
const returnType = error ? { kind: 'unknown' } : (0, type_inference_1.inferType)(output, maxDepth);
|
|
165
|
+
const hash = (0, type_hash_1.hashType)(argsType, returnType);
|
|
166
|
+
if (!error && !fastifyCache.shouldSend(functionKey, hash)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!error) {
|
|
170
|
+
fastifyCache.markSent(functionKey, hash);
|
|
171
|
+
}
|
|
172
|
+
const payload = {
|
|
173
|
+
functionName,
|
|
174
|
+
module: 'fastify',
|
|
175
|
+
language: 'js',
|
|
176
|
+
environment,
|
|
177
|
+
typeHash: hash,
|
|
178
|
+
argsType,
|
|
179
|
+
returnType,
|
|
180
|
+
sampleInput: sanitizeSample(input),
|
|
181
|
+
sampleOutput: error ? undefined : sanitizeSample(output),
|
|
182
|
+
};
|
|
183
|
+
if (durationMs !== undefined) {
|
|
184
|
+
payload.durationMs = Math.round(durationMs * 100) / 100;
|
|
185
|
+
}
|
|
186
|
+
if (error) {
|
|
187
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
188
|
+
payload.error = {
|
|
189
|
+
type: err.constructor?.name || 'Error',
|
|
190
|
+
message: err.message,
|
|
191
|
+
stackTrace: err.stack,
|
|
192
|
+
argsSnapshot: sanitizeSample(input),
|
|
193
|
+
};
|
|
194
|
+
writeErrorToFile(error, input, functionName);
|
|
195
|
+
}
|
|
196
|
+
(0, transport_1.enqueue)(payload);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Never crash the user's app
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ── Handler wrapping ──
|
|
203
|
+
function wrapFastifyHandler(handler, routeName, opts) {
|
|
204
|
+
const wrapped = async function (request, reply) {
|
|
205
|
+
// Sample rate check
|
|
206
|
+
if (opts.sampleRate < 1 && Math.random() > opts.sampleRate) {
|
|
207
|
+
return handler.call(this, request, reply);
|
|
208
|
+
}
|
|
209
|
+
const input = extractFastifyInput(request);
|
|
210
|
+
let captured = false;
|
|
211
|
+
const startTime = performance.now();
|
|
212
|
+
const callId = (0, call_trace_1.traceCall)(routeName, 'fastify');
|
|
213
|
+
// Intercept reply.send() to capture output
|
|
214
|
+
const originalSend = reply.send;
|
|
215
|
+
if (typeof originalSend === 'function') {
|
|
216
|
+
reply.send = function (data) {
|
|
217
|
+
if (!captured) {
|
|
218
|
+
captured = true;
|
|
219
|
+
const durationMs = performance.now() - startTime;
|
|
220
|
+
(0, call_trace_1.traceReturn)(callId, routeName, 'fastify', durationMs);
|
|
221
|
+
const output = typeof data === 'string' ? { __html: true } : data;
|
|
222
|
+
emitFastifyPayload(routeName, opts.environment, opts.maxDepth, input, output, undefined, durationMs);
|
|
223
|
+
}
|
|
224
|
+
reply.send = originalSend; // restore
|
|
225
|
+
return originalSend.call(reply, data);
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const result = await handler.call(this, request, reply);
|
|
230
|
+
// Fastify allows returning a value directly (auto-serialized)
|
|
231
|
+
if (result !== undefined && !captured) {
|
|
232
|
+
captured = true;
|
|
233
|
+
const durationMs = performance.now() - startTime;
|
|
234
|
+
(0, call_trace_1.traceReturn)(callId, routeName, 'fastify', durationMs);
|
|
235
|
+
emitFastifyPayload(routeName, opts.environment, opts.maxDepth, input, result, undefined, durationMs);
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (!captured) {
|
|
241
|
+
captured = true;
|
|
242
|
+
const durationMs = performance.now() - startTime;
|
|
243
|
+
(0, call_trace_1.traceReturn)(callId, routeName, 'fastify', durationMs, (err instanceof Error ? err : new Error(String(err))).message);
|
|
244
|
+
emitFastifyPayload(routeName, opts.environment, opts.maxDepth, input, undefined, err, durationMs);
|
|
245
|
+
}
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
// Preserve function metadata
|
|
250
|
+
Object.defineProperty(wrapped, 'name', { value: handler.name || routeName, configurable: true });
|
|
251
|
+
Object.defineProperty(wrapped, 'length', { value: handler.length, configurable: true });
|
|
252
|
+
return wrapped;
|
|
253
|
+
}
|
|
254
|
+
// ── Public API ──
|
|
255
|
+
/**
|
|
256
|
+
* Instrument a Fastify instance by monkey-patching route registration methods.
|
|
257
|
+
*
|
|
258
|
+
* Must be called BEFORE routes are defined:
|
|
259
|
+
*
|
|
260
|
+
* import Fastify from 'fastify';
|
|
261
|
+
* import { instrumentFastify } from 'trickle';
|
|
262
|
+
*
|
|
263
|
+
* const app = Fastify();
|
|
264
|
+
* instrumentFastify(app);
|
|
265
|
+
*
|
|
266
|
+
* app.get('/api/users', async (request, reply) => { ... });
|
|
267
|
+
*
|
|
268
|
+
* Each registered handler is wrapped to capture:
|
|
269
|
+
* - Input: `{ body, params, query }` from the request
|
|
270
|
+
* - Output: the data returned or passed to `reply.send()`
|
|
271
|
+
* - Errors: exceptions thrown in async handlers
|
|
272
|
+
*/
|
|
273
|
+
function instrumentFastify(fastify, userOpts) {
|
|
274
|
+
const opts = {
|
|
275
|
+
enabled: userOpts?.enabled !== false,
|
|
276
|
+
environment: userOpts?.environment || (0, env_detect_1.detectEnvironment)(),
|
|
277
|
+
sampleRate: userOpts?.sampleRate ?? 1,
|
|
278
|
+
maxDepth: userOpts?.maxDepth ?? 5,
|
|
279
|
+
};
|
|
280
|
+
if (!opts.enabled)
|
|
281
|
+
return;
|
|
282
|
+
// Fastify shorthand methods: .get(), .post(), .put(), .delete(), .patch(), .all()
|
|
283
|
+
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'all'];
|
|
284
|
+
for (const method of methods) {
|
|
285
|
+
const original = fastify[method];
|
|
286
|
+
if (typeof original !== 'function')
|
|
287
|
+
continue;
|
|
288
|
+
fastify[method] = function (path, ...args) {
|
|
289
|
+
const pathStr = typeof path === 'string' ? path : String(path);
|
|
290
|
+
const routeName = `${method.toUpperCase()} ${pathStr}`;
|
|
291
|
+
// Fastify shorthand: .get(path, opts?, handler)
|
|
292
|
+
// args can be [handler] or [opts, handler]
|
|
293
|
+
const wrapped = args.map((arg) => {
|
|
294
|
+
if (typeof arg === 'function') {
|
|
295
|
+
try {
|
|
296
|
+
return wrapFastifyHandler(arg, routeName, opts);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return arg;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// If it's an options object with a handler property, wrap that
|
|
303
|
+
if (arg && typeof arg === 'object' && typeof arg.handler === 'function') {
|
|
304
|
+
try {
|
|
305
|
+
arg.handler = wrapFastifyHandler(arg.handler, routeName, opts);
|
|
306
|
+
}
|
|
307
|
+
catch { }
|
|
308
|
+
}
|
|
309
|
+
return arg;
|
|
310
|
+
});
|
|
311
|
+
return original.call(this, path, ...wrapped);
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
// Also wrap fastify.route({ method, url, handler })
|
|
315
|
+
const originalRoute = fastify.route;
|
|
316
|
+
if (typeof originalRoute === 'function') {
|
|
317
|
+
fastify.route = function (routeOptions) {
|
|
318
|
+
if (routeOptions && typeof routeOptions.handler === 'function') {
|
|
319
|
+
const method = (Array.isArray(routeOptions.method) ? routeOptions.method[0] : routeOptions.method) || 'ALL';
|
|
320
|
+
const url = routeOptions.url || routeOptions.path || '/';
|
|
321
|
+
const routeName = `${method.toUpperCase()} ${url}`;
|
|
322
|
+
try {
|
|
323
|
+
routeOptions.handler = wrapFastifyHandler(routeOptions.handler, routeName, opts);
|
|
324
|
+
}
|
|
325
|
+
catch { }
|
|
326
|
+
}
|
|
327
|
+
return originalRoute.call(this, routeOptions);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Fastify plugin-style instrumentation using onRequest/onResponse hooks.
|
|
333
|
+
*
|
|
334
|
+
* Use this as an alternative to monkey-patching:
|
|
335
|
+
*
|
|
336
|
+
* import Fastify from 'fastify';
|
|
337
|
+
* import { tricklePlugin } from 'trickle';
|
|
338
|
+
*
|
|
339
|
+
* const app = Fastify();
|
|
340
|
+
* app.register(tricklePlugin);
|
|
341
|
+
*/
|
|
342
|
+
function tricklePlugin(fastify, userOpts, done) {
|
|
343
|
+
const opts = {
|
|
344
|
+
enabled: userOpts?.enabled !== false,
|
|
345
|
+
environment: userOpts?.environment || (0, env_detect_1.detectEnvironment)(),
|
|
346
|
+
sampleRate: userOpts?.sampleRate ?? 1,
|
|
347
|
+
maxDepth: userOpts?.maxDepth ?? 5,
|
|
348
|
+
};
|
|
349
|
+
if (!opts.enabled) {
|
|
350
|
+
done();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// Use onRequest to start timing and onResponse to capture result
|
|
354
|
+
fastify.addHook('onRequest', async (request, _reply) => {
|
|
355
|
+
if (opts.sampleRate < 1 && Math.random() > opts.sampleRate)
|
|
356
|
+
return;
|
|
357
|
+
request.__trickleStart = performance.now();
|
|
358
|
+
request.__trickleInput = extractFastifyInput(request);
|
|
359
|
+
request.__trickleCallId = (0, call_trace_1.traceCall)(`${request.method} ${request.url}`, 'fastify');
|
|
360
|
+
});
|
|
361
|
+
fastify.addHook('onSend', async (request, reply, payload) => {
|
|
362
|
+
if (!request.__trickleStart)
|
|
363
|
+
return payload;
|
|
364
|
+
const durationMs = performance.now() - request.__trickleStart;
|
|
365
|
+
const routeName = `${request.method} ${request.routeOptions?.url || request.url}`;
|
|
366
|
+
const input = request.__trickleInput || extractFastifyInput(request);
|
|
367
|
+
(0, call_trace_1.traceReturn)(request.__trickleCallId, routeName, 'fastify', durationMs);
|
|
368
|
+
// Parse payload if it's JSON string
|
|
369
|
+
let output;
|
|
370
|
+
if (typeof payload === 'string') {
|
|
371
|
+
try {
|
|
372
|
+
output = JSON.parse(payload);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
output = { __html: true };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
output = payload;
|
|
380
|
+
}
|
|
381
|
+
emitFastifyPayload(routeName, opts.environment, opts.maxDepth, input, output, undefined, durationMs);
|
|
382
|
+
delete request.__trickleStart;
|
|
383
|
+
delete request.__trickleInput;
|
|
384
|
+
delete request.__trickleCallId;
|
|
385
|
+
return payload; // Must return payload for Fastify hook chain
|
|
386
|
+
});
|
|
387
|
+
fastify.addHook('onError', async (request, _reply, error) => {
|
|
388
|
+
if (!request.__trickleStart)
|
|
389
|
+
return;
|
|
390
|
+
const durationMs = performance.now() - request.__trickleStart;
|
|
391
|
+
const routeName = `${request.method} ${request.routeOptions?.url || request.url}`;
|
|
392
|
+
const input = request.__trickleInput || extractFastifyInput(request);
|
|
393
|
+
(0, call_trace_1.traceReturn)(request.__trickleCallId, routeName, 'fastify', durationMs, error?.message);
|
|
394
|
+
emitFastifyPayload(routeName, opts.environment, opts.maxDepth, input, undefined, error, durationMs);
|
|
395
|
+
delete request.__trickleStart;
|
|
396
|
+
});
|
|
397
|
+
done();
|
|
398
|
+
}
|
package/dist/hono.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instrument a Hono app by monkey-patching route registration methods.
|
|
3
|
+
*
|
|
4
|
+
* Must be called BEFORE routes are defined:
|
|
5
|
+
*
|
|
6
|
+
* import { Hono } from 'hono';
|
|
7
|
+
* import { instrumentHono } from 'trickle';
|
|
8
|
+
*
|
|
9
|
+
* const app = new Hono();
|
|
10
|
+
* instrumentHono(app);
|
|
11
|
+
*
|
|
12
|
+
* app.get('/api/users', (c) => c.json({ users: [] }));
|
|
13
|
+
*
|
|
14
|
+
* Captures:
|
|
15
|
+
* - Input: body (JSON), params, query from the Hono context
|
|
16
|
+
* - Output: the data passed to c.json() / c.text() or returned directly
|
|
17
|
+
* - Errors: exceptions thrown in handlers
|
|
18
|
+
* - Timing: request duration in milliseconds
|
|
19
|
+
*/
|
|
20
|
+
export declare function instrumentHono(app: any, userOpts?: {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
environment?: string;
|
|
23
|
+
sampleRate?: number;
|
|
24
|
+
maxDepth?: number;
|
|
25
|
+
}): void;
|
|
26
|
+
/**
|
|
27
|
+
* Hono middleware for observability. Use this as an alternative to
|
|
28
|
+
* monkey-patching route methods:
|
|
29
|
+
*
|
|
30
|
+
* import { Hono } from 'hono';
|
|
31
|
+
* import { trickleHonoMiddleware } from 'trickle';
|
|
32
|
+
*
|
|
33
|
+
* const app = new Hono();
|
|
34
|
+
* app.use('*', trickleHonoMiddleware());
|
|
35
|
+
*/
|
|
36
|
+
export declare function trickleHonoMiddleware(userOpts?: {
|
|
37
|
+
enabled?: boolean;
|
|
38
|
+
environment?: string;
|
|
39
|
+
sampleRate?: number;
|
|
40
|
+
maxDepth?: number;
|
|
41
|
+
}): (c: any, next: () => Promise<void>) => Promise<void | Response>;
|