@jacraig/woodchuck 1.2.36 → 1.3.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
@@ -7,13 +7,9 @@ WoodChuck is a versatile logging library for TypeScript/JavaScript that simplifi
7
7
  ## Features
8
8
 
9
9
  - **Easy Integration**: Simple setup for quick integration into your projects.
10
-
11
10
  - **Customizable Logging Levels**: Define and use different logging levels to categorize and filter messages.
12
-
13
11
  - **Extensible Plugins**: Extend functionality with plugins for various output formats and destinations.
14
-
15
12
  - **Structured Logging**: Log structured event data to make it easier to analyze and understand.
16
-
17
13
  - **Flexible Configuration**: Configure the logger with a fluent interface to customize the logging experience.
18
14
 
19
15
  ## Installation
@@ -27,58 +23,116 @@ npm i @jacraig/woodchuck
27
23
  1. Configure the logger with a sink to output to.
28
24
 
29
25
  ```typescript
26
+ import { Logger, ConsoleSink } from "@jacraig/woodchuck";
30
27
 
31
- import { Logger, ConsoleSink } from '@jacraig/woodchuck';
28
+ Logger.configure().minimumLevel("Information").writeTo(new ConsoleSink());
29
+ ```
32
30
 
33
- Logger.configure()
34
- .minimumLevel("Information")
35
- .writeTo(new ConsoleSink())
36
- .build();
31
+ 2. Log messages with different levels:
37
32
 
33
+ ```typescript
34
+ Logger.verbose("This is a verbose message: {key}", { key: "value" });
35
+ Logger.debug("This is a debug message: {key}", { key: "value" });
36
+ Logger.information("This is an information message: {key}", { key: "value" });
37
+ Logger.warning("This is a warning message: {key}", { key: "value" });
38
+ Logger.error(
39
+ "This is an error message: {key}",
40
+ { key: "value" },
41
+ new Error("This is an error")
42
+ );
43
+ Logger.fatal(
44
+ "This is a fatal message: {key}",
45
+ { key: "value" },
46
+ new Error("This is a fatal error")
47
+ );
38
48
  ```
39
49
 
40
- 2. Log messages with different levels:
50
+ 3. Customize the logger with plugins:
41
51
 
42
52
  ```typescript
53
+ Logger.configure()
54
+ .enrichWith(new UserAgentEnricher())
55
+ .enrichWith(new UrlEnricher())
56
+ .enrichWith(new CallerEnricher())
57
+ .formatUsing(new DefaultFormatter())
58
+ .minimumLevel("Information")
59
+ .writeTo(new ConsoleSink());
60
+ ```
43
61
 
44
- Logger.verbose("This is a verbose message: {key}", { "key": "value" });
45
- Logger.debug("This is a debug message: {key}", { "key": "value" });
46
- Logger.information("This is an information message: {key}", { "key": "value" });
47
- Logger.warning("This is a warning message: {key}", { "key": "value" });
48
- Logger.error("This is an error message: {key}", { "key": "value" }, new Error("This is an error"));
49
- Logger.fatal("This is a fatal message: {key}", { "key": "value" }, new Error("This is a fatal error"));
62
+ 4. Or build your own plugins:
63
+
64
+ ```typescript
65
+ import { LogEventEnricher, LogEvent } from "@jacraig/woodchuck";
50
66
 
67
+ export class MyCustomPlugin implements LogEventEnricher {
68
+ public enrich(logEvent: LogEvent): void {
69
+ logEvent.properties["myProperty"] =
70
+ "Something, something, something, dark side";
71
+ }
72
+ }
51
73
  ```
52
74
 
53
- 3. Customize the logger with plugins:
75
+ # Additional Usage Examples
76
+
77
+ ## Multiple Sinks
54
78
 
55
79
  ```typescript
80
+ import {
81
+ Logger,
82
+ ConsoleSink,
83
+ BatchedSink,
84
+ BatchedSinkOptions,
85
+ } from "@jacraig/woodchuck";
56
86
 
57
87
  Logger.configure()
58
- .enrichWith(new UserAgentEnricher())
59
- .enrichWith(new UrlEnricher())
60
- .enrichWith(new CallerEnricher())
61
- .formatUsing(new DefaultFormatter())
62
- .minimumLevel("Information")
63
- .writeTo(new ConsoleSink());
64
-
88
+ .minimumLevel("Debug")
89
+ .writeTo(new ConsoleSink())
90
+ .writeTo(new BatchedSink(new ConsoleSink(), new BatchedSinkOptions()));
65
91
  ```
