@logtape/logtape 2.1.0-dev.517 → 2.1.0-dev.520
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 +11 -2
- package/skills/logtape/SKILL.md +670 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logtape/logtape",
|
|
3
|
-
"version": "2.1.0-dev.
|
|
3
|
+
"version": "2.1.0-dev.520+36af6851",
|
|
4
4
|
"description": "Unobtrusive logging library with zero dependencies—library-first design for Deno/Node.js/Bun/browsers/edge",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"logging",
|
|
@@ -66,8 +66,17 @@
|
|
|
66
66
|
},
|
|
67
67
|
"sideEffects": false,
|
|
68
68
|
"files": [
|
|
69
|
-
"dist/"
|
|
69
|
+
"dist/",
|
|
70
|
+
"skills/"
|
|
70
71
|
],
|
|
72
|
+
"agents": {
|
|
73
|
+
"skills": [
|
|
74
|
+
{
|
|
75
|
+
"name": "logtape",
|
|
76
|
+
"path": "./skills/logtape"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
71
80
|
"devDependencies": {
|
|
72
81
|
"@alinea/suite": "^0.6.3",
|
|
73
82
|
"@std/assert": "npm:@jsr/std__assert@^1.0.13",
|
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: logtape
|
|
3
|
+
description: >
|
|
4
|
+
Use this skill when writing any code that uses LogTape for logging in
|
|
5
|
+
JavaScript or TypeScript. Covers getting loggers, the structured message
|
|
6
|
+
syntax, configuration, library author rules, context, lazy evaluation,
|
|
7
|
+
testing, and common mistakes to avoid. Trigger whenever the user is
|
|
8
|
+
adding logging to a project, debugging log output, or integrating LogTape
|
|
9
|
+
with a framework.
|
|
10
|
+
license: MIT
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
LogTape skill for AI coding assistants
|
|
14
|
+
======================================
|
|
15
|
+
|
|
16
|
+
LogTape is a zero-dependency, library-first logging framework for JavaScript
|
|
17
|
+
and TypeScript that works across Deno, Node.js, Bun, browsers, and edge
|
|
18
|
+
functions.
|
|
19
|
+
|
|
20
|
+
Full documentation: <https://logtape.org/>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
Getting a logger
|
|
24
|
+
----------------
|
|
25
|
+
|
|
26
|
+
Always use `getLogger()` with an **array** category to enable hierarchical
|
|
27
|
+
filtering. The hierarchy works like a path: a parent category's configuration
|
|
28
|
+
applies to all children.
|
|
29
|
+
|
|
30
|
+
~~~~ typescript
|
|
31
|
+
import { getLogger } from "@logtape/logtape";
|
|
32
|
+
|
|
33
|
+
// Good: array form enables hierarchical filtering
|
|
34
|
+
const logger = getLogger(["my-app", "users", "auth"]);
|
|
35
|
+
|
|
36
|
+
// Acceptable shorthand for a single-segment category
|
|
37
|
+
const rootLogger = getLogger("my-app");
|
|
38
|
+
~~~~
|
|
39
|
+
|
|
40
|
+
Choose category segments that reflect your module structure so that operators
|
|
41
|
+
can selectively enable/disable logging per subsystem.
|
|
42
|
+
|
|
43
|
+
Use `logger.getChild("sub")` to derive a child logger without repeating the
|
|
44
|
+
full category:
|
|
45
|
+
|
|
46
|
+
~~~~ typescript
|
|
47
|
+
const dbLogger = logger.getChild("database");
|
|
48
|
+
// category = ["my-app", "users", "auth", "database"]
|
|
49
|
+
~~~~
|
|
50
|
+
|
|
51
|
+
See <https://logtape.org/manual/categories.md> for details.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Structured messages
|
|
55
|
+
-------------------
|
|
56
|
+
|
|
57
|
+
Use **named placeholders** with a properties object. This keeps messages
|
|
58
|
+
parseable and properties searchable:
|
|
59
|
+
|
|
60
|
+
~~~~ typescript
|
|
61
|
+
// Correct: structured message with named placeholders
|
|
62
|
+
logger.info("User {userId} logged in from {ip}", { userId, ip });
|
|
63
|
+
|
|
64
|
+
// Correct: structured data without a message
|
|
65
|
+
logger.info({ userId, ip, action: "login" });
|
|
66
|
+
|
|
67
|
+
// Nested property access (since 1.2.0)
|
|
68
|
+
logger.info("Name: {user.name}", { user: { name: "Alice" } });
|
|
69
|
+
~~~~
|
|
70
|
+
|
|
71
|
+
Template literal syntax is available for quick debug logging but does **not**
|
|
72
|
+
produce structured data:
|
|
73
|
+
|
|
74
|
+
~~~~ typescript
|
|
75
|
+
// Template literal: convenient but not structured
|
|
76
|
+
logger.debug`User ${userId} logged in`;
|
|
77
|
+
~~~~
|
|
78
|
+
|
|
79
|
+
See <https://logtape.org/manual/struct.md> for full structured logging details.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Severity levels
|
|
83
|
+
---------------
|
|
84
|
+
|
|
85
|
+
LogTape provides six levels, from most to least verbose:
|
|
86
|
+
|
|
87
|
+
| Level | Use for |
|
|
88
|
+
| --------- | ----------------------------------------------------- |
|
|
89
|
+
| `trace` | Very fine-grained diagnostic output |
|
|
90
|
+
| `debug` | Developer-facing diagnostic messages |
|
|
91
|
+
| `info` | Normal operational events (startup, shutdown, etc.) |
|
|
92
|
+
| `warning` | Unexpected but recoverable situations |
|
|
93
|
+
| `error` | Errors that affect a single operation |
|
|
94
|
+
| `fatal` | Unrecoverable errors that require process termination |
|
|
95
|
+
|
|
96
|
+
Use the lowest appropriate level. Reserve `error`/`fatal` for actual failures;
|
|
97
|
+
avoid using them for expected conditions like validation errors.
|
|
98
|
+
|
|
99
|
+
See <https://logtape.org/manual/levels.md> for details.
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
Configuration
|
|
103
|
+
-------------
|
|
104
|
+
|
|
105
|
+
### Async configuration (most common)
|
|
106
|
+
|
|
107
|
+
`configure()` is **application-only**. It must be `await`ed and called
|
|
108
|
+
**exactly once** at startup (e.g., in your entry point):
|
|
109
|
+
|
|
110
|
+
~~~~ typescript
|
|
111
|
+
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
112
|
+
|
|
113
|
+
await configure({
|
|
114
|
+
sinks: {
|
|
115
|
+
console: getConsoleSink(),
|
|
116
|
+
},
|
|
117
|
+
loggers: [
|
|
118
|
+
{
|
|
119
|
+
category: "my-app",
|
|
120
|
+
lowestLevel: "debug",
|
|
121
|
+
sinks: ["console"],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
~~~~
|
|
126
|
+
|
|
127
|
+
### Synchronous configuration
|
|
128
|
+
|
|
129
|
+
Use `configureSync()` when you cannot use `await` (e.g., top-level in CommonJS,
|
|
130
|
+
or in a synchronous startup path):
|
|
131
|
+
|
|
132
|
+
~~~~ typescript
|
|
133
|
+
import { configureSync, getConsoleSink } from "@logtape/logtape";
|
|
134
|
+
|
|
135
|
+
configureSync({
|
|
136
|
+
sinks: {
|
|
137
|
+
console: getConsoleSink(),
|
|
138
|
+
},
|
|
139
|
+
loggers: [
|
|
140
|
+
{
|
|
141
|
+
category: "my-app",
|
|
142
|
+
lowestLevel: "debug",
|
|
143
|
+
sinks: ["console"],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
~~~~
|
|
148
|
+
|
|
149
|
+
> **Limitation:** `configureSync()` cannot use `AsyncDisposable` sinks such as
|
|
150
|
+
> `getStreamSink()` or sinks created with `fromAsyncSink()`.
|
|
151
|
+
|
|
152
|
+
### Key rules
|
|
153
|
+
|
|
154
|
+
- `configure()` returns a `Promise`; always `await` it.
|
|
155
|
+
- `configureSync()` returns `void`; do not `await` it.
|
|
156
|
+
- Call either one **once**. Calling again without resetting first throws
|
|
157
|
+
`ConfigError`.
|
|
158
|
+
- Do **not** mix async and sync: if you used `configure()`, reset with
|
|
159
|
+
`await reset()`; if you used `configureSync()`, reset with `resetSync()`.
|
|
160
|
+
- For tests, call `await reset()` (or `resetSync()`) in teardown.
|
|
161
|
+
|
|
162
|
+
### Framework-specific patterns
|
|
163
|
+
|
|
164
|
+
- **React**: configure **before** `createRoot()`.
|
|
165
|
+
- **Vue**: configure **before** `app.mount()`.
|
|
166
|
+
- **Next.js**: use *instrumentation.js* (server) or
|
|
167
|
+
*instrumentation-client.js* (client).
|
|
168
|
+
- **SvelteKit**: configure in *hooks.server.ts*.
|
|
169
|
+
|
|
170
|
+
See <https://logtape.org/manual/config.md> for all options.
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
Library author rule
|
|
174
|
+
-------------------
|
|
175
|
+
|
|
176
|
+
**Never call `configure()` or `configureSync()` in library code.** Libraries
|
|
177
|
+
should only call `getLogger()` and log messages. The application that depends
|
|
178
|
+
on your library decides how (or whether) to configure sinks and levels.
|
|
179
|
+
|
|
180
|
+
~~~~ typescript
|
|
181
|
+
// my-lib/src/client.ts — library code
|
|
182
|
+
import { getLogger } from "@logtape/logtape";
|
|
183
|
+
|
|
184
|
+
// Good: just get a logger, don't configure
|
|
185
|
+
const logger = getLogger(["my-lib", "client"]);
|
|
186
|
+
|
|
187
|
+
export function fetchData(url: string) {
|
|
188
|
+
logger.debug("Fetching {url}", { url });
|
|
189
|
+
// ...
|
|
190
|
+
}
|
|
191
|
+
~~~~
|
|
192
|
+
|
|
193
|
+
If your library wraps other LogTape-using libraries, use `withCategoryPrefix()`
|
|
194
|
+
to nest their logs under your category:
|
|
195
|
+
|
|
196
|
+
~~~~ typescript
|
|
197
|
+
import { withCategoryPrefix } from "@logtape/logtape";
|
|
198
|
+
|
|
199
|
+
export function myOperation() {
|
|
200
|
+
return withCategoryPrefix(["my-lib"], () => {
|
|
201
|
+
// Logs from inner libraries appear as ["my-lib", ...their-category]
|
|
202
|
+
innerLib.doWork();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
~~~~
|
|
206
|
+
|
|
207
|
+
> **Note:** `withCategoryPrefix()` requires `contextLocalStorage` to be
|
|
208
|
+
> configured by the application.
|
|
209
|
+
|
|
210
|
+
See <https://logtape.org/manual/library.md> for the full guide.
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
Context with `with()` and lazy evaluation
|
|
214
|
+
-----------------------------------------
|
|
215
|
+
|
|
216
|
+
### Adding explicit context
|
|
217
|
+
|
|
218
|
+
Use `logger.with()` to create a child logger that attaches properties to every
|
|
219
|
+
subsequent log call:
|
|
220
|
+
|
|
221
|
+
~~~~ typescript
|
|
222
|
+
const reqLogger = logger.with({ requestId, userId });
|
|
223
|
+
reqLogger.info("Processing order {orderId}", { orderId });
|
|
224
|
+
// Log record will contain requestId, userId, AND orderId
|
|
225
|
+
~~~~
|
|
226
|
+
|
|
227
|
+
### Implicit context (request tracing)
|
|
228
|
+
|
|
229
|
+
Use `withContext()` to propagate context across an entire call stack without
|
|
230
|
+
threading loggers manually. Requires `contextLocalStorage` in configuration:
|
|
231
|
+
|
|
232
|
+
~~~~ typescript
|
|
233
|
+
import { configure, getConsoleSink, withContext } from "@logtape/logtape";
|
|
234
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
235
|
+
|
|
236
|
+
await configure({
|
|
237
|
+
sinks: { console: getConsoleSink() },
|
|
238
|
+
loggers: [{ category: "app", sinks: ["console"] }],
|
|
239
|
+
contextLocalStorage: new AsyncLocalStorage(),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
function handleRequest(req: Request) {
|
|
243
|
+
withContext({ requestId: crypto.randomUUID() }, () => {
|
|
244
|
+
// All logs inside this callback automatically include requestId
|
|
245
|
+
processRequest(req);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
~~~~
|
|
249
|
+
|
|
250
|
+
> **Note:** Implicit contexts are not available in browsers yet.
|
|
251
|
+
|
|
252
|
+
### Lazy evaluation
|
|
253
|
+
|
|
254
|
+
Wrap expensive computations with `lazy()` so they only run when the level is
|
|
255
|
+
enabled:
|
|
256
|
+
|
|
257
|
+
~~~~ typescript
|
|
258
|
+
import { getLogger, lazy } from "@logtape/logtape";
|
|
259
|
+
|
|
260
|
+
const logger = getLogger(["my-app"]);
|
|
261
|
+
logger.debug("System state: {state}", {
|
|
262
|
+
state: lazy(() => JSON.stringify(getExpensiveState())),
|
|
263
|
+
});
|
|
264
|
+
~~~~
|
|
265
|
+
|
|
266
|
+
For structured data, pass a callback as the second argument:
|
|
267
|
+
|
|
268
|
+
~~~~ typescript
|
|
269
|
+
logger.debug("Diagnostics", () => ({
|
|
270
|
+
heap: process.memoryUsage().heapUsed,
|
|
271
|
+
uptime: process.uptime(),
|
|
272
|
+
}));
|
|
273
|
+
~~~~
|
|
274
|
+
|
|
275
|
+
For **async** lazy evaluation, pass an async callback and `await` the result:
|
|
276
|
+
|
|
277
|
+
~~~~ typescript
|
|
278
|
+
await logger.info("User details", async () => ({
|
|
279
|
+
user: await fetchUserDetails(),
|
|
280
|
+
}));
|
|
281
|
+
~~~~
|
|
282
|
+
|
|
283
|
+
For multiple expensive log calls, use `isEnabledFor()`:
|
|
284
|
+
|
|
285
|
+
~~~~ typescript
|
|
286
|
+
if (logger.isEnabledFor("debug")) {
|
|
287
|
+
const snapshot = await captureExpensiveSnapshot();
|
|
288
|
+
logger.debug("Snapshot: {data}", { data: snapshot });
|
|
289
|
+
}
|
|
290
|
+
~~~~
|
|
291
|
+
|
|
292
|
+
See <https://logtape.org/manual/lazy.md> and
|
|
293
|
+
<https://logtape.org/manual/contexts.md> for details.
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
Logging errors
|
|
297
|
+
--------------
|
|
298
|
+
|
|
299
|
+
Pass `Error` objects directly to `error()` or `fatal()`. You can attach
|
|
300
|
+
extra properties as a second argument:
|
|
301
|
+
|
|
302
|
+
~~~~ typescript
|
|
303
|
+
try {
|
|
304
|
+
await riskyOperation();
|
|
305
|
+
} catch (error) {
|
|
306
|
+
logger.error(error, { operation: "riskyOperation", userId });
|
|
307
|
+
}
|
|
308
|
+
~~~~
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
Sinks and formatters
|
|
312
|
+
--------------------
|
|
313
|
+
|
|
314
|
+
### Built-in sinks
|
|
315
|
+
|
|
316
|
+
~~~~ typescript
|
|
317
|
+
import {
|
|
318
|
+
configure,
|
|
319
|
+
getConsoleSink,
|
|
320
|
+
getStreamSink,
|
|
321
|
+
} from "@logtape/logtape";
|
|
322
|
+
|
|
323
|
+
await configure({
|
|
324
|
+
sinks: {
|
|
325
|
+
console: getConsoleSink(),
|
|
326
|
+
stderr: getStreamSink(Writable.toWeb(process.stderr)),
|
|
327
|
+
},
|
|
328
|
+
loggers: [{ category: "app", sinks: ["console"] }],
|
|
329
|
+
});
|
|
330
|
+
~~~~
|
|
331
|
+
|
|
332
|
+
### Sink filters
|
|
333
|
+
|
|
334
|
+
Use `withFilter()` to route different levels to different sinks:
|
|
335
|
+
|
|
336
|
+
~~~~ typescript
|
|
337
|
+
import { configure, getConsoleSink, withFilter } from "@logtape/logtape";
|
|
338
|
+
|
|
339
|
+
await configure({
|
|
340
|
+
sinks: {
|
|
341
|
+
errorsOnly: withFilter(getConsoleSink(), "error"),
|
|
342
|
+
allLevels: getConsoleSink(),
|
|
343
|
+
},
|
|
344
|
+
loggers: [
|
|
345
|
+
{ category: "app", sinks: ["allLevels", "errorsOnly"] },
|
|
346
|
+
],
|
|
347
|
+
});
|
|
348
|
+
~~~~
|
|
349
|
+
|
|
350
|
+
### Formatters
|
|
351
|
+
|
|
352
|
+
~~~~ typescript
|
|
353
|
+
import {
|
|
354
|
+
configure,
|
|
355
|
+
getAnsiColorFormatter,
|
|
356
|
+
getConsoleSink,
|
|
357
|
+
getJsonLinesFormatter,
|
|
358
|
+
} from "@logtape/logtape";
|
|
359
|
+
|
|
360
|
+
// Pretty ANSI-colored output for development
|
|
361
|
+
getConsoleSink({ formatter: getAnsiColorFormatter() });
|
|
362
|
+
|
|
363
|
+
// JSON Lines for production / log aggregation
|
|
364
|
+
getConsoleSink({ formatter: getJsonLinesFormatter() });
|
|
365
|
+
~~~~
|
|
366
|
+
|
|
367
|
+
For even nicer development output, use `@logtape/pretty`:
|
|
368
|
+
|
|
369
|
+
~~~~ typescript
|
|
370
|
+
import { getPrettyFormatter } from "@logtape/pretty";
|
|
371
|
+
|
|
372
|
+
getConsoleSink({ formatter: getPrettyFormatter() });
|
|
373
|
+
~~~~
|
|
374
|
+
|
|
375
|
+
### Disposal
|
|
376
|
+
|
|
377
|
+
Non-blocking sinks and stream sinks hold resources. In **edge functions** or
|
|
378
|
+
short-lived processes, explicitly dispose before exit:
|
|
379
|
+
|
|
380
|
+
~~~~ typescript
|
|
381
|
+
import { dispose } from "@logtape/logtape";
|
|
382
|
+
|
|
383
|
+
// At shutdown
|
|
384
|
+
await dispose();
|
|
385
|
+
~~~~
|
|
386
|
+
|
|
387
|
+
See <https://logtape.org/manual/sinks.md> and
|
|
388
|
+
<https://logtape.org/manual/formatters.md> for details.
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
Data redaction
|
|
392
|
+
--------------
|
|
393
|
+
|
|
394
|
+
Use `@logtape/redaction` to prevent sensitive data from reaching log output.
|
|
395
|
+
|
|
396
|
+
### Pattern-based redaction (scans formatted text)
|
|
397
|
+
|
|
398
|
+
Wrap a formatter with `redactByPattern()` to catch data like emails, credit
|
|
399
|
+
card numbers, or JWTs anywhere in the log output:
|
|
400
|
+
|
|
401
|
+
~~~~ typescript
|
|
402
|
+
import { defaultConsoleFormatter, getConsoleSink } from "@logtape/logtape";
|
|
403
|
+
import {
|
|
404
|
+
EMAIL_ADDRESS_PATTERN,
|
|
405
|
+
JWT_PATTERN,
|
|
406
|
+
redactByPattern,
|
|
407
|
+
} from "@logtape/redaction";
|
|
408
|
+
|
|
409
|
+
const sink = getConsoleSink({
|
|
410
|
+
formatter: redactByPattern(defaultConsoleFormatter, [
|
|
411
|
+
EMAIL_ADDRESS_PATTERN,
|
|
412
|
+
JWT_PATTERN,
|
|
413
|
+
]),
|
|
414
|
+
});
|
|
415
|
+
~~~~
|
|
416
|
+
|
|
417
|
+
Built-in patterns: `EMAIL_ADDRESS_PATTERN`, `CREDIT_CARD_NUMBER_PATTERN`,
|
|
418
|
+
`JWT_PATTERN`, `US_SSN_PATTERN`, `KR_RRN_PATTERN`.
|
|
419
|
+
|
|
420
|
+
### Field-based redaction (removes/replaces properties by name)
|
|
421
|
+
|
|
422
|
+
Wrap a sink with `redactByField()` to strip sensitive fields from structured
|
|
423
|
+
log data before it reaches the sink:
|
|
424
|
+
|
|
425
|
+
~~~~ typescript
|
|
426
|
+
import { getConsoleSink } from "@logtape/logtape";
|
|
427
|
+
import { redactByField } from "@logtape/redaction";
|
|
428
|
+
|
|
429
|
+
// Uses DEFAULT_REDACT_FIELDS (password, secret, token, etc.)
|
|
430
|
+
const sink = redactByField(getConsoleSink());
|
|
431
|
+
|
|
432
|
+
// Or customize with replacement instead of removal
|
|
433
|
+
const sink2 = redactByField(getConsoleSink(), {
|
|
434
|
+
fieldPatterns: [/password/i, /secret/i, /api[-_]?key/i],
|
|
435
|
+
action: () => "[REDACTED]",
|
|
436
|
+
});
|
|
437
|
+
~~~~
|
|
438
|
+
|
|
439
|
+
### Combining both for maximum security
|
|
440
|
+
|
|
441
|
+
~~~~ typescript
|
|
442
|
+
const sink = redactByField(
|
|
443
|
+
getConsoleSink({
|
|
444
|
+
formatter: redactByPattern(defaultConsoleFormatter, [
|
|
445
|
+
EMAIL_ADDRESS_PATTERN,
|
|
446
|
+
JWT_PATTERN,
|
|
447
|
+
]),
|
|
448
|
+
}),
|
|
449
|
+
);
|
|
450
|
+
~~~~
|
|
451
|
+
|
|
452
|
+
See <https://logtape.org/manual/redaction.md> for details.
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
Adaptors for existing loggers
|
|
456
|
+
-----------------------------
|
|
457
|
+
|
|
458
|
+
If the project already uses winston, Pino, or log4js, use an adaptor instead
|
|
459
|
+
of `configure()`:
|
|
460
|
+
|
|
461
|
+
~~~~ typescript
|
|
462
|
+
import { install } from "@logtape/adaptor-winston";
|
|
463
|
+
import winston from "winston";
|
|
464
|
+
|
|
465
|
+
const winstonLogger = winston.createLogger({ /* ... */ });
|
|
466
|
+
install(winstonLogger);
|
|
467
|
+
// All LogTape logs now route through winston
|
|
468
|
+
~~~~
|
|
469
|
+
|
|
470
|
+
Available: `@logtape/adaptor-winston`, `@logtape/adaptor-pino`,
|
|
471
|
+
`@logtape/adaptor-log4js`.
|
|
472
|
+
|
|
473
|
+
See <https://logtape.org/manual/adaptors.md> for details.
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
Testing
|
|
477
|
+
-------
|
|
478
|
+
|
|
479
|
+
For tests, configure a buffer sink and assert on collected records:
|
|
480
|
+
|
|
481
|
+
~~~~ typescript
|
|
482
|
+
import { configure, getLogger, reset, type LogRecord } from "@logtape/logtape";
|
|
483
|
+
|
|
484
|
+
const buffer: LogRecord[] = [];
|
|
485
|
+
|
|
486
|
+
await configure({
|
|
487
|
+
sinks: { buffer: buffer.push.bind(buffer) },
|
|
488
|
+
loggers: [{ category: "test", sinks: ["buffer"] }],
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
const logger = getLogger(["test"]);
|
|
492
|
+
logger.info("hello");
|
|
493
|
+
|
|
494
|
+
// Assert
|
|
495
|
+
assert(buffer.length === 1);
|
|
496
|
+
assert(buffer[0].level === "info");
|
|
497
|
+
|
|
498
|
+
await reset();
|
|
499
|
+
~~~~
|
|
500
|
+
|
|
501
|
+
If using `configureSync()`, reset with `resetSync()`:
|
|
502
|
+
|
|
503
|
+
~~~~ typescript
|
|
504
|
+
import { configureSync, resetSync } from "@logtape/logtape";
|
|
505
|
+
|
|
506
|
+
configureSync({ /* ... */ });
|
|
507
|
+
// ... test ...
|
|
508
|
+
resetSync();
|
|
509
|
+
~~~~
|
|
510
|
+
|
|
511
|
+
Always call `reset()` / `resetSync()` in test teardown so that each test can
|
|
512
|
+
configure independently.
|
|
513
|
+
|
|
514
|
+
See <https://logtape.org/manual/testing.md> for more patterns.
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
Available packages
|
|
518
|
+
------------------
|
|
519
|
+
|
|
520
|
+
### Sink packages
|
|
521
|
+
|
|
522
|
+
| Package | Description |
|
|
523
|
+
| --------------------------- | ---------------------- |
|
|
524
|
+
| `@logtape/file` | File and rotating file |
|
|
525
|
+
| `@logtape/otel` | OpenTelemetry |
|
|
526
|
+
| `@logtape/sentry` | Sentry |
|
|
527
|
+
| `@logtape/syslog` | Syslog |
|
|
528
|
+
| `@logtape/cloudwatch-logs` | AWS CloudWatch Logs |
|
|
529
|
+
| `@logtape/windows-eventlog` | Windows Event Log |
|
|
530
|
+
|
|
531
|
+
### Framework integrations
|
|
532
|
+
|
|
533
|
+
| Package | Description |
|
|
534
|
+
| ---------------------- | ------------------------- |
|
|
535
|
+
| `@logtape/express` | Express HTTP logging |
|
|
536
|
+
| `@logtape/fastify` | Fastify HTTP logging |
|
|
537
|
+
| `@logtape/hono` | Hono HTTP logging |
|
|
538
|
+
| `@logtape/koa` | Koa HTTP logging |
|
|
539
|
+
| `@logtape/elysia` | Elysia HTTP logging |
|
|
540
|
+
| `@logtape/drizzle-orm` | Drizzle ORM query logging |
|
|
541
|
+
|
|
542
|
+
### Formatters
|
|
543
|
+
|
|
544
|
+
| Package | Description |
|
|
545
|
+
| -------------------- | ------------------------ |
|
|
546
|
+
| `@logtape/pretty` | Pretty console formatter |
|
|
547
|
+
| `@logtape/redaction` | Sensitive data redaction |
|
|
548
|
+
|
|
549
|
+
### Adaptors (for existing loggers)
|
|
550
|
+
|
|
551
|
+
| Package | Description |
|
|
552
|
+
| -------------------------- | --------------------- |
|
|
553
|
+
| `@logtape/adaptor-pino` | Pino compatibility |
|
|
554
|
+
| `@logtape/adaptor-winston` | Winston compatibility |
|
|
555
|
+
| `@logtape/adaptor-log4js` | log4js compatibility |
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
Common mistakes
|
|
559
|
+
---------------
|
|
560
|
+
|
|
561
|
+
### Using template literals for structured data
|
|
562
|
+
|
|
563
|
+
~~~~ typescript
|
|
564
|
+
// WRONG: template literals don't produce structured data
|
|
565
|
+
logger.info`User ${userId} performed ${action}`;
|
|
566
|
+
|
|
567
|
+
// CORRECT: use placeholders for structured, searchable logs
|
|
568
|
+
logger.info("User {userId} performed {action}", { userId, action });
|
|
569
|
+
~~~~
|
|
570
|
+
|
|
571
|
+
### String concatenation
|
|
572
|
+
|
|
573
|
+
~~~~ typescript
|
|
574
|
+
// WRONG: loses structure, always evaluates
|
|
575
|
+
logger.info("User " + userId + " logged in from " + ip);
|
|
576
|
+
|
|
577
|
+
// CORRECT
|
|
578
|
+
logger.info("User {userId} logged in from {ip}", { userId, ip });
|
|
579
|
+
~~~~
|
|
580
|
+
|
|
581
|
+
### Calling `configure()` in library code
|
|
582
|
+
|
|
583
|
+
~~~~ typescript
|
|
584
|
+
// WRONG: library must never configure LogTape
|
|
585
|
+
import { configure } from "@logtape/logtape";
|
|
586
|
+
await configure({ /* ... */ }); // Don't do this in a library!
|
|
587
|
+
|
|
588
|
+
// CORRECT: just use getLogger()
|
|
589
|
+
import { getLogger } from "@logtape/logtape";
|
|
590
|
+
const logger = getLogger(["my-lib"]);
|
|
591
|
+
~~~~
|
|
592
|
+
|
|
593
|
+
### Forgetting to `await configure()`
|
|
594
|
+
|
|
595
|
+
~~~~ typescript
|
|
596
|
+
// WRONG: configure() returns a Promise; without await, logging may
|
|
597
|
+
// not work when you expect it to
|
|
598
|
+
configure({ /* ... */ });
|
|
599
|
+
|
|
600
|
+
// CORRECT
|
|
601
|
+
await configure({ /* ... */ });
|
|
602
|
+
|
|
603
|
+
// Or use the synchronous variant if you can't await
|
|
604
|
+
configureSync({ /* ... */ });
|
|
605
|
+
~~~~
|
|
606
|
+
|
|
607
|
+
### Mixing async/sync configure and reset
|
|
608
|
+
|
|
609
|
+
~~~~ typescript
|
|
610
|
+
// WRONG: mismatched pair causes errors
|
|
611
|
+
await configure({ /* ... */ });
|
|
612
|
+
resetSync(); // Can't sync-reset an async config!
|
|
613
|
+
|
|
614
|
+
// CORRECT: match configure and reset variants
|
|
615
|
+
await configure({ /* ... */ });
|
|
616
|
+
await reset();
|
|
617
|
+
|
|
618
|
+
// Or:
|
|
619
|
+
configureSync({ /* ... */ });
|
|
620
|
+
resetSync();
|
|
621
|
+
~~~~
|
|
622
|
+
|
|
623
|
+
### Using `console.log` alongside LogTape
|
|
624
|
+
|
|
625
|
+
~~~~ typescript
|
|
626
|
+
// WRONG: bypasses LogTape's filtering, formatting, and routing
|
|
627
|
+
console.log("User logged in:", userId);
|
|
628
|
+
|
|
629
|
+
// CORRECT: use the logger so the message goes through configured sinks
|
|
630
|
+
logger.info("User {userId} logged in", { userId });
|
|
631
|
+
~~~~
|
|
632
|
+
|
|
633
|
+
### Flat categories
|
|
634
|
+
|
|
635
|
+
~~~~ typescript
|
|
636
|
+
// WRONG: single flat string prevents granular filtering
|
|
637
|
+
const logger = getLogger("myapp");
|
|
638
|
+
|
|
639
|
+
// CORRECT: hierarchical array enables per-module control
|
|
640
|
+
const logger = getLogger(["myapp", "auth", "oauth"]);
|
|
641
|
+
~~~~
|
|
642
|
+
|
|
643
|
+
### Logging sensitive data without redaction
|
|
644
|
+
|
|
645
|
+
~~~~ typescript
|
|
646
|
+
// WRONG: password ends up in log files
|
|
647
|
+
logger.info("Login attempt for {email} with {password}", { email, password });
|
|
648
|
+
|
|
649
|
+
// CORRECT: never log secrets; use @logtape/redaction if needed
|
|
650
|
+
logger.info("Login attempt for {email}", { email });
|
|
651
|
+
~~~~
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
Best practices summary
|
|
655
|
+
----------------------
|
|
656
|
+
|
|
657
|
+
- One `configure()` or `configureSync()` call at startup
|
|
658
|
+
- Always `await configure()`; never `await configureSync()`
|
|
659
|
+
- Match reset variant to configure variant
|
|
660
|
+
- Hierarchical array categories (e.g., `["app", "module", "sub"]`)
|
|
661
|
+
- Structured messages with named placeholders, not string interpolation
|
|
662
|
+
- `lazy()` or callback for expensive computations
|
|
663
|
+
- `logger.with()` for request-scoped context
|
|
664
|
+
- `withContext()` + `AsyncLocalStorage` for implicit context propagation
|
|
665
|
+
- `error`/`fatal` only for real failures
|
|
666
|
+
- Libraries: never configure, just `getLogger()`
|
|
667
|
+
- Tests: `reset()` / `resetSync()` in teardown, buffer sink for assertions
|
|
668
|
+
- Never log sensitive data (passwords, tokens, PII)
|
|
669
|
+
- Use `dispose()` / `disposeSync()` in edge functions or short-lived
|
|
670
|
+
processes
|