hierarchical-area-logger 0.3.1 → 0.4.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 CHANGED
@@ -4,11 +4,12 @@ A lightweight, structured logging utility for TypeScript applications. It allows
4
4
 
5
5
  ## 🚀 Features
6
6
 
7
- - **Scoped Logging:** Group logs by logical "areas" (e.g., `db-layer`, `auth-service`).
8
- - **Traceability:** Automatic Event ID generation and Parent Event ID mapping for distributed tracing.
7
+ - **Scoped Logging:** Group logs by logical "areas" (e.g., `db-layer`, `auth-instance`).
8
+ - **Traceability:** Event ID generation via a user-supplied factory and Parent Event ID mapping for distributed tracing.
9
9
  - **Structured Payloads:** Log rich objects and Error instances without losing metadata.
10
- - **Snapshot Dumping:** Retrieve the entire log state at any time for analysis or reporting.
11
- - **Lightweight:** Designed to be dependency-lite and predictable.
10
+ - **Snapshot Dumping:** Retrieve the entire log state at any time for analysis or reporting — nested or flattened.
11
+ - **Hydration:** Continue logging on top of existing log data (from another instance or a previous run).
12
+ - **Lightweight:** Zero runtime dependencies.
12
13
 
13
14
  ---
14
15
 
@@ -33,7 +34,8 @@ Instead of a flat stream of text, the logger categorizes logs into logical areas
33
34
  import { createLogger } from 'hierarchical-area-logger';
34
35
 
35
36
  const logger = createLogger({
36
- details: { service: 'payment-gateway' },
37
+ details: { instance: 'payment-gateway' },
38
+ createEventId: () => crypto.randomUUID(),
37
39
  });
38
40
 
39
41
  const checkout = logger.getArea('checkout-process');
@@ -52,30 +54,43 @@ Track a request across your system by linking `eventId` and `parentEventId`.
52
54
 
53
55
  ```typescript
54
56
  const logger = createLogger({
55
- details: { service: 'payment-gateway' }
56
- path: '/api/v1/user',
57
- parentEventId: 'incoming-request-id-001'
57
+ details: { instance: 'payment-gateway' },
58
+ createEventId: () => crypto.randomUUID(),
59
+ path: '/api/v1/user',
60
+ parentEventId: 'incoming-request-id-001',
58
61
  });
59
62
 
60
- console.log(logger.eventId); // Automatically generated unique ID
61
- console.log(logger.parentEventId); // 'incoming-request-id-001'
62
-
63
+ console.log(logger.eventId); // ID created by createEventId
64
+ console.log(logger.retrieveRootPayload().parentEventId); // 'incoming-request-id-001'
63
65
  ```
64
66
 
65
- ### 3. Log Merging & State Management
67
+ ### 3. Log Hydration & State Management
66
68
 
67
- You can append existing log data to a logger instance, which is useful for consolidating logs from multiple micro-tasks.
69
+ You can pre-populate a logger with existing log data — useful for consolidating logs from multiple micro-tasks or continuing a flow started elsewhere. Hydration accepts `LogData` or an array of `OrderedLogEvent`s; when the data carries a valid root payload, it is adopted and its `eventId` is reused.
68
70
 
69
71
  ```typescript
70
- const mainLogger = createLogger({ details: { service: 'orchestrator' } });
71
-
72
- const externalLogs = {
73
- 'worker-1': [
74
- { type: 'info', message: 'Task complete', timestamp: Date.now() },
75
- ],
76
- };
72
+ const mainLogger = createLogger({
73
+ details: { instance: 'orchestrator' },
74
+ createEventId: () => crypto.randomUUID(),
75
+ populateLog: {
76
+ root: [
77
+ {
78
+ type: 'info',
79
+ message: 'Log started',
80
+ payload: {
81
+ eventId: 'upstream-event-001',
82
+ details: { instance: 'orchestrator' },
83
+ },
84
+ timestamp: Date.now(),
85
+ },
86
+ ],
87
+ 'worker-1': [
88
+ { type: 'info', message: 'Task complete', timestamp: Date.now() },
89
+ ],
90
+ },
91
+ });
77
92
 
78
- mainLogger.appendLogData(externalLogs);
93
+ console.log(mainLogger.eventId); // 'upstream-event-001'
79
94
  ```
80
95
 
81
96
  ---
@@ -84,23 +99,155 @@ mainLogger.appendLogData(externalLogs);
84
99
 
85
100
  ### `createLogger(options)`
86
101
 
87
- Factory function to initialize a new Logger instance.
102
+ Factory function to initialize a new `Logger` instance. `details` and `createEventId` are required; `details` can carry any additional fields, typed via the `Details`/`Logger` generic parameter.
103
+
104
+ | Option | Type | Required | Description |
105
+ | --------------------- | ------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------ |
106
+ | `details` | `Details` | ✓ | Metadata about the instance or environment. `instance` is required. |
107
+ | `createEventId` | `(details: Details) => string` | ✓ | Factory that creates the event ID for this logging session. Called with `details`. |
108
+ | `path` | `string` | | The request path/route, stored in the root payload. |
109
+ | `method` | `Method` | | HTTP method (`get`, `post`, `put`, `delete`, `patch`, `head`, `options`, `trace`, `connect`). |
110
+ | `parentEventId` | `string` | | ID of the event that triggered this one, stored in the root payload. |
111
+ | `expectParentEventId` | `boolean` | | Logs an error entry when `parentEventId` is expected but missing. |
112
+ | `populateLog` | `LogData \| OrderedLogEvent[]` | | Pre-populate the logger with existing log data. A valid root payload is adopted and its `eventId` is reused. |
113
+ | `dumpOptions` | `DumpLoggerOptions` | | Default options applied by `dump()`/`dumpFlat()`; per-call options override them. |
114
+
115
+ ### `Logger` instance
116
+
117
+ Properties:
118
+
119
+ - **`eventId: string`** — the unique ID of this logging session (created by `createEventId`, or adopted from populated log data).
120
+
121
+ #### `getArea(name)`
122
+
123
+ Returns a scoped logger for a specific area, creating it lazily on first use.
124
+
125
+ The returned area logger exposes:
126
+
127
+ - `.info(message, payload?)`
128
+ - `.warn(message, payload?)`
129
+ - `.error(message, payload? | Error)` — `Error` instances are converted to `{ message, name, stack }`
130
+ - `.log(message, payload?)`
131
+ - `.debug(message, payload?)`
132
+
133
+ #### `dump(options?)`
134
+
135
+ Returns the current log data as a `LogData` object (areas mapped to arrays of entries), derived from the internal flat list of ordered log events.
136
+
137
+ | Option | Type | Default | Description |
138
+ | ----------------- | ---------------- | ------- | -------------------------------------------------------------------------- |
139
+ | `omitRoot` | `boolean` | `false` | Excludes `root` holder events from the output. |
140
+ | `finalizeLog` | `boolean` | `true` | Appends a "Log completed" entry (with `totalDuration`) to the `root` area. |
141
+ | `excludeLogTypes` | `LogEntryType[]` | — | Entry types to remove from every area. No types are excluded by default. |
142
+
143
+ Areas without entries are omitted from the returned object. Dumps work on a copy of the internal log, so calling `dump()` repeatedly always returns the same result.
144
+
145
+ Options are merged in this order (later wins): built-in defaults → constructor `dumpOptions` → per-call options.
146
+
147
+ ```typescript
148
+ // Configure dump defaults once at construction:
149
+ const logger = createLogger({
150
+ details: { instance: 'payment-gateway' },
151
+ createEventId: () => crypto.randomUUID(),
152
+ dumpOptions: { excludeLogTypes: ['debug'], omitRoot: true },
153
+ });
154
+
155
+ // Both calls honor the construction defaults; per-call options override them:
156
+ logger.dump(); // no root, no debug entries
157
+ logger.dump({ omitRoot: false }); // root included, still no debug entries
158
+ ```
159
+
160
+ #### `dumpFlat(options?)`
161
+
162
+ Same options and processing as `dump`, but returns the internal flat `OrderedLogEvent[]` — the chronological list of events, each tagged with the name of its `holder` area — instead of grouping them into a `LogData` object.
163
+
164
+ #### `retrieveAreaEvents(area, excludeLogTypes?)`
165
+
166
+ Returns every `LogEntry` logged for the given area (an empty array when the area has never been used). When `excludeLogTypes` is provided, entries of the listed types are filtered out.
88
167
 
89
- | Option | Type | Description |
90
- | --------------- | --------- | ------------------------------------------ |
91
- | `details` | `Details` | Metadata about the service or environment. |
92
- | `path` | `string` | (Optional) The execution path/route. |
93
- | `parentEventId` | `string` | (Optional) ID of the triggering event. |
168
+ #### `retrieveRootPayload()`
94
169
 
