@decaf-ts/logging 0.9.11 → 0.10.1
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 +298 -171
- package/dist/logging.cjs +1 -1
- package/dist/logging.cjs.map +1 -1
- package/dist/logging.js +1 -1
- package/dist/logging.js.map +1 -1
- package/lib/LoggedClass.cjs +4 -4
- package/lib/LoggedClass.d.ts +4 -4
- package/lib/constants.cjs +32 -63
- package/lib/constants.d.ts +32 -63
- package/lib/constants.js.map +1 -1
- package/lib/decorators.cjs +28 -28
- package/lib/decorators.d.ts +29 -36
- package/lib/decorators.js.map +1 -1
- package/lib/environment.cjs +34 -41
- package/lib/environment.d.ts +31 -32
- package/lib/environment.js.map +1 -1
- package/lib/esm/LoggedClass.d.ts +4 -4
- package/lib/esm/LoggedClass.js +4 -4
- package/lib/esm/constants.d.ts +32 -63
- package/lib/esm/constants.js +32 -63
- package/lib/esm/constants.js.map +1 -1
- package/lib/esm/decorators.d.ts +29 -36
- package/lib/esm/decorators.js +28 -28
- package/lib/esm/decorators.js.map +1 -1
- package/lib/esm/environment.d.ts +31 -32
- package/lib/esm/environment.js +34 -41
- package/lib/esm/environment.js.map +1 -1
- package/lib/esm/filters/LogFilter.d.ts +11 -11
- package/lib/esm/filters/LogFilter.js +5 -5
- package/lib/esm/filters/PatternFilter.d.ts +15 -15
- package/lib/esm/filters/PatternFilter.js +12 -12
- package/lib/esm/index.d.ts +2 -2
- package/lib/esm/index.js +3 -3
- package/lib/esm/logging.d.ts +118 -113
- package/lib/esm/logging.js +223 -138
- package/lib/esm/logging.js.map +1 -1
- package/lib/esm/pino/index.d.ts +6 -0
- package/lib/esm/pino/index.js +6 -0
- package/lib/esm/pino/index.js.map +1 -1
- package/lib/esm/pino/pino.d.ts +13 -5
- package/lib/esm/pino/pino.js +70 -124
- package/lib/esm/pino/pino.js.map +1 -1
- package/lib/esm/text.d.ts +29 -67
- package/lib/esm/text.js +29 -67
- package/lib/esm/text.js.map +1 -1
- package/lib/esm/time.d.ts +45 -43
- package/lib/esm/time.js +38 -36
- package/lib/esm/time.js.map +1 -1
- package/lib/esm/types.d.ts +59 -50
- package/lib/esm/utils.d.ts +43 -0
- package/lib/esm/utils.js +55 -3
- package/lib/esm/utils.js.map +1 -1
- package/lib/esm/web.d.ts +2 -2
- package/lib/esm/web.js +2 -2
- package/lib/esm/winston/index.d.ts +7 -0
- package/lib/esm/winston/index.js +7 -1
- package/lib/esm/winston/index.js.map +1 -1
- package/lib/esm/winston/winston.d.ts +17 -21
- package/lib/esm/winston/winston.js +29 -36
- package/lib/esm/winston/winston.js.map +1 -1
- package/lib/filters/LogFilter.cjs +5 -5
- package/lib/filters/LogFilter.d.ts +11 -11
- package/lib/filters/PatternFilter.cjs +12 -12
- package/lib/filters/PatternFilter.d.ts +15 -15
- package/lib/index.cjs +3 -3
- package/lib/index.d.ts +2 -2
- package/lib/logging.cjs +223 -138
- package/lib/logging.d.ts +118 -113
- package/lib/logging.js.map +1 -1
- package/lib/pino/index.cjs +6 -0
- package/lib/pino/index.d.ts +6 -0
- package/lib/pino/index.js.map +1 -1
- package/lib/pino/pino.cjs +102 -126
- package/lib/pino/pino.d.ts +13 -5
- package/lib/pino/pino.js.map +1 -1
- package/lib/text.cjs +29 -67
- package/lib/text.d.ts +29 -67
- package/lib/text.js.map +1 -1
- package/lib/time.cjs +38 -36
- package/lib/time.d.ts +45 -43
- package/lib/time.js.map +1 -1
- package/lib/types.d.ts +59 -50
- package/lib/utils.cjs +55 -3
- package/lib/utils.d.ts +43 -0
- package/lib/utils.js.map +1 -1
- package/lib/web.cjs +2 -2
- package/lib/web.d.ts +2 -2
- package/lib/winston/index.cjs +22 -0
- package/lib/winston/index.d.ts +7 -0
- package/lib/winston/index.js.map +1 -1
- package/lib/winston/winston.cjs +29 -36
- package/lib/winston/winston.d.ts +17 -21
- package/lib/winston/winston.js.map +1 -1
- package/package.json +9 -10
- package/lib/accumulate.types.cjs +0 -27
- package/lib/accumulate.types.d.ts +0 -1
- package/lib/accumulate.types.js.map +0 -1
- package/lib/esm/accumulate.types.d.ts +0 -1
- package/lib/esm/accumulate.types.js +0 -25
- package/lib/esm/accumulate.types.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
# Logging Library (decaf-ts/logging)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- Extensibility through a pluggable LoggerFactory (e.g., WinstonLogger) while keeping a minimal default runtime.
|
|
6
|
+
Decaf’s logging toolkit keeps one fast MiniLogger at the core while exposing adapters, filters, and utilities that fit both browser and Node.js runtimes:
|
|
7
|
+
- Configure once through `Logging.setConfig` or the `Environment` accumulator and let impersonated child loggers inherit overrides without allocations.
|
|
8
|
+
- Apply filter chains, transports, and adapter-specific features (Pino, Winston, custom factories) through the shared `LoggingConfig` contract.
|
|
9
|
+
- Instrument classes using decorators, `LoggedClass`, and `Logging.because` while StopWatch, text/time utilities, and environment helpers round out the diagnostics surface.
|
|
11
10
|
|
|
12
11
|

