widelogger 0.4.0 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DottedKey, FieldValue } from "./types";
1
+ import type { DottedKey, ErrorParser, FieldValue } from "./types";
2
2
  export interface WideloggerOptions {
3
3
  service: string;
4
4
  defaultEventName: string;
@@ -11,12 +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;
17
+ }
18
+ interface StickyHandle {
19
+ sticky: () => void;
14
20
  }
15
21
  declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => Promise<T>): Promise<T>;
16
22
  declare function measure<K extends string, T>(key: DottedKey<K>, callback: () => T): T;
17
23
  export declare const widelog: {
18
- set: <K extends string>(key: DottedKey<K>, value: FieldValue) => void;
19
- setFields: (fields: Record<string, unknown>) => void;
24
+ set: <K extends string>(key: DottedKey<K>, value: FieldValue) => StickyHandle;
25
+ setFields: (fields: Record<string, unknown>) => StickyHandle;
20
26
  count: <K extends string>(key: DottedKey<K>, amount?: number) => void;
21
27
  append: <K extends string>(key: DottedKey<K>, value: FieldValue) => void;
22
28
  max: <K extends string>(key: DottedKey<K>, value: number) => void;
@@ -26,6 +32,8 @@ export declare const widelog: {
26
32
  stop: <K extends string>(key: DottedKey<K>) => void;
27
33
  measure: typeof measure;
28
34
  };
35
+ errors: (parser: ErrorParser) => void;
36
+ error: <K extends string>(key: DottedKey<K>, error: unknown) => void;
29
37
  errorFields: (error: unknown, options?: ErrorFieldsOptions) => void;
30
38
  flush: () => void;
31
39
  };
package/dist/index.js CHANGED
@@ -32,9 +32,29 @@ var createAggregators = () => ({
32
32
  arrays: Object.create(null),
33
33
  maxValues: Object.create(null),
34
34
  minValues: Object.create(null),
35
- timers: Object.create(null)
35
+ timers: Object.create(null),
36
+ errors: Object.create(null)
36
37
  });