95
- ### `LoggerInstance` Methods
170
+ Returns the root payload — the payload of the first entry in the `root` area (`path`, `method`, `eventId`, `parentEventId`, `details`).
171
+
172
+ ### Exported Utilities
173
+
174
+ #### `prettyError(error)`
175
+
176
+ Converts an `Error` into a structured `{ message, name, stack }` object, with the stack trace cleaned up (no `Error: ` lines, no `file://` prefixes, truncated absolute paths).
177
+
178
+ ```typescript
179
+ import { prettyError } from 'hierarchical-area-logger';
180
+
181
+ const structured = prettyError(new Error('Boom'));
182
+ // { message: 'Boom', name: 'Error', stack: ['at ...', ...] }
183
+ ```
184
+
185
+ #### `orderLogEvents(logData)`
186
+
187
+ Flattens a `LogData` object into a single array of events sorted by timestamp in ascending order. Each event is tagged with the name of its holder area.
188
+
189
+ ```typescript
190
+ import { orderLogEvents } from 'hierarchical-area-logger';
191
+
192
+ const logData = {
193
+ 'db-layer': [{ type: 'info', message: 'Query complete', timestamp: 200 }],
194
+ root: [{ type: 'info', message: 'Log started', timestamp: 100 }],
195
+ };
196
+
197
+ const ordered = orderLogEvents(logData);
198
+ // [
199
+ // { holder: 'root', type: 'info', message: 'Log started', timestamp: 100 },
200
+ // { holder: 'db-layer', type: 'info', message: 'Query complete', timestamp: 200 },
201
+ // ]
202
+ ```
203
+
204
+ #### `groupLogEvents(events)`
205
+
206
+ The inverse of `orderLogEvents`: groups an array of `OrderedLogEvent`s back into a `LogData` object keyed by the holder area of each event.
207
+
208
+ ```typescript
209
+ import { groupLogEvents } from 'hierarchical-area-logger';
210
+
211
+ const logData = groupLogEvents([
212
+ { holder: 'root', type: 'info', message: 'Log started', timestamp: 100 },
213
+ {
214
+ holder: 'db-layer',
215
+ type: 'info',
216
+ message: 'Query complete',
217
+ timestamp: 200,
218
+ },
219
+ ]);
220
+ ```
221
+
222
+ ### Exported Guards
223
+
224
+ Type guards that narrow an `unknown` value to the library's log types by validating its structure.
225
+
226
+ - **`isLogData(data)`** — narrows to `LogData` (a non-array object whose values are arrays of valid log entries).
227
+ - **`isOrderedLogEvents(data)`** — narrows to `OrderedLogEvent[]` (log entries tagged with a string `holder`).
228
+ - **`hasRootPayload(log)`** — narrows to an `OrderedLogEvent[]` containing a valid root payload (a `root` holder event whose payload carries a string `eventId` and a `details` object with a string `instance`).
229
+
230
+ ```typescript
231
+ import {
232
+ isLogData,
233
+ isOrderedLogEvents,
234
+ hasRootPayload,
235
+ } from 'hierarchical-area-logger';
236
+
237
+ if (isLogData(value)) {
238
+ // value is LogData here
239
+ } else if (isOrderedLogEvents(value)) {
240
+ // value is OrderedLogEvent[] here
241
+ }
242
+
243
+ if (hasRootPayload(value)) {
244
+ // value is an OrderedLogEvent[] with a valid root payload here
245
+ }
246
+ ```
96
247
 
97
- - **`getArea(name: string)`**: Returns a scoped logger for a specific logic block.
98
- - `.info(msg, payload?)`
99
- - `.warn(msg, payload?)`
100
- - `.error(msg, error?)`
248
+ ### Exported Types
101
249
 
102
- - **`dump()`**: Returns an object containing all logs indexed by their area names.
103
- - **`appendLogData(data)`**: Manually injects log entries into the current instance.
250
+ `LogData`, `LogEntry`, `LogEntryType`, `Details`, `RootPayload`, `OrderedLogEvent`, `DumpLoggerOptions`.
104
251
 
105
252
  ---
106
253
 
@@ -117,4 +264,4 @@ npm run coverage
117
264
 
