@decaf-ts/logging 0.10.0 → 0.10.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.
Files changed (100) hide show
  1. package/README.md +298 -171
  2. package/dist/logging.cjs +1 -1
  3. package/dist/logging.cjs.map +1 -1
  4. package/dist/logging.js +1 -1
  5. package/dist/logging.js.map +1 -1
  6. package/lib/LoggedClass.cjs +4 -4
  7. package/lib/LoggedClass.d.ts +4 -4
  8. package/lib/constants.cjs +32 -63
  9. package/lib/constants.d.ts +32 -63
  10. package/lib/constants.js.map +1 -1
  11. package/lib/decorators.cjs +28 -28
  12. package/lib/decorators.d.ts +29 -36
  13. package/lib/decorators.js.map +1 -1
  14. package/lib/environment.cjs +34 -41
  15. package/lib/environment.d.ts +31 -32
  16. package/lib/environment.js.map +1 -1
  17. package/lib/esm/LoggedClass.d.ts +4 -4
  18. package/lib/esm/LoggedClass.js +4 -4
  19. package/lib/esm/constants.d.ts +32 -63
  20. package/lib/esm/constants.js +32 -63
  21. package/lib/esm/constants.js.map +1 -1
  22. package/lib/esm/decorators.d.ts +29 -36
  23. package/lib/esm/decorators.js +28 -28
  24. package/lib/esm/decorators.js.map +1 -1
  25. package/lib/esm/environment.d.ts +31 -32
  26. package/lib/esm/environment.js +34 -41
  27. package/lib/esm/environment.js.map +1 -1
  28. package/lib/esm/filters/LogFilter.d.ts +11 -11
  29. package/lib/esm/filters/LogFilter.js +5 -5
  30. package/lib/esm/filters/PatternFilter.d.ts +15 -15
  31. package/lib/esm/filters/PatternFilter.js +12 -12
  32. package/lib/esm/index.d.ts +2 -2
  33. package/lib/esm/index.js +3 -3
  34. package/lib/esm/logging.d.ts +119 -113
  35. package/lib/esm/logging.js +223 -138
  36. package/lib/esm/logging.js.map +1 -1
  37. package/lib/esm/pino/index.d.ts +6 -0
  38. package/lib/esm/pino/index.js +6 -0
  39. package/lib/esm/pino/index.js.map +1 -1
  40. package/lib/esm/pino/pino.d.ts +13 -5
  41. package/lib/esm/pino/pino.js +70 -124
  42. package/lib/esm/pino/pino.js.map +1 -1
  43. package/lib/esm/text.d.ts +29 -67
  44. package/lib/esm/text.js +29 -67
  45. package/lib/esm/text.js.map +1 -1
  46. package/lib/esm/time.d.ts +45 -43
  47. package/lib/esm/time.js +38 -36
  48. package/lib/esm/time.js.map +1 -1
  49. package/lib/esm/types.d.ts +59 -50
  50. package/lib/esm/utils.d.ts +43 -0
  51. package/lib/esm/utils.js +55 -3
  52. package/lib/esm/utils.js.map +1 -1
  53. package/lib/esm/web.d.ts +2 -2
  54. package/lib/esm/web.js +2 -2
  55. package/lib/esm/winston/index.d.ts +7 -0
  56. package/lib/esm/winston/index.js +7 -1
  57. package/lib/esm/winston/index.js.map +1 -1
  58. package/lib/esm/winston/winston.d.ts +17 -21
  59. package/lib/esm/winston/winston.js +29 -36
  60. package/lib/esm/winston/winston.js.map +1 -1
  61. package/lib/filters/LogFilter.cjs +5 -5
  62. package/lib/filters/LogFilter.d.ts +11 -11
  63. package/lib/filters/PatternFilter.cjs +12 -12
  64. package/lib/filters/PatternFilter.d.ts +15 -15
  65. package/lib/index.cjs +3 -3
  66. package/lib/index.d.ts +2 -2
  67. package/lib/logging.cjs +224 -139
  68. package/lib/logging.d.ts +119 -113
  69. package/lib/logging.js.map +1 -1
  70. package/lib/pino/index.cjs +6 -0
  71. package/lib/pino/index.d.ts +6 -0
  72. package/lib/pino/index.js.map +1 -1
  73. package/lib/pino/pino.cjs +102 -126
  74. package/lib/pino/pino.d.ts +13 -5
  75. package/lib/pino/pino.js.map +1 -1
  76. package/lib/text.cjs +29 -67
  77. package/lib/text.d.ts +29 -67
  78. package/lib/text.js.map +1 -1
  79. package/lib/time.cjs +38 -36
  80. package/lib/time.d.ts +45 -43
  81. package/lib/time.js.map +1 -1
  82. package/lib/types.d.ts +59 -50
  83. package/lib/utils.cjs +55 -3
  84. package/lib/utils.d.ts +43 -0
  85. package/lib/utils.js.map +1 -1
  86. package/lib/web.cjs +2 -2
  87. package/lib/web.d.ts +2 -2
  88. package/lib/winston/index.cjs +22 -0
  89. package/lib/winston/index.d.ts +7 -0
  90. package/lib/winston/index.js.map +1 -1
  91. package/lib/winston/winston.cjs +29 -36
  92. package/lib/winston/winston.d.ts +17 -21
  93. package/lib/winston/winston.js.map +1 -1
  94. package/package.json +9 -10
  95. package/lib/accumulate.types.cjs +0 -27
  96. package/lib/accumulate.types.d.ts +0 -1
  97. package/lib/accumulate.types.js.map +0 -1
  98. package/lib/esm/accumulate.types.d.ts +0 -1
  99. package/lib/esm/accumulate.types.js +0 -25
  100. 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
