@warlock.js/logger 4.0.174 → 4.1.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.
Files changed (135) hide show
  1. package/README.md +145 -422
  2. package/cjs/index.cjs +1003 -0
  3. package/cjs/index.cjs.map +1 -0
  4. package/esm/channels/console-log.d.mts +40 -0
  5. package/esm/channels/console-log.d.mts.map +1 -0
  6. package/esm/channels/console-log.mjs +51 -0
  7. package/esm/channels/console-log.mjs.map +1 -0
  8. package/esm/channels/file-log.d.mts +194 -0
  9. package/esm/channels/file-log.d.mts.map +1 -0
  10. package/esm/channels/file-log.mjs +267 -0
  11. package/esm/channels/file-log.mjs.map +1 -0
  12. package/esm/channels/index.mjs +5 -0
  13. package/esm/channels/json-file-log.d.mts +33 -0
  14. package/esm/channels/json-file-log.d.mts.map +1 -0
  15. package/esm/channels/json-file-log.mjs +137 -0
  16. package/esm/channels/json-file-log.mjs.map +1 -0
  17. package/esm/index.d.mts +11 -0
  18. package/esm/index.mjs +13 -0
  19. package/esm/log-channel.d.mts +78 -0
  20. package/esm/log-channel.d.mts.map +1 -0
  21. package/esm/log-channel.mjs +75 -0
  22. package/esm/log-channel.mjs.map +1 -0
  23. package/esm/logger.d.mts +184 -0
  24. package/esm/logger.d.mts.map +1 -0
  25. package/esm/logger.mjs +282 -0
  26. package/esm/logger.mjs.map +1 -0
  27. package/esm/redact/redact.d.mts +25 -0
  28. package/esm/redact/redact.d.mts.map +1 -0
  29. package/esm/redact/redact.mjs +109 -0
  30. package/esm/redact/redact.mjs.map +1 -0
  31. package/esm/types.d.mts +129 -0
  32. package/esm/types.d.mts.map +1 -0
  33. package/esm/utils/capture-unhandled-errors.d.mts +16 -0
  34. package/esm/utils/capture-unhandled-errors.d.mts.map +1 -0
  35. package/esm/utils/capture-unhandled-errors.mjs +26 -0
  36. package/esm/utils/capture-unhandled-errors.mjs.map +1 -0
  37. package/esm/utils/clear-message.d.mts +8 -0
  38. package/esm/utils/clear-message.d.mts.map +1 -0
  39. package/esm/utils/clear-message.mjs +12 -0
  40. package/esm/utils/clear-message.mjs.map +1 -0
  41. package/esm/utils/index.mjs +5 -0
  42. package/esm/utils/safe-json-stringify.d.mts +14 -0
  43. package/esm/utils/safe-json-stringify.d.mts.map +1 -0
  44. package/esm/utils/safe-json-stringify.mjs +35 -0
  45. package/esm/utils/safe-json-stringify.mjs.map +1 -0
  46. package/llms-full.txt +1296 -0
  47. package/llms.txt +19 -0
  48. package/package.json +39 -39
  49. package/skills/capture-unhandled-errors/SKILL.md +103 -0
  50. package/skills/configure-logger/SKILL.md +105 -0
  51. package/skills/filter-log-entries/SKILL.md +120 -0
  52. package/skills/flush-logs-on-shutdown/SKILL.md +91 -0
  53. package/skills/logger-basics/SKILL.md +85 -0
  54. package/skills/overview/SKILL.md +86 -0
  55. package/skills/pick-log-channel/SKILL.md +139 -0
  56. package/skills/redact-sensitive-log-fields/SKILL.md +122 -0
  57. package/skills/test-logging-code/SKILL.md +169 -0
  58. package/skills/use-log-helpers/SKILL.md +66 -0
  59. package/skills/write-custom-log-channel/SKILL.md +160 -0
  60. package/cjs/channels/console-log.d.ts +0 -17
  61. package/cjs/channels/console-log.d.ts.map +0 -1
  62. package/cjs/channels/console-log.js +0 -47
  63. package/cjs/channels/console-log.js.map +0 -1
  64. package/cjs/channels/file-log.d.ts +0 -171
  65. package/cjs/channels/file-log.d.ts.map +0 -1
  66. package/cjs/channels/file-log.js +0 -293
  67. package/cjs/channels/file-log.js.map +0 -1
  68. package/cjs/channels/index.d.ts +0 -4
  69. package/cjs/channels/index.d.ts.map +0 -1
  70. package/cjs/channels/json-file-log.d.ts +0 -33
  71. package/cjs/channels/json-file-log.d.ts.map +0 -1
  72. package/cjs/channels/json-file-log.js +0 -164
  73. package/cjs/channels/json-file-log.js.map +0 -1
  74. package/cjs/index.d.ts +0 -6
  75. package/cjs/index.d.ts.map +0 -1
  76. package/cjs/index.js +0 -1
  77. package/cjs/index.js.map +0 -1
  78. package/cjs/log-channel.d.ts +0 -67
  79. package/cjs/log-channel.d.ts.map +0 -1
  80. package/cjs/log-channel.js +0 -88
  81. package/cjs/log-channel.js.map +0 -1
  82. package/cjs/logger.d.ts +0 -62
  83. package/cjs/logger.d.ts.map +0 -1
  84. package/cjs/logger.js +0 -124
  85. package/cjs/logger.js.map +0 -1
  86. package/cjs/types.d.ts +0 -104
  87. package/cjs/types.d.ts.map +0 -1
  88. package/cjs/utils/capture-unhandled-errors.d.ts +0 -2
  89. package/cjs/utils/capture-unhandled-errors.d.ts.map +0 -1
  90. package/cjs/utils/capture-unhandled-errors.js +0 -12
  91. package/cjs/utils/capture-unhandled-errors.js.map +0 -1
  92. package/cjs/utils/clear-message.d.ts +0 -5
  93. package/cjs/utils/clear-message.d.ts.map +0 -1
  94. package/cjs/utils/clear-message.js +0 -9
  95. package/cjs/utils/clear-message.js.map +0 -1
  96. package/cjs/utils/index.d.ts +0 -3
  97. package/cjs/utils/index.d.ts.map +0 -1
  98. package/esm/channels/console-log.d.ts +0 -17
  99. package/esm/channels/console-log.d.ts.map +0 -1
  100. package/esm/channels/console-log.js +0 -47
  101. package/esm/channels/console-log.js.map +0 -1
  102. package/esm/channels/file-log.d.ts +0 -171
  103. package/esm/channels/file-log.d.ts.map +0 -1
  104. package/esm/channels/file-log.js +0 -293
  105. package/esm/channels/file-log.js.map +0 -1
  106. package/esm/channels/index.d.ts +0 -4
  107. package/esm/channels/index.d.ts.map +0 -1
  108. package/esm/channels/json-file-log.d.ts +0 -33
  109. package/esm/channels/json-file-log.d.ts.map +0 -1
  110. package/esm/channels/json-file-log.js +0 -164
  111. package/esm/channels/json-file-log.js.map +0 -1
  112. package/esm/index.d.ts +0 -6
  113. package/esm/index.d.ts.map +0 -1
  114. package/esm/index.js +0 -1
  115. package/esm/index.js.map +0 -1
  116. package/esm/log-channel.d.ts +0 -67
  117. package/esm/log-channel.d.ts.map +0 -1
  118. package/esm/log-channel.js +0 -88
  119. package/esm/log-channel.js.map +0 -1
  120. package/esm/logger.d.ts +0 -62
  121. package/esm/logger.d.ts.map +0 -1
  122. package/esm/logger.js +0 -124
  123. package/esm/logger.js.map +0 -1
  124. package/esm/types.d.ts +0 -104
  125. package/esm/types.d.ts.map +0 -1
  126. package/esm/utils/capture-unhandled-errors.d.ts +0 -2
  127. package/esm/utils/capture-unhandled-errors.d.ts.map +0 -1
  128. package/esm/utils/capture-unhandled-errors.js +0 -12
  129. package/esm/utils/capture-unhandled-errors.js.map +0 -1
  130. package/esm/utils/clear-message.d.ts +0 -5
  131. package/esm/utils/clear-message.d.ts.map +0 -1
  132. package/esm/utils/clear-message.js +0 -9
  133. package/esm/utils/clear-message.js.map +0 -1
  134. package/esm/utils/index.d.ts +0 -3
  135. package/esm/utils/index.d.ts.map +0 -1
