@donkeylabs/server 0.1.1 → 0.1.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/cli/donkeylabs +6 -0
- package/context.d.ts +17 -0
- package/docs/api-client.md +520 -0
- package/docs/cache.md +437 -0
- package/docs/cli.md +353 -0
- package/docs/core-services.md +338 -0
- package/docs/cron.md +465 -0
- package/docs/errors.md +303 -0
- package/docs/events.md +460 -0
- package/docs/handlers.md +549 -0
- package/docs/jobs.md +556 -0
- package/docs/logger.md +316 -0
- package/docs/middleware.md +682 -0
- package/docs/plugins.md +524 -0
- package/docs/project-structure.md +493 -0
- package/docs/rate-limiter.md +525 -0
- package/docs/router.md +566 -0
- package/docs/sse.md +542 -0
- package/docs/svelte-frontend.md +324 -0
- package/package.json +12 -9
- package/registry.d.ts +11 -0
- package/src/index.ts +1 -1
- package/src/server.ts +1 -0
package/docs/logger.md
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Logger Service
|
|
2
|
+
|
|
3
|
+
Structured logging with configurable levels, custom transports, and child loggers for contextual logging.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// Access via ctx.core.logger
|
|
9
|
+
ctx.core.logger.info("User logged in", { userId: 123 });
|
|
10
|
+
ctx.core.logger.error("Payment failed", { orderId: 456, error: "Insufficient funds" });
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## API Reference
|
|
16
|
+
|
|
17
|
+
### Interface
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
interface Logger {
|
|
21
|
+
debug(message: string, data?: Record<string, any>): void;
|
|
22
|
+
info(message: string, data?: Record<string, any>): void;
|
|
23
|
+
warn(message: string, data?: Record<string, any>): void;
|
|
24
|
+
error(message: string, data?: Record<string, any>): void;
|
|
25
|
+
child(context: Record<string, any>): Logger;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Log Levels
|
|
30
|
+
|
|
31
|
+
| Level | Priority | Use Case |
|
|
32
|
+
|-------|----------|----------|
|
|
33
|
+
| `debug` | 0 | Detailed debugging information |
|
|
34
|
+
| `info` | 1 | General operational messages |
|
|
35
|
+
| `warn` | 2 | Warning conditions |
|
|
36
|
+
| `error` | 3 | Error conditions |
|
|
37
|
+
|
|
38
|
+
Only messages at or above the configured level are logged.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const server = new AppServer({
|
|
46
|
+
db,
|
|
47
|
+
logger: {
|
|
48
|
+
level: "info", // Minimum level to log (default: "info")
|
|
49
|
+
format: "pretty", // "pretty" or "json" (default: "pretty")
|
|
50
|
+
transports: [], // Custom transports (optional)
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Usage Examples
|
|
58
|
+
|
|
59
|
+
### Basic Logging
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
router.route("checkout").typed({
|
|
63
|
+
handle: async (input, ctx) => {
|
|
64
|
+
ctx.core.logger.info("Checkout started", {
|
|
65
|
+
userId: ctx.user.id,
|
|
66
|
+
cartTotal: input.total,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const order = await processPayment(input);
|
|
71
|
+
ctx.core.logger.info("Payment successful", { orderId: order.id });
|
|
72
|
+
return order;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
ctx.core.logger.error("Payment failed", {
|
|
75
|
+
userId: ctx.user.id,
|
|
76
|
+
error: error.message,
|
|
77
|
+
});
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Child Loggers
|
|
85
|
+
|
|
86
|
+
Child loggers inherit parent settings and add persistent context:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
// In plugin initialization
|
|
90
|
+
service: async (ctx) => {
|
|
91
|
+
// Create logger with plugin context
|
|
92
|
+
const log = ctx.core.logger.child({ plugin: "payments" });
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
async processPayment(orderId: string) {
|
|
96
|
+
// Create request-specific logger
|
|
97
|
+
const requestLog = log.child({ orderId });
|
|
98
|
+
|
|
99
|
+
requestLog.info("Processing payment");
|
|
100
|
+
// Logs: { plugin: "payments", orderId: "123", ... }
|
|
101
|
+
|
|
102
|
+
requestLog.debug("Validating card");
|
|
103
|
+
requestLog.info("Payment complete");
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Request Logging Middleware
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const requestLogger = createMiddleware(async (req, ctx, next) => {
|
|
113
|
+
const start = Date.now();
|
|
114
|
+
const requestId = ctx.requestId;
|
|
115
|
+
|
|
116
|
+
// Create request-scoped logger
|
|
117
|
+
const log = ctx.core.logger.child({
|
|
118
|
+
requestId,
|
|
119
|
+
method: req.method,
|
|
120
|
+
path: new URL(req.url).pathname,
|
|
121
|
+
ip: ctx.ip,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
log.info("Request started");
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await next();
|
|
128
|
+
log.info("Request completed", {
|
|
129
|
+
status: response.status,
|
|
130
|
+
duration: Date.now() - start,
|
|
131
|
+
});
|
|
132
|
+
return response;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
log.error("Request failed", {
|
|
135
|
+
error: error.message,
|
|
136
|
+
duration: Date.now() - start,
|
|
137
|
+
});
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Output Formats
|
|
146
|
+
|
|
147
|
+
### Pretty Format (Default)
|
|
148
|
+
|
|
149
|
+
Human-readable colored output for development:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
[12:34:56.789] INFO User logged in {"userId":123}
|
|
153
|
+
[12:34:56.790] ERROR Payment failed {"orderId":456,"error":"Insufficient funds"}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### JSON Format
|
|
157
|
+
|
|
158
|
+
Structured JSON for production log aggregation:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{"timestamp":"2024-01-15T12:34:56.789Z","level":"info","message":"User logged in","userId":123}
|
|
162
|
+
{"timestamp":"2024-01-15T12:34:56.790Z","level":"error","message":"Payment failed","orderId":456,"error":"Insufficient funds"}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Custom Transports
|
|
168
|
+
|
|
169
|
+
Create custom transports to send logs to external services:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import { createLogger, type LogTransport, type LogEntry } from "./core/logger";
|
|
173
|
+
|
|
174
|
+
// Custom transport for external service
|
|
175
|
+
class DatadogTransport implements LogTransport {
|
|
176
|
+
constructor(private apiKey: string) {}
|
|
177
|
+
|
|
178
|
+
log(entry: LogEntry): void {
|
|
179
|
+
fetch("https://http-intake.logs.datadoghq.com/v1/input", {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: {
|
|
182
|
+
"Content-Type": "application/json",
|
|
183
|
+
"DD-API-KEY": this.apiKey,
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
timestamp: entry.timestamp.toISOString(),
|
|
187
|
+
level: entry.level,
|
|
188
|
+
message: entry.message,
|
|
189
|
+
...entry.data,
|
|
190
|
+
...entry.context,
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// File transport
|
|
197
|
+
class FileTransport implements LogTransport {
|
|
198
|
+
constructor(private filePath: string) {}
|
|
199
|
+
|
|
200
|
+
log(entry: LogEntry): void {
|
|
201
|
+
const line = JSON.stringify({
|
|
202
|
+
timestamp: entry.timestamp.toISOString(),
|
|
203
|
+
level: entry.level,
|
|
204
|
+
message: entry.message,
|
|
205
|
+
...entry.data,
|
|
206
|
+
...entry.context,
|
|
207
|
+
}) + "\n";
|
|
208
|
+
|
|
209
|
+
Bun.write(this.filePath, line, { append: true });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Use custom transports
|
|
214
|
+
const logger = createLogger({
|
|
215
|
+
level: "info",
|
|
216
|
+
transports: [
|
|
217
|
+
new ConsoleTransport("pretty"),
|
|
218
|
+
new DatadogTransport(process.env.DD_API_KEY!),
|
|
219
|
+
new FileTransport("./logs/app.log"),
|
|
220
|
+
],
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Best Practices
|
|
227
|
+
|
|
228
|
+
### 1. Use Appropriate Levels
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// Debug - detailed technical info (disabled in production)
|
|
232
|
+
log.debug("Cache lookup", { key, hit: !!cached });
|
|
233
|
+
|
|
234
|
+
// Info - notable events
|
|
235
|
+
log.info("Order created", { orderId, total });
|
|
236
|
+
|
|
237
|
+
// Warn - unexpected but handled conditions
|
|
238
|
+
log.warn("Retry attempt", { attempt: 3, maxAttempts: 5 });
|
|
239
|
+
|
|
240
|
+
// Error - failures requiring attention
|
|
241
|
+
log.error("Database connection lost", { error: err.message });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### 2. Include Relevant Context
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
// Bad - missing context
|
|
248
|
+
log.error("Failed");
|
|
249
|
+
|
|
250
|
+
// Good - actionable information
|
|
251
|
+
log.error("Payment processing failed", {
|
|
252
|
+
userId: user.id,
|
|
253
|
+
orderId: order.id,
|
|
254
|
+
amount: order.total,
|
|
255
|
+
provider: "stripe",
|
|
256
|
+
error: err.message,
|
|
257
|
+
errorCode: err.code,
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 3. Use Child Loggers for Scopes
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// Create scoped loggers for different concerns
|
|
265
|
+
const dbLog = logger.child({ component: "database" });
|
|
266
|
+
const authLog = logger.child({ component: "auth" });
|
|
267
|
+
const apiLog = logger.child({ component: "api" });
|
|
268
|
+
|
|
269
|
+
// Each log includes its scope
|
|
270
|
+
dbLog.info("Query executed"); // includes component: "database"
|
|
271
|
+
authLog.info("Token validated"); // includes component: "auth"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 4. Don't Log Sensitive Data
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
// Bad - exposes password
|
|
278
|
+
log.info("Login attempt", { email, password });
|
|
279
|
+
|
|
280
|
+
// Good - redact sensitive fields
|
|
281
|
+
log.info("Login attempt", { email, passwordProvided: !!password });
|
|
282
|
+
|
|
283
|
+
// Bad - exposes token
|
|
284
|
+
log.debug("Auth header", { authorization: req.headers.get("authorization") });
|
|
285
|
+
|
|
286
|
+
// Good - mask token
|
|
287
|
+
log.debug("Auth header present", { hasAuth: !!req.headers.get("authorization") });
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## LogEntry Structure
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
interface LogEntry {
|
|
296
|
+
timestamp: Date;
|
|
297
|
+
level: "debug" | "info" | "warn" | "error";
|
|
298
|
+
message: string;
|
|
299
|
+
data?: Record<string, any>; // Per-call data
|
|
300
|
+
context?: Record<string, any>; // From child logger
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Environment-Based Configuration
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const server = new AppServer({
|
|
310
|
+
db,
|
|
311
|
+
logger: {
|
|
312
|
+
level: process.env.NODE_ENV === "production" ? "info" : "debug",
|
|
313
|
+
format: process.env.NODE_ENV === "production" ? "json" : "pretty",
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
```
|