118
265
  ```
119
266
 
120
- > **Note:** The logger automatically creates a `root` area log entry labeled "Request received" if a `path` is provided during initialization.
267
+ > **Note:** Every logger seeds its `root` area with a "Log started" entry. With `finalizeLog` enabled (the default), `dump()` and `dumpFlat()` append a "Log completed" entry carrying the total request duration.
package/dist/index.d.mts CHANGED
@@ -1,57 +1,168 @@
1
- type Details = {
2
- service: string;
3
- } & Record<string, unknown>;
1
+ type Details<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> = {
2
+ instance: string;
3
+ } & AdditionalDetails;
4
4
  type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace' | 'connect';
5
5
  type LogEntryType = 'info' | 'warn' | 'error' | 'log' | 'debug';
6
- type LogEntry = {
6
+ type LogData = Record<string, LogEntry[]>;
7
+ type OrderedLogEvent = LogEntry & {
8
+ holder: string;
9
+ };
10
+ interface LogEntry {
7
11
  type: LogEntryType;
8
12
  message: string;
9
13
  payload?: object;
10
14
  timestamp: number;
11
- };
12
- type RootPayload = {
15
+ }
16
+ interface RootPayload<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
17
+ eventId: string;
18
+ details: Details<AdditionalDetails>;
13
19
  path?: string;
14
20
  method?: Method;
15
- eventId: string;
16
21
  parentEventId?: string;
17
- details: Details;
18
- };
19
- type LogData = Record<string, LogEntry[]>;
20
- interface LoggerOptions {
21
- details: Details;
22
- path?: string;
22
+ }
23
+ interface LoggerOptions<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
24
+ populateLog?: LogData | OrderedLogEvent[];
23
25
  parentEventId?: string;
24
- withParentEventId?: boolean;
26
+ expectParentEventId?: boolean;
27
+ details: Details<AdditionalDetails>;
28
+ path?: string;
25
29
  method?: Method;
26
- overrideEventId?: string;
27
- defaultArea?: string;
30
+ dumpOptions?: DumpLoggerOptions;
31
+ createEventId: (details: Details<AdditionalDetails>) => string;
32
+ }
33
+ interface DumpLoggerOptions {
34
+ omitRoot?: boolean;
35
+ finalizeLog?: boolean;
36
+ excludeLogTypes?: LogEntryType[];
37
+ }
38
+ interface GetAreaMethods {
39
+ info: (message: string, payload?: LogEntry['payload']) => void;
40
+ warn: (message: string, payload?: LogEntry['payload']) => void;
41
+ error: (message: string, payload?: LogEntry['payload'] | Error) => void;
42
+ log: (message: string, payload?: LogEntry['payload']) => void;
43
+ debug: (message: string, payload?: LogEntry['payload']) => void;
28
44
  }
29
45
 
30
- declare class Logger {
46
+ declare class Logger<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
47
+ /** Unique ID identifying this logging session (created via `createEventId` or adopted from populated log data). */
31
48
  eventId: string;
32
49
  private log;
33
- parentEventId?: string;
34
- private defaultArea;
35
- constructor(options: LoggerOptions);
36
- private finalizeRootMessage;
50
+ private logPopulated;
51
+ private dumpOptions?;
52
+ /**
53
+ * Creates a new logger. Unless populated from `populateLog`, a `root`
54
+ * "Log started" event is seeded and `eventId` is created via
55
+ * `createEventId`. When `populateLog` carries a valid root payload, it is
56
+ * adopted instead and `eventId` is re-synced from it. `dumpOptions` act as
57
+ * defaults for `dump`/`dumpFlat` and can be overridden per call.
58
+ */
59
+ constructor({ details, path, method, parentEventId, expectParentEventId, populateLog, dumpOptions, createEventId, }: LoggerOptions<AdditionalDetails>);
60
+ /**
61
+ * Adopts the populated log when it contains a valid root payload
62
+ * (re-syncing `eventId` from it). Populated data without a valid root
63
+ * payload is discarded in favor of the freshly created log.
64
+ */
65
+ private finalizeHydratedLog;
66
+ /**
67
+ * Appends the closing "Log completed" event to the `root` holder,
68
+ * carrying the total duration since the "Log started" event.
69
+ */
70
+ private finalizeLog;
71
+ /**
72
+ * Pushes a single ordered log event into the given area. `Error` payloads
73
+ * are converted into structured objects via `prettyError`.
74
+ */
37
75
  private addMessage;
38
- getArea(name?: string): {
39
- info: (message: string, payload?: LogEntry["payload"]) => void;
40
- warn: (message: string, payload?: LogEntry["payload"]) => void;
41
- error: (message: string, payload?: LogEntry["payload"] | Error) => void;
42
- log: (message: string, payload?: LogEntry["payload"]) => void;
43
- debug: (message: string, payload?: LogEntry["payload"]) => void;
44
- };
45
- dump(): LogData;
46
- dumpProduction(): LogData;
47
- appendLogData(logData: LogData): void;
76
+ /**
77
+ * Extracts the root payload — the payload of the first event held by the
78
+ * `root` area.
79
+ */
80
+ private getRootPayload;
81
+ /**
82
+ * Returns a scoped logging API for an area.
83
+ *
84
+ * The returned API exposes `info`, `warn`, `error`, `log` and `debug`;
85
+ * `error` also accepts `Error` instances as its payload.
86
+ */
87
+ getArea(name: string): GetAreaMethods;
88
+ /**
89
+ * Applies the dump options to a copy of the internal ordered log events
90
+ * and returns the processed list. The internal log is never mutated.
91
+ *
92
+ * Options are merged in this order (later wins):
93
+ * `DEFAULT_PREPARE_DUMP_OPTIONS` → constructor `dumpOptions` → per-call
94
+ * `opts`.
95
+ *
96
+ * Defaults (see `DEFAULT_PREPARE_DUMP_OPTIONS`):
97
+ * - `omitRoot: false` — `root` holder events are kept in the output.
98
+ * - `finalizeLog: true` — a "Log completed" event is appended to the
99
+ * `root` holder.
100
+ * - `excludeLogTypes` — when provided, events of the listed types are
101
+ * removed; no types are excluded by default.
102
+ */
103
+ private prepareLog;
104
+ /**
105
+ * Returns the current log data as a `LogData` object (areas mapped to
106
+ * arrays of entries) after applying the dump options. Areas without
107
+ * entries are omitted from the returned object.
108
+ */
109
+ dump(opts?: DumpLoggerOptions): LogData;
110
+ /**
111
+ * Returns the current log data as an `OrderedLogEvent[]` — the flat,
112
+ * chronological list of events each tagged with the name of its holder
113
+ * area — after applying the same options as `dump`.
114
+ */
115
+ dumpFlat(opts?: DumpLoggerOptions): OrderedLogEvent[];
116
+ /**
117
+ * Returns every entry logged for the given area (an empty array when the
118
+ * area has never been used). When `excludeLogTypes` is provided, entries
119
+ * of the listed types are filtered out.
120
+ */
121
+ retrieveAreaEvents(area: string, excludeLogTypes?: LogEntryType[]): LogEntry[];
122
+ /**
123
+ * Returns the root payload — the payload of the first event held by the
124
+ * `root` area (`path`, `method`, `eventId`, `parentEventId`, `details`).
125
+ */
126
+ retrieveRootPayload(): RootPayload;
48
127
  }
49
- declare const createLogger: (options: LoggerOptions) => Logger;
128
+ declare const createLogger: <AdditionalDetails extends Record<string, unknown> = Record<string, unknown>>(options: LoggerOptions<AdditionalDetails>) => Logger<AdditionalDetails>;
50
129
 
130
+ /**
131
+ * Converts an Error into a `{ message, name, stack }` object with the stack cleaned
132
+ * up by `prettyStack`.
133
+ */
51
134
  declare const prettyError: (err: Error) => {
52
135
  message: string;
136
+ name: string;
53
137
  stack: string[];
54
138
  };
139
+ /**
140
+ * Flattens log data into a single array of events sorted by timestamp in
141
+ * ascending order, tagging each event with the name of its holder area.
142
+ */
143
+ declare const orderLogEvents: (logData: LogData) => OrderedLogEvent[];
144
+ /**
145
+ * Groups ordered log events back into a `LogData` object keyed by the holder
146
+ * area of each event, preserving the relative order within each area.
147
+ */
148
+ declare const groupLogEvents: (events: OrderedLogEvent[]) => LogData;
149
+
150
+ /**
151
+ * Type guard: checks whether the given value is an ordered log event array
152
+ * containing a valid root payload — a `root` holder event whose payload
153
+ * carries a string `eventId` and a `details` object with a string `instance`.
154
+ */
155
+ declare const hasRootPayload: (log: unknown) => log is OrderedLogEvent[];
156
+ /**
157
+ * Type guard: checks whether the given value is a valid `LogData` object (a
158
+ * non-array object whose values are arrays of log entries).
159
+ */
160
+ declare const isLogData: (data: unknown) => data is LogData;
161
+ /**
162
+ * Type guard: checks whether the given value is a valid array of
163
+ * `OrderedLogEvent`s (log entries tagged with a `holder` area name).
164
+ */
165
+ declare const isOrderedLogEvents: (data: unknown) => data is OrderedLogEvent[];
55
166
 
56
- export { Logger, createLogger, prettyError };
57
- export type { Details, LogData, LogEntry, LogEntryType, RootPayload };
167
+ export { Logger, createLogger, groupLogEvents, hasRootPayload, isLogData, isOrderedLogEvents, orderLogEvents, prettyError };
168
+ export type { Details, DumpLoggerOptions, LogData, LogEntry, LogEntryType, OrderedLogEvent, RootPayload };
package/dist/index.d.ts CHANGED
@@ -1,57 +1,168 @@
1
- type Details = {
2
- service: string;
3
- } & Record<string, unknown>;
1
+ type Details<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> = {
2
+ instance: string;
3
+ } & AdditionalDetails;
4
4
  type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace' | 'connect';
5
5
  type LogEntryType = 'info' | 'warn' | 'error' | 'log' | 'debug';
6
- type LogEntry = {
6
+ type LogData = Record<string, LogEntry[]>;
7
+ type OrderedLogEvent = LogEntry & {
8
+ holder: string;
9
+ };
10
+ interface LogEntry {
7
11
  type: LogEntryType;
8
12
  message: string;
9
13
  payload?: object;
10
14
  timestamp: number;
11
- };
12
- type RootPayload = {
15
+ }
16
+ interface RootPayload<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
17
+ eventId: string;
18
+ details: Details<AdditionalDetails>;
13
19
  path?: string;
14
20
  method?: Method;
15
- eventId: string;
16
21
  parentEventId?: string;
17
- details: Details;
18
- };
19
- type LogData = Record<string, LogEntry[]>;
20
- interface LoggerOptions {
21
- details: Details;
22
- path?: string;
22
+ }
23
+ interface LoggerOptions<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
24
+ populateLog?: LogData | OrderedLogEvent[];
23
25
  parentEventId?: string;
24
- withParentEventId?: boolean;
26
+ expectParentEventId?: boolean;
27
+ details: Details<AdditionalDetails>;
28
+ path?: string;
25
29
  method?: Method;
26
- overrideEventId?: string;
27
- defaultArea?: string;
30
+ dumpOptions?: DumpLoggerOptions;
31
+ createEventId: (details: Details<AdditionalDetails>) => string;
32
+ }
33
+ interface DumpLoggerOptions {
34
+ omitRoot?: boolean;
35
+ finalizeLog?: boolean;
36
+ excludeLogTypes?: LogEntryType[];
37
+ }
38
+ interface GetAreaMethods {
39
+ info: (message: string, payload?: LogEntry['payload']) => void;
40
+ warn: (message: string, payload?: LogEntry['payload']) => void;
41
+ error: (message: string, payload?: LogEntry['payload'] | Error) => void;
42
+ log: (message: string, payload?: LogEntry['payload']) => void;
43
+ debug: (message: string, payload?: LogEntry['payload']) => void;
28
44
  }
29
45
 
30
- declare class Logger {
46
+ declare class Logger<AdditionalDetails extends Record<string, unknown> = Record<string, unknown>> {
47
+ /** Unique ID identifying this logging session (created via `createEventId` or adopted from populated log data). */
31
48
  eventId: string;
32
49
  private log;
33
- parentEventId?: string;
34
- private defaultArea;
35
- constructor(options: LoggerOptions);
36
- private finalizeRootMessage;
50
+ private logPopulated;
51
+ private dumpOptions?;
52
+ /**
53
+ * Creates a new logger. Unless populated from `populateLog`, a `root`
54
+ * "Log started" event is seeded and `eventId` is created via
55
+ * `createEventId`. When `populateLog` carries a valid root payload, it is
56
+ * adopted instead and `eventId` is re-synced from it. `dumpOptions` act as
57
+ * defaults for `dump`/`dumpFlat` and can be overridden per call.
58
+ */
59
+ constructor({ details, path, method, parentEventId, expectParentEventId, populateLog, dumpOptions, createEventId, }: LoggerOptions<AdditionalDetails>);
60
+ /**
61
+ * Adopts the populated log when it contains a valid root payload
62
+ * (re-syncing `eventId` from it). Populated data without a valid root
63
+ * payload is discarded in favor of the freshly created log.
64
+ */
65
+ private finalizeHydratedLog;
66
+ /**
67
+ * Appends the closing "Log completed" event to the `root` holder,
68
+ * carrying the total duration since the "Log started" event.
69
+ */
70
+ private finalizeLog;
71
+ /**
72
+ * Pushes a single ordered log event into the given area. `Error` payloads
73
+ * are converted into structured objects via `prettyError`.
74
+ */
37
75
  private addMessage;
38
- getArea(name?: string): {
39
- info: (message: string, payload?: LogEntry["payload"]) => void;
40
- warn: (message: string, payload?: LogEntry["payload"]) => void;
41
- error: (message: string, payload?: LogEntry["payload"] | Error) => void;
42
- log: (message: string, payload?: LogEntry["payload"]) => void;
43
- debug: (message: string, payload?: LogEntry["payload"]) => void;
44
- };
45
- dump(): LogData;
46
- dumpProduction(): LogData;
47
- appendLogData(logData: LogData): void;
76
+ /**
77
+ * Extracts the root payload — the payload of the first event held by the
78
+ * `root` area.
79
+ */
80
+ private getRootPayload;
81
+ /**
82
+ * Returns a scoped logging API for an area.
83
+ *
84
+ * The returned API exposes `info`, `warn`, `error`, `log` and `debug`;
85
+ * `error` also accepts `Error` instances as its payload.
86
+ */
87
+ getArea(name: string): GetAreaMethods;
88
+ /**
89
+ * Applies the dump options to a copy of the internal ordered log events
90
+ * and returns the processed list. The internal log is never mutated.
91
+ *
92
+ * Options are merged in this order (later wins):
93
+ * `DEFAULT_PREPARE_DUMP_OPTIONS` → constructor `dumpOptions` → per-call
94
+ * `opts`.
95
+ *
96
+ * Defaults (see `DEFAULT_PREPARE_DUMP_OPTIONS`):
97
+ * - `omitRoot: false` — `root` holder events are kept in the output.
98
+ * - `finalizeLog: true` — a "Log completed" event is appended to the
99
+ * `root` holder.
100
+ * - `excludeLogTypes` — when provided, events of the listed types are
101
+ * removed; no types are excluded by default.
102
+ */
103
+ private prepareLog;
104
+ /**
105
+ * Returns the current log data as a `LogData` object (areas mapped to
106
+ * arrays of entries) after applying the dump options. Areas without
107
+ * entries are omitted from the returned object.
108
+ */
109
+ dump(opts?: DumpLoggerOptions): LogData;
110
+ /**
111
+ * Returns the current log data as an `OrderedLogEvent[]` — the flat,
112
+ * chronological list of events each tagged with the name of its holder
113
+ * area — after applying the same options as `dump`.
114
+ */
115
+ dumpFlat(opts?: DumpLoggerOptions): OrderedLogEvent[];
116
+ /**
117
+ * Returns every entry logged for the given area (an empty array when the
118
+ * area has never been used). When `excludeLogTypes` is provided, entries
119
+ * of the listed types are filtered out.
120
+ */
121
+ retrieveAreaEvents(area: string, excludeLogTypes?: LogEntryType[]): LogEntry[];
122
+ /**
123
+ * Returns the root payload — the payload of the first event held by the
124
+ * `root` area (`path`, `method`, `eventId`, `parentEventId`, `details`).
125
+ */
126
+ retrieveRootPayload(): RootPayload;
48
127
  }
49
- declare const createLogger: (options: LoggerOptions) => Logger;
128
+ declare const createLogger: <AdditionalDetails extends Record<string, unknown> = Record<string, unknown>>(options: LoggerOptions<AdditionalDetails>) => Logger<AdditionalDetails>;
50
129
 
130
+ /**
131
+ * Converts an Error into a `{ message, name, stack }` object with the stack cleaned
132
+ * up by `prettyStack`.
133
+ */
51
134
  declare const prettyError: (err: Error) => {
52
135
  message: string;
136
+ name: string;
53
137
  stack: string[];
54
138
  };
139
+ /**
140
+ * Flattens log data into a single array of events sorted by timestamp in
141
+ * ascending order, tagging each event with the name of its holder area.
142
+ */
143
+ declare const orderLogEvents: (logData: LogData) => OrderedLogEvent[];
144
+ /**
145
+ * Groups ordered log events back into a `LogData` object keyed by the holder
146
+ * area of each event, preserving the relative order within each area.
147
+ */
148
+ declare const groupLogEvents: (events: OrderedLogEvent[]) => LogData;
149
+
150
+ /**
151
+ * Type guard: checks whether the given value is an ordered log event array
152
+ * containing a valid root payload — a `root` holder event whose payload
153
+ * carries a string `eventId` and a `details` object with a string `instance`.
154
+ */
155
+ declare const hasRootPayload: (log: unknown) => log is OrderedLogEvent[];
156
+ /**
157
+ * Type guard: checks whether the given value is a valid `LogData` object (a
158
+ * non-array object whose values are arrays of log entries).
159
+ */
160
+ declare const isLogData: (data: unknown) => data is LogData;
161
+ /**
162
+ * Type guard: checks whether the given value is a valid array of
163
+ * `OrderedLogEvent`s (log entries tagged with a `holder` area name).
164
+ */
165
+ declare const isOrderedLogEvents: (data: unknown) => data is OrderedLogEvent[];
55
166
 
56
- export { Logger, createLogger, prettyError };
57
- export type { Details, LogData, LogEntry, LogEntryType, RootPayload };
167
+ export { Logger, createLogger, groupLogEvents, hasRootPayload, isLogData, isOrderedLogEvents, orderLogEvents, prettyError };
168
+ export type { Details, DumpLoggerOptions, LogData, LogEntry, LogEntryType, OrderedLogEvent, RootPayload };
package/dist/index.mjs CHANGED
@@ -1,5 +1,3 @@
1
- import { init } from '@paralleldrive/cuid2';
2
-
3
1
  const prettyStack = (stack) => {
4
2
  if (!stack) return [];
5
3
  let toReplace = "";
@@ -13,7 +11,8 @@ const prettyStack = (stack) => {
13
11
  const prettyError = (err) => {
14
12
  const stack = prettyStack(err.stack);
15
13
  const message = err.message;
16
- return { message, stack };
14
+ const name = err.name;
15
+ return { message, name, stack };
17
16
  };
18
17
  const createRootLogEntry = ({
19
18
  path,
@@ -21,128 +20,281 @@ const createRootLogEntry = ({
21
20
  details,
22
21
  eventId,
23
22
  parentEventId,
24
- withParentEventId
23
+ expectParentEventId
25
24
  }) => {
26
- const url = new URL(`http://example.com${path ?? "/"}`);
27
- const rootLogEntry = {
28
- root: [
29
- {
30
- type: "info",
31
- message: "Request received",
32
- payload: {
33
- path: `${url.pathname}${url.search}`,
34
- method,
35
- details,
36
- eventId,
37
- ...parentEventId && { parentEventId }
38
- },
39
- timestamp: Date.now()
40
- }
41
- ]
42
- };
43
- if (withParentEventId && !parentEventId) {
44
- rootLogEntry.root.push({
25
+ const rootLogEvents = [
26
+ {
27
+ holder: "root",
28
+ type: "info",
29
+ message: "Log started",
30
+ payload: {
31
+ eventId,
32
+ details,
33
+ ...path && { path },
34
+ ...method && { method },
35
+ ...parentEventId && { parentEventId }
36
+ },
37
+ timestamp: Date.now()
38
+ }
39
+ ];
40
+ if (expectParentEventId && !parentEventId) {
41
+ rootLogEvents.push({
42
+ holder: "root",
45
43
  type: "error",
46
- message: "Parent event ID expected but not found",
44
+ message: "Parent event ID expected but not provided",
47
45
  timestamp: Date.now()
48
46
  });
49
47
  }
50
- return rootLogEntry;
48
+ return rootLogEvents;
51
49
  };
52
- const removeEmptyAreas = (logData) => {
53
- const newLogData = {};
54
- for (const [key, value] of Object.entries(logData)) {
55
- if (value.length > 0) {
56
- newLogData[key] = value;
50
+ const excludeLogEntryTypes = (events, excludeLogTypes) => {
51
+ if (!excludeLogTypes || excludeLogTypes.length === 0) return events;
52
+ return events.filter((event) => !excludeLogTypes.includes(event.type));
53
+ };
54
+ const orderLogEvents = (logData) => {
55
+ return Object.entries(logData).flatMap(
56
+ ([holder, events]) => events.map((event) => ({
57
+ holder,
58
+ ...event
59
+ }))
60
+ ).sort((a, b) => a.timestamp - b.timestamp);
61
+ };
62
+ const groupLogEvents = (events) => {
63
+ const logData = {};
64
+ for (const { holder, ...entry } of events) {
65
+ if (!logData[holder]) {
66
+ logData[holder] = [];
57
67
  }
68
+ logData[holder].push(entry);
58
69
  }
59
- return newLogData;
70
+ return logData;
60
71
  };
61
- const excludeDebugLogs = (logEntries) => {
62
- return logEntries.filter((entry) => entry.type !== "debug");
72
+
73
+ const isLogEntryType = (value) => {
74
+ return value === "info" || value === "warn" || value === "error" || value === "log" || value === "debug";
63
75
  };
64
- const processProductionLogData = (logData) => {
65
- const newLogData = {};
66
- for (const [key, value] of Object.entries(logData)) {
67
- if (key !== "root") {
68
- newLogData[key] = excludeDebugLogs(value);
69
- }
76
+ const isLogEntry = (value) => {
77
+ if (typeof value !== "object" || value === null) return false;
78
+ const entry = value;
79
+ return isLogEntryType(entry.type) && typeof entry.message === "string" && typeof entry.timestamp === "number" && (entry.payload === void 0 || typeof entry.payload === "object" && entry.payload !== null);
80
+ };
81
+ const hasRootPayload = (log) => {
82
+ if (!isOrderedLogEvents(log)) return false;
83
+ const rootEvent = log.find((event) => event.holder === "root");
84
+ if (!rootEvent) return false;
85
+ const rootPayload = rootEvent.payload;
86
+ if (typeof rootPayload !== "object" || rootPayload === null) return false;
87
+ if (typeof rootPayload.eventId !== "string") return false;
88
+ const details = rootPayload.details;
89
+ if (typeof details !== "object" || details === null) return false;
90
+ return typeof details.instance === "string";
91
+ };
92
+ const isLogData = (data) => {
93
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
94
+ return false;
70
95
  }
71
- return removeEmptyAreas(newLogData);
96
+ return Object.values(data).every(
97
+ (area) => Array.isArray(area) && area.every((entry) => isLogEntry(entry))
98
+ );
99
+ };
100
+ const isOrderedLogEvents = (data) => {
101
+ if (!Array.isArray(data)) return false;
102
+ return data.every((event) => {
103
+ if (typeof event !== "object" || event === null) return false;
104
+ const ordered = event;
105
+ return typeof ordered.holder === "string" && isLogEntry(event);
106
+ });
107
+ };
108
+
109
+ const DEFAULT_PREPARE_DUMP_OPTIONS = {
110
+ omitRoot: false,
111
+ finalizeLog: true
72
112
  };
73
113
 
74
114
  class Logger {
115
+ /** Unique ID identifying this logging session (created via `createEventId` or adopted from populated log data). */
75
116
  eventId;
76
117
  log;
77
- parentEventId;
78
- defaultArea;
79
- constructor(options) {
80
- this.parentEventId = options.parentEventId;
81
- this.eventId = options.overrideEventId || init({ fingerprint: options.details.service })();
82
- this.defaultArea = options.defaultArea || "defaultArea";
83
- this.log = createRootLogEntry({
84
- path: options.path || "/",
85
- method: options.method,
86
- details: options.details,
87
- eventId: this.eventId,
88
- parentEventId: options.parentEventId,
89
- withParentEventId: options.withParentEventId
90
- });
118
+ logPopulated;
119
+ dumpOptions;
120
+ /**
121
+ * Creates a new logger. Unless populated from `populateLog`, a `root`
122
+ * "Log started" event is seeded and `eventId` is created via
123
+ * `createEventId`. When `populateLog` carries a valid root payload, it is
124
+ * adopted instead and `eventId` is re-synced from it. `dumpOptions` act as
125
+ * defaults for `dump`/`dumpFlat` and can be overridden per call.
126
+ */
127
+ constructor({
128
+ details,
129
+ path,
130
+ method,
131
+ parentEventId,
132
+ expectParentEventId,
133
+ populateLog,
134
+ dumpOptions,
135
+ createEventId
136
+ }) {
137
+ this.dumpOptions = dumpOptions;
138
+ this.logPopulated = false;
139
+ this.eventId = "unknown";
140
+ this.log = [];
141
+ if (populateLog) {
142
+ if (isOrderedLogEvents(populateLog)) {
143
+ this.log = this.finalizeHydratedLog(populateLog);
144
+ } else if (isLogData(populateLog)) {
145
+ this.log = this.finalizeHydratedLog(orderLogEvents(populateLog));
146
+ }
147
+ }
148
+ if (!this.logPopulated) {
149
+ this.eventId = createEventId(details);
150
+ this.log = createRootLogEntry({
151
+ eventId: this.eventId,
152
+ path,
153
+ method,
154
+ details,
155
+ parentEventId,
156
+ expectParentEventId
157
+ });
158
+ }
91
159
  }
92
- finalizeRootMessage() {
93
- this.addMessage("root", "info", "Request completed", {
94
- totalDuration: Date.now() - this.log.root[0].timestamp
160
+ /**
161
+ * Adopts the populated log when it contains a valid root payload
162
+ * (re-syncing `eventId` from it). Populated data without a valid root
163
+ * payload is discarded in favor of the freshly created log.
164
+ */
165
+ finalizeHydratedLog(log) {
166
+ if (hasRootPayload(log)) {
167
+ const { eventId } = this.getRootPayload(log);
168
+ this.eventId = eventId;
169
+ this.logPopulated = true;
170
+ return log;
171
+ }
172
+ return this.log;
173
+ }
174
+ /**
175
+ * Appends the closing "Log completed" event to the `root` holder,
176
+ * carrying the total duration since the "Log started" event.
177
+ */
178
+ finalizeLog(log) {
179
+ const rootEvent = log.find((event) => event.holder === "root");
180
+ log.push({
181
+ holder: "root",
182
+ type: "info",
183
+ message: "Log completed",
184
+ payload: {
185
+ totalDuration: Date.now() - rootEvent.timestamp
186
+ },
187
+ timestamp: Date.now()
95
188
  });
189
+ return log;
96
190
  }
97
- addMessage(name, type, message, payload) {
98
- const finalPayload = payload instanceof Error ? prettyError(payload) : payload;
99
- this.log[name].push({
191
+ /**
192
+ * Pushes a single ordered log event into the given area. `Error` payloads
193
+ * are converted into structured objects via `prettyError`.
194
+ */
195
+ addMessage(name, type, message, rawPayload) {
196
+ const payload = rawPayload instanceof Error ? prettyError(rawPayload) : rawPayload;
197
+ this.log.push({
198
+ holder: name,
100
199
  type,
101
200
  message,
102
- payload: finalPayload,
201
+ payload,
103
202
  timestamp: Date.now()
104
203
  });
105
204
  }
106
- // Area function that returns area-specific logger methods
107
- getArea(name = this.defaultArea) {
108
- if (!this.log[name]) {
109
- this.log[name] = [];
110
- }
205
+ /**
206
+ * Extracts the root payload — the payload of the first event held by the
207
+ * `root` area.
208
+ */
209
+ getRootPayload(log) {
210
+ return log.find((event) => event.holder === "root").payload;
211
+ }
212
+ /**
213
+ * Returns a scoped logging API for an area.
214
+ *
215
+ * The returned API exposes `info`, `warn`, `error`, `log` and `debug`;
216
+ * `error` also accepts `Error` instances as its payload.
217
+ */
218
+ getArea(name) {
111
219
  return {
112
- info: (message, payload) => {
113
- this.addMessage(name, "info", message, payload);
114
- },
115
- warn: (message, payload) => {
116
- this.addMessage(name, "warn", message, payload);
117
- },
118
- error: (message, payload) => {
119
- this.addMessage(name, "error", message, payload);
120
- },
121
- log: (message, payload) => {
122
- this.addMessage(name, "log", message, payload);
123
- },
124
- debug: (message, payload) => {
125
- this.addMessage(name, "debug", message, payload);
126
- }
220
+ info: (message, payload) => this.addMessage(name, "info", message, payload),
221
+ warn: (message, payload) => this.addMessage(name, "warn", message, payload),
222
+ error: (message, payload) => this.addMessage(name, "error", message, payload),
223
+ log: (message, payload) => this.addMessage(name, "log", message, payload),
224
+ debug: (message, payload) => this.addMessage(name, "debug", message, payload)
127
225
  };
128
226
  }
129
- // Public methods on main instance
130
- dump() {
131
- this.finalizeRootMessage();
132
- return this.log;
227
+ /**
228
+ * Applies the dump options to a copy of the internal ordered log events
229
+ * and returns the processed list. The internal log is never mutated.
230
+ *
231
+ * Options are merged in this order (later wins):
232
+ * `DEFAULT_PREPARE_DUMP_OPTIONS` → constructor `dumpOptions` → per-call
233
+ * `opts`.
234
+ *
235
+ * Defaults (see `DEFAULT_PREPARE_DUMP_OPTIONS`):
236
+ * - `omitRoot: false` — `root` holder events are kept in the output.
237
+ * - `finalizeLog: true` — a "Log completed" event is appended to the
238
+ * `root` holder.
239
+ * - `excludeLogTypes` — when provided, events of the listed types are
240
+ * removed; no types are excluded by default.
241
+ */
242
+ prepareLog(opts) {
243
+ const { omitRoot, finalizeLog, excludeLogTypes } = {
244
+ ...DEFAULT_PREPARE_DUMP_OPTIONS,
245
+ ...this.dumpOptions,
246
+ ...opts
247
+ };
248
+ let localLog = [...this.log];
249
+ if (omitRoot) {
250
+ localLog = localLog.filter((event) => event.holder !== "root");
251
+ }
252
+ if (finalizeLog && !omitRoot) {
253
+ localLog = this.finalizeLog(localLog);
254
+ }
255
+ if (excludeLogTypes) {
256
+ localLog = excludeLogEntryTypes(localLog, excludeLogTypes);
257
+ }
258
+ return localLog;
259
+ }
260
+ /**
261
+ * Returns the current log data as a `LogData` object (areas mapped to
262
+ * arrays of entries) after applying the dump options. Areas without
263
+ * entries are omitted from the returned object.
264
+ */
265
+ dump(opts) {
266
+ return groupLogEvents(this.prepareLog(opts));
267
+ }
268
+ /**
269
+ * Returns the current log data as an `OrderedLogEvent[]` — the flat,
270
+ * chronological list of events each tagged with the name of its holder
271
+ * area — after applying the same options as `dump`.
272
+ */
273
+ dumpFlat(opts) {
274
+ return this.prepareLog(opts);
133
275
  }
134
- dumpProduction() {
135
- this.finalizeRootMessage();
136
- return processProductionLogData(this.log);
276
+ /**
277
+ * Returns every entry logged for the given area (an empty array when the
278
+ * area has never been used). When `excludeLogTypes` is provided, entries
279
+ * of the listed types are filtered out.
280
+ */
281
+ retrieveAreaEvents(area, excludeLogTypes) {
282
+ const areaEvents = this.log.filter((event) => event.holder === area);
283
+ return excludeLogEntryTypes(areaEvents, excludeLogTypes).map(
284
+ ({ holder: _holder, ...entry }) => entry
285
+ );
137
286
  }
138
- appendLogData(logData) {
139
- delete logData.root;
140
- this.log = { ...logData, ...this.log };
287
+ /**
288
+ * Returns the root payload — the payload of the first event held by the
289
+ * `root` area (`path`, `method`, `eventId`, `parentEventId`, `details`).
290
+ */
291
+ retrieveRootPayload() {
292
+ return this.getRootPayload(this.log);
141
293
  }
142
294
  }
143
295
  const createLogger = (options) => {
144
296
  return new Logger(options);
145
297
  };
146
298
 
147
- export { Logger, createLogger, prettyError };
299
+ export { Logger, createLogger, groupLogEvents, hasRootPayload, isLogData, isOrderedLogEvents, orderLogEvents, prettyError };
148
300
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/utils.ts","../src/Logger.ts"],"sourcesContent":["import {\n CreateRootLogEntryOptions,\n LogData,\n LogEntry,\n RootPayload,\n} from './types';\n\nexport const prettyStack = (stack?: string): string[] => {\n if (!stack) return [];\n let toReplace = '';\n\n const regex = /file:\\/\\/\\/(.*)(\\.wrangler|node_modules)\\/.*\\)/gm;\n const m = regex.exec(stack);\n\n if (m && m.length > 1) {\n toReplace = m[1];\n }\n\n return stack\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => !line.startsWith('Error: '))\n .map((line) => line.replace(toReplace, ''))\n .map((line) => line.replace('file://', ''));\n};\n\nexport const prettyError = (err: Error) => {\n const stack = prettyStack(err.stack);\n const message = err.message;\n return { message, stack };\n};\n\nexport const createRootLogEntry = ({\n path,\n method,\n details,\n eventId,\n parentEventId,\n withParentEventId,\n}: CreateRootLogEntryOptions): LogData => {\n const url = new URL(`http://example.com${path ?? '/'}`);\n\n const rootLogEntry: LogData = {\n root: [\n {\n type: 'info',\n message: 'Request received',\n payload: {\n path: `${url.pathname}${url.search}`,\n method,\n details,\n eventId,\n ...(parentEventId && { parentEventId }),\n } as RootPayload,\n timestamp: Date.now(),\n },\n ],\n };\n\n if (withParentEventId && !parentEventId) {\n rootLogEntry.root.push({\n type: 'error',\n message: 'Parent event ID expected but not found',\n timestamp: Date.now(),\n });\n }\n\n return rootLogEntry;\n};\n\nexport const removeEmptyAreas = (logData: LogData) => {\n const newLogData: LogData = {};\n for (const [key, value] of Object.entries(logData)) {\n if (value.length > 0) {\n newLogData[key] = value;\n }\n }\n return newLogData;\n};\n\nexport const excludeDebugLogs = (logEntries: LogEntry[]) => {\n return logEntries.filter((entry) => entry.type !== 'debug');\n};\n\nexport const processProductionLogData = (logData: LogData) => {\n const newLogData: LogData = {};\n for (const [key, value] of Object.entries(logData)) {\n if (key !== 'root') {\n newLogData[key] = excludeDebugLogs(value);\n }\n }\n return removeEmptyAreas(newLogData);\n};\n","import { init } from '@paralleldrive/cuid2';\nimport {\n createRootLogEntry,\n prettyError,\n processProductionLogData,\n} from './utils';\nimport type { LogData, LoggerOptions, LogEntry, LogEntryType } from './types';\n\nexport class Logger {\n public eventId: string;\n private log: LogData;\n public parentEventId?: string;\n private defaultArea: string;\n\n constructor(options: LoggerOptions) {\n this.parentEventId = options.parentEventId;\n this.eventId =\n options.overrideEventId ||\n init({ fingerprint: options.details.service })();\n this.defaultArea = options.defaultArea || 'defaultArea';\n\n this.log = createRootLogEntry({\n path: options.path || '/',\n method: options.method,\n details: options.details,\n eventId: this.eventId,\n parentEventId: options.parentEventId,\n withParentEventId: options.withParentEventId,\n });\n }\n\n private finalizeRootMessage() {\n this.addMessage('root', 'info', 'Request completed', {\n totalDuration: Date.now() - this.log.root[0].timestamp,\n });\n }\n\n private addMessage(\n name: string,\n type: LogEntryType,\n message: string,\n payload?: LogEntry['payload'] | Error\n ) {\n const finalPayload =\n payload instanceof Error ? prettyError(payload) : payload;\n\n this.log[name]!.push({\n type,\n message,\n payload: finalPayload,\n timestamp: Date.now(),\n });\n }\n\n // Area function that returns area-specific logger methods\n public getArea(name = this.defaultArea) {\n if (!this.log[name]) {\n this.log[name] = [];\n }\n\n return {\n info: (message: string, payload?: LogEntry['payload']) => {\n this.addMessage(name, 'info', message, payload);\n },\n warn: (message: string, payload?: LogEntry['payload']) => {\n this.addMessage(name, 'warn', message, payload);\n },\n error: (message: string, payload?: LogEntry['payload'] | Error) => {\n this.addMessage(name, 'error', message, payload);\n },\n log: (message: string, payload?: LogEntry['payload']) => {\n this.addMessage(name, 'log', message, payload);\n },\n debug: (message: string, payload?: LogEntry['payload']) => {\n this.addMessage(name, 'debug', message, payload);\n },\n };\n }\n\n // Public methods on main instance\n public dump(): LogData {\n this.finalizeRootMessage();\n return this.log;\n }\n\n public dumpProduction(): LogData {\n this.finalizeRootMessage();\n return processProductionLogData(this.log);\n }\n\n public appendLogData(logData: LogData): void {\n delete logData.root;\n this.log = { ...logData, ...this.log };\n }\n}\n\n// Factory function for convenience\nexport const createLogger = (options: LoggerOptions): Logger => {\n return new Logger(options);\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/utils.ts","../src/guards.ts","../src/constants.ts","../src/Logger.ts"],"sourcesContent":["import {\n CreateRootLogEntryOptions,\n LogData,\n LogEntryType,\n OrderedLogEvent,\n RootPayload,\n} from './types';\n\n/**\n * Cleans up an error stack trace: removes `Error: ` lines, trims whitespace,\n * strips `file://` prefixes, and truncates absolute paths up to `.wrangler/`\n * or `node_modules/`.\n */\nexport const prettyStack = (stack?: string): string[] => {\n if (!stack) return [];\n let toReplace = '';\n\n const regex = /file:\\/\\/\\/(.*)(\\.wrangler|node_modules)\\/.*\\)/gm;\n const m = regex.exec(stack);\n\n if (m && m.length > 1) {\n toReplace = m[1];\n }\n\n return stack\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => !line.startsWith('Error: '))\n .map((line) => line.replace(toReplace, ''))\n .map((line) => line.replace('file://', ''));\n};\n\n/**\n * Converts an Error into a `{ message, name, stack }` object with the stack cleaned\n * up by `prettyStack`.\n */\nexport const prettyError = (\n err: Error\n): {\n message: string;\n name: string;\n stack: string[];\n} => {\n const stack = prettyStack(err.stack);\n const message = err.message;\n const name = err.name;\n return { message, name, stack };\n};\n\n/**\n * Creates the initial ordered log events: a `root` \"Request received\" event.\n * When `expectParentEventId` is set but no `parentEventId` is provided, a\n * `root` error event is appended.\n */\nexport const createRootLogEntry = <\n AdditionalDetails extends Record<string, unknown> = Record<string, unknown>,\n>({\n path,\n method,\n details,\n eventId,\n parentEventId,\n expectParentEventId,\n}: CreateRootLogEntryOptions<AdditionalDetails>): OrderedLogEvent[] => {\n const rootLogEvents: OrderedLogEvent[] = [\n {\n holder: 'root',\n type: 'info',\n message: 'Log started',\n payload: {\n eventId,\n details,\n ...(path && { path }),\n ...(method && { method }),\n ...(parentEventId && { parentEventId }),\n } as RootPayload,\n timestamp: Date.now(),\n },\n ];\n\n if (expectParentEventId && !parentEventId) {\n rootLogEvents.push({\n holder: 'root',\n type: 'error',\n message: 'Parent event ID expected but not provided',\n timestamp: Date.now(),\n });\n }\n\n return rootLogEvents;\n};\n\n/**\n * Returns the given ordered log events without those whose type is listed in\n * `excludeLogTypes`. When no types are provided, the events are returned\n * unchanged.\n */\nexport const excludeLogEntryTypes = (\n events: OrderedLogEvent[],\n excludeLogTypes?: LogEntryType[]\n): OrderedLogEvent[] => {\n if (!excludeLogTypes || excludeLogTypes.length === 0) return events;\n return events.filter((event) => !excludeLogTypes.includes(event.type));\n};\n\n/**\n * Flattens log data into a single array of events sorted by timestamp in\n * ascending order, tagging each event with the name of its holder area.\n */\nexport const orderLogEvents = (logData: LogData): OrderedLogEvent[] => {\n return (\n Object.entries(logData)\n // 1. Flatten the object into a 1D array while mapping the holder name\n .flatMap(([holder, events]) =>\n events.map((event) => ({\n holder,\n ...event,\n }))\n )\n // 2. Sort the combined array by timestamp (ascending order)\n .sort((a, b) => a.timestamp - b.timestamp)\n );\n};\n\n/**\n * Groups ordered log events back into a `LogData` object keyed by the holder\n * area of each event, preserving the relative order within each area.\n */\nexport const groupLogEvents = (events: OrderedLogEvent[]): LogData => {\n const logData: LogData = {};\n for (const { holder, ...entry } of events) {\n if (!logData[holder]) {\n logData[holder] = [];\n }\n logData[holder].push(entry);\n }\n return logData;\n};\n","import type {\n Details,\n LogData,\n LogEntry,\n LogEntryType,\n OrderedLogEvent,\n RootPayload,\n} from './types';\n\nconst isLogEntryType = (value: unknown): value is LogEntryType => {\n return (\n value === 'info' ||\n value === 'warn' ||\n value === 'error' ||\n value === 'log' ||\n value === 'debug'\n );\n};\n\nconst isLogEntry = (value: unknown): value is LogEntry => {\n if (typeof value !== 'object' || value === null) return false;\n const entry = value as Record<string, unknown>;\n return (\n isLogEntryType(entry.type) &&\n typeof entry.message === 'string' &&\n typeof entry.timestamp === 'number' &&\n (entry.payload === undefined ||\n (typeof entry.payload === 'object' && entry.payload !== null))\n );\n};\n\n/**\n * Type guard: checks whether the given value is an ordered log event array\n * containing a valid root payload — a `root` holder event whose payload\n * carries a string `eventId` and a `details` object with a string `instance`.\n */\nexport const hasRootPayload = (log: unknown): log is OrderedLogEvent[] => {\n if (!isOrderedLogEvents(log)) return false;\n\n const rootEvent = log.find((event) => event.holder === 'root');\n if (!rootEvent) return false;\n\n const rootPayload = rootEvent.payload as RootPayload | undefined;\n if (typeof rootPayload !== 'object' || rootPayload === null) return false;\n\n if (typeof rootPayload.eventId !== 'string') return false;\n\n const details = rootPayload.details as Details | null;\n if (typeof details !== 'object' || details === null) return false;\n\n return typeof details.instance === 'string';\n};\n\n/**\n * Type guard: checks whether the given value is a valid `LogData` object (a\n * non-array object whose values are arrays of log entries).\n */\nexport const isLogData = (data: unknown): data is LogData => {\n if (typeof data !== 'object' || data === null || Array.isArray(data)) {\n return false;\n }\n return Object.values(data).every(\n (area) => Array.isArray(area) && area.every((entry) => isLogEntry(entry))\n );\n};\n\n/**\n * Type guard: checks whether the given value is a valid array of\n * `OrderedLogEvent`s (log entries tagged with a `holder` area name).\n */\nexport const isOrderedLogEvents = (\n data: unknown\n): data is OrderedLogEvent[] => {\n if (!Array.isArray(data)) return false;\n return data.every((event) => {\n if (typeof event !== 'object' || event === null) return false;\n const ordered = event as Record<string, unknown>;\n return typeof ordered.holder === 'string' && isLogEntry(event);\n });\n};\n","import type { DumpLoggerOptions } from './types';\n\n/**\n * Default options applied by `Logger#dump` / `Logger#dumpFlat`: keep the\n * `root` area and append the final \"Request completed\" entry. No log entry\n * types are excluded unless `excludeLogTypes` is passed explicitly.\n */\nexport const DEFAULT_PREPARE_DUMP_OPTIONS: DumpLoggerOptions = {\n omitRoot: false,\n finalizeLog: true,\n};\n","import {\n createRootLogEntry,\n excludeLogEntryTypes,\n groupLogEvents,\n orderLogEvents,\n prettyError,\n} from './utils';\nimport type {\n DumpLoggerOptions,\n GetAreaMethods,\n LogData,\n LogEntry,\n LogEntryType,\n LoggerOptions,\n OrderedLogEvent,\n RootPayload,\n} from './types';\nimport { hasRootPayload, isLogData, isOrderedLogEvents } from './guards';\nimport { DEFAULT_PREPARE_DUMP_OPTIONS } from './constants';\n\nexport class Logger<\n AdditionalDetails extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique ID identifying this logging session (created via `createEventId` or adopted from populated log data). */\n public eventId: string;\n private log: OrderedLogEvent[];\n private logPopulated: boolean;\n private dumpOptions?: DumpLoggerOptions;\n\n /**\n * Creates a new logger. Unless populated from `populateLog`, a `root`\n * \"Log started\" event is seeded and `eventId` is created via\n * `createEventId`. When `populateLog` carries a valid root payload, it is\n * adopted instead and `eventId` is re-synced from it. `dumpOptions` act as\n * defaults for `dump`/`dumpFlat` and can be overridden per call.\n */\n constructor({\n details,\n path,\n method,\n parentEventId,\n expectParentEventId,\n populateLog,\n dumpOptions,\n createEventId,\n }: LoggerOptions<AdditionalDetails>) {\n this.dumpOptions = dumpOptions;\n this.logPopulated = false;\n this.eventId = 'unknown';\n this.log = [];\n\n if (populateLog) {\n if (isOrderedLogEvents(populateLog)) {\n this.log = this.finalizeHydratedLog(populateLog);\n } else if (isLogData(populateLog)) {\n this.log = this.finalizeHydratedLog(orderLogEvents(populateLog));\n }\n }\n\n if (!this.logPopulated) {\n this.eventId = createEventId(details);\n\n this.log = createRootLogEntry<AdditionalDetails>({\n eventId: this.eventId,\n path,\n method,\n details,\n parentEventId,\n expectParentEventId,\n });\n }\n }\n\n /**\n * Adopts the populated log when it contains a valid root payload\n * (re-syncing `eventId` from it). Populated data without a valid root\n * payload is discarded in favor of the freshly created log.\n */\n private finalizeHydratedLog(log: OrderedLogEvent[]): OrderedLogEvent[] {\n if (hasRootPayload(log)) {\n const { eventId } = this.getRootPayload(log);\n this.eventId = eventId;\n this.logPopulated = true;\n return log;\n }\n return this.log;\n }\n\n /**\n * Appends the closing \"Log completed\" event to the `root` holder,\n * carrying the total duration since the \"Log started\" event.\n */\n private finalizeLog(log: OrderedLogEvent[]): OrderedLogEvent[] {\n // A logger always holds a `root` event after construction\n const rootEvent = log.find((event) => event.holder === 'root')!;\n\n log.push({\n holder: 'root',\n type: 'info',\n message: 'Log completed',\n payload: {\n totalDuration: Date.now() - rootEvent.timestamp,\n },\n timestamp: Date.now(),\n });\n return log;\n }\n\n /**\n * Pushes a single ordered log event into the given area. `Error` payloads\n * are converted into structured objects via `prettyError`.\n */\n private addMessage(\n name: string,\n type: LogEntryType,\n message: string,\n rawPayload?: LogEntry['payload'] | Error\n ): void {\n const payload =\n rawPayload instanceof Error ? prettyError(rawPayload) : rawPayload;\n\n this.log.push({\n holder: name,\n type,\n message,\n payload,\n timestamp: Date.now(),\n });\n }\n\n /**\n * Extracts the root payload — the payload of the first event held by the\n * `root` area.\n */\n private getRootPayload(log: OrderedLogEvent[]): RootPayload {\n return log.find((event) => event.holder === 'root')!.payload as RootPayload;\n }\n\n /**\n * Returns a scoped logging API for an area.\n *\n * The returned API exposes `info`, `warn`, `error`, `log` and `debug`;\n * `error` also accepts `Error` instances as its payload.\n */\n public getArea(name: string): GetAreaMethods {\n return {\n info: (message, payload) =>\n this.addMessage(name, 'info', message, payload),\n warn: (message, payload) =>\n this.addMessage(name, 'warn', message, payload),\n error: (message, payload) =>\n this.addMessage(name, 'error', message, payload),\n log: (message, payload) => this.addMessage(name, 'log', message, payload),\n debug: (message, payload) =>\n this.addMessage(name, 'debug', message, payload),\n };\n }\n\n /**\n * Applies the dump options to a copy of the internal ordered log events\n * and returns the processed list. The internal log is never mutated.\n *\n * Options are merged in this order (later wins):\n * `DEFAULT_PREPARE_DUMP_OPTIONS` → constructor `dumpOptions` → per-call\n * `opts`.\n *\n * Defaults (see `DEFAULT_PREPARE_DUMP_OPTIONS`):\n * - `omitRoot: false` — `root` holder events are kept in the output.\n * - `finalizeLog: true` — a \"Log completed\" event is appended to the\n * `root` holder.\n * - `excludeLogTypes` — when provided, events of the listed types are\n * removed; no types are excluded by default.\n */\n private prepareLog(opts?: DumpLoggerOptions): OrderedLogEvent[] {\n const { omitRoot, finalizeLog, excludeLogTypes } = {\n ...DEFAULT_PREPARE_DUMP_OPTIONS,\n ...this.dumpOptions,\n ...opts,\n };\n\n let localLog: OrderedLogEvent[] = [...this.log];\n\n if (omitRoot) {\n localLog = localLog.filter((event) => event.holder !== 'root');\n }\n\n if (finalizeLog && !omitRoot) {\n localLog = this.finalizeLog(localLog);\n }\n\n if (excludeLogTypes) {\n localLog = excludeLogEntryTypes(localLog, excludeLogTypes);\n }\n\n return localLog;\n }\n\n /**\n * Returns the current log data as a `LogData` object (areas mapped to\n * arrays of entries) after applying the dump options. Areas without\n * entries are omitted from the returned object.\n */\n public dump(opts?: DumpLoggerOptions): LogData {\n return groupLogEvents(this.prepareLog(opts));\n }\n\n /**\n * Returns the current log data as an `OrderedLogEvent[]` — the flat,\n * chronological list of events each tagged with the name of its holder\n * area — after applying the same options as `dump`.\n */\n public dumpFlat(opts?: DumpLoggerOptions): OrderedLogEvent[] {\n return this.prepareLog(opts);\n }\n\n /**\n * Returns every entry logged for the given area (an empty array when the\n * area has never been used). When `excludeLogTypes` is provided, entries\n * of the listed types are filtered out.\n */\n public retrieveAreaEvents(\n area: string,\n excludeLogTypes?: LogEntryType[]\n ): LogEntry[] {\n const areaEvents = this.log.filter((event) => event.holder === area);\n\n return excludeLogEntryTypes(areaEvents, excludeLogTypes).map(\n ({ holder: _holder, ...entry }) => entry\n );\n }\n\n /**\n * Returns the root payload — the payload of the first event held by the\n * `root` area (`path`, `method`, `eventId`, `parentEventId`, `details`).\n */\n public retrieveRootPayload(): RootPayload {\n return this.getRootPayload(this.log);\n }\n}\n\n// Factory function for convenience\nexport const createLogger = <\n AdditionalDetails extends Record<string, unknown> = Record<string, unknown>,\n>(\n options: LoggerOptions<AdditionalDetails>\n): Logger<AdditionalDetails> => {\n return new Logger(options);\n};\n"],"names":[],"mappings":"AAaO,MAAM,WAAA,GAAc,CAAC,KAAA,KAA6B;AACvD,EAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,EAAA,IAAI,SAAA,GAAY,EAAA;AAEhB,EAAA,MAAM,KAAA,GAAQ,kDAAA;AACd,EAAA,MAAM,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAE1B,EAAA,IAAI,CAAA,IAAK,CAAA,CAAE,MAAA,GAAS,CAAA,EAAG;AACrB,IAAA,SAAA,GAAY,EAAE,CAAC,CAAA;AAAA,EACjB;AAEA,EAAA,OAAO,MACJ,KAAA,CAAM,IAAI,CAAA,CACV,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,IAAA,EAAM,EACzB,MAAA,CAAO,CAAC,IAAA,KAAS,CAAC,KAAK,UAAA,CAAW,SAAS,CAAC,CAAA,CAC5C,IAAI,CAAC,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,WAAW,EAAE,CAAC,CAAA,CACzC,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,EAAE,CAAC,CAAA;AAC9C,CAAA;AAMO,MAAM,WAAA,GAAc,CACzB,GAAA,KAKG;AACH,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,GAAA,CAAI,KAAK,CAAA;AACnC,EAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,EAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,EAAA,OAAO,EAAE,OAAA,EAAS,IAAA,EAAM,KAAA,EAAM;AAChC;AAOO,MAAM,qBAAqB,CAEhC;AAAA,EACA,IAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,OAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,KAAuE;AACrE,EAAA,MAAM,aAAA,GAAmC;AAAA,IACvC;AAAA,MACE,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,MAAA;AAAA,MACN,OAAA,EAAS,aAAA;AAAA,MACT,OAAA,EAAS;AAAA,QACP,OAAA;AAAA,QACA,OAAA;AAAA,QACA,GAAI,IAAA,IAAQ,EAAE,IAAA,EAAK;AAAA,QACnB,GAAI,MAAA,IAAU,EAAE,MAAA,EAAO;AAAA,QACvB,GAAI,aAAA,IAAiB,EAAE,aAAA;AAAc,OACvC;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI;AACtB,GACF;AAEA,EAAA,IAAI,mBAAA,IAAuB,CAAC,aAAA,EAAe;AACzC,IAAA,aAAA,CAAc,IAAA,CAAK;AAAA,MACjB,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,OAAA;AAAA,MACN,OAAA,EAAS,2CAAA;AAAA,MACT,SAAA,EAAW,KAAK,GAAA;AAAI,KACrB,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,aAAA;AACT,CAAA;AAOO,MAAM,oBAAA,GAAuB,CAClC,MAAA,EACA,eAAA,KACsB;AACtB,EAAA,IAAI,CAAC,eAAA,IAAmB,eAAA,CAAgB,MAAA,KAAW,GAAG,OAAO,MAAA;AAC7D,EAAA,OAAO,MAAA,CAAO,OAAO,CAAC,KAAA,KAAU,CAAC,eAAA,CAAgB,QAAA,CAAS,KAAA,CAAM,IAAI,CAAC,CAAA;AACvE,CAAA;AAMO,MAAM,cAAA,GAAiB,CAAC,OAAA,KAAwC;AACrE,EAAA,OACE,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,CAEnB,OAAA;AAAA,IAAQ,CAAC,CAAC,MAAA,EAAQ,MAAM,MACvB,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MACrB,MAAA;AAAA,MACA,GAAG;AAAA,KACL,CAAE;AAAA,GACJ,CAEC,KAAK,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,SAAA,GAAY,EAAE,SAAS,CAAA;AAE/C;AAMO,MAAM,cAAA,GAAiB,CAAC,MAAA,KAAuC;AACpE,EAAA,MAAM,UAAmB,EAAC;AAC1B,EAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,GAAG,KAAA,MAAW,MAAA,EAAQ;AACzC,IAAA,IAAI,CAAC,OAAA,CAAQ,MAAM,CAAA,EAAG;AACpB,MAAA,OAAA,CAAQ,MAAM,IAAI,EAAC;AAAA,IACrB;AACA,IAAA,OAAA,CAAQ,MAAM,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AAAA,EAC5B;AACA,EAAA,OAAO,OAAA;AACT;;AChIA,MAAM,cAAA,GAAiB,CAAC,KAAA,KAA0C;AAChE,EAAA,OACE,KAAA,KAAU,UACV,KAAA,KAAU,MAAA,IACV,UAAU,OAAA,IACV,KAAA,KAAU,SACV,KAAA,KAAU,OAAA;AAEd,CAAA;AAEA,MAAM,UAAA,GAAa,CAAC,KAAA,KAAsC;AACxD,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,MAAM,OAAO,KAAA;AACxD,EAAA,MAAM,KAAA,GAAQ,KAAA;AACd,EAAA,OACE,cAAA,CAAe,MAAM,IAAI,CAAA,IACzB,OAAO,KAAA,CAAM,OAAA,KAAY,YACzB,OAAO,KAAA,CAAM,cAAc,QAAA,KAC1B,KAAA,CAAM,YAAY,MAAA,IAChB,OAAO,MAAM,OAAA,KAAY,QAAA,IAAY,MAAM,OAAA,KAAY,IAAA,CAAA;AAE9D,CAAA;AAOO,MAAM,cAAA,GAAiB,CAAC,GAAA,KAA2C;AACxE,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAG,CAAA,EAAG,OAAO,KAAA;AAErC,EAAA,MAAM,YAAY,GAAA,CAAI,IAAA,CAAK,CAAC,KAAA,KAAU,KAAA,CAAM,WAAW,MAAM,CAAA;AAC7D,EAAA,IAAI,CAAC,WAAW,OAAO,KAAA;AAEvB,EAAA,MAAM,cAAc,SAAA,CAAU,OAAA;AAC9B,EAAA,IAAI,OAAO,WAAA,KAAgB,QAAA,IAAY,WAAA,KAAgB,MAAM,OAAO,KAAA;AAEpE,EAAA,IAAI,OAAO,WAAA,CAAY,OAAA,KAAY,QAAA,EAAU,OAAO,KAAA;AAEpD,EAAA,MAAM,UAAU,WAAA,CAAY,OAAA;AAC5B,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,OAAA,KAAY,MAAM,OAAO,KAAA;AAE5D,EAAA,OAAO,OAAO,QAAQ,QAAA,KAAa,QAAA;AACrC;AAMO,MAAM,SAAA,GAAY,CAAC,IAAA,KAAmC;AAC3D,EAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,KAAS,QAAQ,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACpE,IAAA,OAAO,KAAA;AAAA,EACT;AACA,EAAA,OAAO,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA,CAAE,KAAA;AAAA,IACzB,CAAC,IAAA,KAAS,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,IAAK,IAAA,CAAK,KAAA,CAAM,CAAC,KAAA,KAAU,UAAA,CAAW,KAAK,CAAC;AAAA,GAC1E;AACF;AAMO,MAAM,kBAAA,GAAqB,CAChC,IAAA,KAC8B;AAC9B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI,GAAG,OAAO,KAAA;AACjC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAC,KAAA,KAAU;AAC3B,IAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,MAAM,OAAO,KAAA;AACxD,IAAA,MAAM,OAAA,GAAU,KAAA;AAChB,IAAA,OAAO,OAAO,OAAA,CAAQ,MAAA,KAAW,QAAA,IAAY,WAAW,KAAK,CAAA;AAAA,EAC/D,CAAC,CAAA;AACH;;ACxEO,MAAM,4BAAA,GAAkD;AAAA,EAC7D,QAAA,EAAU,KAAA;AAAA,EACV,WAAA,EAAa;AACf,CAAA;;ACUO,MAAM,MAAA,CAEX;AAAA;AAAA,EAEO,OAAA;AAAA,EACC,GAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASR,WAAA,CAAY;AAAA,IACV,OAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,aAAA;AAAA,IACA,mBAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF,EAAqC;AACnC,IAAA,IAAA,CAAK,WAAA,GAAc,WAAA;AACnB,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA;AACpB,IAAA,IAAA,CAAK,OAAA,GAAU,SAAA;AACf,IAAA,IAAA,CAAK,MAAM,EAAC;AAEZ,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,IAAI,kBAAA,CAAmB,WAAW,CAAA,EAAG;AACnC,QAAA,IAAA,CAAK,GAAA,GAAM,IAAA,CAAK,mBAAA,CAAoB,WAAW,CAAA;AAAA,MACjD,CAAA,MAAA,IAAW,SAAA,CAAU,WAAW,CAAA,EAAG;AACjC,QAAA,IAAA,CAAK,GAAA,GAAM,IAAA,CAAK,mBAAA,CAAoB,cAAA,CAAe,WAAW,CAAC,CAAA;AAAA,MACjE;AAAA,IACF;AAEA,IAAA,IAAI,CAAC,KAAK,YAAA,EAAc;AACtB,MAAA,IAAA,CAAK,OAAA,GAAU,cAAc,OAAO,CAAA;AAEpC,MAAA,IAAA,CAAK,MAAM,kBAAA,CAAsC;AAAA,QAC/C,SAAS,IAAA,CAAK,OAAA;AAAA,QACd,IAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAA;AAAA,QACA,aAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,GAAA,EAA2C;AACrE,IAAA,IAAI,cAAA,CAAe,GAAG,CAAA,EAAG;AACvB,MAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,IAAA,CAAK,eAAe,GAAG,CAAA;AAC3C,MAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,MAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AACpB,MAAA,OAAO,GAAA;AAAA,IACT;AACA,IAAA,OAAO,IAAA,CAAK,GAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAY,GAAA,EAA2C;AAE7D,IAAA,MAAM,YAAY,GAAA,CAAI,IAAA,CAAK,CAAC,KAAA,KAAU,KAAA,CAAM,WAAW,MAAM,CAAA;AAE7D,IAAA,GAAA,CAAI,IAAA,CAAK;AAAA,MACP,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,MAAA;AAAA,MACN,OAAA,EAAS,eAAA;AAAA,MACT,OAAA,EAAS;AAAA,QACP,aAAA,EAAe,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA,CAAU;AAAA,OACxC;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACrB,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAA,CACN,IAAA,EACA,IAAA,EACA,OAAA,EACA,UAAA,EACM;AACN,IAAA,MAAM,OAAA,GACJ,UAAA,YAAsB,KAAA,GAAQ,WAAA,CAAY,UAAU,CAAA,GAAI,UAAA;AAE1D,IAAA,IAAA,CAAK,IAAI,IAAA,CAAK;AAAA,MACZ,MAAA,EAAQ,IAAA;AAAA,MACR,IAAA;AAAA,MACA,OAAA;AAAA,MACA,OAAA;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACrB,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,GAAA,EAAqC;AAC1D,IAAA,OAAO,IAAI,IAAA,CAAK,CAAC,UAAU,KAAA,CAAM,MAAA,KAAW,MAAM,CAAA,CAAG,OAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,QAAQ,IAAA,EAA8B;AAC3C,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,CAAC,OAAA,EAAS,OAAA,KACd,KAAK,UAAA,CAAW,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,OAAO,CAAA;AAAA,MAChD,IAAA,EAAM,CAAC,OAAA,EAAS,OAAA,KACd,KAAK,UAAA,CAAW,IAAA,EAAM,MAAA,EAAQ,OAAA,EAAS,OAAO,CAAA;AAAA,MAChD,KAAA,EAAO,CAAC,OAAA,EAAS,OAAA,KACf,KAAK,UAAA,CAAW,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS,OAAO,CAAA;AAAA,MACjD,GAAA,EAAK,CAAC,OAAA,EAAS,OAAA,KAAY,KAAK,UAAA,CAAW,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,OAAO,CAAA;AAAA,MACxE,KAAA,EAAO,CAAC,OAAA,EAAS,OAAA,KACf,KAAK,UAAA,CAAW,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS,OAAO;AAAA,KACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBQ,WAAW,IAAA,EAA6C;AAC9D,IAAA,MAAM,EAAE,QAAA,EAAU,WAAA,EAAa,eAAA,EAAgB,GAAI;AAAA,MACjD,GAAG,4BAAA;AAAA,MACH,GAAG,IAAA,CAAK,WAAA;AAAA,MACR,GAAG;AAAA,KACL;AAEA,IAAA,IAAI,QAAA,GAA8B,CAAC,GAAG,IAAA,CAAK,GAAG,CAAA;AAE9C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,GAAW,SAAS,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,WAAW,MAAM,CAAA;AAAA,IAC/D;AAEA,IAAA,IAAI,WAAA,IAAe,CAAC,QAAA,EAAU;AAC5B,MAAA,QAAA,GAAW,IAAA,CAAK,YAAY,QAAQ,CAAA;AAAA,IACtC;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,QAAA,GAAW,oBAAA,CAAqB,UAAU,eAAe,CAAA;AAAA,IAC3D;AAEA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,KAAK,IAAA,EAAmC;AAC7C,IAAA,OAAO,cAAA,CAAe,IAAA,CAAK,UAAA,CAAW,IAAI,CAAC,CAAA;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,SAAS,IAAA,EAA6C;AAC3D,IAAA,OAAO,IAAA,CAAK,WAAW,IAAI,CAAA;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,kBAAA,CACL,MACA,eAAA,EACY;AACZ,IAAA,MAAM,UAAA,GAAa,KAAK,GAAA,CAAI,MAAA,CAAO,CAAC,KAAA,KAAU,KAAA,CAAM,WAAW,IAAI,CAAA;AAEnE,IAAA,OAAO,oBAAA,CAAqB,UAAA,EAAY,eAAe,CAAA,CAAE,GAAA;AAAA,MACvD,CAAC,EAAE,MAAA,EAAQ,OAAA,EAAS,GAAG,OAAM,KAAM;AAAA,KACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,mBAAA,GAAmC;AACxC,IAAA,OAAO,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,GAAG,CAAA;AAAA,EACrC;AACF;AAGO,MAAM,YAAA,GAAe,CAG1B,OAAA,KAC8B;AAC9B,EAAA,OAAO,IAAI,OAAO,OAAO,CAAA;AAC3B;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hierarchical-area-logger",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -58,9 +58,6 @@
58
58
  "unbuild": "^3.6.1",
59
59
  "vitest": "^4.1.10"
60
60
  },
61
- "dependencies": {
62
- "@paralleldrive/cuid2": "^3.0.6"
63
- },
64
61
  "keywords": [
65
62
  "logger",
66
63
  "logging",