@logtape/hono 1.3.0 → 1.3.2
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 +5 -2
- package/deno.json +0 -34
- package/src/mod.test.ts +0 -670
- package/src/mod.ts +0 -356
- package/tsdown.config.ts +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logtape/hono",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Hono adapter for LogTape logging library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logging",
|
|
@@ -50,9 +50,12 @@
|
|
|
50
50
|
"./package.json": "./package.json"
|
|
51
51
|
},
|
|
52
52
|
"sideEffects": false,
|
|
53
|
+
"files": [
|
|
54
|
+
"dist/"
|
|
55
|
+
],
|
|
53
56
|
"peerDependencies": {
|
|
54
57
|
"hono": "^4.0.0",
|
|
55
|
-
"@logtape/logtape": "^1.3.
|
|
58
|
+
"@logtape/logtape": "^1.3.2"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
61
|
"@alinea/suite": "^0.6.3",
|
package/deno.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@logtape/hono",
|
|
3
|
-
"version": "1.3.0",
|
|
4
|
-
"license": "MIT",
|
|
5
|
-
"exports": "./src/mod.ts",
|
|
6
|
-
"exclude": [
|
|
7
|
-
"coverage/",
|
|
8
|
-
"npm/",
|
|
9
|
-
"dist/"
|
|
10
|
-
],
|
|
11
|
-
"tasks": {
|
|
12
|
-
"build": "pnpm build",
|
|
13
|
-
"test": "deno test --allow-env --allow-sys --allow-net",
|
|
14
|
-
"test:node": {
|
|
15
|
-
"dependencies": [
|
|
16
|
-
"build"
|
|
17
|
-
],
|
|
18
|
-
"command": "node --experimental-transform-types --test"
|
|
19
|
-
},
|
|
20
|
-
"test:bun": {
|
|
21
|
-
"dependencies": [
|
|
22
|
-
"build"
|
|
23
|
-
],
|
|
24
|
-
"command": "bun test"
|
|
25
|
-
},
|
|
26
|
-
"test-all": {
|
|
27
|
-
"dependencies": [
|
|
28
|
-
"test",
|
|
29
|
-
"test:node",
|
|
30
|
-
"test:bun"
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
package/src/mod.test.ts
DELETED
|
@@ -1,670 +0,0 @@
|
|
|
1
|
-
import { suite } from "@alinea/suite";
|
|
2
|
-
import { assert, assertEquals, assertExists } from "@std/assert";
|
|
3
|
-
import { configure, type LogRecord, reset } from "@logtape/logtape";
|
|
4
|
-
import { Hono } from "hono";
|
|
5
|
-
import { honoLogger } from "./mod.ts";
|
|
6
|
-
|
|
7
|
-
const test = suite(import.meta);
|
|
8
|
-
|
|
9
|
-
// Test fixture: Collect log records, filtering out internal LogTape meta logs
|
|
10
|
-
function createTestSink(): {
|
|
11
|
-
sink: (record: LogRecord) => void;
|
|
12
|
-
logs: LogRecord[];
|
|
13
|
-
} {
|
|
14
|
-
const logs: LogRecord[] = [];
|
|
15
|
-
return {
|
|
16
|
-
sink: (record: LogRecord) => {
|
|
17
|
-
if (record.category[0] !== "logtape") {
|
|
18
|
-
logs.push(record);
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
logs,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Setup helper
|
|
26
|
-
async function setupLogtape(): Promise<{
|
|
27
|
-
logs: LogRecord[];
|
|
28
|
-
cleanup: () => Promise<void>;
|
|
29
|
-
}> {
|
|
30
|
-
const { sink, logs } = createTestSink();
|
|
31
|
-
await configure({
|
|
32
|
-
sinks: { test: sink },
|
|
33
|
-
loggers: [{ category: [], sinks: ["test"] }],
|
|
34
|
-
});
|
|
35
|
-
return { logs, cleanup: () => reset() };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ============================================
|
|
39
|
-
// Basic Middleware Creation Tests
|
|
40
|
-
// ============================================
|
|
41
|
-
|
|
42
|
-
test("honoLogger(): creates a middleware function", async () => {
|
|
43
|
-
const { cleanup } = await setupLogtape();
|
|
44
|
-
try {
|
|
45
|
-
const middleware = honoLogger();
|
|
46
|
-
assertEquals(typeof middleware, "function");
|
|
47
|
-
} finally {
|
|
48
|
-
await cleanup();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("honoLogger(): logs request after response", async () => {
|
|
53
|
-
const { logs, cleanup } = await setupLogtape();
|
|
54
|
-
try {
|
|
55
|
-
const app = new Hono();
|
|
56
|
-
app.use(honoLogger());
|
|
57
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
58
|
-
|
|
59
|
-
const res = await app.request("/test");
|
|
60
|
-
assertEquals(res.status, 200);
|
|
61
|
-
assertEquals(logs.length, 1);
|
|
62
|
-
} finally {
|
|
63
|
-
await cleanup();
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// ============================================
|
|
68
|
-
// Category Configuration Tests
|
|
69
|
-
// ============================================
|
|
70
|
-
|
|
71
|
-
test("honoLogger(): uses default category ['hono']", async () => {
|
|
72
|
-
const { logs, cleanup } = await setupLogtape();
|
|
73
|
-
try {
|
|
74
|
-
const app = new Hono();
|
|
75
|
-
app.use(honoLogger());
|
|
76
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
77
|
-
|
|
78
|
-
await app.request("/test");
|
|
79
|
-
|
|
80
|
-
assertEquals(logs.length, 1);
|
|
81
|
-
assertEquals(logs[0].category, ["hono"]);
|
|
82
|
-
} finally {
|
|
83
|
-
await cleanup();
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("honoLogger(): uses custom category array", async () => {
|
|
88
|
-
const { logs, cleanup } = await setupLogtape();
|
|
89
|
-
try {
|
|
90
|
-
const app = new Hono();
|
|
91
|
-
app.use(honoLogger({ category: ["myapp", "http"] }));
|
|
92
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
93
|
-
|
|
94
|
-
await app.request("/test");
|
|
95
|
-
|
|
96
|
-
assertEquals(logs.length, 1);
|
|
97
|
-
assertEquals(logs[0].category, ["myapp", "http"]);
|
|
98
|
-
} finally {
|
|
99
|
-
await cleanup();
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("honoLogger(): accepts string category", async () => {
|
|
104
|
-
const { logs, cleanup } = await setupLogtape();
|
|
105
|
-
try {
|
|
106
|
-
const app = new Hono();
|
|
107
|
-
app.use(honoLogger({ category: "myapp" }));
|
|
108
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
109
|
-
|
|
110
|
-
await app.request("/test");
|
|
111
|
-
|
|
112
|
-
assertEquals(logs.length, 1);
|
|
113
|
-
assertEquals(logs[0].category, ["myapp"]);
|
|
114
|
-
} finally {
|
|
115
|
-
await cleanup();
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ============================================
|
|
120
|
-
// Log Level Tests
|
|
121
|
-
// ============================================
|
|
122
|
-
|
|
123
|
-
test("honoLogger(): uses default log level 'info'", async () => {
|
|
124
|
-
const { logs, cleanup } = await setupLogtape();
|
|
125
|
-
try {
|
|
126
|
-
const app = new Hono();
|
|
127
|
-
app.use(honoLogger());
|
|
128
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
129
|
-
|
|
130
|
-
await app.request("/test");
|
|
131
|
-
|
|
132
|
-
assertEquals(logs.length, 1);
|
|
133
|
-
assertEquals(logs[0].level, "info");
|
|
134
|
-
} finally {
|
|
135
|
-
await cleanup();
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test("honoLogger(): uses custom log level 'debug'", async () => {
|
|
140
|
-
const { logs, cleanup } = await setupLogtape();
|
|
141
|
-
try {
|
|
142
|
-
const app = new Hono();
|
|
143
|
-
app.use(honoLogger({ level: "debug" }));
|
|
144
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
145
|
-
|
|
146
|
-
await app.request("/test");
|
|
147
|
-
|
|
148
|
-
assertEquals(logs.length, 1);
|
|
149
|
-
assertEquals(logs[0].level, "debug");
|
|
150
|
-
} finally {
|
|
151
|
-
await cleanup();
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("honoLogger(): uses custom log level 'warning'", async () => {
|
|
156
|
-
const { logs, cleanup } = await setupLogtape();
|
|
157
|
-
try {
|
|
158
|
-
const app = new Hono();
|
|
159
|
-
app.use(honoLogger({ level: "warning" }));
|
|
160
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
161
|
-
|
|
162
|
-
await app.request("/test");
|
|
163
|
-
|
|
164
|
-
assertEquals(logs.length, 1);
|
|
165
|
-
assertEquals(logs[0].level, "warning");
|
|
166
|
-
} finally {
|
|
167
|
-
await cleanup();
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// ============================================
|
|
172
|
-
// Format Tests - Combined (default)
|
|
173
|
-
// ============================================
|
|
174
|
-
|
|
175
|
-
test("honoLogger(): combined format logs structured properties", async () => {
|
|
176
|
-
const { logs, cleanup } = await setupLogtape();
|
|
177
|
-
try {
|
|
178
|
-
const app = new Hono();
|
|
179
|
-
app.use(honoLogger({ format: "combined" }));
|
|
180
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
181
|
-
|
|
182
|
-
await app.request("/test", {
|
|
183
|
-
headers: {
|
|
184
|
-
"User-Agent": "test-agent/1.0",
|
|
185
|
-
"Referer": "http://example.com",
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
assertEquals(logs.length, 1);
|
|
190
|
-
const props = logs[0].properties;
|
|
191
|
-
assertEquals(props.method, "GET");
|
|
192
|
-
assert((props.url as string).includes("/test"));
|
|
193
|
-
assertEquals(props.path, "/test");
|
|
194
|
-
assertEquals(props.status, 200);
|
|
195
|
-
assertExists(props.responseTime);
|
|
196
|
-
assertEquals(props.userAgent, "test-agent/1.0");
|
|
197
|
-
assertEquals(props.referrer, "http://example.com");
|
|
198
|
-
} finally {
|
|
199
|
-
await cleanup();
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// ============================================
|
|
204
|
-
// Format Tests - Common
|
|
205
|
-
// ============================================
|
|
206
|
-
|
|
207
|
-
test("honoLogger(): common format excludes referrer and userAgent", async () => {
|
|
208
|
-
const { logs, cleanup } = await setupLogtape();
|
|
209
|
-
try {
|
|
210
|
-
const app = new Hono();
|
|
211
|
-
app.use(honoLogger({ format: "common" }));
|
|
212
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
213
|
-
|
|
214
|
-
await app.request("/test", {
|
|
215
|
-
headers: {
|
|
216
|
-
"User-Agent": "test-agent/1.0",
|
|
217
|
-
"Referer": "http://example.com",
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
assertEquals(logs.length, 1);
|
|
222
|
-
const props = logs[0].properties;
|
|
223
|
-
assertEquals(props.method, "GET");
|
|
224
|
-
assertEquals(props.path, "/test");
|
|
225
|
-
assertEquals(props.status, 200);
|
|
226
|
-
assertEquals(props.referrer, undefined);
|
|
227
|
-
assertEquals(props.userAgent, undefined);
|
|
228
|
-
} finally {
|
|
229
|
-
await cleanup();
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// ============================================
|
|
234
|
-
// Format Tests - Dev
|
|
235
|
-
// ============================================
|
|
236
|
-
|
|
237
|
-
test("honoLogger(): dev format returns string message", async () => {
|
|
238
|
-
const { logs, cleanup } = await setupLogtape();
|
|
239
|
-
try {
|
|
240
|
-
const app = new Hono();
|
|
241
|
-
app.use(honoLogger({ format: "dev" }));
|
|
242
|
-
app.post("/api/users", (c) => {
|
|
243
|
-
c.status(201);
|
|
244
|
-
return c.text("Created");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
await app.request("/api/users", { method: "POST" });
|
|
248
|
-
|
|
249
|
-
assertEquals(logs.length, 1);
|
|
250
|
-
const msg = logs[0].rawMessage;
|
|
251
|
-
assert(msg.includes("POST"));
|
|
252
|
-
assert(msg.includes("/api/users"));
|
|
253
|
-
assert(msg.includes("201"));
|
|
254
|
-
assert(msg.includes("ms"));
|
|
255
|
-
} finally {
|
|
256
|
-
await cleanup();
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// ============================================
|
|
261
|
-
// Format Tests - Short
|
|
262
|
-
// ============================================
|
|
263
|
-
|
|
264
|
-
test("honoLogger(): short format includes url", async () => {
|
|
265
|
-
const { logs, cleanup } = await setupLogtape();
|
|
266
|
-
try {
|
|
267
|
-
const app = new Hono();
|
|
268
|
-
app.use(honoLogger({ format: "short" }));
|
|
269
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
270
|
-
|
|
271
|
-
await app.request("/test");
|
|
272
|
-
|
|
273
|
-
assertEquals(logs.length, 1);
|
|
274
|
-
const msg = logs[0].rawMessage;
|
|
275
|
-
assert(msg.includes("GET"));
|
|
276
|
-
assert(msg.includes("/test"));
|
|
277
|
-
assert(msg.includes("200"));
|
|
278
|
-
assert(msg.includes("ms"));
|
|
279
|
-
} finally {
|
|
280
|
-
await cleanup();
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// ============================================
|
|
285
|
-
// Format Tests - Tiny
|
|
286
|
-
// ============================================
|
|
287
|
-
|
|
288
|
-
test("honoLogger(): tiny format is minimal", async () => {
|
|
289
|
-
const { logs, cleanup } = await setupLogtape();
|
|
290
|
-
try {
|
|
291
|
-
const app = new Hono();
|
|
292
|
-
app.use(honoLogger({ format: "tiny" }));
|
|
293
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
294
|
-
|
|
295
|
-
const res = await app.request("/test");
|
|
296
|
-
assertEquals(res.status, 200);
|
|
297
|
-
|
|
298
|
-
assertEquals(logs.length, 1);
|
|
299
|
-
const msg = logs[0].rawMessage;
|
|
300
|
-
assert(msg.includes("GET"));
|
|
301
|
-
assert(msg.includes("/test"));
|
|
302
|
-
assert(msg.includes("200"));
|
|
303
|
-
assert(msg.includes("ms"));
|
|
304
|
-
} finally {
|
|
305
|
-
await cleanup();
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// ============================================
|
|
310
|
-
// Custom Format Function Tests
|
|
311
|
-
// ============================================
|
|
312
|
-
|
|
313
|
-
test("honoLogger(): custom format returning string", async () => {
|
|
314
|
-
const { logs, cleanup } = await setupLogtape();
|
|
315
|
-
try {
|
|
316
|
-
const app = new Hono();
|
|
317
|
-
app.use(honoLogger({
|
|
318
|
-
format: (c, _responseTime) => `Custom: ${c.req.method} ${c.res.status}`,
|
|
319
|
-
}));
|
|
320
|
-
app.delete("/test", (c) => {
|
|
321
|
-
c.status(204);
|
|
322
|
-
return c.body(null);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
await app.request("/test", { method: "DELETE" });
|
|
326
|
-
|
|
327
|
-
assertEquals(logs.length, 1);
|
|
328
|
-
assertEquals(logs[0].rawMessage, "Custom: DELETE 204");
|
|
329
|
-
} finally {
|
|
330
|
-
await cleanup();
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
test("honoLogger(): custom format returning object", async () => {
|
|
335
|
-
const { logs, cleanup } = await setupLogtape();
|
|
336
|
-
try {
|
|
337
|
-
const app = new Hono();
|
|
338
|
-
app.use(honoLogger({
|
|
339
|
-
format: (c, responseTime) => ({
|
|
340
|
-
customMethod: c.req.method,
|
|
341
|
-
customStatus: c.res.status,
|
|
342
|
-
customDuration: responseTime,
|
|
343
|
-
}),
|
|
344
|
-
}));
|
|
345
|
-
app.patch("/test", (c) => {
|
|
346
|
-
c.status(202);
|
|
347
|
-
return c.text("Accepted");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
await app.request("/test", { method: "PATCH" });
|
|
351
|
-
|
|
352
|
-
assertEquals(logs.length, 1);
|
|
353
|
-
assertEquals(logs[0].properties.customMethod, "PATCH");
|
|
354
|
-
assertEquals(logs[0].properties.customStatus, 202);
|
|
355
|
-
assertExists(logs[0].properties.customDuration);
|
|
356
|
-
} finally {
|
|
357
|
-
await cleanup();
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// ============================================
|
|
362
|
-
// Skip Function Tests
|
|
363
|
-
// ============================================
|
|
364
|
-
|
|
365
|
-
test("honoLogger(): skip function prevents logging when returns true", async () => {
|
|
366
|
-
const { logs, cleanup } = await setupLogtape();
|
|
367
|
-
try {
|
|
368
|
-
const app = new Hono();
|
|
369
|
-
app.use(honoLogger({
|
|
370
|
-
skip: () => true,
|
|
371
|
-
}));
|
|
372
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
373
|
-
|
|
374
|
-
await app.request("/test");
|
|
375
|
-
|
|
376
|
-
assertEquals(logs.length, 0);
|
|
377
|
-
} finally {
|
|
378
|
-
await cleanup();
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test("honoLogger(): skip function allows logging when returns false", async () => {
|
|
383
|
-
const { logs, cleanup } = await setupLogtape();
|
|
384
|
-
try {
|
|
385
|
-
const app = new Hono();
|
|
386
|
-
app.use(honoLogger({
|
|
387
|
-
skip: () => false,
|
|
388
|
-
}));
|
|
389
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
390
|
-
|
|
391
|
-
await app.request("/test");
|
|
392
|
-
|
|
393
|
-
assertEquals(logs.length, 1);
|
|
394
|
-
} finally {
|
|
395
|
-
await cleanup();
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("honoLogger(): skip function receives context", async () => {
|
|
400
|
-
const { logs, cleanup } = await setupLogtape();
|
|
401
|
-
try {
|
|
402
|
-
const app = new Hono();
|
|
403
|
-
app.use(honoLogger({
|
|
404
|
-
skip: (c) => c.req.path === "/health",
|
|
405
|
-
}));
|
|
406
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
407
|
-
app.get("/health", (c) => c.text("OK"));
|
|
408
|
-
|
|
409
|
-
// Health endpoint should be skipped
|
|
410
|
-
await app.request("/health");
|
|
411
|
-
assertEquals(logs.length, 0);
|
|
412
|
-
|
|
413
|
-
// Other endpoints should be logged
|
|
414
|
-
await app.request("/test");
|
|
415
|
-
assertEquals(logs.length, 1);
|
|
416
|
-
} finally {
|
|
417
|
-
await cleanup();
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// ============================================
|
|
422
|
-
// logRequest (Immediate) Mode Tests
|
|
423
|
-
// ============================================
|
|
424
|
-
|
|
425
|
-
test("honoLogger(): logRequest mode logs at request start", async () => {
|
|
426
|
-
const { logs, cleanup } = await setupLogtape();
|
|
427
|
-
try {
|
|
428
|
-
const app = new Hono();
|
|
429
|
-
app.use(honoLogger({ logRequest: true }));
|
|
430
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
431
|
-
|
|
432
|
-
await app.request("/test");
|
|
433
|
-
|
|
434
|
-
assertEquals(logs.length, 1);
|
|
435
|
-
assertEquals(logs[0].properties.responseTime, 0); // Zero because it's immediate
|
|
436
|
-
} finally {
|
|
437
|
-
await cleanup();
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
test("honoLogger(): non-logRequest mode logs after response", async () => {
|
|
442
|
-
const { logs, cleanup } = await setupLogtape();
|
|
443
|
-
try {
|
|
444
|
-
const app = new Hono();
|
|
445
|
-
app.use(honoLogger({ logRequest: false }));
|
|
446
|
-
app.get("/test", async (c) => {
|
|
447
|
-
// Small delay to ensure non-zero response time
|
|
448
|
-
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
449
|
-
return c.text("Hello");
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
await app.request("/test");
|
|
453
|
-
|
|
454
|
-
assertEquals(logs.length, 1);
|
|
455
|
-
assert((logs[0].properties.responseTime as number) >= 0);
|
|
456
|
-
} finally {
|
|
457
|
-
await cleanup();
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// ============================================
|
|
462
|
-
// Request Property Tests
|
|
463
|
-
// ============================================
|
|
464
|
-
|
|
465
|
-
test("honoLogger(): logs correct method", async () => {
|
|
466
|
-
const { logs, cleanup } = await setupLogtape();
|
|
467
|
-
try {
|
|
468
|
-
const app = new Hono();
|
|
469
|
-
app.use(honoLogger());
|
|
470
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
471
|
-
app.post("/test", (c) => c.text("Hello"));
|
|
472
|
-
app.put("/test", (c) => c.text("Hello"));
|
|
473
|
-
app.delete("/test", (c) => c.text("Hello"));
|
|
474
|
-
|
|
475
|
-
const methods = ["GET", "POST", "PUT", "DELETE"];
|
|
476
|
-
for (const method of methods) {
|
|
477
|
-
await app.request("/test", { method });
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
assertEquals(logs.length, methods.length);
|
|
481
|
-
for (let i = 0; i < methods.length; i++) {
|
|
482
|
-
assertEquals(logs[i].properties.method, methods[i]);
|
|
483
|
-
}
|
|
484
|
-
} finally {
|
|
485
|
-
await cleanup();
|
|
486
|
-
}
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
test("honoLogger(): logs path correctly", async () => {
|
|
490
|
-
const { logs, cleanup } = await setupLogtape();
|
|
491
|
-
try {
|
|
492
|
-
const app = new Hono();
|
|
493
|
-
app.use(honoLogger());
|
|
494
|
-
app.get("/api/v1/users", (c) => c.text("Hello"));
|
|
495
|
-
|
|
496
|
-
await app.request("/api/v1/users");
|
|
497
|
-
|
|
498
|
-
assertEquals(logs.length, 1);
|
|
499
|
-
assertEquals(logs[0].properties.path, "/api/v1/users");
|
|
500
|
-
} finally {
|
|
501
|
-
await cleanup();
|
|
502
|
-
}
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
test("honoLogger(): logs status code", async () => {
|
|
506
|
-
const { logs, cleanup } = await setupLogtape();
|
|
507
|
-
try {
|
|
508
|
-
const app = new Hono();
|
|
509
|
-
app.use(honoLogger());
|
|
510
|
-
|
|
511
|
-
app.get("/200", (c) => c.text("OK"));
|
|
512
|
-
app.get("/201", (c) => {
|
|
513
|
-
c.status(201);
|
|
514
|
-
return c.text("Created");
|
|
515
|
-
});
|
|
516
|
-
app.get("/400", (c) => {
|
|
517
|
-
c.status(400);
|
|
518
|
-
return c.text("Bad Request");
|
|
519
|
-
});
|
|
520
|
-
app.get("/500", (c) => {
|
|
521
|
-
c.status(500);
|
|
522
|
-
return c.text("Error");
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
const paths = ["/200", "/201", "/400", "/500"];
|
|
526
|
-
const expectedStatuses = [200, 201, 400, 500];
|
|
527
|
-
|
|
528
|
-
for (const path of paths) {
|
|
529
|
-
await app.request(path);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
assertEquals(logs.length, paths.length);
|
|
533
|
-
for (let i = 0; i < paths.length; i++) {
|
|
534
|
-
assertEquals(logs[i].properties.status, expectedStatuses[i]);
|
|
535
|
-
}
|
|
536
|
-
} finally {
|
|
537
|
-
await cleanup();
|
|
538
|
-
}
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
test("honoLogger(): logs response time as number", async () => {
|
|
542
|
-
const { logs, cleanup } = await setupLogtape();
|
|
543
|
-
try {
|
|
544
|
-
const app = new Hono();
|
|
545
|
-
app.use(honoLogger());
|
|
546
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
547
|
-
|
|
548
|
-
await app.request("/test");
|
|
549
|
-
|
|
550
|
-
assertEquals(logs.length, 1);
|
|
551
|
-
assertEquals(typeof logs[0].properties.responseTime, "number");
|
|
552
|
-
assert((logs[0].properties.responseTime as number) >= 0);
|
|
553
|
-
} finally {
|
|
554
|
-
await cleanup();
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
test("honoLogger(): logs user agent", async () => {
|
|
559
|
-
const { logs, cleanup } = await setupLogtape();
|
|
560
|
-
try {
|
|
561
|
-
const app = new Hono();
|
|
562
|
-
app.use(honoLogger());
|
|
563
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
564
|
-
|
|
565
|
-
await app.request("/test", {
|
|
566
|
-
headers: { "User-Agent": "TestClient/1.0" },
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
assertEquals(logs.length, 1);
|
|
570
|
-
assertEquals(logs[0].properties.userAgent, "TestClient/1.0");
|
|
571
|
-
} finally {
|
|
572
|
-
await cleanup();
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
test("honoLogger(): logs referrer", async () => {
|
|
577
|
-
const { logs, cleanup } = await setupLogtape();
|
|
578
|
-
try {
|
|
579
|
-
const app = new Hono();
|
|
580
|
-
app.use(honoLogger());
|
|
581
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
582
|
-
|
|
583
|
-
await app.request("/test", {
|
|
584
|
-
headers: { "Referer": "https://example.com/page" },
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
assertEquals(logs.length, 1);
|
|
588
|
-
assertEquals(logs[0].properties.referrer, "https://example.com/page");
|
|
589
|
-
} finally {
|
|
590
|
-
await cleanup();
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
// ============================================
|
|
595
|
-
// Multiple Requests Tests
|
|
596
|
-
// ============================================
|
|
597
|
-
|
|
598
|
-
test("honoLogger(): handles multiple sequential requests", async () => {
|
|
599
|
-
const { logs, cleanup } = await setupLogtape();
|
|
600
|
-
try {
|
|
601
|
-
const app = new Hono();
|
|
602
|
-
app.use(honoLogger());
|
|
603
|
-
app.get("/path/:id", (c) => c.text(`Path: ${c.req.param("id")}`));
|
|
604
|
-
|
|
605
|
-
for (let i = 0; i < 5; i++) {
|
|
606
|
-
await app.request(`/path/${i}`);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
assertEquals(logs.length, 5);
|
|
610
|
-
for (let i = 0; i < 5; i++) {
|
|
611
|
-
assertEquals(logs[i].properties.path, `/path/${i}`);
|
|
612
|
-
}
|
|
613
|
-
} finally {
|
|
614
|
-
await cleanup();
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
|
|
618
|
-
// ============================================
|
|
619
|
-
// Edge Cases
|
|
620
|
-
// ============================================
|
|
621
|
-
|
|
622
|
-
test("honoLogger(): handles missing user-agent", async () => {
|
|
623
|
-
const { logs, cleanup } = await setupLogtape();
|
|
624
|
-
try {
|
|
625
|
-
const app = new Hono();
|
|
626
|
-
app.use(honoLogger());
|
|
627
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
628
|
-
|
|
629
|
-
await app.request("/test");
|
|
630
|
-
|
|
631
|
-
assertEquals(logs.length, 1);
|
|
632
|
-
assertEquals(logs[0].properties.userAgent, undefined);
|
|
633
|
-
} finally {
|
|
634
|
-
await cleanup();
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
test("honoLogger(): handles missing referrer", async () => {
|
|
639
|
-
const { logs, cleanup } = await setupLogtape();
|
|
640
|
-
try {
|
|
641
|
-
const app = new Hono();
|
|
642
|
-
app.use(honoLogger());
|
|
643
|
-
app.get("/test", (c) => c.text("Hello"));
|
|
644
|
-
|
|
645
|
-
await app.request("/test");
|
|
646
|
-
|
|
647
|
-
assertEquals(logs.length, 1);
|
|
648
|
-
assertEquals(logs[0].properties.referrer, undefined);
|
|
649
|
-
} finally {
|
|
650
|
-
await cleanup();
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
test("honoLogger(): handles query parameters in url", async () => {
|
|
655
|
-
const { logs, cleanup } = await setupLogtape();
|
|
656
|
-
try {
|
|
657
|
-
const app = new Hono();
|
|
658
|
-
app.use(honoLogger());
|
|
659
|
-
app.get("/search", (c) => c.text(`Query: ${c.req.query("q")}`));
|
|
660
|
-
|
|
661
|
-
await app.request("/search?q=test&limit=10");
|
|
662
|
-
|
|
663
|
-
assertEquals(logs.length, 1);
|
|
664
|
-
assertEquals(logs[0].properties.path, "/search");
|
|
665
|
-
assert((logs[0].properties.url as string).includes("q=test"));
|
|
666
|
-
assert((logs[0].properties.url as string).includes("limit=10"));
|
|
667
|
-
} finally {
|
|
668
|
-
await cleanup();
|
|
669
|
-
}
|
|
670
|
-
});
|
package/src/mod.ts
DELETED
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
import { getLogger, type LogLevel } from "@logtape/logtape";
|
|
2
|
-
import { createMiddleware } from "hono/factory";
|
|
3
|
-
import type { MiddlewareHandler } from "hono";
|
|
4
|
-
|
|
5
|
-
export type { LogLevel } from "@logtape/logtape";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Minimal Hono Context interface for compatibility across Hono versions.
|
|
9
|
-
* @since 1.3.0
|
|
10
|
-
*/
|
|
11
|
-
export interface HonoContext {
|
|
12
|
-
req: {
|
|
13
|
-
method: string;
|
|
14
|
-
url: string;
|
|
15
|
-
path: string;
|
|
16
|
-
header(name: string): string | undefined;
|
|
17
|
-
};
|
|
18
|
-
res: {
|
|
19
|
-
status: number;
|
|
20
|
-
headers: {
|
|
21
|
-
get(name: string): string | null;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Predefined log format names compatible with Morgan.
|
|
28
|
-
* @since 1.3.0
|
|
29
|
-
*/
|
|
30
|
-
export type PredefinedFormat = "combined" | "common" | "dev" | "short" | "tiny";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Custom format function for request logging.
|
|
34
|
-
*
|
|
35
|
-
* @param c The Hono context object.
|
|
36
|
-
* @param responseTime The response time in milliseconds.
|
|
37
|
-
* @returns A string message or an object with structured properties.
|
|
38
|
-
* @since 1.3.0
|
|
39
|
-
*/
|
|
40
|
-
export type FormatFunction = (
|
|
41
|
-
c: HonoContext,
|
|
42
|
-
responseTime: number,
|
|
43
|
-
) => string | Record<string, unknown>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Structured log properties for HTTP requests.
|
|
47
|
-
* @since 1.3.0
|
|
48
|
-
*/
|
|
49
|
-
export interface RequestLogProperties {
|
|
50
|
-
/** HTTP request method */
|
|
51
|
-
method: string;
|
|
52
|
-
/** Request URL */
|
|
53
|
-
url: string;
|
|
54
|
-
/** Request path */
|
|
55
|
-
path: string;
|
|
56
|
-
/** HTTP response status code */
|
|
57
|
-
status: number;
|
|
58
|
-
/** Response time in milliseconds */
|
|
59
|
-
responseTime: number;
|
|
60
|
-
/** Response content-length header value */
|
|
61
|
-
contentLength: string | undefined;
|
|
62
|
-
/** User-Agent header value */
|
|
63
|
-
userAgent: string | undefined;
|
|
64
|
-
/** Referrer header value */
|
|
65
|
-
referrer: string | undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Options for configuring the Hono LogTape middleware.
|
|
70
|
-
* @since 1.3.0
|
|
71
|
-
*/
|
|
72
|
-
export interface HonoLogTapeOptions {
|
|
73
|
-
/**
|
|
74
|
-
* The LogTape category to use for logging.
|
|
75
|
-
* @default ["hono"]
|
|
76
|
-
*/
|
|
77
|
-
readonly category?: string | readonly string[];
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* The log level to use for request logging.
|
|
81
|
-
* @default "info"
|
|
82
|
-
*/
|
|
83
|
-
readonly level?: LogLevel;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* The format for log output.
|
|
87
|
-
* Can be a predefined format name or a custom format function.
|
|
88
|
-
*
|
|
89
|
-
* Predefined formats:
|
|
90
|
-
* - `"combined"` - Apache Combined Log Format (structured, default)
|
|
91
|
-
* - `"common"` - Apache Common Log Format (structured, no referrer/userAgent)
|
|
92
|
-
* - `"dev"` - Concise colored output for development (string)
|
|
93
|
-
* - `"short"` - Shorter than common (string)
|
|
94
|
-
* - `"tiny"` - Minimal output (string)
|
|
95
|
-
*
|
|
96
|
-
* @default "combined"
|
|
97
|
-
*/
|
|
98
|
-
readonly format?: PredefinedFormat | FormatFunction;
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Function to determine whether logging should be skipped.
|
|
102
|
-
* Return `true` to skip logging for a request.
|
|
103
|
-
*
|
|
104
|
-
* @example Skip logging for health check endpoint
|
|
105
|
-
* ```typescript
|
|
106
|
-
* app.use(honoLogger({
|
|
107
|
-
* skip: (c) => c.req.path === "/health",
|
|
108
|
-
* }));
|
|
109
|
-
* ```
|
|
110
|
-
*
|
|
111
|
-
* @default () => false
|
|
112
|
-
*/
|
|
113
|
-
readonly skip?: (c: HonoContext) => boolean;
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* If `true`, logs are written immediately when the request is received.
|
|
117
|
-
* If `false` (default), logs are written after the response is sent.
|
|
118
|
-
*
|
|
119
|
-
* Note: When `logRequest` is `true`, response-related properties
|
|
120
|
-
* (status, responseTime, contentLength) will not be available.
|
|
121
|
-
*
|
|
122
|
-
* @default false
|
|
123
|
-
*/
|
|
124
|
-
readonly logRequest?: boolean;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Get referrer from request headers.
|
|
129
|
-
*/
|
|
130
|
-
function getReferrer(c: HonoContext): string | undefined {
|
|
131
|
-
return c.req.header("referrer") || c.req.header("referer");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get user agent from request headers.
|
|
136
|
-
*/
|
|
137
|
-
function getUserAgent(c: HonoContext): string | undefined {
|
|
138
|
-
return c.req.header("user-agent");
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get content length from response headers.
|
|
143
|
-
*/
|
|
144
|
-
function getContentLength(c: HonoContext): string | undefined {
|
|
145
|
-
const contentLength = c.res.headers.get("content-length");
|
|
146
|
-
if (contentLength === null) return undefined;
|
|
147
|
-
return contentLength;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Build structured log properties from context.
|
|
152
|
-
*/
|
|
153
|
-
function buildProperties(
|
|
154
|
-
c: HonoContext,
|
|
155
|
-
responseTime: number,
|
|
156
|
-
): RequestLogProperties {
|
|
157
|
-
return {
|
|
158
|
-
method: c.req.method,
|
|
159
|
-
url: c.req.url,
|
|
160
|
-
path: c.req.path,
|
|
161
|
-
status: c.res.status,
|
|
162
|
-
responseTime,
|
|
163
|
-
contentLength: getContentLength(c),
|
|
164
|
-
userAgent: getUserAgent(c),
|
|
165
|
-
referrer: getReferrer(c),
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Combined format (Apache Combined Log Format).
|
|
171
|
-
* Returns all structured properties.
|
|
172
|
-
*/
|
|
173
|
-
function formatCombined(
|
|
174
|
-
c: HonoContext,
|
|
175
|
-
responseTime: number,
|
|
176
|
-
): Record<string, unknown> {
|
|
177
|
-
return { ...buildProperties(c, responseTime) };
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Common format (Apache Common Log Format).
|
|
182
|
-
* Like combined but without referrer and userAgent.
|
|
183
|
-
*/
|
|
184
|
-
function formatCommon(
|
|
185
|
-
c: HonoContext,
|
|
186
|
-
responseTime: number,
|
|
187
|
-
): Record<string, unknown> {
|
|
188
|
-
const props = buildProperties(c, responseTime);
|
|
189
|
-
const { referrer: _referrer, userAgent: _userAgent, ...rest } = props;
|
|
190
|
-
return rest;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Dev format (colored output for development).
|
|
195
|
-
* :method :path :status :response-time ms - :res[content-length]
|
|
196
|
-
*/
|
|
197
|
-
function formatDev(
|
|
198
|
-
c: HonoContext,
|
|
199
|
-
responseTime: number,
|
|
200
|
-
): string {
|
|
201
|
-
const contentLength = getContentLength(c) ?? "-";
|
|
202
|
-
return `${c.req.method} ${c.req.path} ${c.res.status} ${
|
|
203
|
-
responseTime.toFixed(3)
|
|
204
|
-
} ms - ${contentLength}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Short format.
|
|
209
|
-
* :method :url :status :res[content-length] - :response-time ms
|
|
210
|
-
*/
|
|
211
|
-
function formatShort(
|
|
212
|
-
c: HonoContext,
|
|
213
|
-
responseTime: number,
|
|
214
|
-
): string {
|
|
215
|
-
const contentLength = getContentLength(c) ?? "-";
|
|
216
|
-
return `${c.req.method} ${c.req.url} ${c.res.status} ${contentLength} - ${
|
|
217
|
-
responseTime.toFixed(3)
|
|
218
|
-
} ms`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Tiny format (minimal output).
|
|
223
|
-
* :method :path :status :res[content-length] - :response-time ms
|
|
224
|
-
*/
|
|
225
|
-
function formatTiny(
|
|
226
|
-
c: HonoContext,
|
|
227
|
-
responseTime: number,
|
|
228
|
-
): string {
|
|
229
|
-
const contentLength = getContentLength(c) ?? "-";
|
|
230
|
-
return `${c.req.method} ${c.req.path} ${c.res.status} ${contentLength} - ${
|
|
231
|
-
responseTime.toFixed(3)
|
|
232
|
-
} ms`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Map of predefined format functions.
|
|
237
|
-
*/
|
|
238
|
-
const predefinedFormats: Record<PredefinedFormat, FormatFunction> = {
|
|
239
|
-
combined: formatCombined,
|
|
240
|
-
common: formatCommon,
|
|
241
|
-
dev: formatDev,
|
|
242
|
-
short: formatShort,
|
|
243
|
-
tiny: formatTiny,
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Normalize category to array format.
|
|
248
|
-
*/
|
|
249
|
-
function normalizeCategory(
|
|
250
|
-
category: string | readonly string[],
|
|
251
|
-
): readonly string[] {
|
|
252
|
-
return typeof category === "string" ? [category] : category;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Creates Hono middleware for HTTP request logging using LogTape.
|
|
257
|
-
*
|
|
258
|
-
* This middleware provides Morgan-compatible request logging with LogTape
|
|
259
|
-
* as the backend, supporting structured logging and customizable formats.
|
|
260
|
-
*
|
|
261
|
-
* @example Basic usage
|
|
262
|
-
* ```typescript
|
|
263
|
-
* import { Hono } from "hono";
|
|
264
|
-
* import { configure, getConsoleSink } from "@logtape/logtape";
|
|
265
|
-
* import { honoLogger } from "@logtape/hono";
|
|
266
|
-
*
|
|
267
|
-
* await configure({
|
|
268
|
-
* sinks: { console: getConsoleSink() },
|
|
269
|
-
* loggers: [
|
|
270
|
-
* { category: ["hono"], sinks: ["console"], lowestLevel: "info" }
|
|
271
|
-
* ],
|
|
272
|
-
* });
|
|
273
|
-
*
|
|
274
|
-
* const app = new Hono();
|
|
275
|
-
* app.use(honoLogger());
|
|
276
|
-
*
|
|
277
|
-
* app.get("/", (c) => c.json({ hello: "world" }));
|
|
278
|
-
*
|
|
279
|
-
* export default app;
|
|
280
|
-
* ```
|
|
281
|
-
*
|
|
282
|
-
* @example With custom options
|
|
283
|
-
* ```typescript
|
|
284
|
-
* app.use(honoLogger({
|
|
285
|
-
* category: ["myapp", "http"],
|
|
286
|
-
* level: "debug",
|
|
287
|
-
* format: "dev",
|
|
288
|
-
* skip: (c) => c.req.path === "/health",
|
|
289
|
-
* }));
|
|
290
|
-
* ```
|
|
291
|
-
*
|
|
292
|
-
* @example With custom format function
|
|
293
|
-
* ```typescript
|
|
294
|
-
* app.use(honoLogger({
|
|
295
|
-
* format: (c, responseTime) => ({
|
|
296
|
-
* method: c.req.method,
|
|
297
|
-
* path: c.req.path,
|
|
298
|
-
* status: c.res.status,
|
|
299
|
-
* duration: responseTime,
|
|
300
|
-
* }),
|
|
301
|
-
* }));
|
|
302
|
-
* ```
|
|
303
|
-
*
|
|
304
|
-
* @param options Configuration options for the middleware.
|
|
305
|
-
* @returns Hono middleware function.
|
|
306
|
-
* @since 1.3.0
|
|
307
|
-
*/
|
|
308
|
-
export function honoLogger(
|
|
309
|
-
options: HonoLogTapeOptions = {},
|
|
310
|
-
): MiddlewareHandler {
|
|
311
|
-
const category = normalizeCategory(options.category ?? ["hono"]);
|
|
312
|
-
const logger = getLogger(category);
|
|
313
|
-
const level = options.level ?? "info";
|
|
314
|
-
const formatOption = options.format ?? "combined";
|
|
315
|
-
const skip = options.skip ?? (() => false);
|
|
316
|
-
const logRequest = options.logRequest ?? false;
|
|
317
|
-
|
|
318
|
-
// Resolve format function
|
|
319
|
-
const formatFn: FormatFunction = typeof formatOption === "string"
|
|
320
|
-
? predefinedFormats[formatOption]
|
|
321
|
-
: formatOption;
|
|
322
|
-
|
|
323
|
-
const logMethod = logger[level].bind(logger);
|
|
324
|
-
|
|
325
|
-
return createMiddleware(async (c, next) => {
|
|
326
|
-
const startTime = Date.now();
|
|
327
|
-
|
|
328
|
-
// For immediate logging, log when request arrives
|
|
329
|
-
if (logRequest) {
|
|
330
|
-
if (!skip(c as unknown as HonoContext)) {
|
|
331
|
-
const result = formatFn(c as unknown as HonoContext, 0);
|
|
332
|
-
if (typeof result === "string") {
|
|
333
|
-
logMethod(result);
|
|
334
|
-
} else {
|
|
335
|
-
logMethod("{method} {url}", result);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
await next();
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Log after response is sent
|
|
343
|
-
await next();
|
|
344
|
-
|
|
345
|
-
if (skip(c as unknown as HonoContext)) return;
|
|
346
|
-
|
|
347
|
-
const responseTime = Date.now() - startTime;
|
|
348
|
-
const result = formatFn(c as unknown as HonoContext, responseTime);
|
|
349
|
-
|
|
350
|
-
if (typeof result === "string") {
|
|
351
|
-
logMethod(result);
|
|
352
|
-
} else {
|
|
353
|
-
logMethod("{method} {url} {status} - {responseTime} ms", result);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
}
|