|
|
13
12
|

|
|
@@ -38,7 +37,7 @@ A small, flexible TypeScript logging library designed for framework-agnostic pro
|
|
|
38
37
|
|
|
39
38
|
Documentation available [here](https://decaf-ts.github.io/logging/)
|
|
40
39
|
|
|
41
|
-
Minimal size: 5.
|
|
40
|
+
Minimal size: 5.9 KB kb gzipped
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
# Logging Library — Detailed Description
|
|
@@ -118,232 +117,360 @@ Intended usage
|
|
|
118
117
|
|
|
119
118
|
# How to Use the Logging Library
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
All snippets import from `@decaf-ts/logging` (swap to a relative path when working inside this repo). Each item below contains a short description, an optional sequence diagram for complex flows, and runnable TypeScript code.
|
|
121
|
+
|
|
122
|
+
## 1. Bootstrapping Global Configuration
|
|
123
|
+
Description: Initialize `Logging` once (or hydrate `LoggedEnvironment`) to define levels, formatting, colors, transports, and app identifiers that downstream impersonated loggers inherit without per-call overhead.
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
sequenceDiagram
|
|
127
|
+
autonumber
|
|
128
|
+
participant App as App
|
|
129
|
+
participant Env as LoggedEnvironment
|
|
130
|
+
participant Logging as Logging.setConfig
|
|
131
|
+
participant Root as MiniLogger (root)
|
|
132
|
+
App->>Env: Environment.accumulate(defaults)
|
|
133
|
+
Env-->>App: Env proxy (app name, theme, format)
|
|
134
|
+
App->>Logging: setConfig({ level, format, transports })
|
|
135
|
+
Logging->>Root: ensureRoot() memoizes MiniLogger
|
|
136
|
+
```
|
|
131
137
|
|
|
132
138
|
```ts
|
|
133
|
-
import { Logging, LogLevel } from "@decaf-ts/logging";
|
|
139
|
+
import { Logging, LogLevel, DefaultLoggingConfig, LoggedEnvironment } from "@decaf-ts/logging";
|
|
134
140
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
timestamp: false, // omit timestamp for simplicity in this example
|
|
141
|
+
// seed the environment before configuring Logging.
|
|
142
|
+
LoggedEnvironment.accumulate({
|
|
143
|
+
app: "InventoryAPI",
|
|
144
|
+
logging: { separator: "•" },
|
|
140
145
|
});
|
|
141
146
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
Logging.setConfig({
|
|
148
|
+
...DefaultLoggingConfig,
|
|
149
|
+
level: LogLevel.debug,
|
|
150
|
+
verbose: 2,
|
|
151
|
+
style: true,
|
|
152
|
+
format: "raw",
|
|
153
|
+
});
|
|
146
154
|
|
|
147
|
-
|
|
148
|
-
Logging.
|
|
149
|
-
Logging.silly("
|
|
150
|
-
Logging.verbose("Even more details", 2); // only with verbose >= 2
|
|
155
|
+
Logging.info("Boot complete");
|
|
156
|
+
Logging.verbose("Dependency graph built", 1);
|
|
157
|
+
Logging.silly("Deep diagnostics", 3); // ignored because verbose < 3
|
|
151
158
|
```
|
|
152
159
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
## 2. Impersonation & Proxy Performance
|
|
161
|
+
Description: ONE `MiniLogger` instance powers every context. Calls to `.for(...)` return lightweight proxies that temporarily override context/config without allocating new drivers, so a hot path can derive thousands of child loggers without GC churn.
|
|
162
|
+
|
|
163
|
+
```mermaid
|
|
164
|
+
sequenceDiagram
|
|
165
|
+
autonumber
|
|
166
|
+
participant App as App
|
|
167
|
+
participant Logging as Logging.for(...)
|
|
168
|
+
participant Root as MiniLogger (root)
|
|
169
|
+
participant Proxy as Logger Proxy
|
|
170
|
+
App->>Logging: Logging.for("OrderService")
|
|
171
|
+
Logging->>Root: ensureRoot() reuse
|
|
172
|
+
Root-->>Proxy: Proxy bound to ["OrderService"]
|
|
173
|
+
Proxy->>Proxy: `.for("create")` extends context
|
|
174
|
+
Proxy-->>App: Proxy emits create log (no new logger)
|
|
175
|
+
```
|
|
156
176
|
|
|
157
177
|
```ts
|
|
158
|
-
import { Logging, LogLevel } from "@decaf-ts/logging";
|
|
178
|
+
import { Logging, LogLevel, type LoggingConfig } from "@decaf-ts/logging";
|
|
159
179
|
|
|
160
|
-
Logging.setConfig({ level: LogLevel.
|
|
180
|
+
Logging.setConfig({ level: LogLevel.info });
|
|
181
|
+
const logger = Logging.for("OrderService");
|
|
161
182
|
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
183
|
+
function runScenario(overrides?: Partial<LoggingConfig>) {
|
|
184
|
+
const scoped = logger.for("createOrUpdate", overrides);
|
|
185
|
+
scoped.info("Validating payload");
|
|
186
|
+
scoped.for("db").debug("Executing UPSERT..."); // reuses the same proxy target
|
|
187
|
+
scoped.clear(); // resets context/config so the proxy can serve the next call
|
|
188
|
+
}
|
|
165
189
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
methodLogger.debug("Querying repository...");
|
|
190
|
+
runScenario({ correlationId: "req-1" });
|
|
191
|
+
runScenario({ correlationId: "req-2", style: false });
|
|
169
192
|
```
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
194
|
+
## 3. Filtering Sensitive Data
|
|
195
|
+
Description: Attach `PatternFilter` or custom filters via `LoggingConfig.filters` to redact PII/passwords before formatting. Filters run on the message string after it was rendered in RAW format; JSON output serializes the already-filtered content, so sensitive values disappear in both outputs.
|
|
196
|
+
|
|
197
|
+
```mermaid
|
|
198
|
+
sequenceDiagram
|
|
199
|
+
autonumber
|
|
200
|
+
participant App as App
|
|
201
|
+
participant Proxy as Logger Proxy
|
|
202
|
+
participant Filter as PatternFilter
|
|
203
|
+
participant Console as Transport
|
|
204
|
+
App->>Proxy: log("password=secret")
|
|
205
|
+
Proxy->>Filter: filter(config, message, context)
|
|
206
|
+
Filter-->>Proxy: "password=***"
|
|
207
|
+
Proxy->>Console: emit RAW or JSON string
|
|
208
|
+
```
|
|
174
209
|
|
|
175
210
|
```ts
|
|
176
|
-
import {
|
|
211
|
+
import { Logging, LogLevel, PatternFilter, type LoggingConfig } from "@decaf-ts/logging";
|
|
177
212
|
|
|
178
|
-
|
|
179
|
-
|
|
213
|
+
class PiiFilter extends PatternFilter {
|
|
214
|
+
constructor() {
|
|
215
|
+
super(/(password|ssn)=([^&\s]+)/gi, (_full, key) => `${key}=***`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
180
218
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
219
|
+
const filters: LoggingConfig["filters"] = [new PiiFilter()];
|
|
220
|
+
Logging.setConfig({
|
|
221
|
+
level: LogLevel.debug,
|
|
222
|
+
filters,
|
|
223
|
+
format: "raw",
|
|
224
|
+
});
|
|
185
225
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
traced.info("Tracing this operation");
|
|
189
|
-
```
|
|
226
|
+
const logger = Logging.for("SignupFlow");
|
|
227
|
+
logger.info("Attempt password=abc123&email=user@example.com"); // prints password=***
|
|
190
228
|
|
|
229
|
+
Logging.setConfig({ format: "json" });
|
|
230
|
+
logger.info("ssn=123-45-6789"); // JSON payload contains ssn=*** already
|
|
231
|
+
```
|
|
191
232
|
|
|
192
|
-
|
|
193
|
-
Description:
|
|
233
|
+
## 4. Transports & Custom Destinations
|
|
234
|
+
Description: Supply writable streams via `LoggingConfig.transports` when using adapters (Pino/Winston) to branch logs to files, sockets, or monitoring systems. Each adapter inspects `transports` and builds either a native transport (Winston) or Pino multistream.
|
|
235
|
+
|
|
236
|
+
```mermaid
|
|
237
|
+
sequenceDiagram
|
|
238
|
+
autonumber
|
|
239
|
+
participant App as App
|
|
240
|
+
participant Logging as Logging.setFactory
|
|
241
|
+
participant Adapter as Adapter Logger
|
|
242
|
+
participant Transport as DestinationStream
|
|
243
|
+
App->>Logging: setFactory(WinstonFactory)
|
|
244
|
+
App->>Adapter: Logging.for("Gateway", { transports })
|
|
245
|
+
Adapter->>Transport: write(formatted line)
|
|
246
|
+
```
|
|
194
247
|
|
|
195
248
|
```ts
|
|
196
|
-
import
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
249
|
+
import fs from "node:fs";
|
|
250
|
+
import { Logging, LogLevel } from "@decaf-ts/logging";
|
|
251
|
+
import { WinstonFactory } from "@decaf-ts/logging/winston/winston";
|
|
252
|
+
import Transport from "winston-transport";
|
|
200
253
|
|
|
201
|
-
class
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
254
|
+
class AuditTransport extends Transport {
|
|
255
|
+
log(info: any, callback: () => void) {
|
|
256
|
+
fs.appendFileSync("audit.log", `${info.message}\n`);
|
|
257
|
+
callback();
|
|
205
258
|
}
|
|
259
|
+
}
|
|
206
260
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
261
|
+
Logging.setFactory(WinstonFactory);
|
|
262
|
+
const auditLogger = Logging.for("AuditTrail", {
|
|
263
|
+
level: LogLevel.info,
|
|
264
|
+
transports: [new AuditTransport()],
|
|
265
|
+
});
|
|
212
266
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
267
|
+
auditLogger.info("policy=PASSWORD_RESET user=u-9 timestamp=...");
|
|
268
|
+
```
|
|
217
269
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return Promise.resolve("ok");
|
|
221
|
-
}
|
|
270
|
+
## 5. Pino & Winston Native Features
|
|
271
|
+
Description: Use `PinoLogger` and `WinstonLogger` directly (or via `Logging.setFactory`) to access adapter-only functions such as `child()`, `flush()`, or Pino/Winston-specific levels while still honoring the shared `LoggingConfig`.
|
|
222
272
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
273
|
+
```ts
|
|
274
|
+
import pino from "pino";
|
|
275
|
+
import { Logging, LogLevel } from "@decaf-ts/logging";
|
|
276
|
+
import { PinoLogger, PinoFactory } from "@decaf-ts/logging/pino/pino";
|
|
277
|
+
import { WinstonLogger } from "@decaf-ts/logging/winston/winston";
|
|
278
|
+
|
|
279
|
+
// Pino: reuse an existing driver and call child()
|
|
280
|
+
const sink = pino({ level: "trace", name: "Api" });
|
|
281
|
+
const pinoLogger = new PinoLogger("Api", { level: LogLevel.debug }, sink);
|
|
282
|
+
pinoLogger.child({ context: "handler" }).info("Child context respects config");
|
|
283
|
+
pinoLogger.flush?.();
|
|
284
|
+
|
|
285
|
+
// Register the adapter globally so Logging.for uses it
|
|
286
|
+
Logging.setFactory(PinoFactory);
|
|
287
|
+
Logging.for("BatchJob").debug("Runs through native Pino now");
|
|
288
|
+
|
|
289
|
+
// Winston: pass custom transports or formats through LoggingConfig
|
|
290
|
+
const winstonLogger = new WinstonLogger("Worker", {
|
|
291
|
+
transports: [
|
|
292
|
+
new (WinstonLogger as any).prototype.winston.transports.Console(), // or custom
|
|
293
|
+
],
|
|
294
|
+
correlationId: "cid-1",
|
|
295
|
+
});
|
|
296
|
+
winstonLogger.error("Failure");
|
|
297
|
+
```
|
|
228
298
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
299
|
+
## 6. Environment Accumulator & Runtime Overrides
|
|
300
|
+
Description: `Environment.accumulate` builds a proxy whose properties resolve to runtime ENV variables (Node or browser) with helpers such as `keys`, `get`, and `orThrow`. Extend the shape by calling `accumulate` repeatedly; an empty string marks required values.
|
|
301
|
+
|
|
302
|
+
```mermaid
|
|
303
|
+
sequenceDiagram
|
|
304
|
+
autonumber
|
|
305
|
+
participant App as App
|
|
306
|
+
participant Env as Environment.accumulate
|
|
307
|
+
participant Proxy as Env Proxy
|
|
308
|
+
participant Runtime as process.env/ENV
|
|
309
|
+
App->>Env: accumulate({ service: { host: "", port: 8080 } })
|
|
310
|
+
Env-->>Proxy: proxy w/ compose-toString keys
|
|
311
|
+
App->>Proxy: proxy.service.host
|
|
312
|
+
Proxy->>Runtime: check SERVICE__HOST
|
|
313
|
+
Runtime-->>Proxy: "api.internal"
|
|
314
|
+
Proxy-->>App: returns runtime override or default
|
|
235
315
|
```
|
|
236
316
|
|
|
317
|
+
```ts
|
|
318
|
+
import { Environment, LoggedEnvironment } from "@decaf-ts/logging/environment";
|
|
237
319
|
|
|
238
|
-
|
|
239
|
-
|
|
320
|
+
// Extend the singleton shape; string "" means "required"
|
|
321
|
+
const Config = Environment.accumulate({
|
|
322
|
+
service: { host: "", port: 8080 },
|
|
323
|
+
logging: LoggedEnvironment,
|
|
324
|
+
});
|
|
240
325
|
|
|
241
|
-
|
|
242
|
-
|
|
326
|
+
console.log(String((Config as any).service.host)); // SERVICE__HOST
|
|
327
|
+
console.log(Environment.keys()); // ["SERVICE", "LOGGING", ...]
|
|
243
328
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return { id };
|
|
248
|
-
}
|
|
249
|
-
}
|
|
329
|
+
// Fail fast when required env is missing or empty
|
|
330
|
+
const runtime = Config.orThrow();
|
|
331
|
+
const serviceHost = runtime.service.host; // throws if missing at runtime
|
|
250
332
|
|
|
251
|
-
|
|
252
|
-
|
|
333
|
+
// Programmatic lookups
|
|
334
|
+
const verbose = Environment.get("logging.verbose");
|
|
253
335
|
```
|
|
254
336
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
Description: Route all logging through WinstonLogger by installing WinstonFactory. This mirrors the optional adapter in src/winston.
|
|
337
|
+
## 7. LoggedClass for Drop-in Contextual Logging
|
|
338
|
+
Description: Extend `LoggedClass` to gain a protected `this.log` that’s already scoped to the subclass and works with decorators, impersonation, and adapters.
|
|
258
339
|
|
|
259
340
|
```ts
|
|
260
|
-
import { Logging } from "@
|
|
261
|
-
import { WinstonFactory } from "@your-scope/logging/winston/winston";
|
|
341
|
+
import { LoggedClass, Logging, LogLevel } from "@decaf-ts/logging";
|
|
262
342
|
|
|
263
|
-
|
|
264
|
-
Logging.setFactory(WinstonFactory);
|
|
343
|
+
Logging.setConfig({ level: LogLevel.info });
|
|
265
344
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
log.info(
|
|
269
|
-
|
|
345
|
+
class EmailService extends LoggedClass {
|
|
346
|
+
async send(to: string, template: string) {
|
|
347
|
+
this.log.info(`Dispatching template=${template} to ${to}`);
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
270
351
|
|
|
352
|
+
const svc = new EmailService();
|
|
353
|
+
await svc.send("user@example.com", "welcome");
|
|
354
|
+
```
|
|
271
355
|
|
|
272
|
-
|
|
273
|
-
Description:
|
|
356
|
+
## 8. StopWatch for Benchmarking
|
|
357
|
+
Description: `StopWatch` uses the highest resolution clock available (browser `performance.now`, Node `process.hrtime.bigint`, or `Date.now`) to measure laps, pause/resume, and report JSON snapshots—useful for the `@benchmark` decorator or manual instrumentation.
|
|
274
358
|
|
|
275
359
|
```ts
|
|
276
|
-
import {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// Apply at runtime by passing to Logging.theme where needed (MiniLogger does this internally)
|
|
292
|
-
const styled = Logging.theme("debug", "logLevel", LogLevel.debug, theme);
|
|
293
|
-
|
|
294
|
-
// Regular logging picks up style=true and formats output accordingly
|
|
295
|
-
Logging.debug("This is a styled debug message");
|
|
360
|
+
import { StopWatch } from "@decaf-ts/logging/time";
|
|
361
|
+
|
|
362
|
+
const sw = new StopWatch(true);
|
|
363
|
+
await new Promise((r) => setTimeout(r, 15));
|
|
364
|
+
sw.lap("load config");
|
|
365
|
+
sw.pause();
|
|
366
|
+
|
|
367
|
+
await new Promise((r) => setTimeout(r, 5)); // paused time ignored
|
|
368
|
+
sw.resume();
|
|
369
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
370
|
+
const lap = sw.lap("connect db");
|
|
371
|
+
console.log(lap.ms, lap.totalMs, sw.toString());
|
|
372
|
+
console.log(JSON.stringify(sw));
|
|
296
373
|
```
|
|
297
374
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
375
|
+
## 9. Decorators & Advanced Instrumentation
|
|
376
|
+
Description: Use the stock decorators (`@log`, `@debug`, `@info`, `@verbose`, `@silly`, `@benchmark`, `@final`) or extend `@log` to emit structured entry/exit details. Decorators work with `LoggedClass` instances and plain classes alike.
|
|
377
|
+
|
|
378
|
+
```mermaid
|
|
379
|
+
sequenceDiagram
|
|
380
|
+
autonumber
|
|
381
|
+
participant Caller as Caller
|
|
382
|
+
participant Decorator as @log
|
|
383
|
+
participant Method as Original Method
|
|
384
|
+
participant Logger as Logger Proxy
|
|
385
|
+
Caller->>Decorator: invoke decorated method
|
|
386
|
+
Decorator->>Logger: log(entryMessage(args))
|
|
387
|
+
Decorator->>Method: Reflect.apply(...)
|
|
388
|
+
Method-->>Decorator: result / Promise
|
|
389
|
+
Decorator->>Logger: log(exitMessage or benchmark)
|
|
390
|
+
```
|
|
301
391
|
|
|
302
392
|
```ts
|
|
303
|
-
import {
|
|
393
|
+
import {
|
|
394
|
+
log,
|
|
395
|
+
debug,
|
|
396
|
+
info,
|
|
397
|
+
silly,
|
|
398
|
+
verbose,
|
|
399
|
+
benchmark,
|
|
400
|
+
LogLevel,
|
|
401
|
+
LoggedClass,
|
|
402
|
+
} from "@decaf-ts/logging";
|
|
403
|
+
|
|
404
|
+
class CustomLogDecorator {
|
|
405
|
+
static payload(label: string) {
|
|
406
|
+
return log(
|
|
407
|
+
LogLevel.info,
|
|
408
|
+
0,
|
|
409
|
+
(...args) => `${label}: ${JSON.stringify(args)}`,
|
|
410
|
+
(err, result) =>
|
|
411
|
+
err ? `${label} failed: ${err.message}` : `${label} ok: ${result}`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
304
415
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
416
|
+
class BillingService extends LoggedClass {
|
|
417
|
+
@CustomLogDecorator.payload("charge")
|
|
418
|
+
@benchmark()
|
|
419
|
+
async charge(userId: string, amount: number) {
|
|
420
|
+
if (amount <= 0) throw new Error("invalid amount");
|
|
421
|
+
return `charged:${userId}:${amount}`;
|
|
422
|
+
}
|
|
309
423
|
|
|
424
|
+
@debug()
|
|
425
|
+
rebuildIndex() {}
|
|
310
426
|
|
|
311
|
-
|
|
312
|
-
|
|
427
|
+
@info()
|
|
428
|
+
activate() {}
|
|
313
429
|
|
|
314
|
-
|
|
315
|
-
|
|
430
|
+
@silly()
|
|
431
|
+
ping() {}
|
|
316
432
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
config?: Partial<LoggingConfig>;
|
|
433
|
+
@verbose(1)
|
|
434
|
+
sync() {}
|
|
320
435
|
}
|
|
321
436
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
charge(amount: number) {
|
|
325
|
-
this.deps.log.info(`Charging ${amount}`);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
437
|
+
const svc = new BillingService();
|
|
438
|
+
await svc.charge("u-1", 25);
|
|
328
439
|
```
|
|
329
440
|
|
|
441
|
+
## 10. Utility Modules (text, time, utils, web)
|
|
442
|
+
Description: Helper functions complement logging by formatting identifiers, generating ENV keys, and detecting runtimes.
|
|
330
443
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
444
|
+
```ts
|
|
445
|
+
import {
|
|
446
|
+
padEnd,
|
|
447
|
+
patchPlaceholders,
|
|
448
|
+
sf,
|
|
449
|
+
toCamelCase,
|
|
450
|
+
toENVFormat,
|
|
451
|
+
toSnakeCase,
|
|
452
|
+
toKebabCase,
|
|
453
|
+
toPascalCase,
|
|
454
|
+
} from "@decaf-ts/logging/text";
|
|
455
|
+
import { formatMs, now } from "@decaf-ts/logging/time";
|
|
456
|
+
import { getObjectName, isClass, isFunction, isInstance } from "@decaf-ts/logging/utils";
|
|
457
|
+
import { isBrowser } from "@decaf-ts/logging/web";
|
|
458
|
+
|
|
459
|
+
const padded = padEnd("id", 5, "_"); // "id___"
|
|
460
|
+
const greeting = patchPlaceholders("Hello ${name}", { name: "Ada" });
|
|
461
|
+
const formatted = sf("{0}-{name}", "A", { name: "B" });
|
|
462
|
+
const snake = toSnakeCase("HelloWorld Test"); // "hello_world_test"
|
|
463
|
+
const envKey = toENVFormat("service.host"); // "SERVICE_HOST"
|
|
464
|
+
const camel = toCamelCase("hello world");
|
|
465
|
+
const pascal = toPascalCase("hello world");
|
|
466
|
+
const kebab = toKebabCase("Hello World");
|
|
467
|
+
|
|
468
|
+
const duration = formatMs(now() - now()); // hh:mm:ss.mmm string
|
|
469
|
+
const typeName = getObjectName(new (class Repo {})());
|
|
470
|
+
const runtimeIsBrowser = isBrowser();
|
|
471
|
+
const plainFn = () => true;
|
|
472
|
+
console.log(isFunction(plainFn), isClass(class A {}), isInstance({})); // type guards
|
|
473
|
+
```
|
|
347
474
|
|
|
348
475
|
|
|
349
476
|
### Related
|