hierarchical-area-logger 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Logger: () => Logger,
24
- createLogger: () => createLogger
24
+ createLogger: () => createLogger,
25
+ prettyError: () => prettyError
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
 
@@ -84,6 +85,7 @@ var Logger = class {
84
85
  constructor(options) {
85
86
  this.parentEventId = options.parentEventId;
86
87
  this.eventId = (0, import_cuid2.init)({ fingerprint: options.details.service })();
88
+ this.defaultArea = options.defaultArea || "defaultArea";
87
89
  this.log = createRootLogEntry({
88
90
  path: options.path || "/",
89
91
  method: options.method,
@@ -93,40 +95,39 @@ var Logger = class {
93
95
  withParentEventId: options.withParentEventId
94
96
  });
95
97
  }
98
+ finalizeRootMessage() {
99
+ this.addMessage("root", "info", "Request completed", {
100
+ totalDuration: Date.now() - this.log.root[0].timestamp
101
+ });
102
+ }
103
+ addMessage(name, type, message, payload) {
104
+ this.log[name].push({
105
+ type,
106
+ message,
107
+ payload: payload instanceof Error ? prettyError(payload) : payload,
108
+ timestamp: Date.now()
109
+ });
110
+ }
96
111
  // Area function that returns area-specific logger methods
97
- getArea(name = "dummy") {
112
+ getArea(name = this.defaultArea) {
98
113
  if (!this.log[name]) {
99
114
  this.log[name] = [];
100
115
  }
101
116
  return {
102
117
  info: (message, payload) => {
103
- this.log[name].push({
104
- type: "info",
105
- message,
106
- payload,
107
- timestamp: Date.now()
108
- });
118
+ this.addMessage(name, "info", message, payload);
109
119
  },
110
120
  warn: (message, payload) => {
111
- this.log[name].push({
112
- type: "warn",
113
- message,
114
- payload,
115
- timestamp: Date.now()
116
- });
121
+ this.addMessage(name, "warn", message, payload);
117
122
  },
118
123
  error: (message, payload) => {
119
- this.log[name].push({
120
- type: "error",
121
- message,
122
- payload: payload instanceof Error ? prettyError(payload) : payload,
123
- timestamp: Date.now()
124
- });
124
+ this.addMessage(name, "error", message, payload);
125
125
  }
126
126
  };
127
127
  }
128
128
  // Public methods on main instance
129
129
  dump() {
130
+ this.finalizeRootMessage();
130
131
  return this.log;
131
132
  }
132
133
  appendLogData(logData) {
@@ -140,6 +141,7 @@ var createLogger = (options) => {
140
141
  // Annotate the CommonJS export names for ESM import in node:
141
142
  0 && (module.exports = {
142
143
  Logger,
143
- createLogger
144
+ createLogger,
145
+ prettyError
144
146
  });
145
147
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Logger.ts","../src/utils.ts"],"sourcesContent":["export { Logger, createLogger } from './Logger';\nexport { LogData, LogEntry } from './types';\n","import { init } from '@paralleldrive/cuid2';\nimport { createRootLogEntry, prettyError } from './utils';\nimport { LogData, LoggerOptions, LogEntry } from './types';\n\nexport class Logger {\n public eventId: string;\n private log: LogData;\n public parentEventId?: string;\n\n constructor(options: LoggerOptions) {\n this.parentEventId = options.parentEventId;\n this.eventId = init({ fingerprint: options.details.service })();\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 // Area function that returns area-specific logger methods\n public getArea(name = 'dummy') {\n if (!this.log[name]) {\n this.log[name] = [];\n }\n\n return {\n info: (message: string, payload?: LogEntry['payload']) => {\n this.log[name]!.push({\n type: 'info',\n message,\n payload,\n timestamp: Date.now(),\n });\n },\n warn: (message: string, payload?: LogEntry['payload']) => {\n this.log[name]!.push({\n type: 'warn',\n message,\n payload,\n timestamp: Date.now(),\n });\n },\n error: (message: string, payload?: LogEntry['payload'] | Error) => {\n this.log[name]!.push({\n type: 'error',\n message,\n payload: payload instanceof Error ? prettyError(payload) : payload,\n timestamp: Date.now(),\n });\n },\n };\n }\n\n // Public methods on main instance\n public dump(): LogData {\n return 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","import { CreateRootLogEntryOptions, LogData, RootPayload } 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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Logger.ts","../src/utils.ts"],"sourcesContent":["export { Logger, createLogger } from './Logger';\nexport type { LogData, LogEntry, LogEntryType, Details, RootPayload } from './types';\nexport { prettyError } from './utils';\n","import { init } from '@paralleldrive/cuid2';\nimport { createRootLogEntry, prettyError } 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 = 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 this.log[name]!.push({\n type,\n message,\n payload: payload instanceof Error ? prettyError(payload) : payload,\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 };\n }\n\n // Public methods on main instance\n public dump(): LogData {\n this.finalizeRootMessage();\n return 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","import { CreateRootLogEntryOptions, LogData, RootPayload } 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"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -2,12 +2,20 @@ type Details = {
2
2
  service: string;
3
3
  } & Record<string, unknown>;
4
4
  type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace' | 'connect';
5
+ type LogEntryType = 'info' | 'warn' | 'error';
5
6
  type LogEntry = {
6
- type: 'info' | 'warn' | 'error';
7
+ type: LogEntryType;
7
8
  message: string;
8
9
  payload?: object;
9
10
  timestamp: number;
10
11
  };
12
+ type RootPayload = {
13
+ path?: string;
14
+ method?: Method;
15
+ eventId: string;
16
+ parentEventId?: string;
17
+ details: Details;
18
+ };
11
19
  type LogData = Record<string, LogEntry[]>;
12
20
  interface LoggerOptions {
13
21
  details: Details;
@@ -15,13 +23,17 @@ interface LoggerOptions {
15
23
  parentEventId?: string;
16
24
  withParentEventId?: boolean;
17
25
  method?: Method;
26
+ defaultArea?: string;
18
27
  }
19
28
 
20
29
  declare class Logger {
21
30
  eventId: string;
22
31
  private log;
23
32
  parentEventId?: string;
33
+ private defaultArea;
24
34
  constructor(options: LoggerOptions);
35
+ private finalizeRootMessage;
36
+ private addMessage;
25
37
  getArea(name?: string): {
26
38
  info: (message: string, payload?: LogEntry["payload"]) => void;
27
39
  warn: (message: string, payload?: LogEntry["payload"]) => void;
@@ -32,4 +44,9 @@ declare class Logger {
32
44
  }
33
45
  declare const createLogger: (options: LoggerOptions) => Logger;
34
46
 
35
- export { type LogData, type LogEntry, Logger, createLogger };
47
+ declare const prettyError: (err: Error) => {
48
+ message: string;
49
+ stack: string[];
50
+ };
51
+
52
+ export { type Details, type LogData, type LogEntry, type LogEntryType, Logger, type RootPayload, createLogger, prettyError };
package/dist/index.d.ts CHANGED
@@ -2,12 +2,20 @@ type Details = {
2
2
  service: string;
3
3
  } & Record<string, unknown>;
4
4
  type Method = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace' | 'connect';
5
+ type LogEntryType = 'info' | 'warn' | 'error';
5
6
  type LogEntry = {
6
- type: 'info' | 'warn' | 'error';
7
+ type: LogEntryType;
7
8
  message: string;
8
9
  payload?: object;
9
10
  timestamp: number;
10
11
  };
12
+ type RootPayload = {
13
+ path?: string;
14
+ method?: Method;
15
+ eventId: string;
16
+ parentEventId?: string;
17
+ details: Details;
18
+ };
11
19
  type LogData = Record<string, LogEntry[]>;
12
20
  interface LoggerOptions {
13
21
  details: Details;
@@ -15,13 +23,17 @@ interface LoggerOptions {
15
23
  parentEventId?: string;
16
24
  withParentEventId?: boolean;
17
25
  method?: Method;
26
+ defaultArea?: string;
18
27
  }
19
28
 
20
29
  declare class Logger {
21
30
  eventId: string;
22
31
  private log;
23
32
  parentEventId?: string;
33
+ private defaultArea;
24
34
  constructor(options: LoggerOptions);
35
+ private finalizeRootMessage;
36
+ private addMessage;
25
37
  getArea(name?: string): {
26
38
  info: (message: string, payload?: LogEntry["payload"]) => void;
27
39
  warn: (message: string, payload?: LogEntry["payload"]) => void;
@@ -32,4 +44,9 @@ declare class Logger {
32
44
  }
33
45
  declare const createLogger: (options: LoggerOptions) => Logger;
34
46
 
35
- export { type LogData, type LogEntry, Logger, createLogger };
47
+ declare const prettyError: (err: Error) => {
48
+ message: string;
49
+ stack: string[];
50
+ };
51
+
52
+ export { type Details, type LogData, type LogEntry, type LogEntryType, Logger, type RootPayload, createLogger, prettyError };
package/dist/index.js CHANGED
@@ -57,6 +57,7 @@ var Logger = class {
57
57
  constructor(options) {
58
58
  this.parentEventId = options.parentEventId;
59
59
  this.eventId = init({ fingerprint: options.details.service })();
60
+ this.defaultArea = options.defaultArea || "defaultArea";
60
61
  this.log = createRootLogEntry({
61
62
  path: options.path || "/",
62
63
  method: options.method,
@@ -66,40 +67,39 @@ var Logger = class {
66
67
  withParentEventId: options.withParentEventId
67
68
  });
68
69
  }
70
+ finalizeRootMessage() {
71
+ this.addMessage("root", "info", "Request completed", {
72
+ totalDuration: Date.now() - this.log.root[0].timestamp
73
+ });
74
+ }
75
+ addMessage(name, type, message, payload) {
76
+ this.log[name].push({
77
+ type,
78
+ message,
79
+ payload: payload instanceof Error ? prettyError(payload) : payload,
80
+ timestamp: Date.now()
81
+ });
82
+ }
69
83
  // Area function that returns area-specific logger methods
70
- getArea(name = "dummy") {
84
+ getArea(name = this.defaultArea) {
71
85
  if (!this.log[name]) {
72
86
  this.log[name] = [];
73
87
  }
74
88
  return {
75
89
  info: (message, payload) => {
76
- this.log[name].push({
77
- type: "info",
78
- message,
79
- payload,
80
- timestamp: Date.now()
81
- });
90
+ this.addMessage(name, "info", message, payload);
82
91
  },
83
92
  warn: (message, payload) => {
84
- this.log[name].push({
85
- type: "warn",
86
- message,
87
- payload,
88
- timestamp: Date.now()
89
- });
93
+ this.addMessage(name, "warn", message, payload);
90
94
  },
91
95
  error: (message, payload) => {
92
- this.log[name].push({
93
- type: "error",
94
- message,
95
- payload: payload instanceof Error ? prettyError(payload) : payload,
96
- timestamp: Date.now()
97
- });
96
+ this.addMessage(name, "error", message, payload);
98
97
  }
99
98
  };
100
99
  }
101
100
  // Public methods on main instance
102
101
  dump() {
102
+ this.finalizeRootMessage();
103
103
  return this.log;
104
104
  }
105
105
  appendLogData(logData) {
@@ -112,6 +112,7 @@ var createLogger = (options) => {
112
112
  };
113
113
  export {
114
114
  Logger,
115
- createLogger
115
+ createLogger,
116
+ prettyError
116
117
  };
117
118
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/Logger.ts","../src/utils.ts"],"sourcesContent":["import { init } from '@paralleldrive/cuid2';\nimport { createRootLogEntry, prettyError } from './utils';\nimport { LogData, LoggerOptions, LogEntry } from './types';\n\nexport class Logger {\n public eventId: string;\n private log: LogData;\n public parentEventId?: string;\n\n constructor(options: LoggerOptions) {\n this.parentEventId = options.parentEventId;\n this.eventId = init({ fingerprint: options.details.service })();\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 // Area function that returns area-specific logger methods\n public getArea(name = 'dummy') {\n if (!this.log[name]) {\n this.log[name] = [];\n }\n\n return {\n info: (message: string, payload?: LogEntry['payload']) => {\n this.log[name]!.push({\n type: 'info',\n message,\n payload,\n timestamp: Date.now(),\n });\n },\n warn: (message: string, payload?: LogEntry['payload']) => {\n this.log[name]!.push({\n type: 'warn',\n message,\n payload,\n timestamp: Date.now(),\n });\n },\n error: (message: string, payload?: LogEntry['payload'] | Error) => {\n this.log[name]!.push({\n type: 'error',\n message,\n payload: payload instanceof Error ? prettyError(payload) : payload,\n timestamp: Date.now(),\n });\n },\n };\n }\n\n // Public methods on main instance\n public dump(): LogData {\n return 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","import { CreateRootLogEntryOptions, LogData, RootPayload } 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"],"mappings":";AAAA,SAAS,YAAY;;;ACEd,IAAM,cAAc,CAAC,UAA6B;AACvD,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,MAAI,YAAY;AAEhB,QAAM,QAAQ;AACd,QAAM,IAAI,MAAM,KAAK,KAAK;AAE1B,MAAI,KAAK,EAAE,SAAS,GAAG;AACrB,gBAAY,EAAE,CAAC;AAAA,EACjB;AAEA,SAAO,MACJ,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW,SAAS,CAAC,EAC5C,IAAI,CAAC,SAAS,KAAK,QAAQ,WAAW,EAAE,CAAC,EACzC,IAAI,CAAC,SAAS,KAAK,QAAQ,WAAW,EAAE,CAAC;AAC9C;AAEO,IAAM,cAAc,CAAC,QAAe;AACzC,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,QAAM,UAAU,IAAI;AACpB,SAAO,EAAE,SAAS,MAAM;AAC1B;AAEO,IAAM,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA0C;AACxC,QAAM,MAAM,IAAI,IAAI,qBAAqB,QAAQ,GAAG,EAAE;AAEtD,QAAM,eAAwB;AAAA,IAC5B,MAAM;AAAA,MACJ;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,SAAS;AAAA,UACP,MAAM,GAAG,IAAI,QAAQ,GAAG,IAAI,MAAM;AAAA,UAClC;AAAA,UACA;AAAA,UACA;AAAA,UACA,GAAI,iBAAiB,EAAE,cAAc;AAAA,QACvC;AAAA,QACA,WAAW,KAAK,IAAI;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,qBAAqB,CAAC,eAAe;AACvC,iBAAa,KAAK,KAAK;AAAA,MACrB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,WAAW,KAAK,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AD3DO,IAAM,SAAN,MAAa;AAAA,EAKlB,YAAY,SAAwB;AAClC,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,UAAU,KAAK,EAAE,aAAa,QAAQ,QAAQ,QAAQ,CAAC,EAAE;AAE9D,SAAK,MAAM,mBAAmB;AAAA,MAC5B,MAAM,QAAQ,QAAQ;AAAA,MACtB,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,MACjB,SAAS,KAAK;AAAA,MACd,eAAe,QAAQ;AAAA,MACvB,mBAAmB,QAAQ;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA,EAGO,QAAQ,OAAO,SAAS;AAC7B,QAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,WAAK,IAAI,IAAI,IAAI,CAAC;AAAA,IACpB;AAEA,WAAO;AAAA,MACL,MAAM,CAAC,SAAiB,YAAkC;AACxD,aAAK,IAAI,IAAI,EAAG,KAAK;AAAA,UACnB,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,MACA,MAAM,CAAC,SAAiB,YAAkC;AACxD,aAAK,IAAI,IAAI,EAAG,KAAK;AAAA,UACnB,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,MACA,OAAO,CAAC,SAAiB,YAA0C;AACjE,aAAK,IAAI,IAAI,EAAG,KAAK;AAAA,UACnB,MAAM;AAAA,UACN;AAAA,UACA,SAAS,mBAAmB,QAAQ,YAAY,OAAO,IAAI;AAAA,UAC3D,WAAW,KAAK,IAAI;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGO,OAAgB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,cAAc,SAAwB;AAC3C,WAAO,QAAQ;AACf,SAAK,MAAM,EAAE,GAAG,SAAS,GAAG,KAAK,IAAI;AAAA,EACvC;AACF;AAGO,IAAM,eAAe,CAAC,YAAmC;AAC9D,SAAO,IAAI,OAAO,OAAO;AAC3B;","names":[]}
1
+ {"version":3,"sources":["../src/Logger.ts","../src/utils.ts"],"sourcesContent":["import { init } from '@paralleldrive/cuid2';\nimport { createRootLogEntry, prettyError } 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 = 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 this.log[name]!.push({\n type,\n message,\n payload: payload instanceof Error ? prettyError(payload) : payload,\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 };\n }\n\n // Public methods on main instance\n public dump(): LogData {\n this.finalizeRootMessage();\n return 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","import { CreateRootLogEntryOptions, LogData, RootPayload } 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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hierarchical-area-logger",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,7 +24,7 @@
24
24
  "license": "MIT",
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "https://github.com/Em3ODMe/hierarchical-area-logger.git"
27
+ "url": "git+https://github.com/Em3ODMe/hierarchical-area-logger.git"
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=18.0.0"
package/src/Logger.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { init } from '@paralleldrive/cuid2';
2
2
  import { createRootLogEntry, prettyError } from './utils';
3
- import { LogData, LoggerOptions, LogEntry } from './types';
3
+ import type { LogData, LoggerOptions, LogEntry, LogEntryType } from './types';
4
4
 
5
5
  export class Logger {
6
6
  public eventId: string;
7
7
  private log: LogData;
8
8
  public parentEventId?: string;
9
+ private defaultArea: string;
9
10
 
10
11
  constructor(options: LoggerOptions) {
11
12
  this.parentEventId = options.parentEventId;
12
13
  this.eventId = init({ fingerprint: options.details.service })();
14
+ this.defaultArea = options.defaultArea || 'defaultArea';
13
15
 
14
16
  this.log = createRootLogEntry({
15
17
  path: options.path || '/',
@@ -21,42 +23,48 @@ export class Logger {
21
23
  });
22
24
  }
23
25
 
26
+ private finalizeRootMessage() {
27
+ this.addMessage('root', 'info', 'Request completed', {
28
+ totalDuration: Date.now() - this.log.root[0].timestamp,
29
+ });
30
+ }
31
+
32
+ private addMessage(
33
+ name: string,
34
+ type: LogEntryType,
35
+ message: string,
36
+ payload?: LogEntry['payload'] | Error
37
+ ) {
38
+ this.log[name]!.push({
39
+ type,
40
+ message,
41
+ payload: payload instanceof Error ? prettyError(payload) : payload,
42
+ timestamp: Date.now(),
43
+ });
44
+ }
45
+
24
46
  // Area function that returns area-specific logger methods
25
- public getArea(name = 'dummy') {
47
+ public getArea(name = this.defaultArea) {
26
48
  if (!this.log[name]) {
27
49
  this.log[name] = [];
28
50
  }
29
51
 
30
52
  return {
31
53
  info: (message: string, payload?: LogEntry['payload']) => {
32
- this.log[name]!.push({
33
- type: 'info',
34
- message,
35
- payload,
36
- timestamp: Date.now(),
37
- });
54
+ this.addMessage(name, 'info', message, payload);
38
55
  },
39
56
  warn: (message: string, payload?: LogEntry['payload']) => {
40
- this.log[name]!.push({
41
- type: 'warn',
42
- message,
43
- payload,
44
- timestamp: Date.now(),
45
- });
57
+ this.addMessage(name, 'warn', message, payload);
46
58
  },
47
59
  error: (message: string, payload?: LogEntry['payload'] | Error) => {
48
- this.log[name]!.push({
49
- type: 'error',
50
- message,
51
- payload: payload instanceof Error ? prettyError(payload) : payload,
52
- timestamp: Date.now(),
53
- });
60
+ this.addMessage(name, 'error', message, payload);
54
61
  },
55
62
  };
56
63
  }
57
64
 
58
65
  // Public methods on main instance
59
66
  public dump(): LogData {
67
+ this.finalizeRootMessage();
60
68
  return this.log;
61
69
  }
62
70
 
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Logger, createLogger } from './Logger';
2
- export { LogData, LogEntry } from './types';
2
+ export type { LogData, LogEntry, LogEntryType, Details, RootPayload } from './types';
3
+ export { prettyError } from './utils';
package/src/types.ts CHANGED
@@ -10,8 +10,10 @@ export type Method =
10
10
  | 'trace'
11
11
  | 'connect';
12
12
 
13
+ export type LogEntryType = 'info' | 'warn' | 'error';
14
+
13
15
  export type LogEntry = {
14
- type: 'info' | 'warn' | 'error';
16
+ type: LogEntryType;
15
17
  message: string;
16
18
  payload?: object;
17
19
  timestamp: number;
@@ -35,4 +37,5 @@ export interface LoggerOptions {
35
37
  parentEventId?: string;
36
38
  withParentEventId?: boolean;
37
39
  method?: Method;
40
+ defaultArea?: string;
38
41
  }
@@ -1,136 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createLogger, Logger } from '../src/Logger';
3
- import { Details } from '../src/types';
4
-
5
- describe('Logger', () => {
6
- const mockdetails: Details = { service: 'test-service' };
7
-
8
- it('should create a logger with details', () => {
9
- const logger = createLogger({ details: mockdetails });
10
- expect(logger).toBeInstanceOf(Logger);
11
- });
12
-
13
- it('should create a logger with custom options', () => {
14
- const logger = createLogger({
15
- details: mockdetails,
16
- path: 'test-path',
17
- parentEventId: 'parent-123',
18
- });
19
- expect(logger).toBeInstanceOf(Logger);
20
- });
21
-
22
- it('should create area logger and log messages', () => {
23
- const logger = createLogger({ details: mockdetails });
24
- const areaLogger = logger.getArea('test-area');
25
-
26
- areaLogger.info('test info message');
27
- areaLogger.warn('test warn message');
28
- areaLogger.error('test error message');
29
-
30
- const logs = logger.dump();
31
- expect(logs['test-area']).toHaveLength(3);
32
- expect(logs['test-area'][0].message).toBe('test info message');
33
- expect(logs['test-area'][1].type).toBe('warn');
34
- expect(logs['test-area'][2].type).toBe('error');
35
- });
36
-
37
- it('should generate event IDs correctly', () => {
38
- const logger = createLogger({ details: mockdetails });
39
-
40
- expect(logger.eventId).toBeDefined();
41
- expect(typeof logger.eventId).toBe('string');
42
- });
43
-
44
- it('should handle parent event ID', () => {
45
- const logger = createLogger({
46
- details: mockdetails,
47
- parentEventId: 'parent-123',
48
- });
49
-
50
- expect(logger.parentEventId).toBe('parent-123');
51
- });
52
-
53
- it('should append log data', () => {
54
- const logger = createLogger({ details: mockdetails });
55
- const logDataToAdd = {
56
- area1: [
57
- {
58
- type: 'info' as const,
59
- message: 'existing log',
60
- timestamp: Date.now(),
61
- },
62
- ],
63
- };
64
-
65
- logger.appendLogData(logDataToAdd);
66
- const logs = logger.dump();
67
-
68
- expect(logs.area1).toBeDefined();
69
- expect(logs.area1[0].message).toBe('existing log');
70
- });
71
-
72
- it('should create root log entry', () => {
73
- const logger = createLogger({ details: mockdetails, path: 'test' });
74
- const logs = logger.dump();
75
-
76
- expect(logs.root).toBeDefined();
77
- expect(logs.root).toHaveLength(1);
78
- expect(logs.root[0].message).toBe('Request received');
79
- });
80
-
81
- it('should handle errors in error logging', () => {
82
- const logger = createLogger({ details: mockdetails });
83
- const areaLogger = logger.getArea('test-area');
84
-
85
- const testError = new Error('Test error');
86
- areaLogger.error('error occurred', testError);
87
-
88
- const logs = logger.dump();
89
- expect(logs['test-area'][0].payload).toHaveProperty(
90
- 'message',
91
- 'Test error'
92
- );
93
- });
94
-
95
- it('should use default area name when no name provided', () => {
96
- const logger = createLogger({ details: mockdetails });
97
- const areaLogger = logger.getArea();
98
-
99
- areaLogger.info('test message');
100
-
101
- const logs = logger.dump();
102
- expect(logs.dummy).toBeDefined();
103
- expect(logs.dummy).toHaveLength(1);
104
- expect(logs.dummy[0].message).toBe('test message');
105
- });
106
-
107
- it('should handle withParentEventId true without parentEventId', () => {
108
- const logger = createLogger({
109
- details: mockdetails,
110
- withParentEventId: true,
111
- });
112
-
113
- const logs = logger.dump();
114
- expect(logs.root).toHaveLength(2);
115
- expect(logs.root[1].type).toBe('error');
116
- expect(logs.root[1].message).toBe('Parent event ID expected but not found');
117
- });
118
- });
119
-
120
- describe('createLogger', () => {
121
- const mockdetails: Details = { service: 'test-service' };
122
-
123
- it('should create a logger instance', () => {
124
- const logger = createLogger({ details: mockdetails });
125
- expect(logger).toBeInstanceOf(Logger);
126
- });
127
-
128
- it('should create a logger with options', () => {
129
- const logger = createLogger({
130
- details: mockdetails,
131
- path: 'test-path',
132
- parentEventId: 'parent-123',
133
- });
134
- expect(logger).toBeInstanceOf(Logger);
135
- });
136
- });
@@ -1,453 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { prettyStack, prettyError, createRootLogEntry } from '../src/utils';
3
- import { Details, Method, RootPayload } from '../src/types';
4
-
5
- describe('prettyStack', () => {
6
- it('should return empty array for undefined input', () => {
7
- const result = prettyStack(undefined);
8
- expect(result).toEqual([]);
9
- });
10
-
11
- it('should return empty array for null input', () => {
12
- const result = prettyStack(null as unknown as string);
13
- expect(result).toEqual([]);
14
- });
15
-
16
- it('should return empty array for empty string input', () => {
17
- const result = prettyStack('');
18
- expect(result).toEqual([]);
19
- });
20
-
21
- it('should filter out lines starting with "Error: "', () => {
22
- const stack = `Error: Test error
23
- at Object.test (/path/to/file.js:1:1)
24
- at Object.test2 (/path/to/file.js:2:2)`;
25
-
26
- const result = prettyStack(stack);
27
- expect(result.join('\n')).not.toContain('Error: Test error');
28
- expect(result).toContain('at Object.test (/path/to/file.js:1:1)');
29
- expect(result).toContain('at Object.test2 (/path/to/file.js:2:2)');
30
- });
31
-
32
- it('should trim whitespace from each line', () => {
33
- const stack = ` at Object.test (/path/to/file.js:1:1)
34
- at Object.test2 (/path/to/file.js:2:2)`;
35
-
36
- const result = prettyStack(stack);
37
- expect(result).toContain('at Object.test (/path/to/file.js:1:1)');
38
- expect(result).toContain('at Object.test2 (/path/to/file.js:2:2)');
39
- });
40
-
41
- it('should remove file:// prefix from lines', () => {
42
- const stack = `at Object.test (file:///path/to/file.js:1:1)
43
- at Object.test2 (file:///another/path.js:2:2)`;
44
-
45
- const result = prettyStack(stack);
46
-
47
- expect(result).toEqual(
48
- expect.arrayContaining([
49
- 'at Object.test (/path/to/file.js:1:1)',
50
- 'at Object.test2 (/another/path.js:2:2)',
51
- ])
52
- );
53
- expect(result.join('\n')).not.toContain('file://');
54
- });
55
-
56
- it('should replace .wrangler paths in stack traces', () => {
57
- const stack = `at Object.test (file:///some/path/.wrangler/test/file.js:1:1)
58
- at Object.test2 (file:///another/path/file.js:2:2)`;
59
-
60
- const result = prettyStack(stack);
61
- // Based on actual behavior, it keeps the .wrangler prefix
62
- expect(result).toEqual(
63
- expect.arrayContaining([
64
- 'at Object.test (/.wrangler/test/file.js:1:1)',
65
- 'at Object.test2 (/another/path/file.js:2:2)',
66
- ])
67
- );
68
- });
69
-
70
- it('should replace node_modules paths in stack traces', () => {
71
- const stack = `at Object.test (file:///some/path/node_modules/module/file.js:1:1)
72
- at Object.test2 (file:///another/path/file.js:2:2)`;
73
-
74
- const result = prettyStack(stack);
75
- // Based on actual behavior, it keeps the /node_modules prefix
76
- expect(result).toEqual(
77
- expect.arrayContaining([
78
- 'at Object.test (/node_modules/module/file.js:1:1)',
79
- 'at Object.test2 (/another/path/file.js:2:2)',
80
- ])
81
- );
82
- });
83
-
84
- it('should handle complex stack traces with mixed paths', () => {
85
- const stack = `Error: Complex error
86
- at Object.test1 (file:///project/.wrangler/test.js:1:1)
87
- at Object.test2 (file:///project/node_modules/module/index.js:2:2)
88
- at Object.test3 (file:///normal/path/file.js:3:3)`;
89
-
90
- const result = prettyStack(stack);
91
-
92
- expect(result).toEqual(
93
- expect.arrayContaining([
94
- 'at Object.test1 (/.wrangler/test.js:1:1)',
95
- 'at Object.test2 (/node_modules/module/index.js:2:2)',
96
- 'at Object.test3 (/normal/path/file.js:3:3)',
97
- ])
98
- );
99
- expect(result.join('\n')).not.toContain('file://');
100
- });
101
-
102
- it('should handle stack traces without matching regex pattern', () => {
103
- const stack = `at Object.test (file:///simple/path/file.js:1:1)
104
- at Object.test2 (file:///another/path/file.js:2:2)`;
105
-
106
- const result = prettyStack(stack);
107
- expect(result).toEqual(
108
- expect.arrayContaining([
109
- 'at Object.test (/simple/path/file.js:1:1)',
110
- 'at Object.test2 (/another/path/file.js:2:2)',
111
- ])
112
- );
113
- });
114
-
115
- it('should preserve order of stack lines', () => {
116
- const stack = `Error: Test error
117
- at Object.first (/path/first.js:1:1)
118
- at Object.second (/path/second.js:2:2)
119
- at Object.third (/path/third.js:3:3)`;
120
-
121
- const result = prettyStack(stack);
122
- expect(result[0]).toContain('at Object.first (/path/first.js:1:1)');
123
- expect(result[1]).toContain('at Object.second (/path/second.js:2:2)');
124
- expect(result[2]).toContain('at Object.third (/path/third.js:3:3)');
125
- });
126
- });
127
-
128
- describe('prettyError', () => {
129
- it('should extract message and stack from standard Error', () => {
130
- const errorMessage = 'Test error message';
131
- const stackMessage =
132
- 'Error: Test error message\n at Object.test (/path/to/file.js:1:1)';
133
-
134
- const error = new Error(errorMessage);
135
- error.stack = stackMessage;
136
-
137
- const result = prettyError(error);
138
-
139
- expect(result).toHaveProperty('message', errorMessage);
140
- expect(result).toHaveProperty('stack');
141
- expect(Array.isArray(result.stack)).toBe(true);
142
- });
143
-
144
- it('should handle Error without stack', () => {
145
- const error = new Error('Test error');
146
- delete (error as Error & { stack?: string }).stack;
147
-
148
- const result = prettyError(error);
149
-
150
- expect(result.message).toBe('Test error');
151
- expect(result.stack).toEqual([]);
152
- });
153
-
154
- it('should handle Error with empty message', () => {
155
- const error = new Error('');
156
- error.stack = 'Error: \n at Object.test (/path/to/file.js:1:1)';
157
-
158
- const result = prettyError(error);
159
-
160
- expect(result.message).toBe('');
161
- expect(result.stack).toBeDefined();
162
- });
163
-
164
- it('should handle custom Error classes', () => {
165
- class CustomError extends Error {
166
- constructor(message: string) {
167
- super(message);
168
- this.name = 'CustomError';
169
- }
170
- }
171
-
172
- const error = new CustomError('Custom error message');
173
- error.stack =
174
- 'CustomError: Custom error message\n at Object.test (/path/to/file.js:1:1)';
175
-
176
- const result = prettyError(error);
177
-
178
- expect(result.message).toBe('Custom error message');
179
- expect(result.stack).toBeDefined();
180
- });
181
-
182
- it('should process the stack through prettyStack', () => {
183
- const error = new Error('Test error');
184
- error.stack = `Error: Test error
185
- at Object.test (file:///path/.wrangler/file.js:1:1)`;
186
-
187
- const result = prettyError(error);
188
-
189
- const stackString = result.stack.join('\n');
190
- expect(stackString).not.toContain('file://');
191
- expect(stackString).toContain('file.js:1:1');
192
- expect(stackString).not.toContain('Error: Test error');
193
- });
194
- });
195
-
196
- describe('createRootLogEntry', () => {
197
- const mockDetails: Details = { service: 'test-service', version: '1.0.0' };
198
- const mockEventId = 'test-event-123';
199
-
200
- it('should create basic root log entry with required fields', () => {
201
- const options = {
202
- path: '/api/test',
203
- method: 'get' as Method,
204
- details: mockDetails,
205
- eventId: mockEventId,
206
- };
207
-
208
- const result = createRootLogEntry(options);
209
-
210
- expect(result).toHaveProperty('root');
211
- expect(Array.isArray(result.root)).toBe(true);
212
- expect(result.root).toHaveLength(1);
213
-
214
- const entry = result.root[0];
215
- expect(entry.type).toBe('info');
216
- expect(entry.message).toBe('Request received');
217
- expect(entry.payload).toBeDefined();
218
- expect(entry.timestamp).toBeDefined();
219
- expect(typeof entry.timestamp).toBe('number');
220
- });
221
-
222
- it('should handle path correctly', () => {
223
- const options = {
224
- path: '/api/users/123',
225
- method: 'get' as Method,
226
- details: mockDetails,
227
- eventId: mockEventId,
228
- };
229
-
230
- const result = createRootLogEntry(options);
231
- const payload = result.root[0].payload as RootPayload;
232
-
233
- expect(payload.path).toBe('/api/users/123');
234
- });
235
-
236
- it('should handle method correctly', () => {
237
- const options = {
238
- path: '/api/test',
239
- method: 'post' as Method,
240
- details: mockDetails,
241
- eventId: mockEventId,
242
- };
243
-
244
- const result = createRootLogEntry(options);
245
- const payload = result.root[0].payload as RootPayload;
246
-
247
- expect(payload.path).toBe('/api/test');
248
- expect(payload.method).toBe('post');
249
- });
250
-
251
- it('should include parentEventId when provided', () => {
252
- const parentEventId = 'parent-event-456';
253
- const options = {
254
- path: '/api/test',
255
- method: 'put' as Method,
256
- details: mockDetails,
257
- eventId: mockEventId,
258
- parentEventId,
259
- };
260
-
261
- const result = createRootLogEntry(options);
262
- const payload = result.root[0].payload as RootPayload;
263
-
264
- expect(payload.parentEventId).toBe(parentEventId);
265
- });
266
-
267
- it('should not include parentEventId when not provided', () => {
268
- const options = {
269
- path: '/api/test',
270
- method: 'delete' as Method,
271
- details: mockDetails,
272
- eventId: mockEventId,
273
- };
274
-
275
- const result = createRootLogEntry(options);
276
- const payload = result.root[0].payload as RootPayload;
277
-
278
- expect(payload.parentEventId).toBeUndefined();
279
- });
280
-
281
- it('should add error log when withParentEventId is true but no parentEventId provided', () => {
282
- const options = {
283
- path: '/api/test',
284
- method: 'patch' as Method,
285
- details: mockDetails,
286
- eventId: mockEventId,
287
- withParentEventId: true,
288
- };
289
-
290
- const result = createRootLogEntry(options);
291
-
292
- expect(result.root).toHaveLength(2);
293
-
294
- const infoEntry = result.root[0];
295
- const errorEntry = result.root[1];
296
-
297
- expect(infoEntry.type).toBe('info');
298
- expect(infoEntry.message).toBe('Request received');
299
-
300
- expect(errorEntry.type).toBe('error');
301
- expect(errorEntry.message).toBe('Parent event ID expected but not found');
302
- expect(errorEntry.payload).toBeUndefined();
303
- });
304
-
305
- it('should not add error log when withParentEventId is true and parentEventId is provided', () => {
306
- const options = {
307
- path: '/api/test',
308
- method: 'head' as Method,
309
- details: mockDetails,
310
- eventId: mockEventId,
311
- parentEventId: 'parent-123',
312
- withParentEventId: true,
313
- };
314
-
315
- const result = createRootLogEntry(options);
316
-
317
- expect(result.root).toHaveLength(1);
318
- expect(result.root[0].type).toBe('info');
319
- });
320
-
321
- it('should not add error log when withParentEventId is false', () => {
322
- const options = {
323
- path: '/api/test',
324
- method: 'options' as Method,
325
- details: mockDetails,
326
- eventId: mockEventId,
327
- withParentEventId: false,
328
- };
329
-
330
- const result = createRootLogEntry(options);
331
-
332
- expect(result.root).toHaveLength(1);
333
- expect(result.root[0].type).toBe('info');
334
- });
335
-
336
- it('should handle all HTTP methods', () => {
337
- const methods: Method[] = [
338
- 'get',
339
- 'post',
340
- 'put',
341
- 'delete',
342
- 'patch',
343
- 'head',
344
- 'options',
345
- 'trace',
346
- 'connect',
347
- ];
348
-
349
- methods.forEach((method) => {
350
- const options = {
351
- path: '/api/test',
352
- method,
353
- details: mockDetails,
354
- eventId: mockEventId,
355
- };
356
-
357
- const result = createRootLogEntry(options);
358
- const payload = result.root[0].payload as RootPayload;
359
-
360
- expect(payload.method).toBe(method);
361
- });
362
- });
363
-
364
- it('should preserve complex details object', () => {
365
- const complexDetails: Details = {
366
- service: 'api-gateway',
367
- version: '2.1.0',
368
- environment: 'production',
369
- region: 'us-east-1',
370
- userId: 12345,
371
- metadata: { feature: 'logging', enabled: true },
372
- };
373
-
374
- const options = {
375
- path: '/api/test',
376
- method: 'get' as Method,
377
- details: complexDetails,
378
- eventId: mockEventId,
379
- };
380
-
381
- const result = createRootLogEntry(options);
382
- const payload = result.root[0].payload as RootPayload;
383
-
384
- expect(payload.details).toEqual(complexDetails);
385
- });
386
-
387
- it('should generate reasonable timestamps', () => {
388
- const beforeTime = Date.now();
389
-
390
- const options = {
391
- path: '/api/test',
392
- method: 'get' as Method,
393
- details: mockDetails,
394
- eventId: mockEventId,
395
- };
396
-
397
- const result = createRootLogEntry(options);
398
- const afterTime = Date.now();
399
-
400
- const timestamp = result.root[0].timestamp;
401
-
402
- expect(timestamp).toBeGreaterThanOrEqual(beforeTime);
403
- expect(timestamp).toBeLessThanOrEqual(afterTime);
404
- });
405
-
406
- it('should handle different paths', () => {
407
- const options = {
408
- path: '/api/users',
409
- method: 'get' as Method,
410
- details: mockDetails,
411
- eventId: mockEventId,
412
- };
413
-
414
- const result = createRootLogEntry(options);
415
- const payload = result.root[0].payload as RootPayload;
416
-
417
- expect(payload.path).toBe('/api/users');
418
- });
419
-
420
- it('should handle paths with different segments', () => {
421
- const options = {
422
- path: '/users?page=2&limit=10',
423
- method: 'get' as Method,
424
- details: mockDetails,
425
- eventId: mockEventId,
426
- };
427
-
428
- const result = createRootLogEntry(options);
429
- const payload = result.root[0].payload as RootPayload;
430
-
431
- // Based on actual behavior, the full path including query params is preserved
432
- expect(payload.path).toBe('/users?page=2&limit=10');
433
- });
434
-
435
- it('should maintain correct payload structure type', () => {
436
- const options = {
437
- path: '/api/test',
438
- method: 'get' as Method,
439
- details: mockDetails,
440
- eventId: mockEventId,
441
- parentEventId: 'parent-123',
442
- };
443
-
444
- const result = createRootLogEntry(options);
445
- const payload = result.root[0].payload as RootPayload;
446
-
447
- expect(payload).toHaveProperty('path');
448
- expect(payload).toHaveProperty('method');
449
- expect(payload).toHaveProperty('details');
450
- expect(payload).toHaveProperty('eventId');
451
- expect(payload).toHaveProperty('parentEventId');
452
- });
453
- });