- A small, flexible TypeScript logging library designed for framework-agnostic projects. It provides:
7
- - Context-aware loggers with hierarchical contexts (class.method) via MiniLogger and the static Logging facade.
8
- - Configurable output (level filtering, verbosity, separators, timestamps) and optional ANSI styling/theming.
9
- - Simple method decorators (log/debug/info/verbose/silly) to instrument class methods without boilerplate.
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
  ![Licence](https://img.shields.io/github/license/decaf-ts/logging.svg?style=plastic)
13
12
  ![GitHub language count](https://img.shields.io/github/languages/count/decaf-ts/logging?style=plastic)
@@ -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.5 KB kb gzipped
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
- This guide provides concise, non-redundant examples for each public API. Examples are inspired by the package’s unit tests and reflect real usage.
122
-
123
- Note: Replace the import path with your actual package name. In this monorepo, tests import from "../../src".
124
-
125
- - import { ... } from "@your-scope/logging" // typical
126
- - import { ... } from "../../src" // inside this repo while developing
127
-
128
-
129
- Basic setup and global logging via Logging
130
- Description: Configure global logging and write messages through the static facade, similar to unit tests that verify console output and level filtering.
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
- // Set global configuration
136
- Logging.setConfig({
137
- level: LogLevel.debug, // allow debug and above
138
- style: false, // plain output (tests use both styled and themeless)
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
- // Log using the global logger
143
- Logging.info("Application started");
144
- Logging.debug("Debug details");
145
- Logging.error("Something went wrong");
147
+ Logging.setConfig({
148
+ ...DefaultLoggingConfig,
149
+ level: LogLevel.debug,
150
+ verbose: 2,
151
+ style: true,
152
+ format: "raw",
153
+ });
146
154
 
147
- // Verbosity-controlled logs (silly delegates to verbose internally)
148
- Logging.setConfig({ verbose: 2 });
149
- Logging.silly("Extra details at verbosity 1"); // emitted when verbose >= 1
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
- Create a class-scoped logger and child method logger
155
- Description: Create a logger bound to a specific context (class) and derive a child logger for a method, matching patterns used in tests.
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.debug });
180
+ Logging.setConfig({ level: LogLevel.info });
181
+ const logger = Logging.for("OrderService");
161
182
 
162
- // A class-scoped logger
163
- const classLogger = Logging.for("UserService");
164
- classLogger.info("Fetching users");
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
- // A child logger for a specific method with temporary config overrides
167
- const methodLogger = classLogger.for("list", { style: false });
168
- methodLogger.debug("Querying repository...");
190
+ runScenario({ correlationId: "req-1" });
191
+ runScenario({ correlationId: "req-2", style: false });
169
192
  ```
170
193
 
171
-
172
- MiniLogger: direct use and per-instance config
173
- Description: Instantiate MiniLogger directly (the default implementation behind Logging.setFactory). Tests create MiniLogger with and without custom config.
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 { MiniLogger, LogLevel, type LoggingConfig } from "@decaf-ts/logging";
211
+ import { Logging, LogLevel, PatternFilter, type LoggingConfig } from "@decaf-ts/logging";
177
212
 
178
- const logger = new MiniLogger("TestContext");
179
- logger.info("Info from MiniLogger");
213
+ class PiiFilter extends PatternFilter {
214
+ constructor() {
215
+ super(/(password|ssn)=([^&\s]+)/gi, (_full, key) => `${key}=***`);
216
+ }
217
+ }
180
218
 
181
- // With custom configuration
182
- const custom: Partial<LoggingConfig> = { level: LogLevel.debug, verbose: 2 };
183
- const customLogger = new MiniLogger("TestContext", custom);
184
- customLogger.debug("Debug with custom level");
219
+ const filters: LoggingConfig["filters"] = [new PiiFilter()];
220
+ Logging.setConfig({
221
+ level: LogLevel.debug,
222
+ filters,
223
+ format: "raw",
224
+ });
185
225
 
186
- // Child logger with correlation id
187
- const traced = customLogger.for("run", { correlationId: "req-123" });
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
- Decorators: log, debug, info, verbose, silly
193
- Description: Instrument methods to log calls and optional benchmarks. Tests validate decorator behavior for call and completion messages.
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 { log, debug, info as infoDecor, verbose as verboseDecor, silly as sillyDecor, LogLevel, Logging } from "@decaf-ts/logging";
197
-
198
- // Configure logging for demo
199
- Logging.setConfig({ level: LogLevel.debug, style: false, timestamp: false });
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 AccountService {
202
- @log(LogLevel.info) // logs method call with args
203
- create(name: string) {
204
- return { id: "1", name };
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
- @debug(true) // logs call and completion time at debug level
208
- rebuildIndex() {
209
- // heavy work...
210
- return true;
211
- }
261
+ Logging.setFactory(WinstonFactory);
262
+ const auditLogger = Logging.for("AuditTrail", {
263
+ level: LogLevel.info,
264
+ transports: [new AuditTransport()],
265
+ });
212
266
 
213
- @info() // convenience wrapper for info level
214
- enable() {
215
- return true;
216
- }
267
+ auditLogger.info("policy=PASSWORD_RESET user=u-9 timestamp=...");
268
+ ```
217
269
 
218
- @verbose(1, true) // verbose with verbosity threshold and benchmark
219
- syncAll() {
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
- @silly() // very chatty, only emitted when verbose allows
224
- ping() {
225
- return "pong";
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
- const svc = new AccountService();
230
- svc.create("Alice");
231
- svc.rebuildIndex();
232
- svc.enable();
233
- await svc.syncAll();
234
- svc.ping();
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
- LoggedClass: zero-boilerplate logging inside classes
239
- Description: Extend LoggedClass to gain a protected this.log with the correct context (class name). Tests use Logging.for to build similar context.
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
- ```ts
242
- import { LoggedClass } from "@your-scope/logging";
326
+ console.log(String((Config as any).service.host)); // SERVICE__HOST
327
+ console.log(Environment.keys()); // ["SERVICE", "LOGGING", ...]
243
328
 
244
- class UserRepository extends LoggedClass {
245
- findById(id: string) {
246
- this.log.info(`Finding ${id}`);
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
- const repo = new UserRepository();
252
- repo.findById("42");
333
+ // Programmatic lookups
334
+ const verbose = Environment.get("logging.verbose");
253
335
  ```
254
336
 
255
-
256
- Winston integration: swap the logger factory
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 "@your-scope/logging";
261
- import { WinstonFactory } from "@your-scope/logging/winston/winston";
341
+ import { LoggedClass, Logging, LogLevel } from "@decaf-ts/logging";
262
342
 
263
- // Install Winston as the logger factory
264
- Logging.setFactory(WinstonFactory);
343
+ Logging.setConfig({ level: LogLevel.info });
265
344
 
266
- // Now any logger created will use Winston under the hood
267
- const log = Logging.for("ApiGateway");
268
- log.info("Gateway started");
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
- Theming and styling with Logging.theme and config
273
- Description: Enable style and customize theme to colorize parts of the log (tests check styled output patterns).
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 { Logging, LogLevel, DefaultTheme, type Theme } from "@your-scope/logging";
277
-
278
- // Enable styling globally
279
- Logging.setConfig({ style: true, timestamp: true, context: false });
280
-
281
- // Optionally override theme: make debug level yellow (fg:33) and error red+bold
282
- const theme: Theme = {
283
- ...DefaultTheme,
284
- logLevel: {
285
- ...DefaultTheme.logLevel,
286
- debug: { fg: 33 },
287
- error: { fg: 31, style: ["bold"] },
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
- Factory basics and because(reason, id)
300
- Description: Create ad-hoc, labeled loggers and use factory semantics.
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 { Logging } from "@your-scope/logging";
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
- // Ad-hoc logger labeled with a reason and optional id (handy for correlation)
306
- const jobLog = Logging.because("reindex", "job-77");
307
- jobLog.info("Starting reindex");
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
- Types: Logger and LoggingConfig in your code
312
- Description: Use the library’s types for better APIs.
427
+ @info()
428
+ activate() {}
313
429
 
314
- ```ts
315
- import type { Logger, LoggingConfig } from "@your-scope/logging";
430
+ @silly()
431
+ ping() {}
316
432
 
317
- export interface ServiceDeps {
318
- log: Logger;
319
- config?: Partial<LoggingConfig>;
433
+ @verbose(1)
434
+ sync() {}
320
435
  }
321
436
 
322
- export class PaymentService {
323
- constructor(private deps: ServiceDeps) {}
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
- ## Coding Principles
332
-
333
- - group similar functionality in folders (analog to namespaces but without any namespace declaration)
334
- - one class per file;
335
- - one interface per file (unless interface is just used as a type);
336
- - group types as other interfaces in a types.ts file per folder;
337
- - group constants or enums in a constants.ts file per folder;
338
- - group decorators in a decorators.ts file per folder;
339
- - always import from the specific file, never from a folder or index file (exceptions for dependencies on other packages);
340
- - prefer the usage of established design patters where applicable:
341
- - Singleton (can be an anti-pattern. use with care);
342
- - factory;
343
- - observer;
344
- - strategy;
345
- - builder;
346
- - etc;
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