37
- var processOperation = (agg, entry) => {
38
+ var aggregateError = (agg, key, slug) => {
39
+ const existing = agg.errors[key];
40
+ if (existing) {
41
+ existing.total += 1;
42
+ const previousCount = existing.counts[slug];
43
+ if (typeof previousCount === "number") {
44
+ existing.counts[slug] = previousCount + 1;
45
+ } else {
46
+ existing.counts[slug] = 1;
47
+ existing.slugs.push(slug);
48
+ }
49
+ } else {
50
+ agg.errors[key] = {
51
+ slugs: [slug],
52
+ counts: { [slug]: 1 },
53
+ total: 1
54
+ };
55
+ }
56
+ };
57
+ var processOperation = (agg, entry, errorParser) => {
38
58
  switch (entry.operation) {
39
59
  case "set":
40
60
  setNested(agg.event, entry.key, entry.value);
@@ -66,9 +86,9 @@ var processOperation = (agg, entry) => {
66
86
  break;
67
87
  }
68
88
  case "time.start": {
69
- const existing = agg.timers[entry.key];
70
- if (existing) {
71
- existing.start = entry.time;
89
+ const existingTimer = agg.timers[entry.key];
90
+ if (existingTimer) {
91
+ existingTimer.start = entry.time;
72
92
  } else {
73
93
  agg.timers[entry.key] = { start: entry.time, accumulated: 0 };
74
94
  }
@@ -82,6 +102,13 @@ var processOperation = (agg, entry) => {
82
102
  }
83
103
  break;
84
104
  }
105
+ case "error": {
106
+ if (errorParser) {
107
+ const slug = errorParser(entry.error);
108
+ aggregateError(agg, entry.key, slug);
109
+ }
110
+ break;
111
+ }
85
112
  default:
86
113
  }
87
114
  };
@@ -90,7 +117,8 @@ var mergeAggregators = (agg) => {
90
117
  agg.counters,
91
118
  agg.arrays,
92
119
  agg.maxValues,
93
- agg.minValues
120
+ agg.minValues,
121
+ agg.errors
94
122
  ];
95
123
  for (const source of sources) {
96
124
  for (const key of Object.keys(source)) {
@@ -105,9 +133,17 @@ var flush = (context) => {
105
133
  if (!context) {
106
134
  return {};
107
135
  }
136
+ const hasStickyOperations = context.stickyOperations.length > 0;
137
+ const hasOperations = context.operations.length > 0;
138
+ if (!(hasStickyOperations || hasOperations)) {
139
+ return {};
140
+ }
108
141
  const agg = createAggregators();
142
+ for (const entry of context.stickyOperations) {
143
+ processOperation(agg, entry, context.errorParser);
144
+ }
109
145
  for (const entry of context.operations) {
110
- processOperation(agg, entry);
146
+ processOperation(agg, entry, context.errorParser);
111
147
  }
112
148
  mergeAggregators(agg);
113
149
  context.operations = [];
@@ -117,6 +153,7 @@ var flush = (context) => {
117
153
  // src/index.ts
118
154
  var isFieldValue = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
119
155
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
156
+ var isFieldValueArray = (value) => Array.isArray(value) && value.every(isFieldValue);
120
157
  function getErrorFields(error, includeStack = true) {
121
158
  if (error instanceof Error) {
122
159
  return {
@@ -136,9 +173,32 @@ function getErrorFields(error, includeStack = true) {
136
173
  error_message: "Unknown error"
137
174
  };
138
175
  }
176
+ var extractErrorProperty = (error, property) => {
177
+ if (isRecord2(error) && property in error) {
178
+ return error[property];
179
+ }
180
+ return;
181
+ };
182
+ var noop = () => {
183
+ return;
184
+ };
185
+ var noopSticky = { sticky: noop };
139
186
  var storage = new AsyncLocalStorage;
140
187
  function pushOp(operation) {
141
- storage.getStore()?.operations.push(operation);
188
+ const store = storage.getStore();
189
+ if (!store) {
190
+ return noopSticky;
191
+ }
192
+ store.operations.push(operation);
193
+ return {
194
+ sticky: () => {
195
+ const index = store.operations.indexOf(operation);
196
+ if (index !== -1) {
197
+ store.operations.splice(index, 1);
198
+ }
199
+ store.stickyOperations.push(operation);
200
+ }
201
+ };
142
202
  }
143
203
  function applyFields(operations, fields, parentKey) {
144
204
  for (const key of Object.keys(fields)) {
@@ -148,6 +208,12 @@ function applyFields(operations, fields, parentKey) {
148
208
  operations.push({ operation: "set", key: fullKey, value });
149
209
  continue;
150
210
  }
211
+ if (isFieldValueArray(value)) {
212
+ for (const element of value) {
213
+ operations.push({ operation: "append", key: fullKey, value: element });
214
+ }
215
+ continue;
216
+ }
151
217
  if (isRecord2(value)) {
152
218
  applyFields(operations, value, fullKey);
153
219
  }
@@ -177,13 +243,21 @@ function measure(key, callback) {
177
243
  }
178
244
  var widelog = {
179
245
  set: (key, value) => {
180
- pushOp({ operation: "set", key, value });
246
+ return pushOp({ operation: "set", key, value });
181
247
  },
182
248
  setFields: (fields) => {
183
- const operations = storage.getStore()?.operations;
184
- if (operations) {
185
- applyFields(operations, fields);
249
+ const store = storage.getStore();
250
+ if (!store) {
251
+ return noopSticky;
186
252
  }
253
+ const startIndex = store.operations.length;
254
+ applyFields(store.operations, fields);
255
+ return {
256
+ sticky: () => {
257
+ const added = store.operations.splice(startIndex);
258
+ store.stickyOperations.push(...added);
259
+ }
260
+ };
187
261
  },
188
262
  count: (key, amount = 1) => {
189
263
  pushOp({ operation: "count", key, amount });
@@ -206,6 +280,16 @@ var widelog = {
206
280
  },
207
281
  measure
208
282
  },
283
+ errors: (parser) => {
284
+ const store = storage.getStore();
285
+ if (!store) {
286
+ return;
287
+ }
288
+ store.errorParser = parser;
289
+ },
290
+ error: (key, error) => {
291
+ pushOp({ operation: "error", key, error });
292
+ },
209
293
  errorFields: (error, options = {}) => {
210
294
  const context = storage.getStore();
211
295
  if (!context) {
@@ -229,10 +313,37 @@ var widelog = {
229
313
  value: fields.error_stack
230
314
  });
231
315
  }
316
+ const slug = options.slug ?? extractErrorProperty(error, "slug");
317
+ if (typeof slug === "string") {
318
+ context.operations.push({
319
+ operation: "set",
320
+ key: `${prefix}.slug`,
321
+ value: slug
322
+ });
323
+ }
324
+ const retriable = options.retriable ?? extractErrorProperty(error, "retriable");
325
+ if (typeof retriable === "boolean") {
326
+ context.operations.push({
327
+ operation: "set",
328
+ key: `${prefix}.retriable`,
329
+ value: retriable
330
+ });
331
+ }
332
+ const requiresReauth = options.requiresReauth ?? extractErrorProperty(error, "requiresReauth");
333
+ if (typeof requiresReauth === "boolean") {
334
+ context.operations.push({
335
+ operation: "set",
336
+ key: `${prefix}.requires_reauth`,
337
+ value: requiresReauth
338
+ });
339
+ }
232
340
  },
233
341
  flush: () => {
234
342
  const store = storage.getStore();
235
- if (!store || store.operations.length === 0) {
343
+ if (!store) {
344
+ return;
345
+ }
346
+ if (store.operations.length === 0 && store.stickyOperations.length === 0) {
236
347
  return;
237
348
  }
238
349
  const event = flush(store);
@@ -276,12 +387,18 @@ var widelogger = (options) => {
276
387
  };
277
388
  const clearContext = () => {
278
389
  const context2 = storage.getStore();
279
- if (context2 && context2.operations.length > 0) {
390
+ if (context2) {
280
391
  context2.operations = [];
392
+ context2.stickyOperations = [];
281
393
  }
282
394
  };
283
395
  function context(callback) {
284
- return storage.run({ operations: [], transport }, () => {
396
+ return storage.run({
397
+ operations: [],
398
+ stickyOperations: [],
399
+ errorParser: null,
400
+ transport
401
+ }, () => {
285
402
  let result;
286
403
  try {
287
404
  result = callback();
package/dist/types.d.ts CHANGED
@@ -5,6 +5,7 @@ interface KeyErrorBrand {
5
5
  }
6
6
  type ValidateKey<T extends string> = T extends "" ? "widelog keys cannot be empty" & KeyErrorBrand : T extends `.${string}` ? "widelog keys cannot start with a dot" & KeyErrorBrand : T extends `${string}.` ? "widelog keys cannot end with a dot" & KeyErrorBrand : T extends `${string}..${string}` ? "widelog keys cannot contain empty segments" & KeyErrorBrand : T;
7
7
  export type DottedKey<T extends string> = ValidateKey<T>;
8
+ export type ErrorParser = (error: unknown) => string;
8
9
  export type Operation = {
9
10
  operation: "set";
10
11
  key: string;
@@ -33,9 +34,15 @@ export type Operation = {
33
34
  operation: "time.stop";
34
35
  key: string;
35
36
  time: number;
37
+ } | {
38
+ operation: "error";
39
+ key: string;
40
+ error: unknown;
36
41
  };
37
42
  export interface Context {
38
43
  operations: Operation[];
44
+ stickyOperations: Operation[];
45
+ errorParser: ErrorParser | null;
39
46
  transport: (event: Record<string, unknown>) => void;
40
47
  }
41
48
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "widelogger",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",