widelogger 0.3.0 → 0.5.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 +35 -10
- package/dist/index.d.ts +12 -1
- package/dist/index.js +145 -27
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,12 +104,11 @@ import { widelog } from "widelogger";
|
|
|
104
104
|
export const checkout = async (request, response) => {
|
|
105
105
|
const { userId } = request.body;
|
|
106
106
|
|
|
107
|
-
widelog.
|
|
108
|
-
widelog.set("user.plan", "premium");
|
|
107
|
+
widelog.setFields({ user: { id: userId, plan: "premium" } });
|
|
109
108
|
|
|
110
|
-
widelog.time.
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const order = await widelog.time.measure("db_ms", () =>
|
|
110
|
+
processOrder(userId),
|
|
111
|
+
);
|
|
113
112
|
|
|
114
113
|
widelog.set("order.total_cents", order.totalCents);
|
|
115
114
|
widelog.count("order.items", order.itemCount);
|
|
@@ -141,12 +140,11 @@ import { widelog } from "widelogger";
|
|
|
141
140
|
export const checkout = async (request: Request) => {
|
|
142
141
|
const { userId } = await request.json();
|
|
143
142
|
|
|
144
|
-
widelog.
|
|
145
|
-
widelog.set("user.plan", "premium");
|
|
143
|
+
widelog.setFields({ user: { id: userId, plan: "premium" } });
|
|
146
144
|
|
|
147
|
-
widelog.time.
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
const order = await widelog.time.measure("db_ms", () =>
|
|
146
|
+
processOrder(userId),
|
|
147
|
+
);
|
|
150
148
|
|
|
151
149
|
widelog.set("order.total_cents", order.totalCents);
|
|
152
150
|
widelog.count("order.items", order.itemCount);
|
|
@@ -202,6 +200,31 @@ widelog.set("user.plan", "premium");
|
|
|
202
200
|
// Output: { user: { id: "usr_123", plan: "premium" } }
|
|
203
201
|
```
|
|
204
202
|
|
|
203
|
+
### Bulk Fields
|
|
204
|
+
|
|
205
|
+
`setFields` accepts a nested object and recursively flattens it into individual `set` calls. Non-primitive values (other than plain objects) are silently ignored.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
widelog.setFields({
|
|
209
|
+
user: { id: "usr_123", plan: "premium" },
|
|
210
|
+
status_code: 200,
|
|
211
|
+
});
|
|
212
|
+
// Equivalent to:
|
|
213
|
+
// widelog.set("user.id", "usr_123");
|
|
214
|
+
// widelog.set("user.plan", "premium");
|
|
215
|
+
// widelog.set("status_code", 200);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Measured Timing
|
|
219
|
+
|
|
220
|
+
`time.measure` wraps a sync or async callback, automatically recording start and stop times. It returns the callback's result and still records timing even if the callback throws.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
const order = await widelog.time.measure("db_ms", () =>
|
|
224
|
+
processOrder(userId),
|
|
225
|
+
);
|
|
226
|
+
```
|
|
227
|
+
|
|
205
228
|
### Log Routing
|
|
206
229
|
|
|
207
230
|
Events with `status_code >= 500` or `outcome === "error"` are emitted at `error` level. Everything else is `info`. In development, logs are pretty-printed; in production, they're structured JSON.
|
|
@@ -233,12 +256,14 @@ Imported directly from `"widelogger"`. All methods operate on the current async
|
|
|
233
256
|
| Method | Description |
|
|
234
257
|
|--------|-------------|
|
|
235
258
|
| `set(key, value)` | Set a field value (last write wins) |
|
|
259
|
+
| `setFields(fields)` | Recursively flatten a nested object into dotted-key `set` calls |
|
|
236
260
|
| `count(key, amount?)` | Increment a counter (default +1) |
|
|
237
261
|
| `append(key, value)` | Append a value to an array |
|
|
238
262
|
| `max(key, value)` | Track the maximum value for a key |
|
|
239
263
|
| `min(key, value)` | Track the minimum value for a key |
|
|
240
264
|
| `time.start(key)` | Start a timer |
|
|
241
265
|
| `time.stop(key)` | Stop a timer and record elapsed ms |
|
|
266
|
+
| `time.measure(key, fn)` | Time a sync or async callback, returns the callback's result |
|
|
242
267
|
| `errorFields(error, opts?)` | Extract error name, message, and stack |
|
|
243
268
|
| `flush()` | Aggregate all operations and emit the event |
|
|
244
269
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,9 +11,18 @@ export interface WideloggerOptions {
|
|
|
11
11
|
export interface ErrorFieldsOptions {
|
|
12
12
|
prefix?: string;
|
|
13
13
|
includeStack?: boolean;
|
|
14
|
+
slug?: string;
|
|
15
|
+
retriable?: boolean;
|
|
16
|
+
requiresReauth?: boolean;
|
|
14
17
|
}
|
|
18
|
+
interface StickyHandle {
|
|
19
|
+
sticky: () => void;
|
|
20
|
+
}
|
|
21
|
+
declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => Promise<T>): Promise<T>;
|
|
22
|
+
declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => T): T;
|
|
15
23
|
export declare const widelog: {
|
|
16
|
-
set: <K extends string>(key: DottedKey<K>, value: FieldValue) =>
|
|
24
|
+
set: <K extends string>(key: DottedKey<K>, value: FieldValue) => StickyHandle;
|
|
25
|
+
setFields: (fields: Record<string, unknown>) => StickyHandle;
|
|
17
26
|
count: <K extends string>(key: DottedKey<K>, amount?: number) => void;
|
|
18
27
|
append: <K extends string>(key: DottedKey<K>, value: FieldValue) => void;
|
|
19
28
|
max: <K extends string>(key: DottedKey<K>, value: number) => void;
|
|
@@ -21,6 +30,7 @@ export declare const widelog: {
|
|
|
21
30
|
time: {
|
|
22
31
|
start: <K extends string>(key: DottedKey<K>) => void;
|
|
23
32
|
stop: <K extends string>(key: DottedKey<K>) => void;
|
|
33
|
+
measure: typeof measure;
|
|
24
34
|
};
|
|
25
35
|
errorFields: (error: unknown, options?: ErrorFieldsOptions) => void;
|
|
26
36
|
flush: () => void;
|
|
@@ -33,3 +43,4 @@ export declare const widelogger: (options: WideloggerOptions) => {
|
|
|
33
43
|
destroy: () => Promise<void>;
|
|
34
44
|
};
|
|
35
45
|
export type Widelog = typeof widelog;
|
|
46
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -105,7 +105,15 @@ var flush = (context) => {
|
|
|
105
105
|
if (!context) {
|
|
106
106
|
return {};
|
|
107
107
|
}
|
|
108
|
+
const hasStickyOperations = context.stickyOperations.length > 0;
|
|
109
|
+
const hasOperations = context.operations.length > 0;
|
|
110
|
+
if (!(hasStickyOperations || hasOperations)) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
108
113
|
const agg = createAggregators();
|
|
114
|
+
for (const entry of context.stickyOperations) {
|
|
115
|
+
processOperation(agg, entry);
|
|
116
|
+
}
|
|
109
117
|
for (const entry of context.operations) {
|
|
110
118
|
processOperation(agg, entry);
|
|
111
119
|
}
|
|
@@ -115,6 +123,9 @@ var flush = (context) => {
|
|
|
115
123
|
};
|
|
116
124
|
|
|
117
125
|
// src/index.ts
|
|
126
|
+
var isFieldValue = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
127
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
128
|
+
var isFieldValueArray = (value) => Array.isArray(value) && value.every(isFieldValue);
|
|
118
129
|
function getErrorFields(error, includeStack = true) {
|
|
119
130
|
if (error instanceof Error) {
|
|
120
131
|
return {
|
|
@@ -134,38 +145,112 @@ function getErrorFields(error, includeStack = true) {
|
|
|
134
145
|
error_message: "Unknown error"
|
|
135
146
|
};
|
|
136
147
|
}
|
|
148
|
+
var extractErrorProperty = (error, property) => {
|
|
149
|
+
if (isRecord2(error) && property in error) {
|
|
150
|
+
return error[property];
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
};
|
|
154
|
+
var noop = () => {
|
|
155
|
+
return;
|
|
156
|
+
};
|
|
157
|
+
var noopSticky = { sticky: noop };
|
|
137
158
|
var storage = new AsyncLocalStorage;
|
|
159
|
+
function pushOp(operation) {
|
|
160
|
+
const store = storage.getStore();
|
|
161
|
+
if (!store) {
|
|
162
|
+
return noopSticky;
|
|
163
|
+
}
|
|
164
|
+
store.operations.push(operation);
|
|
165
|
+
return {
|
|
166
|
+
sticky: () => {
|
|
167
|
+
const index = store.operations.indexOf(operation);
|
|
168
|
+
if (index !== -1) {
|
|
169
|
+
store.operations.splice(index, 1);
|
|
170
|
+
}
|
|
171
|
+
store.stickyOperations.push(operation);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function applyFields(operations, fields, parentKey) {
|
|
176
|
+
for (const key of Object.keys(fields)) {
|
|
177
|
+
const value = fields[key];
|
|
178
|
+
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
|
179
|
+
if (isFieldValue(value)) {
|
|
180
|
+
operations.push({ operation: "set", key: fullKey, value });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (isFieldValueArray(value)) {
|
|
184
|
+
for (const element of value) {
|
|
185
|
+
operations.push({ operation: "append", key: fullKey, value: element });
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (isRecord2(value)) {
|
|
190
|
+
applyFields(operations, value, fullKey);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function measure(key, callback) {
|
|
195
|
+
const operations = storage.getStore()?.operations;
|
|
196
|
+
operations?.push({ operation: "time.start", key, time: performance.now() });
|
|
197
|
+
let result;
|
|
198
|
+
try {
|
|
199
|
+
result = callback();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
operations?.push({ operation: "time.stop", key, time: performance.now() });
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
if (result instanceof Promise) {
|
|
205
|
+
return result.finally(() => {
|
|
206
|
+
operations?.push({
|
|
207
|
+
operation: "time.stop",
|
|
208
|
+
key,
|
|
209
|
+
time: performance.now()
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
operations?.push({ operation: "time.stop", key, time: performance.now() });
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
138
216
|
var widelog = {
|
|
139
217
|
set: (key, value) => {
|
|
140
|
-
|
|
218
|
+
return pushOp({ operation: "set", key, value });
|
|
219
|
+
},
|
|
220
|
+
setFields: (fields) => {
|
|
221
|
+
const store = storage.getStore();
|
|
222
|
+
if (!store) {
|
|
223
|
+
return noopSticky;
|
|
224
|
+
}
|
|
225
|
+
const startIndex = store.operations.length;
|
|
226
|
+
applyFields(store.operations, fields);
|
|
227
|
+
return {
|
|
228
|
+
sticky: () => {
|
|
229
|
+
const added = store.operations.splice(startIndex);
|
|
230
|
+
store.stickyOperations.push(...added);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
141
233
|
},
|
|
142
234
|
count: (key, amount = 1) => {
|
|
143
|
-
|
|
235
|
+
pushOp({ operation: "count", key, amount });
|
|
144
236
|
},
|
|
145
237
|
append: (key, value) => {
|
|
146
|
-
|
|
238
|
+
pushOp({ operation: "append", key, value });
|
|
147
239
|
},
|
|
148
240
|
max: (key, value) => {
|
|
149
|
-
|
|
241
|
+
pushOp({ operation: "max", key, value });
|
|
150
242
|
},
|
|
151
243
|
min: (key, value) => {
|
|
152
|
-
|
|
244
|
+
pushOp({ operation: "min", key, value });
|
|
153
245
|
},
|
|
154
246
|
time: {
|
|
155
247
|
start: (key) => {
|
|
156
|
-
|
|
157
|
-
operation: "time.start",
|
|
158
|
-
key,
|
|
159
|
-
time: performance.now()
|
|
160
|
-
});
|
|
248
|
+
pushOp({ operation: "time.start", key, time: performance.now() });
|
|
161
249
|
},
|
|
162
250
|
stop: (key) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
time: performance.now()
|
|
167
|
-
});
|
|
168
|
-
}
|
|
251
|
+
pushOp({ operation: "time.stop", key, time: performance.now() });
|
|
252
|
+
},
|
|
253
|
+
measure
|
|
169
254
|
},
|
|
170
255
|
errorFields: (error, options = {}) => {
|
|
171
256
|
const context = storage.getStore();
|
|
@@ -174,9 +259,15 @@ var widelog = {
|
|
|
174
259
|
}
|
|
175
260
|
const prefix = options.prefix ?? "error";
|
|
176
261
|
const fields = getErrorFields(error, options.includeStack ?? true);
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
262
|
+
context.operations.push({
|
|
263
|
+
operation: "set",
|
|
264
|
+
key: `${prefix}.error_name`,
|
|
265
|
+
value: fields.error_name
|
|
266
|
+
}, {
|
|
267
|
+
operation: "set",
|
|
268
|
+
key: `${prefix}.error_message`,
|
|
269
|
+
value: fields.error_message
|
|
270
|
+
});
|
|
180
271
|
if (fields.error_stack !== undefined) {
|
|
181
272
|
context.operations.push({
|
|
182
273
|
operation: "set",
|
|
@@ -184,21 +275,46 @@ var widelog = {
|
|
|
184
275
|
value: fields.error_stack
|
|
185
276
|
});
|
|
186
277
|
}
|
|
278
|
+
const slug = options.slug ?? extractErrorProperty(error, "slug");
|
|
279
|
+
if (typeof slug === "string") {
|
|
280
|
+
context.operations.push({
|
|
281
|
+
operation: "set",
|
|
282
|
+
key: `${prefix}.slug`,
|
|
283
|
+
value: slug
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
const retriable = options.retriable ?? extractErrorProperty(error, "retriable");
|
|
287
|
+
if (typeof retriable === "boolean") {
|
|
288
|
+
context.operations.push({
|
|
289
|
+
operation: "set",
|
|
290
|
+
key: `${prefix}.retriable`,
|
|
291
|
+
value: retriable
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
const requiresReauth = options.requiresReauth ?? extractErrorProperty(error, "requiresReauth");
|
|
295
|
+
if (typeof requiresReauth === "boolean") {
|
|
296
|
+
context.operations.push({
|
|
297
|
+
operation: "set",
|
|
298
|
+
key: `${prefix}.requires_reauth`,
|
|
299
|
+
value: requiresReauth
|
|
300
|
+
});
|
|
301
|
+
}
|
|
187
302
|
},
|
|
188
303
|
flush: () => {
|
|
189
304
|
const store = storage.getStore();
|
|
190
305
|
if (!store) {
|
|
191
306
|
return;
|
|
192
307
|
}
|
|
193
|
-
|
|
194
|
-
if (Object.keys(event).length === 0) {
|
|
308
|
+
if (store.operations.length === 0 && store.stickyOperations.length === 0) {
|
|
195
309
|
return;
|
|
196
310
|
}
|
|
311
|
+
const event = flush(store);
|
|
197
312
|
store.transport(event);
|
|
198
313
|
}
|
|
199
314
|
};
|
|
200
315
|
var widelogger = (options) => {
|
|
201
|
-
const
|
|
316
|
+
const nodeEnvironment = typeof process.env === "object" ? "development" : undefined;
|
|
317
|
+
const environment = options.environment ?? nodeEnvironment ?? "development";
|
|
202
318
|
const isDevelopment = environment !== "production";
|
|
203
319
|
const pinoTransport = isDevelopment ? pino.transport({
|
|
204
320
|
target: "pino-pretty",
|
|
@@ -220,24 +336,26 @@ var widelogger = (options) => {
|
|
|
220
336
|
environment
|
|
221
337
|
}
|
|
222
338
|
}, pinoTransport);
|
|
339
|
+
const defaultEventName = options.defaultEventName;
|
|
223
340
|
const transport = (event) => {
|
|
224
341
|
const statusCode = typeof event.status_code === "number" ? event.status_code : undefined;
|
|
225
342
|
const isError = statusCode !== undefined ? statusCode >= 500 : event.outcome === "error";
|
|
226
|
-
|
|
343
|
+
event.event_name = defaultEventName;
|
|
227
344
|
if (isError) {
|
|
228
|
-
logger.error(
|
|
345
|
+
logger.error(event);
|
|
229
346
|
return;
|
|
230
347
|
}
|
|
231
|
-
logger.info(
|
|
348
|
+
logger.info(event);
|
|
232
349
|
};
|
|
233
350
|
const clearContext = () => {
|
|
234
351
|
const context2 = storage.getStore();
|
|
235
|
-
if (context2
|
|
352
|
+
if (context2) {
|
|
236
353
|
context2.operations = [];
|
|
354
|
+
context2.stickyOperations = [];
|
|
237
355
|
}
|
|
238
356
|
};
|
|
239
357
|
function context(callback) {
|
|
240
|
-
return storage.run({ operations: [], transport }, () => {
|
|
358
|
+
return storage.run({ operations: [], stickyOperations: [], transport }, () => {
|
|
241
359
|
let result;
|
|
242
360
|
try {
|
|
243
361
|
result = callback();
|
package/dist/types.d.ts
CHANGED