widelogger 0.3.0 → 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
@@ -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.set("user.id", userId);
108
- widelog.set("user.plan", "premium");
107
+ widelog.setFields({ user: { id: userId, plan: "premium" } });
109
108
 
110
- widelog.time.start("db_ms");
111
- const order = await processOrder(userId);
112
- widelog.time.stop("db_ms");
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.set("user.id", userId);
145
- widelog.set("user.plan", "premium");
143
+ widelog.setFields({ user: { id: userId, plan: "premium" } });
146
144
 
147
- widelog.time.start("db_ms");
148
- const order = await processOrder(userId);
149
- widelog.time.stop("db_ms");
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
@@ -12,8 +12,11 @@ export interface ErrorFieldsOptions {
12
12
  prefix?: string;
13
13
  includeStack?: boolean;
14
14
  }
15
+ declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => Promise<T>): Promise<T>;
16
+ declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => T): T;
15
17
  export declare const widelog: {
16
18
  set: <K extends string>(key: DottedKey<K>, value: FieldValue) => void;
19
+ setFields: (fields: Record<string, unknown>) => void;
17
20
  count: <K extends string>(key: DottedKey<K>, amount?: number) => void;
18
21
  append: <K extends string>(key: DottedKey<K>, value: FieldValue) => void;
19
22
  max: <K extends string>(key: DottedKey<K>, value: number) => void;
@@ -21,6 +24,7 @@ export declare const widelog: {
21
24
  time: {
22
25
  start: <K extends string>(key: DottedKey<K>) => void;
23
26
  stop: <K extends string>(key: DottedKey<K>) => void;
27
+ measure: typeof measure;
24
28
  };
25
29
  errorFields: (error: unknown, options?: ErrorFieldsOptions) => void;
26
30
  flush: () => void;
@@ -33,3 +37,4 @@ export declare const widelogger: (options: WideloggerOptions) => {
33
37
  destroy: () => Promise<void>;
34
38
  };
35
39
  export type Widelog = typeof widelog;
40
+ export {};
package/dist/index.js CHANGED
@@ -115,6 +115,8 @@ var flush = (context) => {
115
115
  };
116
116
 
117
117
  // src/index.ts
118
+ var isFieldValue = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
119
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
118
120
  function getErrorFields(error, includeStack = true) {
119
121
  if (error instanceof Error) {
120
122
  return {
@@ -135,37 +137,74 @@ function getErrorFields(error, includeStack = true) {
135
137
  };
136
138
  }
137
139
  var storage = new AsyncLocalStorage;
140
+ function pushOp(operation) {
141
+ storage.getStore()?.operations.push(operation);
142
+ }
143
+ function applyFields(operations, fields, parentKey) {
144
+ for (const key of Object.keys(fields)) {
145
+ const value = fields[key];
146
+ const fullKey = parentKey ? `${parentKey}.${key}` : key;
147
+ if (isFieldValue(value)) {
148
+ operations.push({ operation: "set", key: fullKey, value });
149
+ continue;
150
+ }
151
+ if (isRecord2(value)) {
152
+ applyFields(operations, value, fullKey);
153
+ }
154
+ }
155
+ }
156
+ function measure(key, callback) {
157
+ const operations = storage.getStore()?.operations;
158
+ operations?.push({ operation: "time.start", key, time: performance.now() });
159
+ let result;
160
+ try {
161
+ result = callback();
162
+ } catch (error) {
163
+ operations?.push({ operation: "time.stop", key, time: performance.now() });
164
+ throw error;
165
+ }
166
+ if (result instanceof Promise) {
167
+ return result.finally(() => {
168
+ operations?.push({
169
+ operation: "time.stop",
170
+ key,
171
+ time: performance.now()
172
+ });
173
+ });
174
+ }
175
+ operations?.push({ operation: "time.stop", key, time: performance.now() });
176
+ return result;
177
+ }
138
178
  var widelog = {
139
179
  set: (key, value) => {
140
- storage.getStore()?.operations.push({ operation: "set", key, value });
180
+ pushOp({ operation: "set", key, value });
181
+ },
182
+ setFields: (fields) => {
183
+ const operations = storage.getStore()?.operations;
184
+ if (operations) {
185
+ applyFields(operations, fields);
186
+ }
141
187
  },
142
188
  count: (key, amount = 1) => {
143
- storage.getStore()?.operations.push({ operation: "count", key, amount });
189
+ pushOp({ operation: "count", key, amount });
144
190
  },
145
191
  append: (key, value) => {
146
- storage.getStore()?.operations.push({ operation: "append", key, value });
192
+ pushOp({ operation: "append", key, value });
147
193
  },
148
194
  max: (key, value) => {
149
- storage.getStore()?.operations.push({ operation: "max", key, value });
195
+ pushOp({ operation: "max", key, value });
150
196
  },
151
197
  min: (key, value) => {
152
- storage.getStore()?.operations.push({ operation: "min", key, value });
198
+ pushOp({ operation: "min", key, value });
153
199
  },
154
200
  time: {
155
201
  start: (key) => {
156
- storage.getStore()?.operations.push({
157
- operation: "time.start",
158
- key,
159
- time: performance.now()
160
- });
202
+ pushOp({ operation: "time.start", key, time: performance.now() });
161
203
  },
162
204
  stop: (key) => {
163
- storage.getStore()?.operations.push({
164
- operation: "time.stop",
165
- key,
166
- time: performance.now()
167
- });
168
- }
205
+ pushOp({ operation: "time.stop", key, time: performance.now() });
206
+ },
207
+ measure
169
208
  },
170
209
  errorFields: (error, options = {}) => {
171
210
  const context = storage.getStore();
@@ -174,9 +213,15 @@ var widelog = {
174
213
  }
175
214
  const prefix = options.prefix ?? "error";
176
215
  const fields = getErrorFields(error, options.includeStack ?? true);
177
- const nameKey = `${prefix}.error_name`;
178
- const messageKey = `${prefix}.error_message`;
179
- context.operations.push({ operation: "set", key: nameKey, value: fields.error_name }, { operation: "set", key: messageKey, value: fields.error_message });
216
+ context.operations.push({
217
+ operation: "set",
218
+ key: `${prefix}.error_name`,
219
+ value: fields.error_name
220
+ }, {
221
+ operation: "set",
222
+ key: `${prefix}.error_message`,
223
+ value: fields.error_message
224
+ });
180
225
  if (fields.error_stack !== undefined) {
181
226
  context.operations.push({
182
227
  operation: "set",
@@ -187,18 +232,16 @@ var widelog = {
187
232
  },
188
233
  flush: () => {
189
234
  const store = storage.getStore();
190
- if (!store) {
235
+ if (!store || store.operations.length === 0) {
191
236
  return;
192
237
  }
193
238
  const event = flush(store);
194
- if (Object.keys(event).length === 0) {
195
- return;
196
- }
197
239
  store.transport(event);
198
240
  }
199
241
  };
200
242
  var widelogger = (options) => {
201
- const environment = options.environment ?? "development" ?? "development";
243
+ const nodeEnvironment = typeof process.env === "object" ? "development" : undefined;
244
+ const environment = options.environment ?? nodeEnvironment ?? "development";
202
245
  const isDevelopment = environment !== "production";
203
246
  const pinoTransport = isDevelopment ? pino.transport({
204
247
  target: "pino-pretty",
@@ -220,15 +263,16 @@ var widelogger = (options) => {
220
263
  environment
221
264
  }
222
265
  }, pinoTransport);
266
+ const defaultEventName = options.defaultEventName;
223
267
  const transport = (event) => {
224
268
  const statusCode = typeof event.status_code === "number" ? event.status_code : undefined;
225
269
  const isError = statusCode !== undefined ? statusCode >= 500 : event.outcome === "error";
226
- const payload = { event_name: options.defaultEventName, ...event };
270
+ event.event_name = defaultEventName;
227
271
  if (isError) {
228
- logger.error(payload);
272
+ logger.error(event);
229
273
  return;
230
274
  }
231
- logger.info(payload);
275
+ logger.info(event);
232
276
  };
233
277
  const clearContext = () => {
234
278
  const context2 = storage.getStore();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "widelogger",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",