@@ -0,0 +1,267 @@
1
+ import { LogChannel } from "../log-channel.mjs";
2
+ import { ensureDirectoryAsync } from "@warlock.js/fs";
3
+ import dayjs from "dayjs";
4
+ import fs from "fs";
5
+ import { EOL } from "os";
6
+ import path from "path";
7
+
8
+ //#region ../../@warlock.js/logger/src/channels/file-log.ts
9
+ var FileLog = class extends LogChannel {
10
+ constructor(..._args) {
11
+ super(..._args);
12
+ this.name = "file";
13
+ this.messages = [];
14
+ this.groupedMessages = {};
15
+ this.defaultConfigurations = {
16
+ storagePath: process.cwd() + "/storage/logs",
17
+ rotate: true,
18
+ name: "app",
19
+ extension: "log",
20
+ chunk: "single",
21
+ maxMessagesToWrite: 100,
22
+ filter: () => true,
23
+ maxFileSize: 10 * 1024 * 1024,
24
+ get rotateFileName() {
25
+ return dayjs().format("DD-MM-YYYY");
26
+ },
27
+ dateFormat: {
28
+ date: "DD-MM-YYYY",
29
+ time: "HH:mm:ss"
30
+ }
31
+ };
32
+ this.lastWriteTime = Date.now();
33
+ this.isWriting = false;
34
+ }
35
+ /**
36
+ * Check file size for file rotation
37
+ */
38
+ async checkAndRotateFile(filePath = this.filePath) {
39
+ if (!this.config("rotate")) return;
40
+ try {
41
+ if ((await fs.promises.stat(filePath)).size >= this.config("maxFileSize")) await this.rotateLogFile();
42
+ } catch (error) {
43
+ if (error.code !== "ENOENT") console.error("Error checking log file:", error);
44
+ }
45
+ }
46
+ /**
47
+ * Rotate log file
48
+ */
49
+ async rotateLogFile() {
50
+ const fileName = `${this.fileName}-${this.config("rotateFileName")}-${Date.now()}`;
51
+ const extension = this.extension;
52
+ const rotatedFilePath = path.join(this.storagePath, `${fileName}.${extension}`);
53
+ await fs.promises.rename(this.filePath, rotatedFilePath).catch((error) => {
54
+ console.error("Error rotating file:", error);
55
+ });
56
+ }
57
+ /**
58
+ * Flush messages
59
+ *
60
+ * Starts a periodic re-check so low-traffic channels don't sit on buffered
61
+ * entries indefinitely. The handle is stored on the instance so `dispose()`
62
+ * can stop it — without this, every channel leaks a timer for the lifetime
63
+ * of the process.
64
+ */
65
+ initMessageFlush() {
66
+ this.flushIntervalHandle = setInterval(() => {
67
+ if (this.messages.length > 0 && (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5e3)) this.writeMessagesToFile();
68
+ }, 5e3);
69
+ }
70
+ /**
71
+ * Stop the background flush interval and drain any buffered entries.
72
+ *
73
+ * Call this when discarding a channel (e.g. reconfiguring the logger at
74
+ * runtime) so the 5-second timer doesn't keep the event loop alive. Safe to
75
+ * call more than once.
76
+ */
77
+ dispose() {
78
+ if (this.flushIntervalHandle) {
79
+ clearInterval(this.flushIntervalHandle);
80
+ this.flushIntervalHandle = void 0;
81
+ }
82
+ this.flushSync();
83
+ }
84
+ /**
85
+ * Get file path
86
+ */
87
+ get filePath() {
88
+ const fileName = this.fileName;
89
+ const extension = this.extension;
90
+ return path.join(this.storagePath, `${fileName}.${extension}`);
91
+ }
92
+ /**
93
+ * Get max messages
94
+ */
95
+ get maxMessagesToWrite() {
96
+ return this.config("maxMessagesToWrite");
97
+ }
98
+ /**
99
+ * Get file name
100
+ */
101
+ get fileName() {
102
+ switch (this.config("chunk")) {
103
+ case "single":
104
+ default: return this.config("name");
105
+ case "daily": return dayjs().format("DD-MM-YYYY");
106
+ case "hourly": return dayjs().format("DD-MM-YYYY-HH-00-00-a");
107
+ }
108
+ }
109
+ /**
110
+ * Get file extension
111
+ */
112
+ get extension() {
113
+ return this.config("extension");
114
+ }
115
+ /**
116
+ * Get content
117
+ */
118
+ get content() {
119
+ return this.messages.map((message) => message.content).join(EOL) + EOL;
120
+ }
121
+ /**
122
+ * Get storage path
123
+ */
124
+ get storagePath() {
125
+ return this.config("storagePath");
126
+ }
127
+ /**
128
+ * {@inheritdoc}
129
+ */
130
+ async init() {
131
+ const logsDirectory = this.storagePath;
132
+ await ensureDirectoryAsync(logsDirectory);
133
+ this.initMessageFlush();
134
+ }
135
+ /**
136
+ * Synchronously flush messages
137
+ */
138
+ flushSync() {
139
+ if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;
140
+ if (this.messagedShouldBeGrouped) {
141
+ this.prepareGroupedMessages();
142
+ for (const key in this.groupedMessages) {
143
+ const directoryPath = path.join(this.storagePath, key);
144
+ fs.mkdirSync(directoryPath, { recursive: true });
145
+ const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
146
+ const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;
147
+ fs.appendFileSync(filePath, content);
148
+ }
149
+ } else {
150
+ fs.mkdirSync(this.storagePath, { recursive: true });
151
+ fs.appendFileSync(this.filePath, this.content);
152
+ }
153
+ this.onSave();
154
+ }
155
+ /**
156
+ * {@inheritdoc}
157
+ */
158
+ async log(data) {
159
+ const { module, action, message, type: level, context } = data;
160
+ if (!this.shouldBeLogged(data)) return;
161
+ const { date: dateFormat, time } = this.getDateAndTimeFormat();
162
+ const date = dayjs().format(dateFormat + " " + time);
163
+ let content = `[${date}] [${level}] [${module}][${action}]: `;
164
+ let stack;
165
+ if (message instanceof Error) {
166
+ content += message.message + EOL;
167
+ content += `[trace]` + EOL;
168
+ content += message.stack;
169
+ stack = message.stack;
170
+ } else content += message;
171
+ this.messages.push({
172
+ content,
173
+ level,
174
+ date,
175
+ module,
176
+ action,
177
+ stack,
178
+ context,
179
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
180
+ });
181
+ await this.checkIfMessagesShouldBeWritten();
182
+ }
183
+ /**
184
+ * Check if messages should be written
185
+ */
186
+ async checkIfMessagesShouldBeWritten() {
187
+ if (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5e3) await this.writeMessagesToFile();
188
+ }
189
+ /**
190
+ * Should be called after messages are saved
191
+ */
192
+ onSave() {
193
+ this.messages = [];
194
+ this.groupedMessages = {};
195
+ this.isWriting = false;
196
+ this.lastWriteTime = Date.now();
197
+ }
198
+ /**
199
+ * Check if messages should be grouped
200
+ */
201
+ get messagedShouldBeGrouped() {
202
+ return Number(this.config("groupBy")?.length) > 0;
203
+ }
204
+ /**
205
+ * Write messages to the file
206
+ */
207
+ async writeMessagesToFile() {
208
+ if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;
209
+ this.isWriting = true;
210
+ if (this.messagedShouldBeGrouped) return await this.writeGroupedMessagesToFile();
211
+ await this.checkAndRotateFile();
212
+ try {
213
+ await this.write(this.filePath, this.content);
214
+ this.onSave();
215
+ } catch (error) {
216
+ console.error("Failed to write log:", error);
217
+ this.isWriting = false;
218
+ }
219
+ }
220
+ /**
221
+ * Write grouped messages to the file
222
+ */
223
+ async writeGroupedMessagesToFile() {
224
+ this.prepareGroupedMessages();
225
+ for (const key in this.groupedMessages) {
226
+ const directoryPath = path.join(this.storagePath, key);
227
+ await ensureDirectoryAsync(directoryPath);
228
+ const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
229
+ await this.checkAndRotateFile(filePath);
230
+ const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;
231
+ try {
232
+ await this.write(filePath, content);
233
+ } catch (error) {
234
+ console.error("Failed to write log:", error);
235
+ }
236
+ }
237
+ this.onSave();
238
+ this.isWriting = false;
239
+ }
240
+ /**
241
+ * Prepare grouped messages
242
+ */
243
+ prepareGroupedMessages() {
244
+ this.messages.forEach((message) => {
245
+ const key = this.config("groupBy").map((groupKey) => encodeURIComponent(message[groupKey])).join("/");
246
+ this.groupedMessages[key] = this.groupedMessages[key] || [];
247
+ this.groupedMessages[key].push(message);
248
+ });
249
+ }
250
+ /**
251
+ * Start writing to the file
252
+ */
253
+ async write(filePath, content) {
254
+ return new Promise((resolve, reject) => {
255
+ const writer = fs.createWriteStream(filePath, { flags: "a" });
256
+ writer.write(content, (error) => {
257
+ writer.end();
258
+ if (error) reject(error);
259
+ else resolve(true);
260
+ });
261
+ });
262
+ }
263
+ };
264
+
265
+ //#endregion
266
+ export { FileLog };
267
+ //# sourceMappingURL=file-log.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-log.mjs","names":[],"sources":["../../../../../../@warlock.js/logger/src/channels/file-log.ts"],"sourcesContent":["import { ensureDirectoryAsync } from \"@warlock.js/fs\";\r\nimport dayjs from \"dayjs\";\r\nimport fs from \"fs\";\r\nimport { EOL } from \"os\";\r\nimport path from \"path\";\r\nimport { LogChannel } from \"../log-channel\";\r\nimport type {\r\n BasicLogConfigurations,\r\n LogContract,\r\n LoggingData,\r\n LogLevel,\r\n LogMessage,\r\n} from \"../types\";\r\n\r\n// TODO: Add max messages per file before rotation\r\n\r\nexport type FileLogConfig = BasicLogConfigurations & {\r\n storagePath?: string;\r\n /**\r\n * File name, without extension\r\n */\r\n name?: string;\r\n /**\r\n * chunk mode\r\n * If set to `single`, the logs will be created in a single file, unless the rotate is set to true\r\n * If set to `daily`, the logs will be created in a daily file, unless the rotate is set to true\r\n * If set to `hourly`, the logs will be created in an hourly file, unless the rotate is set to true\r\n * @default single\r\n */\r\n chunk?: \"single\" | \"daily\" | \"hourly\";\r\n /**\r\n * Whether to rotate the file\r\n *\r\n * @default true\r\n */\r\n rotate?: boolean;\r\n /**\r\n * File Extension\r\n *\r\n * @default log\r\n */\r\n extension?: string;\r\n /**\r\n * If rotate is set, the rotate name will be added to the file name suffixed with `-`\r\n *\r\n * @default DD-MM-YYYY\r\n */\r\n rotateFileName?: string;\r\n /**\r\n * Max file size before rotating the file\r\n *\r\n * @default 10MB\r\n */\r\n maxFileSize?: number;\r\n /**\r\n * Set the max messages that needs to be added before writing to the file\r\n *\r\n * @default 100\r\n */\r\n maxMessagesToWrite?: number;\r\n /**\r\n * Group logs by\r\n * Please note that the order matters here\r\n * For example, if you set `groupBy: ['level', 'module']`, the logs will be added in level name first, then by module\r\n *\r\n * @default none\r\n */\r\n groupBy?: (\"level\" | \"module\" | \"action\")[];\r\n /**\r\n * Define what levels should be logged\r\n *\r\n * @default all\r\n */\r\n levels?: LogLevel[];\r\n /**\r\n * Date and time format\r\n */\r\n dateFormat?: {\r\n date?: string;\r\n time?: string;\r\n };\r\n};\r\n\r\nexport class FileLog extends LogChannel<FileLogConfig> implements LogContract {\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"file\";\r\n\r\n /**\r\n * Messages buffer\r\n */\r\n protected messages: LogMessage[] = [];\r\n\r\n /**\r\n * Grouped messages\r\n */\r\n protected groupedMessages: Record<string, LogMessage[]> = {};\r\n\r\n /**\r\n * Default channel configurations\r\n */\r\n protected defaultConfigurations: FileLogConfig = {\r\n storagePath: process.cwd() + \"/storage/logs\",\r\n rotate: true,\r\n name: \"app\",\r\n extension: \"log\",\r\n chunk: \"single\",\r\n maxMessagesToWrite: 100,\r\n filter: () => true,\r\n maxFileSize: 10 * 1024 * 1024, // 10MB\r\n get rotateFileName() {\r\n return dayjs().format(\"DD-MM-YYYY\");\r\n },\r\n dateFormat: {\r\n date: \"DD-MM-YYYY\",\r\n time: \"HH:mm:ss\",\r\n },\r\n };\r\n\r\n /**\r\n * Last write time\r\n */\r\n protected lastWriteTime = Date.now();\r\n\r\n /**\r\n * A flag to determine if the file is being written\r\n */\r\n protected isWriting = false;\r\n\r\n /**\r\n * Handle for the periodic flush interval. Stored so it can be cleared\r\n * in `dispose()` — long-lived processes that create channels dynamically\r\n * would otherwise leak one timer per channel.\r\n */\r\n protected flushIntervalHandle?: NodeJS.Timeout;\r\n\r\n /**\r\n * Check file size for file rotation\r\n */\r\n protected async checkAndRotateFile(filePath = this.filePath) {\r\n if (!this.config(\"rotate\")) return;\r\n\r\n try {\r\n const stats = await fs.promises.stat(filePath);\r\n if (stats.size >= this.config(\"maxFileSize\")!) {\r\n await this.rotateLogFile();\r\n }\r\n } catch (error: any) {\r\n // ENOENT is expected when the file hasn't been created yet — there is\r\n // nothing to rotate, so stay silent. Surface anything else.\r\n if (error.code !== \"ENOENT\") {\r\n console.error(\"Error checking log file:\", error);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Rotate log file\r\n */\r\n protected async rotateLogFile() {\r\n const fileName = `${this.fileName}-${this.config(\"rotateFileName\")}-${Date.now()}`;\r\n\r\n const extension = this.extension;\r\n\r\n const rotatedFilePath = path.join(this.storagePath, `${fileName}.${extension}`);\r\n\r\n await fs.promises.rename(this.filePath, rotatedFilePath).catch((error) => {\r\n console.error(\"Error rotating file:\", error);\r\n });\r\n }\r\n\r\n /**\r\n * Flush messages\r\n *\r\n * Starts a periodic re-check so low-traffic channels don't sit on buffered\r\n * entries indefinitely. The handle is stored on the instance so `dispose()`\r\n * can stop it — without this, every channel leaks a timer for the lifetime\r\n * of the process.\r\n */\r\n protected initMessageFlush() {\r\n this.flushIntervalHandle = setInterval(() => {\r\n if (\r\n this.messages.length > 0 &&\r\n (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5000)\r\n ) {\r\n this.writeMessagesToFile();\r\n }\r\n }, 5000);\r\n }\r\n\r\n /**\r\n * Stop the background flush interval and drain any buffered entries.\r\n *\r\n * Call this when discarding a channel (e.g. reconfiguring the logger at\r\n * runtime) so the 5-second timer doesn't keep the event loop alive. Safe to\r\n * call more than once.\r\n */\r\n public dispose(): void {\r\n if (this.flushIntervalHandle) {\r\n clearInterval(this.flushIntervalHandle);\r\n this.flushIntervalHandle = undefined;\r\n }\r\n\r\n this.flushSync();\r\n }\r\n\r\n /**\r\n * Get file path\r\n */\r\n public get filePath() {\r\n const fileName = this.fileName;\r\n\r\n const extension = this.extension;\r\n\r\n return path.join(this.storagePath, `${fileName}.${extension}`);\r\n }\r\n\r\n /**\r\n * Get max messages\r\n */\r\n protected get maxMessagesToWrite(): number {\r\n return this.config(\"maxMessagesToWrite\")!;\r\n }\r\n\r\n /**\r\n * Get file name\r\n */\r\n public get fileName(): string {\r\n const debugLevel = this.config(\"chunk\")!;\r\n\r\n switch (debugLevel) {\r\n case \"single\":\r\n default:\r\n return this.config(\"name\")!;\r\n case \"daily\":\r\n return dayjs().format(\"DD-MM-YYYY\");\r\n case \"hourly\":\r\n return dayjs().format(\"DD-MM-YYYY-HH-00-00-a\");\r\n }\r\n }\r\n\r\n /**\r\n * Get file extension\r\n */\r\n public get extension(): string {\r\n return this.config(\"extension\")!;\r\n }\r\n\r\n /**\r\n * Get content\r\n */\r\n protected get content() {\r\n return this.messages.map((message) => message.content).join(EOL) + EOL;\r\n }\r\n\r\n /**\r\n * Get storage path\r\n */\r\n public get storagePath(): string {\r\n return this.config(\"storagePath\")!;\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n protected async init() {\r\n const logsDirectory = this.storagePath;\r\n\r\n await ensureDirectoryAsync(logsDirectory);\r\n\r\n this.initMessageFlush();\r\n }\r\n\r\n /**\r\n * Synchronously flush messages\r\n */\r\n public flushSync(): void {\r\n if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;\r\n\r\n if (this.messagedShouldBeGrouped) {\r\n this.prepareGroupedMessages();\r\n for (const key in this.groupedMessages) {\r\n const directoryPath = path.join(this.storagePath, key);\r\n fs.mkdirSync(directoryPath, { recursive: true });\r\n const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;\r\n fs.appendFileSync(filePath, content);\r\n }\r\n } else {\r\n fs.mkdirSync(this.storagePath, { recursive: true });\r\n fs.appendFileSync(this.filePath, this.content);\r\n }\r\n\r\n this.onSave();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async log(data: LoggingData) {\r\n const { module, action, message, type: level, context } = data;\r\n\r\n if (!this.shouldBeLogged(data)) return;\r\n\r\n const { date: dateFormat, time } = this.getDateAndTimeFormat();\r\n\r\n const date = dayjs().format(dateFormat + \" \" + time);\r\n\r\n let content = `[${date}] [${level}] [${module}][${action}]: `;\r\n\r\n let stack: string | undefined;\r\n\r\n // check if message is an instance of Error\r\n if (message instanceof Error) {\r\n // in that case we need to store the error message and stack trace\r\n content += message.message + EOL;\r\n content += `[trace]` + EOL;\r\n content += message.stack;\r\n stack = message.stack;\r\n } else {\r\n content += message;\r\n }\r\n\r\n this.messages.push({\r\n content,\r\n level,\r\n date,\r\n module,\r\n action,\r\n stack,\r\n context,\r\n timestamp: new Date().toISOString(),\r\n });\r\n\r\n await this.checkIfMessagesShouldBeWritten(); // Immediate check on buffer size\r\n }\r\n\r\n /**\r\n * Check if messages should be written\r\n */\r\n protected async checkIfMessagesShouldBeWritten() {\r\n if (this.messages.length >= this.maxMessagesToWrite || Date.now() - this.lastWriteTime > 5000) {\r\n await this.writeMessagesToFile();\r\n }\r\n }\r\n\r\n /**\r\n * Should be called after messages are saved\r\n */\r\n protected onSave() {\r\n this.messages = [];\r\n this.groupedMessages = {};\r\n this.isWriting = false;\r\n this.lastWriteTime = Date.now();\r\n }\r\n\r\n /**\r\n * Check if messages should be grouped\r\n */\r\n protected get messagedShouldBeGrouped(): boolean {\r\n return Number(this.config(\"groupBy\")?.length) > 0;\r\n }\r\n\r\n /**\r\n * Write messages to the file\r\n */\r\n protected async writeMessagesToFile() {\r\n if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;\r\n\r\n this.isWriting = true;\r\n\r\n if (this.messagedShouldBeGrouped) {\r\n return await this.writeGroupedMessagesToFile();\r\n }\r\n\r\n await this.checkAndRotateFile(); // Ensure we check file size before writing\r\n\r\n try {\r\n await this.write(this.filePath, this.content);\r\n this.onSave();\r\n } catch (error) {\r\n console.error(\"Failed to write log:\", error);\r\n // Implement fallback logic here\r\n this.isWriting = false;\r\n }\r\n }\r\n\r\n /**\r\n * Write grouped messages to the file\r\n */\r\n protected async writeGroupedMessagesToFile(): Promise<void> {\r\n // first step, is to group the messages\r\n this.prepareGroupedMessages();\r\n\r\n // now each key in the grouped messages, represents the directory path that should extend the storage path\r\n for (const key in this.groupedMessages) {\r\n const directoryPath = path.join(this.storagePath, key);\r\n\r\n await ensureDirectoryAsync(directoryPath);\r\n\r\n const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n\r\n await this.checkAndRotateFile(filePath); // Ensure we check file size before writing\r\n\r\n const content = this.groupedMessages[key].map((message) => message.content).join(EOL) + EOL;\r\n\r\n try {\r\n await this.write(filePath, content);\r\n } catch (error) {\r\n console.error(\"Failed to write log:\", error);\r\n }\r\n }\r\n\r\n this.onSave();\r\n this.isWriting = false;\r\n }\r\n\r\n /**\r\n * Prepare grouped messages\r\n */\r\n protected prepareGroupedMessages(): void {\r\n this.messages.forEach((message) => {\r\n const key = this.config(\"groupBy\")!\r\n .map((groupKey) => encodeURIComponent(message[groupKey]))\r\n .join(\"/\");\r\n\r\n this.groupedMessages[key] = this.groupedMessages[key] || [];\r\n this.groupedMessages[key].push(message);\r\n });\r\n }\r\n\r\n /**\r\n * Start writing to the file\r\n */\r\n protected async write(filePath: string, content: string) {\r\n return new Promise((resolve, reject) => {\r\n const writer = fs.createWriteStream(filePath, { flags: \"a\" });\r\n\r\n writer.write(content, (error) => {\r\n writer.end();\r\n if (error) {\r\n reject(error);\r\n } else {\r\n resolve(true);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { ConsoleLog } from "./console-log.mjs";
2
+ import { FileLog } from "./file-log.mjs";
3
+ import { JSONFileLog } from "./json-file-log.mjs";
4
+
5
+ export { };
@@ -0,0 +1,33 @@
1
+ import { LogContract, LoggingData } from "../types.mjs";
2
+ import { FileLog } from "./file-log.mjs";
3
+
4
+ //#region ../../@warlock.js/logger/src/channels/json-file-log.d.ts
5
+ declare class JSONFileLog extends FileLog implements LogContract {
6
+ /**
7
+ * {@inheritdoc}
8
+ */
9
+ name: string;
10
+ /**
11
+ * Get file extension
12
+ */
13
+ get extension(): string;
14
+ /**
15
+ * Synchronously flush messages
16
+ */
17
+ flushSync(): void;
18
+ /**
19
+ * {@inheritdoc}
20
+ */
21
+ log(data: LoggingData): Promise<void>;
22
+ /**
23
+ * Write messages to the file
24
+ */
25
+ protected writeMessagesToFile(): Promise<void>;
26
+ /**
27
+ * Write grouped messages to the file
28
+ */
29
+ protected writeGroupedMessagesToFile(): Promise<void>;
30
+ }
31
+ //#endregion
32
+ export { JSONFileLog };
33
+ //# sourceMappingURL=json-file-log.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-file-log.d.mts","names":[],"sources":["../../../../../../@warlock.js/logger/src/channels/json-file-log.ts"],"mappings":";;;;cAQa,WAAA,SAAoB,OAAA,YAAmB,WAAA;;AAApD;;EAIS,IAAA;EAuDgB;;;EAAA,IAlDZ,SAAA,CAAA;EAToB;;;EAgBxB,SAAA,CAAA;EAhBwB;;;EA2DlB,GAAA,CAAI,IAAA,EAAM,WAAA,GAAW,OAAA;EA3C3B;;;EAAA,UA4ES,mBAAA,CAAA,GAAuB,OAAA;EAjCL;;;EAAA,UAyElB,0BAAA,CAAA,GAA8B,OAAA;AAAA"}
@@ -0,0 +1,137 @@
1
+ import { FileLog } from "./file-log.mjs";
2
+ import { safeJsonStringify } from "../utils/safe-json-stringify.mjs";
3
+ import { ensureDirectoryAsync, fileExistsAsync, getJsonFileAsync } from "@warlock.js/fs";
4
+ import dayjs from "dayjs";
5
+ import fs from "fs";
6
+ import path from "path";
7
+
8
+ //#region ../../@warlock.js/logger/src/channels/json-file-log.ts
9
+ var JSONFileLog = class extends FileLog {
10
+ constructor(..._args) {
11
+ super(..._args);
12
+ this.name = "fileJson";
13
+ }
14
+ /**
15
+ * Get file extension
16
+ */
17
+ get extension() {
18
+ return "json";
19
+ }
20
+ /**
21
+ * Synchronously flush messages
22
+ */
23
+ flushSync() {
24
+ if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;
25
+ if (this.messagedShouldBeGrouped) {
26
+ this.prepareGroupedMessages();
27
+ for (const key in this.groupedMessages) {
28
+ const directoryPath = path.join(this.storagePath, key);
29
+ fs.mkdirSync(directoryPath, { recursive: true });
30
+ const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
31
+ let fileContents = { messages: [] };
32
+ if (fs.existsSync(filePath)) try {
33
+ fileContents = JSON.parse(fs.readFileSync(filePath, "utf-8"));
34
+ if (!Array.isArray(fileContents.messages)) fileContents.messages = [];
35
+ } catch (e) {
36
+ fileContents = { messages: [] };
37
+ }
38
+ fileContents.messages.push(...this.groupedMessages[key]);
39
+ fs.writeFileSync(filePath, safeJsonStringify(fileContents, 2));
40
+ }
41
+ } else {
42
+ fs.mkdirSync(this.storagePath, { recursive: true });
43
+ let fileContents = { messages: [] };
44
+ if (fs.existsSync(this.filePath)) try {
45
+ fileContents = JSON.parse(fs.readFileSync(this.filePath, "utf-8"));
46
+ if (!Array.isArray(fileContents.messages)) fileContents.messages = [];
47
+ } catch (e) {
48
+ fileContents = { messages: [] };
49
+ }
50
+ fileContents.messages.push(...this.messages);
51
+ fs.writeFileSync(this.filePath, safeJsonStringify(fileContents, 2));
52
+ }
53
+ this.onSave();
54
+ }
55
+ /**
56
+ * {@inheritdoc}
57
+ */
58
+ async log(data) {
59
+ let stack;
60
+ if (data.message instanceof Error) {
61
+ stack = data.message.stack?.split("\n");
62
+ data.message = data.message.message;
63
+ }
64
+ const { module, action, message, type: level, context } = data;
65
+ if (!this.shouldBeLogged(data)) return;
66
+ const { date: dateFormat, time } = this.getDateAndTimeFormat();
67
+ const date = dayjs().format(dateFormat + " " + time);
68
+ this.messages.push({
69
+ content: message,
70
+ level,
71
+ date,
72
+ module,
73
+ action,
74
+ stack,
75
+ context,
76
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
77
+ });
78
+ await this.checkIfMessagesShouldBeWritten();
79
+ }
80
+ /**
81
+ * Write messages to the file
82
+ */
83
+ async writeMessagesToFile() {
84
+ if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;
85
+ this.isWriting = true;
86
+ if (this.messagedShouldBeGrouped) return await this.writeGroupedMessagesToFile();
87
+ await this.checkAndRotateFile();
88
+ let fileContents = { messages: [] };
89
+ if (await fileExistsAsync(this.filePath)) try {
90
+ fileContents = await getJsonFileAsync(this.filePath);
91
+ } catch (error) {
92
+ console.error("Error reading log file, reinitializing:", error);
93
+ fileContents = { messages: [] };
94
+ }
95
+ else fileContents = { messages: [] };
96
+ fileContents.messages.push(...this.messages);
97
+ try {
98
+ await fs.promises.writeFile(this.filePath, safeJsonStringify(fileContents, 2));
99
+ this.onSave();
100
+ } catch (error) {
101
+ console.error("Failed to write log:", error);
102
+ this.isWriting = false;
103
+ }
104
+ }
105
+ /**
106
+ * Write grouped messages to the file
107
+ */
108
+ async writeGroupedMessagesToFile() {
109
+ this.prepareGroupedMessages();
110
+ for (const key in this.groupedMessages) {
111
+ const directoryPath = path.join(this.storagePath, key);
112
+ await ensureDirectoryAsync(directoryPath);
113
+ const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);
114
+ await this.checkAndRotateFile(filePath);
115
+ let fileContents = { messages: [] };
116
+ if (await fileExistsAsync(filePath)) try {
117
+ fileContents = await getJsonFileAsync(filePath);
118
+ } catch (error) {
119
+ console.error("Error reading log file, reinitializing:", error);
120
+ fileContents = { messages: [] };
121
+ }
122
+ else fileContents = { messages: [] };
123
+ fileContents.messages.push(...this.groupedMessages[key]);
124
+ try {
125
+ await fs.promises.writeFile(filePath, safeJsonStringify(fileContents, 2));
126
+ } catch (error) {
127
+ console.error("Failed to write log:", error);
128
+ this.isWriting = false;
129
+ }
130
+ }
131
+ this.onSave();
132
+ }
133
+ };
134
+
135
+ //#endregion
136
+ export { JSONFileLog };
137
+ //# sourceMappingURL=json-file-log.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-file-log.mjs","names":[],"sources":["../../../../../../@warlock.js/logger/src/channels/json-file-log.ts"],"sourcesContent":["import { ensureDirectoryAsync, fileExistsAsync, getJsonFileAsync } from \"@warlock.js/fs\";\r\nimport dayjs from \"dayjs\";\r\nimport fs from \"fs\";\r\nimport path from \"path\";\r\nimport type { LogContract, LogMessage, LoggingData } from \"../types\";\r\nimport { safeJsonStringify } from \"../utils/safe-json-stringify\";\r\nimport { FileLog } from \"./file-log\";\r\n\r\nexport class JSONFileLog extends FileLog implements LogContract {\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public name = \"fileJson\";\r\n\r\n /**\r\n * Get file extension\r\n */\r\n public get extension(): string {\r\n return \"json\";\r\n }\r\n\r\n /**\r\n * Synchronously flush messages\r\n */\r\n public flushSync(): void {\r\n if (this.messages.length === 0 && Object.keys(this.groupedMessages).length === 0) return;\r\n\r\n if (this.messagedShouldBeGrouped) {\r\n this.prepareGroupedMessages();\r\n for (const key in this.groupedMessages) {\r\n const directoryPath = path.join(this.storagePath, key);\r\n fs.mkdirSync(directoryPath, { recursive: true });\r\n const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n\r\n let fileContents = { messages: [] as any[] };\r\n if (fs.existsSync(filePath)) {\r\n try {\r\n fileContents = JSON.parse(fs.readFileSync(filePath, \"utf-8\"));\r\n if (!Array.isArray(fileContents.messages)) fileContents.messages = [];\r\n } catch (e) {\r\n fileContents = { messages: [] };\r\n }\r\n }\r\n fileContents.messages.push(...this.groupedMessages[key]);\r\n fs.writeFileSync(filePath, safeJsonStringify(fileContents, 2));\r\n }\r\n } else {\r\n fs.mkdirSync(this.storagePath, { recursive: true });\r\n let fileContents = { messages: [] as any[] };\r\n if (fs.existsSync(this.filePath)) {\r\n try {\r\n fileContents = JSON.parse(fs.readFileSync(this.filePath, \"utf-8\"));\r\n if (!Array.isArray(fileContents.messages)) fileContents.messages = [];\r\n } catch (e) {\r\n fileContents = { messages: [] };\r\n }\r\n }\r\n fileContents.messages.push(...this.messages);\r\n fs.writeFileSync(this.filePath, safeJsonStringify(fileContents, 2));\r\n }\r\n\r\n this.onSave();\r\n }\r\n\r\n /**\r\n * {@inheritdoc}\r\n */\r\n public async log(data: LoggingData) {\r\n let stack: string[] | undefined;\r\n\r\n if (data.message instanceof Error) {\r\n stack = data.message.stack?.split(\"\\n\");\r\n data.message = data.message.message;\r\n }\r\n\r\n const { module, action, message, type: level, context } = data;\r\n\r\n if (!this.shouldBeLogged(data)) return;\r\n\r\n const { date: dateFormat, time } = this.getDateAndTimeFormat();\r\n\r\n const date = dayjs().format(dateFormat + \" \" + time);\r\n\r\n this.messages.push({\r\n content: message,\r\n level,\r\n date,\r\n module,\r\n action,\r\n stack,\r\n context,\r\n timestamp: new Date().toISOString(),\r\n } as LogMessage);\r\n\r\n await this.checkIfMessagesShouldBeWritten(); // Immediate check on buffer size\r\n }\r\n\r\n /**\r\n * Write messages to the file\r\n */\r\n protected async writeMessagesToFile(): Promise<void> {\r\n if (this.messages.length === 0 || this.isWriting || !this.isInitialized) return;\r\n\r\n this.isWriting = true;\r\n\r\n if (this.messagedShouldBeGrouped) {\r\n return await this.writeGroupedMessagesToFile();\r\n }\r\n\r\n await this.checkAndRotateFile(); // Ensure file rotation is handled\r\n\r\n let fileContents = { messages: [] as any[] };\r\n\r\n if (await fileExistsAsync(this.filePath)) {\r\n try {\r\n fileContents = (await getJsonFileAsync(this.filePath)) as { messages: any[] };\r\n } catch (error) {\r\n console.error(\"Error reading log file, reinitializing:\", error);\r\n fileContents = { messages: [] }; // Reinitialize the file if corrupted\r\n }\r\n } else {\r\n fileContents = { messages: [] }; // Reinitialize the file if corrupted\r\n }\r\n\r\n fileContents.messages.push(...this.messages);\r\n\r\n try {\r\n await fs.promises.writeFile(this.filePath, safeJsonStringify(fileContents, 2));\r\n\r\n this.onSave();\r\n } catch (error) {\r\n console.error(\"Failed to write log:\", error);\r\n // Implement fallback logic here\r\n this.isWriting = false;\r\n }\r\n }\r\n\r\n /**\r\n * Write grouped messages to the file\r\n */\r\n protected async writeGroupedMessagesToFile(): Promise<void> {\r\n // first step, is to group the messages\r\n this.prepareGroupedMessages();\r\n\r\n // now each key in the grouped messages, represents the directory path that should extend the storage path\r\n for (const key in this.groupedMessages) {\r\n const directoryPath = path.join(this.storagePath, key);\r\n\r\n await ensureDirectoryAsync(directoryPath);\r\n\r\n const filePath = path.join(directoryPath, `${this.fileName}.${this.extension}`);\r\n\r\n await this.checkAndRotateFile(filePath); // Ensure we check file size before writing\r\n\r\n let fileContents: { messages: any[] } = { messages: [] };\r\n if (await fileExistsAsync(filePath)) {\r\n try {\r\n fileContents = (await getJsonFileAsync(filePath)) as { messages: any[] };\r\n } catch (error) {\r\n console.error(\"Error reading log file, reinitializing:\", error);\r\n fileContents = { messages: [] };\r\n }\r\n } else {\r\n fileContents = { messages: [] };\r\n }\r\n\r\n fileContents.messages.push(...this.groupedMessages[key]);\r\n\r\n try {\r\n await fs.promises.writeFile(filePath, safeJsonStringify(fileContents, 2));\r\n } catch (error) {\r\n console.error(\"Failed to write log:\", error);\r\n this.isWriting = false;\r\n }\r\n }\r\n\r\n this.onSave();\r\n }\r\n}\r\n"],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { AutoFlushEvent, BasicLogConfigurations, DebugMode, LogContract, LogLevel, LogMessage, LoggingData, OmittedLoggingData, RedactCensor, RedactConfig } from "./types.mjs";
2
+ import { LogChannel } from "./log-channel.mjs";
3
+ import { ConsoleLog, ConsoleLogConfig } from "./channels/console-log.mjs";
4
+ import { FileLog, FileLogConfig } from "./channels/file-log.mjs";
5
+ import { JSONFileLog } from "./channels/json-file-log.mjs";
6
+ import { Logger, log } from "./logger.mjs";
7
+ import { applyRedact, mergeRedact } from "./redact/redact.mjs";
8
+ import { captureAnyUnhandledRejection } from "./utils/capture-unhandled-errors.mjs";
9
+ import { clearMessage } from "./utils/clear-message.mjs";
10
+ import { safeJsonStringify } from "./utils/safe-json-stringify.mjs";
11
+ export { AutoFlushEvent, BasicLogConfigurations, ConsoleLog, ConsoleLogConfig, DebugMode, FileLog, FileLogConfig, JSONFileLog, LogChannel, LogContract, LogLevel, LogMessage, Logger, LoggingData, OmittedLoggingData, RedactCensor, RedactConfig, applyRedact, captureAnyUnhandledRejection, clearMessage, log, mergeRedact, safeJsonStringify };
package/esm/index.mjs ADDED
@@ -0,0 +1,13 @@
1
+ import { LogChannel } from "./log-channel.mjs";
2
+ import { ConsoleLog } from "./channels/console-log.mjs";
3
+ import { FileLog } from "./channels/file-log.mjs";
4
+ import { safeJsonStringify } from "./utils/safe-json-stringify.mjs";
5
+ import { JSONFileLog } from "./channels/json-file-log.mjs";
6
+ import "./channels/index.mjs";
7
+ import { applyRedact, mergeRedact } from "./redact/redact.mjs";
8
+ import { clearMessage } from "./utils/clear-message.mjs";
9
+ import { Logger, log } from "./logger.mjs";
10
+ import { captureAnyUnhandledRejection } from "./utils/capture-unhandled-errors.mjs";
11
+ import "./utils/index.mjs";
12
+
13
+ export { ConsoleLog, FileLog, JSONFileLog, LogChannel, Logger, applyRedact, captureAnyUnhandledRejection, clearMessage, log, mergeRedact, safeJsonStringify };
@@ -0,0 +1,78 @@
1
+ import { BasicLogConfigurations, LogContract, LoggingData, RedactConfig } from "./types.mjs";
2
+
3
+ //#region ../../@warlock.js/logger/src/log-channel.d.ts
4
+ declare abstract class LogChannel<Options extends BasicLogConfigurations = BasicLogConfigurations> implements LogContract {
5
+ /**
6
+ * Channel name
7
+ */
8
+ name: string;
9
+ /**
10
+ * Channel description
11
+ */
12
+ description?: string;
13
+ /**
14
+ * Determine if channel is logging in terminal
15
+ */
16
+ terminal: boolean;
17
+ /**
18
+ * Default Configurations
19
+ */
20
+ protected defaultConfigurations: Options;
21
+ /**
22
+ * Channel configurations
23
+ */
24
+ protected channelConfigurations: Options;
25
+ /**
26
+ * Determine whether the channel is fully initialized
27
+ */
28
+ protected isInitialized: boolean;
29
+ /**
30
+ * Constructor
31
+ */
32
+ constructor(configurations?: Options);
33
+ /**
34
+ * Initialize the channel
35
+ */
36
+ protected init?(): void | Promise<void>;
37
+ /**
38
+ * Get config value
39
+ */
40
+ protected config<K extends keyof Options>(key: K): Options[K];
41
+ /**
42
+ * Set configurations
43
+ */
44
+ protected setConfigurations(configurations: Options): this;
45
+ /**
46
+ * Determine if the message should be logged
47
+ */
48
+ protected shouldBeLogged(data: LoggingData): boolean;
49
+ /**
50
+ * Log the given message
51
+ */
52
+ abstract log(data: LoggingData): void | Promise<void>;
53
+ /**
54
+ * Synchronously flush messages
55
+ */
56
+ flushSync?(): void;
57
+ /**
58
+ * Read the channel's redact config (if any). Used by `Logger` to apply
59
+ * per-channel additive redaction on top of the logger-wide floor.
60
+ * Subclasses normally don't override this — set `redact` in your channel
61
+ * configuration instead.
62
+ */
63
+ getRedactConfig(): RedactConfig | undefined;
64
+ /**
65
+ * Get date and time formats
66
+ */
67
+ protected getDateAndTimeFormat(): {
68
+ date: string;
69
+ time: string;
70
+ };
71
+ /**
72
+ * get basic configurations with the given ones
73
+ */
74
+ protected withBasicConfigurations(configurations: Partial<Options>): Options;
75
+ }
76
+ //#endregion
77
+ export { LogChannel };
78
+ //# sourceMappingURL=log-channel.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-channel.d.mts","names":[],"sources":["../../../../../@warlock.js/logger/src/log-channel.ts"],"mappings":";;;uBAOsB,UAAA,iBACJ,sBAAA,GAAyB,sBAAA,aAC9B,WAAA;;AAFb;;EAOS,IAAA;EANS;;;EAWT,WAAA;EAyB6B;;;EApB7B,QAAA;EA0C4C;;;EAAA,UArCzC,qBAAA,EAAuB,OAAA;EA6EP;;;EAAA,UAxEhB,qBAAA,EAAuB,OAAA;EAyGiB;;;EAAA,UApGxC,aAAA;EA9BY;;;cAmCH,cAAA,GAAiB,OAAA;EAnCzB;;;EAAA,UAoDD,IAAA,CAAA,CAAA,UAAgB,OAAA;EAhChB;;;EAAA,UAqCA,MAAA,iBAAuB,OAAA,CAAA,CAAS,GAAA,EAAK,CAAA,GAAI,OAAA,CAAQ,CAAA;EA3BjD;;;EAAA,UAoCA,iBAAA,CAAkB,cAAA,EAAgB,OAAA;EAdlC;;;EAAA,UA0BA,cAAA,CAAe,IAAA,EAAM,WAAA;EArBE;;;EAAA,SAwCjB,GAAA,CAAI,IAAA,EAAM,WAAA,UAAqB,OAAA;EAxCY;;;EA6CpD,SAAA,CAAA,CAAA;EAxBG;;;;;;EAgCH,eAAA,CAAA,GAAmB,YAAA;EARnB;;;EAAA,UAiBG,oBAAA,CAAA;;;;EAWwC;;;EAAA,UAAxC,uBAAA,CAAwB,cAAA,EAAgB,OAAA,CAAQ,OAAA,IAAW,OAAA;AAAA"}