66
- 4. Or build your own plugins:
92
+
93
+ ## Custom Enricher
67
94
 
68
95
  ```typescript
96
+ import { Logger, LogEventEnricher } from "@jacraig/woodchuck";
69
97
 
70
- import { LogEventEnricher, LogEvent } from '@jacraig/woodchuck';
98
+ class MyEnricher implements LogEventEnricher {
99
+ enrich(event) {
100
+ event.properties["custom"] = "myValue";
101
+ }
102
+ }
71
103
 
72
- export class MyCustomPlugin implements LogEventEnricher {
73
- public enrich(logEvent: LogEvent): void {
74
- logEvent.properties["myProperty"] = "Something, something, something, dark side";
75
- }
104
+ Logger.configure().enrichWith(new MyEnricher()).writeTo(new ConsoleSink());
105
+
106
+ Logger.debug("With custom property");
107
+ ```
108
+
109
+ ## Custom Filter
110
+
111
+ ```typescript
112
+ import { Logger, LogFilter } from "@jacraig/woodchuck";
113
+
114
+ class OnlyErrorsFilter implements LogFilter {
115
+ filter(event) {
116
+ return event.level === "Error";
117
+ }
76
118
  }
77
119
 
120
+ Logger.configure().filter(new OnlyErrorsFilter()).writeTo(new ConsoleSink());
121
+
122
+ Logger.error("This will be logged");
123
+ Logger.information("This will NOT be logged");
124
+ ```
125
+
126
+ ## Logging with Custom Properties
127
+
128
+ ```typescript
129
+ Logger.information("User logged in", { userId: 123, userName: "alice" });
78
130
  ```
79
131
 
80
132
  # Contributing
133
+
81
134
  If you'd like to contribute to WoodChuck, please follow our [contribution guidelines](https://github.com/JaCraig/Woodchuck/blob/main/CONTRIBUTING.md).
82
135
 
83
136
  # License
