@lssm/lib.logger 1.2.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 +550 -0
- package/dist/_virtual/rolldown_runtime.cjs +1 -0
- package/dist/context.cjs +1 -0
- package/dist/context.d.cts +44 -0
- package/dist/context.d.cts.map +1 -0
- package/dist/context.d.ts +44 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/elysia-plugin.cjs +1 -0
- package/dist/elysia-plugin.d.cts +64 -0
- package/dist/elysia-plugin.d.cts.map +1 -0
- package/dist/elysia-plugin.d.ts +64 -0
- package/dist/elysia-plugin.d.ts.map +1 -0
- package/dist/elysia-plugin.js +2 -0
- package/dist/elysia-plugin.js.map +1 -0
- package/dist/formatters.cjs +9 -0
- package/dist/formatters.d.cts +29 -0
- package/dist/formatters.d.cts.map +1 -0
- package/dist/formatters.d.ts +29 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +10 -0
- package/dist/formatters.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +1 -0
- package/dist/logger.cjs +1 -0
- package/dist/logger.d.cts +51 -0
- package/dist/logger.d.cts.map +1 -0
- package/dist/logger.d.ts +51 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +2 -0
- package/dist/logger.js.map +1 -0
- package/dist/timer.cjs +1 -0
- package/dist/timer.d.cts +103 -0
- package/dist/timer.d.cts.map +1 -0
- package/dist/timer.d.ts +103 -0
- package/dist/timer.d.ts.map +1 -0
- package/dist/timer.js +2 -0
- package/dist/timer.js.map +1 -0
- package/dist/tracer.cjs +1 -0
- package/dist/tracer.d.cts +51 -0
- package/dist/tracer.d.cts.map +1 -0
- package/dist/tracer.d.ts +51 -0
- package/dist/tracer.d.ts.map +1 -0
- package/dist/tracer.js +2 -0
- package/dist/tracer.js.map +1 -0
- package/dist/types.cjs +1 -0
- package/dist/types.d.cts +71 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# @lssm/lib.logger
|
|
2
|
+
|
|
3
|
+
This library builds with tsdown using the shared `@lssm/tool.tsdown` presets.
|
|
4
|
+
|
|
5
|
+
A comprehensive logging library optimized for Bun with **first-class ElysiaJS integration**, distributed tracing, context management, and beautiful development output.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Optimized for Bun** - Uses Bun's native performance APIs and optimized for Bun runtime
|
|
10
|
+
- 🦋 **ElysiaJS Native** - Seamless integration with [ElysiaJS](https://elysiajs.com) lifecycle and context
|
|
11
|
+
- 🔍 **Distributed Tracing** - Track operations across HTTP, WebSocket, cron, and queue processing
|
|
12
|
+
- ⏱️ **Performance Timing** - Built-in high-precision timing for operations
|
|
13
|
+
- 🔄 **Context Management** - Maintain context across async operations (like cls-hooked/nestjs-cls)
|
|
14
|
+
- 🎨 **Environment-aware Output** - Pretty logs in dev, structured JSON in production
|
|
15
|
+
- 🔌 **Easy Integrations** - Built-in middleware for HTTP, WebSocket, cron, and queues
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bun add @lssm/lib.logger
|
|
21
|
+
# If using ElysiaJS
|
|
22
|
+
bun add elysia @lssm/lib.logger
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { logger, Logger, LogLevel } from "@lssm/lib.logger";
|
|
29
|
+
|
|
30
|
+
// Basic logging
|
|
31
|
+
logger.info("Server started", { port: 3000 });
|
|
32
|
+
logger.error("Database error", { table: "users" }, error);
|
|
33
|
+
|
|
34
|
+
// Create a custom logger
|
|
35
|
+
const customLogger = new Logger({
|
|
36
|
+
level: LogLevel.DEBUG,
|
|
37
|
+
environment: "production",
|
|
38
|
+
enableTracing: true,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Core Logging
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { logger } from "@lssm/lib.logger";
|
|
46
|
+
|
|
47
|
+
// Different log levels
|
|
48
|
+
logger.trace("Detailed debug info"); // TRACE level
|
|
49
|
+
logger.debug("Debug information"); // DEBUG level
|
|
50
|
+
logger.info("General information"); // INFO level
|
|
51
|
+
logger.warn("Warning message"); // WARN level
|
|
52
|
+
logger.error("Error occurred", {}, err); // ERROR level
|
|
53
|
+
logger.fatal("Critical error", {}, err); // FATAL level
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Context Management
|
|
57
|
+
|
|
58
|
+
The logger maintains context across async operations using AsyncLocalStorage:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { logger } from "@lssm/lib.logger";
|
|
62
|
+
|
|
63
|
+
// Run code within a context
|
|
64
|
+
logger.withContext({ userId: "123", requestId: "abc" }, () => {
|
|
65
|
+
logger.info("Processing user request"); // Will include userId and requestId
|
|
66
|
+
|
|
67
|
+
someAsyncOperation(); // Context is maintained across async calls
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Extend existing context
|
|
71
|
+
logger.extendContext({ operation: "payment" }, () => {
|
|
72
|
+
logger.info("Processing payment"); // Includes all previous context + operation
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Set individual context values
|
|
76
|
+
logger.setContext("sessionId", "xyz789");
|
|
77
|
+
logger.info("User action"); // Will include sessionId
|
|
78
|
+
|
|
79
|
+
// Get current context
|
|
80
|
+
const context = logger.getContext();
|
|
81
|
+
console.log(context); // { userId: '123', requestId: 'abc', sessionId: 'xyz789' }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Distributed Tracing
|
|
85
|
+
|
|
86
|
+
Track operations across your application with automatic span management:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { logger } from "@lssm/lib.logger";
|
|
90
|
+
|
|
91
|
+
// Trace an operation
|
|
92
|
+
await logger.trace(
|
|
93
|
+
{
|
|
94
|
+
operationType: "http",
|
|
95
|
+
operationName: "GET /api/users",
|
|
96
|
+
metadata: { userId: "123" },
|
|
97
|
+
tags: ["api", "users"],
|
|
98
|
+
autoTiming: true,
|
|
99
|
+
},
|
|
100
|
+
async () => {
|
|
101
|
+
// Your operation code here
|
|
102
|
+
const users = await fetchUsers();
|
|
103
|
+
return users;
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Manual span management
|
|
108
|
+
const span = logger.startSpan({
|
|
109
|
+
operationType: "database",
|
|
110
|
+
operationName: "user-query",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await database.query("SELECT * FROM users");
|
|
115
|
+
logger.addTraceMetadata("rowCount", result.length);
|
|
116
|
+
logger.addTraceTags("database", "users");
|
|
117
|
+
return result;
|
|
118
|
+
} finally {
|
|
119
|
+
logger.finishSpan(span.spanId);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Performance Timing
|
|
124
|
+
|
|
125
|
+
Measure execution times with high precision:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { logger } from "@lssm/lib.logger";
|
|
129
|
+
|
|
130
|
+
// Start a timer
|
|
131
|
+
const timer = logger.startTimer("database-operation");
|
|
132
|
+
|
|
133
|
+
// ... do some work ...
|
|
134
|
+
|
|
135
|
+
const duration = timer.stop();
|
|
136
|
+
console.log(`Operation took ${duration}ms`);
|
|
137
|
+
|
|
138
|
+
// Profile function execution
|
|
139
|
+
const result = await logger.profile(
|
|
140
|
+
"expensive-calculation",
|
|
141
|
+
async () => {
|
|
142
|
+
return await someExpensiveOperation();
|
|
143
|
+
},
|
|
144
|
+
{ logResult: true, logLevel: LogLevel.INFO }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// Manual timing with laps
|
|
148
|
+
const timer2 = logger.startTimer("multi-step-process");
|
|
149
|
+
timer2.lap("step-1-complete");
|
|
150
|
+
// ... more work ...
|
|
151
|
+
timer2.lap("step-2-complete");
|
|
152
|
+
const total = timer2.stop();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## ElysiaJS Integration
|
|
156
|
+
|
|
157
|
+
### Basic Setup
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { Elysia } from "elysia";
|
|
161
|
+
import { elysiaLogger } from "@lssm/lib.logger";
|
|
162
|
+
|
|
163
|
+
const app = new Elysia()
|
|
164
|
+
.use(
|
|
165
|
+
elysiaLogger({
|
|
166
|
+
logRequests: true,
|
|
167
|
+
logResponses: true,
|
|
168
|
+
excludePaths: ["/health", "/metrics"],
|
|
169
|
+
})
|
|
170
|
+
)
|
|
171
|
+
.get("/", ({ logInfo }) => {
|
|
172
|
+
logInfo("Processing request");
|
|
173
|
+
return "Hello World";
|
|
174
|
+
})
|
|
175
|
+
.listen(3000);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The plugin automatically:
|
|
179
|
+
|
|
180
|
+
- ✅ Logs all incoming requests with timing
|
|
181
|
+
- ✅ Logs responses with status codes and duration
|
|
182
|
+
- ✅ Traces operations with correlation IDs
|
|
183
|
+
- ✅ Maintains context across async operations
|
|
184
|
+
- ✅ Provides helper functions in route context
|
|
185
|
+
|
|
186
|
+
### Advanced ElysiaJS Usage
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { Elysia } from "elysia";
|
|
190
|
+
import {
|
|
191
|
+
elysiaLogger,
|
|
192
|
+
createDatabaseUtils,
|
|
193
|
+
Logger,
|
|
194
|
+
LogLevel,
|
|
195
|
+
} from "@lssm/lib.logger";
|
|
196
|
+
|
|
197
|
+
const customLogger = new Logger({
|
|
198
|
+
level: LogLevel.DEBUG,
|
|
199
|
+
environment: "production",
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const db = createDatabaseUtils(customLogger);
|
|
203
|
+
|
|
204
|
+
const app = new Elysia()
|
|
205
|
+
.use(
|
|
206
|
+
elysiaLogger({
|
|
207
|
+
logger: customLogger,
|
|
208
|
+
maskSensitiveData: true,
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
.derive(() => ({ db }))
|
|
212
|
+
.get("/users", async ({ logInfo, traceOperation, db }) => {
|
|
213
|
+
logInfo("Fetching users from database");
|
|
214
|
+
|
|
215
|
+
const users = await traceOperation("fetch-users", async () => {
|
|
216
|
+
return db.query("select-users", () =>
|
|
217
|
+
// Your database query here
|
|
218
|
+
Promise.resolve([{ id: 1, name: "John" }])
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return users;
|
|
223
|
+
})
|
|
224
|
+
.listen(3000);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### ElysiaJS WebSocket Logging
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { Elysia } from "elysia";
|
|
231
|
+
import { elysiaLogger, createWebSocketUtils } from "@lssm/lib.logger";
|
|
232
|
+
|
|
233
|
+
const wsLogger = createWebSocketUtils();
|
|
234
|
+
|
|
235
|
+
const app = new Elysia()
|
|
236
|
+
.use(elysiaLogger())
|
|
237
|
+
.ws("/chat", {
|
|
238
|
+
open(ws) {
|
|
239
|
+
const connectionId = crypto.randomUUID();
|
|
240
|
+
wsLogger.logConnection(connectionId, {
|
|
241
|
+
userAgent: ws.data.headers?.["user-agent"],
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
message(ws, message) {
|
|
245
|
+
wsLogger.logMessage(ws.id, typeof message, message.length);
|
|
246
|
+
ws.send(`Echo: ${message}`);
|
|
247
|
+
},
|
|
248
|
+
close(ws, code, reason) {
|
|
249
|
+
wsLogger.logDisconnection(ws.id, code, reason);
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
.listen(3000);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### ElysiaJS with Authentication Logging
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { Elysia } from "elysia";
|
|
259
|
+
import { elysiaLogger, createAuthUtils } from "@lssm/lib.logger";
|
|
260
|
+
|
|
261
|
+
const authLogger = createAuthUtils();
|
|
262
|
+
|
|
263
|
+
const app = new Elysia()
|
|
264
|
+
.use(elysiaLogger())
|
|
265
|
+
.derive(() => ({ authLogger }))
|
|
266
|
+
.post("/auth/login", async ({ body, authLogger }) => {
|
|
267
|
+
try {
|
|
268
|
+
// Your authentication logic
|
|
269
|
+
const user = await authenticateUser(body.email, body.password);
|
|
270
|
+
|
|
271
|
+
authLogger.logLogin(user.id, {
|
|
272
|
+
email: body.email,
|
|
273
|
+
ip: request.headers.get("x-forwarded-for"),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
return { success: true, token: generateToken(user) };
|
|
277
|
+
} catch (error) {
|
|
278
|
+
authLogger.logAuthFailure("invalid_credentials", {
|
|
279
|
+
email: body.email,
|
|
280
|
+
});
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
.listen(3000);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Production Configuration for ElysiaJS
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { Elysia } from "elysia";
|
|
291
|
+
import { elysiaLogger, Logger, LogLevel } from "@lssm/lib.logger";
|
|
292
|
+
|
|
293
|
+
const productionLogger = new Logger({
|
|
294
|
+
level: LogLevel.INFO,
|
|
295
|
+
environment: "production",
|
|
296
|
+
enableTracing: true,
|
|
297
|
+
enableTiming: true,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const app = new Elysia()
|
|
301
|
+
.use(
|
|
302
|
+
elysiaLogger({
|
|
303
|
+
logger: productionLogger,
|
|
304
|
+
logRequests: true,
|
|
305
|
+
logResponses: true,
|
|
306
|
+
excludePaths: ["/health", "/metrics", "/favicon.ico"],
|
|
307
|
+
maskSensitiveData: true,
|
|
308
|
+
})
|
|
309
|
+
)
|
|
310
|
+
.get("/health", () => ({ status: "ok" })) // This won't be logged
|
|
311
|
+
.get("/api/*", ({ logInfo, traceOperation }) => {
|
|
312
|
+
// All API routes automatically logged and traced
|
|
313
|
+
return traceOperation("business-logic", async () => {
|
|
314
|
+
// Your business logic here
|
|
315
|
+
return { success: true };
|
|
316
|
+
});
|
|
317
|
+
})
|
|
318
|
+
.listen(3000);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## HTTP Integration (Non-ElysiaJS)
|
|
322
|
+
|
|
323
|
+
### Express/Hono Middleware
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { createHttpMiddleware } from "@lssm/lib.logger";
|
|
327
|
+
import express from "express";
|
|
328
|
+
|
|
329
|
+
const app = express();
|
|
330
|
+
|
|
331
|
+
// Add logging middleware
|
|
332
|
+
app.use(createHttpMiddleware()); // Uses default logger
|
|
333
|
+
|
|
334
|
+
// All subsequent requests will have context and tracing
|
|
335
|
+
app.get("/api/users", (req, res) => {
|
|
336
|
+
logger.info("Fetching users"); // Automatically includes request context
|
|
337
|
+
// ... handle request
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Bun HTTP Server
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { createBunHttpHandler } from "@lssm/lib.logger";
|
|
345
|
+
|
|
346
|
+
const handler = createBunHttpHandler(async (req) => {
|
|
347
|
+
// Request is automatically traced and timed
|
|
348
|
+
logger.info("Processing request"); // Includes request context
|
|
349
|
+
|
|
350
|
+
return new Response("Hello World");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
Bun.serve({
|
|
354
|
+
fetch: handler,
|
|
355
|
+
port: 3000,
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## WebSocket Integration
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { createWebSocketMiddleware } from "@lssm/lib.logger";
|
|
363
|
+
|
|
364
|
+
const wsMiddleware = createWebSocketMiddleware();
|
|
365
|
+
|
|
366
|
+
// WebSocket server setup
|
|
367
|
+
const server = Bun.serve({
|
|
368
|
+
websocket: {
|
|
369
|
+
open: wsMiddleware.onOpen,
|
|
370
|
+
message: wsMiddleware.onMessage,
|
|
371
|
+
close: wsMiddleware.onClose,
|
|
372
|
+
error: wsMiddleware.onError,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Cron Job Integration
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { createCronWrapper } from "@lssm/lib.logger";
|
|
381
|
+
|
|
382
|
+
const cronWrapper = createCronWrapper();
|
|
383
|
+
|
|
384
|
+
// Wrap your cron jobs
|
|
385
|
+
const wrappedJob = cronWrapper("daily-cleanup", async () => {
|
|
386
|
+
logger.info("Starting daily cleanup");
|
|
387
|
+
await cleanupOldData();
|
|
388
|
+
logger.info("Cleanup completed");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Schedule with your preferred cron library
|
|
392
|
+
schedule.scheduleJob("0 2 * * *", wrappedJob);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Queue Processing Integration
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { createQueueWrapper } from "@lssm/lib.logger";
|
|
399
|
+
|
|
400
|
+
const queueWrapper = createQueueWrapper();
|
|
401
|
+
|
|
402
|
+
// Wrap queue processors
|
|
403
|
+
const processEmailJob = queueWrapper.wrapProcessor(
|
|
404
|
+
"email-queue",
|
|
405
|
+
async (job: EmailJob, jobId: string) => {
|
|
406
|
+
logger.info("Processing email job", { recipient: job.recipient });
|
|
407
|
+
await sendEmail(job);
|
|
408
|
+
return { success: true };
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// Log queue events
|
|
413
|
+
queueWrapper.logEnqueue("email-queue", "job-123", emailData);
|
|
414
|
+
queueWrapper.logDequeue("email-queue", "job-123");
|
|
415
|
+
queueWrapper.logRetry("email-queue", "job-123", 2, 5);
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Database Integration
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { createDatabaseWrapper } from "@lssm/lib.logger";
|
|
422
|
+
|
|
423
|
+
const dbWrapper = createDatabaseWrapper();
|
|
424
|
+
|
|
425
|
+
// Wrap database operations
|
|
426
|
+
const users = await dbWrapper.wrapQuery(
|
|
427
|
+
"fetch-users",
|
|
428
|
+
() => prisma.user.findMany(),
|
|
429
|
+
{ table: "users", operation: "findMany" }
|
|
430
|
+
);
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
## Configuration
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
import { Logger, LogLevel } from "@lssm/lib.logger";
|
|
437
|
+
|
|
438
|
+
const logger = new Logger({
|
|
439
|
+
level: LogLevel.INFO, // Minimum log level
|
|
440
|
+
environment: "production", // 'development' | 'production' | 'test'
|
|
441
|
+
enableTracing: true, // Enable distributed tracing
|
|
442
|
+
enableTiming: true, // Enable performance timing
|
|
443
|
+
enableContext: true, // Enable context management
|
|
444
|
+
enableColors: true, // Enable colors in dev output
|
|
445
|
+
maxContextDepth: 10, // Max nested context depth
|
|
446
|
+
timestampFormat: "iso", // 'iso' | 'epoch' | 'relative'
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Custom Formatters
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
import {
|
|
454
|
+
Logger,
|
|
455
|
+
CustomFormatter,
|
|
456
|
+
DevFormatter,
|
|
457
|
+
ProductionFormatter,
|
|
458
|
+
} from "@lssm/lib.logger";
|
|
459
|
+
|
|
460
|
+
// Use custom formatter
|
|
461
|
+
const customFormatter = new CustomFormatter(
|
|
462
|
+
"{timestamp} [{level}] {traceId} {message}",
|
|
463
|
+
(date) => date.toLocaleDateString()
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const logger = new Logger();
|
|
467
|
+
logger.setFormatter(customFormatter);
|
|
468
|
+
|
|
469
|
+
// Or create your own
|
|
470
|
+
class MyFormatter implements Formatter {
|
|
471
|
+
format(entry: LogEntry): string {
|
|
472
|
+
return `${entry.timestamp} - ${entry.message}`;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## Example Output
|
|
478
|
+
|
|
479
|
+
### Development Mode (Pretty)
|
|
480
|
+
|
|
481
|
+
```
|
|
482
|
+
14:32:15.123 ● INFO [trace:a1b2c3d4|span:e5f6g7h8] HTTP request started (12.34ms)
|
|
483
|
+
Context: { userId: "123", requestId: "req-456" }
|
|
484
|
+
Metadata: { method: "GET", url: "/api/users", userAgent: "Mozilla/5.0..." }
|
|
485
|
+
|
|
486
|
+
14:32:15.135 ✖ ERROR Database connection failed (156.78ms)
|
|
487
|
+
Error: ConnectionError: Unable to connect to database
|
|
488
|
+
at DatabaseClient.connect (database.js:45:12)
|
|
489
|
+
at UserService.fetchUsers (user-service.js:23:8)
|
|
490
|
+
at ApiHandler.getUsers (api-handler.js:67:15)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Production Mode (JSON)
|
|
494
|
+
|
|
495
|
+
```json
|
|
496
|
+
{"timestamp":"2024-01-15T14:32:15.123Z","level":"info","message":"HTTP request started","traceId":"a1b2c3d4e5f6g7h8","spanId":"e5f6g7h8","duration":12.34,"context":{"userId":"123","requestId":"req-456"},"metadata":{"method":"GET","url":"/api/users"}}
|
|
497
|
+
|
|
498
|
+
{"timestamp":"2024-01-15T14:32:15.135Z","level":"error","message":"Database connection failed","traceId":"a1b2c3d4e5f6g7h8","duration":156.78,"error":{"name":"ConnectionError","message":"Unable to connect to database","stack":"ConnectionError: Unable to connect to database\n at DatabaseClient.connect..."}}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Best Practices
|
|
502
|
+
|
|
503
|
+
1. **Use context for request correlation**:
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
logger.withContext({ requestId, userId }, () => {
|
|
507
|
+
// All logs in this scope will include requestId and userId
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
2. **Trace important operations**:
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
await logger.trace(
|
|
515
|
+
{
|
|
516
|
+
operationType: "database",
|
|
517
|
+
operationName: "user-creation",
|
|
518
|
+
},
|
|
519
|
+
() => createUser(userData)
|
|
520
|
+
);
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
3. **Profile performance-critical code**:
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
const result = await logger.profile("data-processing", () => {
|
|
527
|
+
return processLargeDataset(data);
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
4. **Use appropriate log levels**:
|
|
532
|
+
|
|
533
|
+
- `trace`: Very detailed debugging (typically disabled in production)
|
|
534
|
+
- `debug`: Debugging information
|
|
535
|
+
- `info`: General application flow
|
|
536
|
+
- `warn`: Warning conditions
|
|
537
|
+
- `error`: Error conditions
|
|
538
|
+
- `fatal`: Critical errors that might cause the application to exit
|
|
539
|
+
|
|
540
|
+
5. **Clean up in production**:
|
|
541
|
+
```typescript
|
|
542
|
+
process.on("SIGTERM", async () => {
|
|
543
|
+
await logger.flush();
|
|
544
|
+
process.exit(0);
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## License
|
|
549
|
+
|
|
550
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));exports.__toESM=s;
|
package/dist/context.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require(`./_virtual/rolldown_runtime.cjs`);let t=require(`node:async_hooks`);t=e.__toESM(t);var n=class e{static instance;storage;constructor(){this.storage=new t.AsyncLocalStorage}static getInstance(){return e.instance||=new e,e.instance}run(e,t){let n={context:{...e},trace:this.getCurrentTrace()};return this.storage.run(n,t)}extend(e,t){let n={...this.getContext(),...e};return this.run(n,t)}set(e,t){let n=this.storage.getStore();n&&(n.context[e]=t)}get(e){return this.storage.getStore()?.context?.[e]}getContext(){return this.storage.getStore()?.context||{}}setTrace(e){let t=this.storage.getStore();t&&(t.trace=e)}getCurrentTrace(){return this.storage.getStore()?.trace}generateId(){return crypto.randomUUID()}};exports.LogContext=n;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ContextData, TraceContext } from "./types.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
declare class LogContext {
|
|
5
|
+
private static instance;
|
|
6
|
+
private storage;
|
|
7
|
+
constructor();
|
|
8
|
+
static getInstance(): LogContext;
|
|
9
|
+
/**
|
|
10
|
+
* Run a function with a new context
|
|
11
|
+
*/
|
|
12
|
+
run<T>(context: ContextData, fn: () => T): T;
|
|
13
|
+
/**
|
|
14
|
+
* Run a function with an extended context (merges with current)
|
|
15
|
+
*/
|
|
16
|
+
extend<T>(additionalContext: Partial<ContextData>, fn: () => T): T;
|
|
17
|
+
/**
|
|
18
|
+
* Set context data for the current execution context
|
|
19
|
+
*/
|
|
20
|
+
set(key: string, value: any): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get a specific context value
|
|
23
|
+
*/
|
|
24
|
+
get<T>(key: string): T | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Get all context data
|
|
27
|
+
*/
|
|
28
|
+
getContext(): ContextData;
|
|
29
|
+
/**
|
|
30
|
+
* Set trace context
|
|
31
|
+
*/
|
|
32
|
+
setTrace(trace: TraceContext): void;
|
|
33
|
+
/**
|
|
34
|
+
* Get current trace context
|
|
35
|
+
*/
|
|
36
|
+
getCurrentTrace(): TraceContext | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Generate a unique ID for requests/operations
|
|
39
|
+
*/
|
|
40
|
+
generateId(): string;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { LogContext };
|
|
44
|
+
//# sourceMappingURL=context.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.cts","names":[],"sources":["../src/context.ts"],"sourcesContent":[],"mappings":";;;cAQa,UAAA;;EAAA,QAAA,OAAU;EAQC,WAAA,CAAA;EAUN,OAAA,WAAA,CAAA,CAAA,EAVM,UAUN;EAAuB;;;EAWV,GAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAXb,WAWa,EAAA,EAAA,EAAA,GAAA,GAXU,CAWV,CAAA,EAXc,CAWd;EAAgC;;;EA+B/C,MAAA,CAAA,CAAA,CAAA,CAAA,iBAAA,EA/Be,OA+Bf,CA/BuB,WA+BvB,CAAA,EAAA,EAAA,EAAA,GAAA,GA/B+C,CA+B/C,CAAA,EA/BmD,CA+BnD;EAQE;;;;;;;uBAhBK;;;;gBAQP;;;;kBAQE;;;;qBAUG"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ContextData, TraceContext } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/context.d.ts
|
|
4
|
+
declare class LogContext {
|
|
5
|
+
private static instance;
|
|
6
|
+
private storage;
|
|
7
|
+
constructor();
|
|
8
|
+
static getInstance(): LogContext;
|
|
9
|
+
/**
|
|
10
|
+
* Run a function with a new context
|
|
11
|
+
*/
|
|
12
|
+
run<T>(context: ContextData, fn: () => T): T;
|
|
13
|
+
/**
|
|
14
|
+
* Run a function with an extended context (merges with current)
|
|
15
|
+
*/
|
|
16
|
+
extend<T>(additionalContext: Partial<ContextData>, fn: () => T): T;
|
|
17
|
+
/**
|
|
18
|
+
* Set context data for the current execution context
|
|
19
|
+
*/
|
|
20
|
+
set(key: string, value: any): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get a specific context value
|
|
23
|
+
*/
|
|
24
|
+
get<T>(key: string): T | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Get all context data
|
|
27
|
+
*/
|
|
28
|
+
getContext(): ContextData;
|
|
29
|
+
/**
|
|
30
|
+
* Set trace context
|
|
31
|
+
*/
|
|
32
|
+
setTrace(trace: TraceContext): void;
|
|
33
|
+
/**
|
|
34
|
+
* Get current trace context
|
|
35
|
+
*/
|
|
36
|
+
getCurrentTrace(): TraceContext | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Generate a unique ID for requests/operations
|
|
39
|
+
*/
|
|
40
|
+
generateId(): string;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { LogContext };
|
|
44
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","names":[],"sources":["../src/context.ts"],"sourcesContent":[],"mappings":";;;cAQa,UAAA;;EAAA,QAAA,OAAU;EAQC,WAAA,CAAA;EAUN,OAAA,WAAA,CAAA,CAAA,EAVM,UAUN;EAAuB;;;EAWV,GAAA,CAAA,CAAA,CAAA,CAAA,OAAA,EAXb,WAWa,EAAA,EAAA,EAAA,GAAA,GAXU,CAWV,CAAA,EAXc,CAWd;EAAgC;;;EA+B/C,MAAA,CAAA,CAAA,CAAA,CAAA,iBAAA,EA/Be,OA+Bf,CA/BuB,WA+BvB,CAAA,EAAA,EAAA,EAAA,GAAA,GA/B+C,CA+B/C,CAAA,EA/BmD,CA+BnD;EAQE;;;;;;;uBAhBK;;;;gBAQP;;;;kBAQE;;;;qBAUG"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{AsyncLocalStorage as e}from"node:async_hooks";var t=class t{static instance;storage;constructor(){this.storage=new e}static getInstance(){return t.instance||=new t,t.instance}run(e,t){let n={context:{...e},trace:this.getCurrentTrace()};return this.storage.run(n,t)}extend(e,t){let n={...this.getContext(),...e};return this.run(n,t)}set(e,t){let n=this.storage.getStore();n&&(n.context[e]=t)}get(e){return this.storage.getStore()?.context?.[e]}getContext(){return this.storage.getStore()?.context||{}}setTrace(e){let t=this.storage.getStore();t&&(t.trace=e)}getCurrentTrace(){return this.storage.getStore()?.trace}generateId(){return crypto.randomUUID()}};export{t as LogContext};
|
|
2
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","names":["contextData: LogContextData","mergedContext: ContextData"],"sources":["../src/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ContextData, TraceContext } from './types';\n\ninterface LogContextData {\n context: ContextData;\n trace?: TraceContext;\n}\n\nexport class LogContext {\n private static instance: LogContext;\n private storage: AsyncLocalStorage<LogContextData>;\n\n constructor() {\n this.storage = new AsyncLocalStorage<LogContextData>();\n }\n\n static getInstance(): LogContext {\n if (!LogContext.instance) {\n LogContext.instance = new LogContext();\n }\n return LogContext.instance;\n }\n\n /**\n * Run a function with a new context\n */\n run<T>(context: ContextData, fn: () => T): T {\n const contextData: LogContextData = {\n context: { ...context },\n trace: this.getCurrentTrace(),\n };\n return this.storage.run(contextData, fn) as T;\n }\n\n /**\n * Run a function with an extended context (merges with current)\n */\n extend<T>(additionalContext: Partial<ContextData>, fn: () => T): T {\n const currentContext = this.getContext();\n const mergedContext: ContextData = {\n ...currentContext,\n ...additionalContext,\n };\n return this.run(mergedContext, fn);\n }\n\n /**\n * Set context data for the current execution context\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n set(key: string, value: any): void {\n const current = this.storage.getStore();\n if (current) {\n current.context[key] = value;\n }\n }\n\n /**\n * Get a specific context value\n */\n get<T>(key: string): T | undefined {\n const current = this.storage.getStore();\n return current?.context?.[key];\n }\n\n /**\n * Get all context data\n */\n getContext(): ContextData {\n const current = this.storage.getStore();\n return current?.context || {};\n }\n\n /**\n * Set trace context\n */\n setTrace(trace: TraceContext): void {\n const current = this.storage.getStore();\n if (current) {\n current.trace = trace;\n }\n }\n\n /**\n * Get current trace context\n */\n getCurrentTrace(): TraceContext | undefined {\n const current = this.storage.getStore();\n return current?.trace;\n }\n /**\n * Generate a unique ID for requests/operations\n */\n generateId(): string {\n return crypto.randomUUID();\n }\n}\n"],"mappings":"qDAQA,IAAa,EAAb,MAAa,CAAW,CACtB,OAAe,SACf,QAEA,aAAc,CACZ,KAAK,QAAU,IAAI,EAGrB,OAAO,aAA0B,CAI/B,MAHA,CACE,EAAW,WAAW,IAAI,EAErB,EAAW,SAMpB,IAAO,EAAsB,EAAgB,CAC3C,IAAMA,EAA8B,CAClC,QAAS,CAAE,GAAG,EAAS,CACvB,MAAO,KAAK,iBAAiB,CAC9B,CACD,OAAO,KAAK,QAAQ,IAAI,EAAa,EAAG,CAM1C,OAAU,EAAyC,EAAgB,CAEjE,IAAMC,EAA6B,CACjC,GAFqB,KAAK,YAAY,CAGtC,GAAG,EACJ,CACD,OAAO,KAAK,IAAI,EAAe,EAAG,CAOpC,IAAI,EAAa,EAAkB,CACjC,IAAM,EAAU,KAAK,QAAQ,UAAU,CACnC,IACF,EAAQ,QAAQ,GAAO,GAO3B,IAAO,EAA4B,CAEjC,OADgB,KAAK,QAAQ,UAAU,EACvB,UAAU,GAM5B,YAA0B,CAExB,OADgB,KAAK,QAAQ,UAAU,EACvB,SAAW,EAAE,CAM/B,SAAS,EAA2B,CAClC,IAAM,EAAU,KAAK,QAAQ,UAAU,CACnC,IACF,EAAQ,MAAQ,GAOpB,iBAA4C,CAE1C,OADgB,KAAK,QAAQ,UAAU,EACvB,MAKlB,YAAqB,CACnB,OAAO,OAAO,YAAY"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require(`./_virtual/rolldown_runtime.cjs`),t=require(`./context.cjs`),n=require(`./logger.cjs`);require(`elysia`);function r(e={}){let{logger:r=new n.Logger,logRequests:i=!0,logResponses:a=!0,excludePaths:o=[`/health`,`/metrics`]}=e,s=t.LogContext.getInstance();return function(e){return e.derive(e=>{let{request:t,path:n}=e;if(o.some(e=>n.startsWith(e)))return{logger:r};let a=new URL(t.url),c={requestId:s.generateId(),method:t.method,url:t.url,path:a.pathname,userAgent:t.headers.get(`user-agent`)||void 0,timestamp:new Date().toISOString()},l=performance.now();return s.run(c,()=>{i&&r.info(`→ ${t.method} ${n}`,{method:t.method,path:n,userAgent:c.userAgent,requestId:c.requestId})}),{logger:r,requestContext:c,startTime:l}}).onAfterHandle(e=>{let{request:t,startTime:n,requestContext:r,logger:i}=e;if(!n||!r)return;let o=performance.now()-n,s=new URL(t.url).pathname;a&&i.info(`← 200 ${t.method} ${s}`,{method:t.method,path:s,duration:`${o.toFixed(2)}ms`,requestId:r.requestId})}).onError(e=>{let{request:t,error:n,code:r,startTime:i,requestContext:a,logger:o}=e;if(!i||!a)return;let s=performance.now()-i,c=new URL(t.url).pathname;o?.error(`✖ ${r} ${t.method} ${c}`,{method:t.method,path:c,error:n?.toString?.()||`Unknown error`,code:r,duration:`${s.toFixed(2)}ms`,requestId:a.requestId})}).derive(()=>({logInfo:(e,t)=>{r.info(e,t)},logError:(e,t,n)=>{r.error(e,n,t)},traceOperation:async(e,t)=>r.trace({operationType:`custom`,operationName:e,autoTiming:!0},t)}))}}const i=r;exports.createElysiaLogger=i,exports.elysiaLogger=r;
|