84
- WoodChuck is licensed under the [Apache 2 License](https://github.com/JaCraig/Woodchuck/blob/main/LICENSE).
137
+
138
+ WoodChuck is licensed under the [Apache 2 License](https://github.com/JaCraig/Woodchuck/blob/main/LICENSE).
@@ -1 +1 @@
1
- {"version":3,"file":"BatchedSink.d.ts","sourceRoot":"","sources":["../src/BatchedSink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,qBAAa,WAAY,YAAW,OAAO;gBAEpB,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB;IAkB7D,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,MAAM,CAAkB;IAGhC,OAAO,CAAC,IAAI,CAAU;IAGtB,OAAO,CAAC,OAAO,CAAqB;IAGpC,OAAO,CAAC,KAAK,CAAS;IAGtB,OAAO,CAAC,KAAK;IAYN,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAOtC"}
1
+ {"version":3,"file":"BatchedSink.d.ts","sourceRoot":"","sources":["../src/BatchedSink.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,qBAAa,WAAY,YAAW,OAAO;gBAEtB,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB;IAoB7D,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,MAAM,CAAkB;IAGhC,OAAO,CAAC,IAAI,CAAU;IAGtB,OAAO,CAAC,OAAO,CAAqB;IAGpC,OAAO,CAAC,KAAK,CAAgC;IAG7C,OAAO,CAAC,KAAK;IAiBN,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAepC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BatchedSinkOptions.d.ts","sourceRoot":"","sources":["../src/BatchedSinkOptions.ts"],"names":[],"mappings":"AACA,qBAAa,kBAAkB;IAEpB,YAAY,EAAE,MAAM,CAAM;IAG1B,WAAW,EAAE,MAAM,CAAO;IAG1B,OAAO,EAAE,OAAO,CAAgB;CAC1C"}
1
+ {"version":3,"file":"BatchedSinkOptions.d.ts","sourceRoot":"","sources":["../src/BatchedSinkOptions.ts"],"names":[],"mappings":"AAYA,qBAAa,kBAAkB;IAEtB,YAAY,EAAE,MAAM,CAAM;IAG1B,WAAW,EAAE,MAAM,CAAO;IAG1B,OAAO,EAAE,OAAO,CAGK;CAC7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConsoleSink.d.ts","sourceRoot":"","sources":["../src/ConsoleSink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,qBAAa,WAAY,YAAW,OAAO;IACvC,OAAO,CAAC,MAAM,CAOZ;IACF,OAAO,CAAC,cAAc,CAOpB;IAGK,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CActC"}
1
+ {"version":3,"file":"ConsoleSink.d.ts","sourceRoot":"","sources":["../src/ConsoleSink.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,qBAAa,WAAY,YAAW,OAAO;IACzC,OAAO,CAAC,MAAM,CAOZ;IACF,OAAO,CAAC,cAAc,CAmBpB;IAGK,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAwCpC"}
@@ -4,5 +4,6 @@ export declare class DefaultFormatter implements OutputFormatter {
4
4
  constructor(outputFormat?: string);
5
5
  private outputFormat;
6
6
  format(event: LogEvent): string;
7
+ private static formatRegex;
7
8
  }
8
9
  //# sourceMappingURL=DefaultFormatter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DefaultFormatter.d.ts","sourceRoot":"","sources":["../src/DefaultFormatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,qBAAa,gBAAiB,YAAW,eAAe;gBAUxC,YAAY,CAAC,EAAE,MAAM;IAKjC,OAAO,CAAC,YAAY,CAAS;IAKtB,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM;CAiBzC"}
1
+ {"version":3,"file":"DefaultFormatter.d.ts","sourceRoot":"","sources":["../src/DefaultFormatter.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,qBAAa,gBAAiB,YAAW,eAAe;gBAU1C,YAAY,CAAC,EAAE,MAAM;IAMjC,OAAO,CAAC,YAAY,CAAS;IAKtB,MAAM,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM;IAyBtC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAc;CACzC"}
@@ -0,0 +1,10 @@
1
+ export declare class InMemoryStorage implements Storage {
2
+ private store;
3
+ get length(): number;
4
+ clear(): void;
5
+ getItem(key: string): string | null;
6
+ key(index: number): string | null;
7
+ removeItem(key: string): void;
8
+ setItem(key: string, value: string): void;
9
+ }
10
+ //# sourceMappingURL=InMemoryStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InMemoryStorage.d.ts","sourceRoot":"","sources":["../src/InMemoryStorage.ts"],"names":[],"mappings":"AAWA,qBAAa,eAAgB,YAAW,OAAO;IAI7C,OAAO,CAAC,KAAK,CAA8B;IAK3C,IAAI,MAAM,IAAI,MAAM,CAEnB;IAKD,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IASnC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAQjC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAS7B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAG1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"LogSink.d.ts","sourceRoot":"","sources":["../src/LogSink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,OAAO;IAGpB,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CAChC"}
1
+ {"version":3,"file":"LogSink.d.ts","sourceRoot":"","sources":["../src/LogSink.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,MAAM,WAAW,OAAO;IAGtB,KAAK,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC9B"}
@@ -2,9 +2,9 @@ import { LogEvent } from "./LogEvent";
2
2
  import { LogEventEnricher } from "./LogEventEnricher";
3
3
  import { LogFilter } from "./LogFilter";
4
4
  import { LogLevel } from "./LogLevel";
5
- import { OutputFormatter } from "./OutputFormatter";
6
5
  import { LogSink } from "./LogSink";
7
6
  import { LoggerConfiguration } from "./LoggerConfiguration";
7
+ import { OutputFormatter } from "./OutputFormatter";
8
8
  export declare class LogSinkPipeline {
9
9
  constructor(loggerConfiguration: LoggerConfiguration);
10
10
  private sink;
@@ -1 +1 @@
1
- {"version":3,"file":"LogSinkPipeline.d.ts","sourceRoot":"","sources":["../src/LogSinkPipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAI5D,qBAAa,eAAe;gBAGZ,mBAAmB,EAAE,mBAAmB;IAIpD,OAAO,CAAC,IAAI,CAA8B;IAE1C,OAAO,CAAC,OAAO,CAAmB;IAElC,OAAO,CAAC,SAAS,CAA2C;IAE5D,OAAO,CAAC,SAAS,CAA0B;IAE3C,OAAO,CAAC,mBAAmB,CAAsB;IAK1C,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB;IAQ3C,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,eAAe;IAQ9C,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe;IAQ1C,WAAW,CAAC,SAAS,EAAE,eAAe,GAAG,eAAe;IAQxD,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,eAAe;IAOvD,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAWxC"}
1
+ {"version":3,"file":"LogSinkPipeline.d.ts","sourceRoot":"","sources":["../src/LogSinkPipeline.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,qBAAa,eAAe;gBAGd,mBAAmB,EAAE,mBAAmB;IAIpD,OAAO,CAAC,IAAI,CAA8B;IAE1C,OAAO,CAAC,OAAO,CAAmB;IAElC,OAAO,CAAC,SAAS,CAA2C;IAE5D,OAAO,CAAC,SAAS,CAA0B;IAE3C,OAAO,CAAC,mBAAmB,CAAsB;IAK1C,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB;IAQ3C,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,eAAe;IAQ9C,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,eAAe;IAQ1C,WAAW,CAAC,SAAS,EAAE,eAAe,GAAG,eAAe;IAQxD,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,eAAe;IAOvD,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAgBtC"}
package/lib/Logger.cjs.js CHANGED
@@ -1,67 +1,221 @@
1
1
  'use strict';
2
2
 
3
+ class BatchedSink {
4
+ constructor(sink, options) {
5
+ this.buffer = [];
6
+ this.sink = sink;
7
+ this.options = options;
8
+ this.timer = setInterval(() => this.flush(), this.options.maxWaitTime);
9
+ this.buffer = (JSON.parse(this.options.storage.getItem("logBuffer") || "[]"));
10
+ if (this.isBrowser()) {
11
+ window.addEventListener("beforeunload", () => this.flush());
12
+ document.addEventListener("visibilitychange", () => {
13
+ if (document.visibilityState !== "hidden") {
14
+ return;
15
+ }
16
+ this.flush();
17
+ });
18
+ }
19
+ }
20
+ isBrowser() {
21
+ return typeof window !== "undefined";
22
+ }
23
+ flush() {
24
+ if (this.buffer.length == 0) {
25
+ return;
26
+ }
27
+ for (let event of this.buffer) {
28
+ try {
29
+ this.sink.write(event);
30
+ }
31
+ catch (err) {
32
+ console.error("BatchedSink: Failed to write event to sink", err, event);
33
+ }
34
+ }
35
+ this.buffer = [];
36
+ }
37
+ write(event) {
38
+ try {
39
+ this.buffer.push(event);
40
+ this.options.storage.setItem("logBuffer", JSON.stringify(this.buffer));
41
+ if (this.buffer.length >= this.options.maxBatchSize) {
42
+ this.flush();
43
+ }
44
+ }
45
+ catch (err) {
46
+ console.error("BatchedSink: Failed to buffer or persist event", err, event);
47
+ }
48
+ }
49
+ }
50
+
51
+ class InMemoryStorage {
52
+ constructor() {
53
+ this.store = {};
54
+ }
55
+ get length() {
56
+ return Object.keys(this.store).length;
57
+ }
58
+ clear() {
59
+ this.store = {};
60
+ }
61
+ getItem(key) {
62
+ return this.store[key] ?? null;
63
+ }
64
+ key(index) {
65
+ return Object.keys(this.store)[index] ?? null;
66
+ }
67
+ removeItem(key) {
68
+ delete this.store[key];
69
+ }
70
+ setItem(key, value) {
71
+ this.store[key] = value;
72
+ }
73
+ }
74
+
75
+ class BatchedSinkOptions {
76
+ constructor() {
77
+ this.maxBatchSize = 10;
78
+ this.maxWaitTime = 500;
79
+ this.storage = typeof window !== "undefined" && window.localStorage
80
+ ? window.localStorage
81
+ : new InMemoryStorage();
82
+ }
83
+ }
84
+
85
+ class CallerEnricher {
86
+ enrich(event) {
87
+ event.properties ??= {};
88
+ let callerUrl = this.matchAll(new Error().stack?.toString() ?? "", /at([\s\w\d\.$]+)[\(]?((http|https|ftp)[^\)\n]+)[\)]?/gi).map(match => match[2].trim());
89
+ event.properties.caller = callerUrl[callerUrl.length - 1];
90
+ }
91
+ matchAll(str, regexp) {
92
+ const flags = regexp.global ? regexp.flags : regexp.flags + "g";
93
+ const re = new RegExp(regexp, flags);
94
+ let matches = [];
95
+ let match;
96
+ while (match = re.exec(str)) {
97
+ matches.push(match);
98
+ }
99
+ return matches;
100
+ }
101
+ }
102
+
3
103
  class ConsoleSink {
4
104
  constructor() {
5
105
  this.styles = {
6
- "Verbose": "color: white;",
7
- "Debug": "color: green",
8
- "Information": "color: blue",
9
- "Warning": "color: yellow",
10
- "Error": "color: red",
11
- "Fatal": "color: palevioletred"
106
+ Verbose: "color: white;",
107
+ Debug: "color: green",
108
+ Information: "color: blue",
109
+ Warning: "color: yellow",
110
+ Error: "color: red",
111
+ Fatal: "color: palevioletred",
12
112
  };
13
113
  this.consoleMethods = {
14
- "Verbose": (message, style, args) => { console.log(message, style, args); },
15
- "Debug": (message, style, args) => { console.debug(message, style, args); },
16
- "Information": (message, style, args) => { console.info(message, style, args); },
17
- "Warning": (message, style, args) => { console.warn(message, style, args); },
18
- "Error": (message, style, args) => { console.error(message, style, args); },
19
- "Fatal": (message, style, args) => { console.error(message, style, args); }
114
+ Verbose: (message, style, args) => {
115
+ console.log(message, style, args);
116
+ },
117
+ Debug: (message, style, args) => {
118
+ console.debug(message, style, args);
119
+ },
120
+ Information: (message, style, args) => {
121
+ console.info(message, style, args);
122
+ },
123
+ Warning: (message, style, args) => {
124
+ console.warn(message, style, args);
125
+ },
126
+ Error: (message, style, args) => {
127
+ console.error(message, style, args);
128
+ },
129
+ Fatal: (message, style, args) => {
130
+ console.error(message, style, args);
131
+ },
20
132
  };
21
133
  }
22
134
  write(event) {
23
- let displayInlineArgs = (event.args && (typeof event.args != "object"));
24
- let displayTableArgs = (event.args && (typeof event.args == "object"));
25
- if (displayTableArgs) {
26
- let shortenedMessage = event.message.length > 200 ? event.message.substring(0, 200) + "..." : event.message;
27
- shortenedMessage = shortenedMessage.replace(/(\r\n|\n|\r)/gm, " ");
28
- console.groupCollapsed(shortenedMessage);
135
+ try {
136
+ let displayInlineArgs = event.args && typeof event.args != "object";
137
+ let displayTableArgs = event.args && typeof event.args == "object";
138
+ if (displayTableArgs) {
139
+ let shortenedMessage = event.message.length > 200
140
+ ? event.message.substring(0, 200) + "..."
141
+ : event.message;
142
+ shortenedMessage = shortenedMessage.replace(/(\r\n|\n|\r)/gm, " ");
143
+ if (typeof console.groupCollapsed === "function") {
144
+ console.groupCollapsed(shortenedMessage);
145
+ }
146
+ }
147
+ const method = this.consoleMethods[event.level] ||
148
+ ((msg, style, args) => {
149
+ if (typeof console.log === "function") {
150
+ console.log(msg, style, args);
151
+ }
152
+ });
153
+ const isBrowser = typeof window !== "undefined" &&
154
+ typeof window.navigator !== "undefined";
155
+ const style = isBrowser ? this.styles[event.level] : "";
156
+ method("%c" + event.message, style, displayInlineArgs ? event.args : "");
157
+ if (displayTableArgs && typeof console.table === "function") {
158
+ console.table(event.args);
159
+ if (typeof console.groupEnd === "function") {
160
+ console.groupEnd();
161
+ }
162
+ }
29
163
  }
30
- this.consoleMethods[event.level]("%c" + event.message, this.styles[event.level], displayInlineArgs ? event.args : "");
31
- if (displayTableArgs) {
32
- console.table(event.args);
33
- console.groupEnd();
164
+ catch (err) {
165
+ if (typeof console.error === "function") {
166
+ console.error("ConsoleSink: Failed to write log event", err, event);
167
+ }
34
168
  }
35
169
  }
36
170
  }
37
171
 
172
+ class CustomEnricher {
173
+ constructor(propertyName, methodCall) {
174
+ this.propertyName = propertyName;
175
+ this.methodCall = methodCall;
176
+ }
177
+ enrich(event) {
178
+ event.properties ??= {};
179
+ event.properties[this.propertyName] = this.methodCall(event);
180
+ }
181
+ }
182
+
38
183
  class DefaultFormatter {
39
184
  constructor(outputFormat) {
40
- this.outputFormat = outputFormat ?? "[{Timestamp}]\t[{Level}]\t{Message}{Exception}";
185
+ this.outputFormat =
186
+ outputFormat ?? "[{Timestamp}]\t[{Level}]\t{Message}{Exception}";
41
187
  }
42
188
  format(event) {
43
- return this.outputFormat.replace(/{(\w+)}/g, (match, propertyName) => {
44
- if (propertyName === "Id") {
45
- return event.id;
46
- }
47
- else if (propertyName === "Timestamp") {
48
- return event.timestamp.toISOString();
49
- }
50
- else if (propertyName === "Level") {
51
- return event.level;
52
- }
53
- else if (propertyName === "Message") {
54
- return event.message;
55
- }
56
- else if (propertyName === "Exception") {
57
- return event.exception ? "\n" + event.exception.stack : "";
58
- }
59
- else {
60
- return event.properties[propertyName];
189
+ const formatRegex = DefaultFormatter.formatRegex;
190
+ return this.outputFormat.replace(formatRegex, (match, propertyName) => {
191
+ switch (propertyName) {
192
+ case "Id":
193
+ return event.id;
194
+ case "Timestamp":
195
+ return event.timestamp.toISOString();
196
+ case "Level":
197
+ return event.level;
198
+ case "Message":
199
+ return event.message;
200
+ case "Exception":
201
+ return event.exception ? "\n" + event.exception.stack : "";
202
+ default:
203
+ return event.properties &&
204
+ event.properties[propertyName] !== undefined
205
+ ? String(event.properties[propertyName])
206
+ : "";
61
207
  }
62
208
  });
63
209
  }
64
210
  }
211
+ DefaultFormatter.formatRegex = /{(\w+)}/g;
212
+
213
+ class LanguageEnricher {
214
+ enrich(event) {
215
+ event.properties ??= {};
216
+ event.properties.language = navigator.language;
217
+ }
218
+ }
65
219
 
66
220
  class MinimumLevelLogFilter {
67
221
  constructor(minimumLevel = "Debug") {
@@ -103,15 +257,20 @@ class LogSinkPipeline {
103
257
  return this;
104
258
  }
105
259
  process(event) {
106
- this.formatter ??= new DefaultFormatter();
107
- this.sink ??= new ConsoleSink();
108
- let eventCopy = Object.assign({}, event);
109
- if (!this.filters.some(filter => filter.filter(eventCopy))) {
110
- return;
260
+ try {
261
+ this.formatter ??= new DefaultFormatter();
262
+ this.sink ??= new ConsoleSink();
263
+ let eventCopy = Object.assign({}, event);
264
+ if (!this.filters.every((filter) => filter.filter(eventCopy))) {
265
+ return;
266
+ }
267
+ this.enrichers.forEach((enricher) => enricher.enrich(eventCopy));
268
+ eventCopy.message = this.formatter.format(eventCopy) || eventCopy.message;
269
+ this.sink.write(eventCopy);
270
+ }
271
+ catch (err) {
272
+ console.error("LogSinkPipeline: Failed to process log event", err, event);
111
273
  }
112
- this.enrichers.forEach(enricher => enricher.enrich(eventCopy));
113
- eventCopy.message = this.formatter.format(eventCopy) || eventCopy.message;
114
- this.sink.write(eventCopy);
115
274
  }
116
275
  }
117
276
 
@@ -152,44 +311,28 @@ class LoggerConfiguration {
152
311
  exception: exception,
153
312
  timestamp: new Date(),
154
313
  id: this.generateId(),
155
- args: properties
314
+ args: properties,
156
315
  };
157
- this.pipelines.forEach(pipeline => pipeline.process(currentEvent));
316
+ this.pipelines.forEach((pipeline) => pipeline.process(currentEvent));
158
317
  }
159
318
  generateId() {
160
319
  if (typeof crypto !== "undefined") {
161
320
  return crypto.randomUUID();
162
321
  }
163
322
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
164
- let r = Math.random() * 16 | 0;
165
- let v = c === "x" ? r : (r & 0x3 | 0x8);
323
+ let r = (Math.random() * 16) | 0;
324
+ let v = c === "x" ? r : (r & 0x3) | 0x8;
166
325
  return v.toString(16);
167
326
  });
168
327
  }
169
328
  }
170
329
 
171
- class CallerEnricher {
172
- enrich(event) {
173
- event.properties ??= {};
174
- let callerUrl = this.matchAll(new Error().stack?.toString() ?? "", /at([\s\w\d\.$]+)[\(]?((http|https|ftp)[^\)\n]+)[\)]?/gi).map(match => match[2].trim());
175
- event.properties.caller = callerUrl[callerUrl.length - 1];
176
- }
177
- matchAll(str, regexp) {
178
- const flags = regexp.global ? regexp.flags : regexp.flags + "g";
179
- const re = new RegExp(regexp, flags);
180
- let matches = [];
181
- let match;
182
- while (match = re.exec(str)) {
183
- matches.push(match);
184
- }
185
- return matches;
186
- }
187
- }
188
-
189
- class UserAgentEnricher {
330
+ class PageEnricher {
190
331
  enrich(event) {
191
332
  event.properties ??= {};
192
- event.properties.userAgent = navigator.userAgent;
333
+ event.properties.url = location.href;
334
+ event.properties.referrer = document.referrer;
335
+ event.properties.title = document.title;
193
336
  }
194
337
  }
195
338
 
@@ -200,83 +343,18 @@ class UrlEnricher {
200
343
  }
201
344
  }
202
345
 
203
- class BatchedSink {
204
- constructor(sink, options) {
205
- this.buffer = [];
206
- this.sink = sink;
207
- this.options = options;
208
- this.timer = setInterval(() => this.flush(), this.options.maxWaitTime);
209
- this.buffer = JSON.parse(this.options.storage.getItem("logBuffer") || "[]");
210
- if (this.isBrowser()) {
211
- window.addEventListener("beforeunload", () => this.flush());
212
- document.addEventListener("visibilitychange", () => {
213
- if (document.visibilityState !== "hidden") {
214
- return;
215
- }
216
- this.flush();
217
- });
218
- }
219
- }
220
- isBrowser() {
221
- return typeof window !== "undefined";
222
- }
223
- flush() {
224
- if (this.buffer.length == 0) {
225
- return;
226
- }
227
- for (let event of this.buffer) {
228
- this.sink.write(event);
229
- }
230
- this.buffer = [];
231
- }
232
- write(event) {
233
- this.buffer.push(event);
234
- this.options.storage.setItem("logBuffer", JSON.stringify(this.buffer));
235
- if (this.buffer.length >= this.options.maxBatchSize) {
236
- this.flush();
237
- }
238
- }
239
- }
240
-
241
- class BatchedSinkOptions {
242
- constructor() {
243
- this.maxBatchSize = 10;
244
- this.maxWaitTime = 500;
245
- this.storage = localStorage;
246
- }
247
- }
248
-
249
- class LanguageEnricher {
250
- enrich(event) {
251
- event.properties ??= {};
252
- event.properties.language = navigator.language;
253
- }
254
- }
255
-
256
- class PageEnricher {
257
- enrich(event) {
258
- event.properties ??= {};
259
- event.properties.url = location.href;
260
- event.properties.referrer = document.referrer;
261
- event.properties.title = document.title;
262
- }
263
- }
264
-
265
- class CustomEnricher {
266
- constructor(propertyName, methodCall) {
267
- this.propertyName = propertyName;
268
- this.methodCall = methodCall;
269
- }
346
+ class UserAgentEnricher {
270
347
  enrich(event) {
271
348
  event.properties ??= {};
272
- event.properties[this.propertyName] = this.methodCall(event);
349
+ event.properties.userAgent = navigator.userAgent;
273
350
  }
274
351
  }
275
352
 
276
353
  class Logger {
277
354
  constructor() { }
278
355
  static configure() {
279
- this.loggerConfiguration ??= globalThis.LoggerConfiguration || new LoggerConfiguration();
356
+ this.loggerConfiguration ??=
357
+ globalThis.LoggerConfiguration || new LoggerConfiguration();
280
358
  globalThis.LoggerConfiguration = this.loggerConfiguration;
281
359
  return this.loggerConfiguration;
282
360
  }