@stonyx/logs 1.0.1-alpha.18 → 1.0.1-alpha.19
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 +74 -45
- package/dist/index.d.ts +107 -0
- package/dist/index.js +34 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,10 +58,9 @@ const log = new Log();
|
|
|
58
58
|
log.info('Info: sample application has started');
|
|
59
59
|
log.warn('Warning: this is just a sample');
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
log.error('Error: no application logic detected', true) // logs to logs/error.log file
|
|
61
|
+
// logs to logs/error.log; a failed write rejects, and handling that rejection is required
|
|
62
|
+
// see "Handling write failures" below
|
|
63
|
+
log.error('Error: no application logic detected', true)
|
|
65
64
|
.catch(err => { /* err.code is the underlying fs error code, e.g. 'EACCES' */ });
|
|
66
65
|
```
|
|
67
66
|
|
|
@@ -100,9 +99,10 @@ log.info('Info: sample application has started')
|
|
|
100
99
|
.catch(err => { /* see "Handling write failures" */ });
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
> **Caveat:** with `logToFileByDefault: true`,
|
|
104
|
-
>
|
|
105
|
-
> `log.info()` into a process-terminating rejection. See
|
|
102
|
+
> **Caveat:** with `logToFileByDefault: true`, every log call *except* [`debug()`](#the-debug-method)
|
|
103
|
+
> becomes a file write, and therefore returns a promise that can reject. A failing log directory turns
|
|
104
|
+
> an unhandled `log.info()` into a process-terminating rejection. See
|
|
105
|
+
> [handling write failures](#handling-write-failures).
|
|
106
106
|
|
|
107
107
|

|
|
108
108
|
|
|
@@ -167,10 +167,8 @@ Color settings are handled by determining whether your input is a color name or
|
|
|
167
167
|
|
|
168
168
|
Additionally, these methods return a promise when `logToFile` is true, allowing you to use them with `await` in an async method, or to append `then()`, `catch()` or `finally()`.
|
|
169
169
|
|
|
170
|
-
When `logToFile` is true that promise **can reject**, and handling the rejection is **required
|
|
171
|
-
|
|
172
|
-
terminates your process under Node's default settings. Wrap the call in `try`/`catch` (or attach
|
|
173
|
-
`.catch()`) for every call that writes to a file.
|
|
170
|
+
When `logToFile` is true that promise **can reject**, and handling the rejection is **required** --
|
|
171
|
+
see [handling write failures](#handling-write-failures) for the full contract.
|
|
174
172
|
|
|
175
173
|
```js
|
|
176
174
|
async method() {
|
|
@@ -184,8 +182,6 @@ async method() {
|
|
|
184
182
|
}
|
|
185
183
|
```
|
|
186
184
|
|
|
187
|
-
See [handling write failures](#handling-write-failures) for the full contract.
|
|
188
|
-
|
|
189
185
|
### The Debug Method
|
|
190
186
|
|
|
191
187
|
**Log** allows for the `log.debug()` method to be overridden by a color setting. However, by default we do not define a color for debug and debug is handled differently. For console logging, all **debug** does is output the following:
|
|
@@ -200,9 +196,10 @@ JSON.stringify(content, null, 2);
|
|
|
200
196
|
|
|
201
197
|
We believe that when wanting to output complicated objects or debug **typescript** applications, there are better methods than utilizing this **Log** package. But for anyone who's fully incorporated **Log** into their project, this function offers some convenience.
|
|
202
198
|
|
|
203
|
-
`debug(content, logToFile)` writes to file on
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
`debug(content, logToFile)` writes to file on the same contract as every other log type -- a failed
|
|
200
|
+
write rejects with the underlying `fs` error and must be handled -- with one difference: `debug()`
|
|
201
|
+
ignores [`logToFileByDefault`](#configuration), so it writes to file only when you pass `true`.
|
|
202
|
+
See [handling write failures](#handling-write-failures).
|
|
206
203
|
|
|
207
204
|
### Logging Parameters
|
|
208
205
|
|
|
@@ -218,26 +215,33 @@ log.error('error message', true, false); // content, logToFile, overwrite
|
|
|
218
215
|
|
|
219
216
|
**logToFile** will log to *<project-root>/logs* unless [configured](#configuration) differently during instantiation. <br>
|
|
220
217
|
|
|
221
|
-
### Handling
|
|
218
|
+
### Handling Write Failures
|
|
222
219
|
|
|
223
220
|
Any call that writes to a file returns a promise that **rejects when the write fails**. That covers
|
|
224
|
-
`log.error(content, true)`, `log.debug(content, true)`, and
|
|
225
|
-
[`logToFileByDefault`](#configuration) is `true
|
|
221
|
+
`log.error(content, true)`, `log.debug(content, true)`, and every log call except `debug()` when
|
|
222
|
+
[`logToFileByDefault`](#configuration) is `true` -- `debug()` is a direct method that never consults
|
|
223
|
+
`logToFileByDefault`, so it only writes to file when you pass `true` explicitly.
|
|
226
224
|
|
|
227
225
|
That rejection is the only actionable failure signal, so every file-writing call must be awaited
|
|
228
|
-
inside `try`/`catch`, or have `.catch()` attached.
|
|
226
|
+
inside `try`/`catch`, or have `.catch()` attached. The console line is written *before* the file
|
|
227
|
+
write is attempted, so a rejection means only the file copy was lost -- do not re-log the message in
|
|
228
|
+
your handler, or it prints twice.
|
|
229
229
|
|
|
230
230
|
> **A rejection is terminal. Do not retry it.**
|
|
231
231
|
>
|
|
232
|
-
>
|
|
233
|
-
>
|
|
234
|
-
>
|
|
235
|
-
>
|
|
232
|
+
> For the recoverable directory-level codes -- `ENOENT`, `EACCES`, `EPERM`, `EROFS` -- **Log** has
|
|
233
|
+
> already retried internally by the time the rejection reaches you (see
|
|
234
|
+
> [self-healing retry](#self-healing-retry) below), so that retry ran and failed too. Every other
|
|
235
|
+
> code -- `EISDIR`, `ENOTDIR` and `ENOSPC` among them -- is never retried, because a retry cannot
|
|
236
|
+
> help it. Either way a consumer-side retry layer races the internal self-heal or repeats a call that
|
|
237
|
+
> already cannot succeed, and only delays the disable or back-off that your error path exists to
|
|
238
|
+
> trigger.
|
|
236
239
|
>
|
|
237
|
-
> Treat the **first** rejection as the signal to disable file logging or back off
|
|
238
|
-
> write again.
|
|
240
|
+
> Treat the **first** rejection as the signal to disable file logging or back off, and do not attempt
|
|
241
|
+
> the write again straight away. If you do want file logging to come back without a restart, re-arm
|
|
242
|
+
> on a timer -- see [recovering after a latch](#recovering-after-a-latch).
|
|
239
243
|
|
|
240
|
-
#### What the
|
|
244
|
+
#### What the Promise Rejects With
|
|
241
245
|
|
|
242
246
|
The rejection value is the underlying Node `fs` error - a `NodeJS.ErrnoException` passed through
|
|
243
247
|
unmodified, with `err.code`, `err.syscall`, `err.errno` and `err.path` all preserved.
|
|
@@ -277,29 +281,52 @@ async function logError(message) {
|
|
|
277
281
|
}
|
|
278
282
|
```
|
|
279
283
|
|
|
280
|
-
|
|
284
|
+
That latch is deliberately terminal for the lifetime of the process: nothing in it ever re-enables
|
|
285
|
+
file logging. Records are degraded rather than lost, because the console line is still written on
|
|
286
|
+
every call -- but a consumer using the file sink for audit or compliance should treat the first
|
|
287
|
+
rejection as an **alertable** event, or fail closed, rather than degrade silently.
|
|
288
|
+
|
|
289
|
+
#### Recovering After a Latch
|
|
290
|
+
|
|
291
|
+
If file logging must come back without a restart, re-arm the latch on a **timer**, never on the next
|
|
292
|
+
log call. Hold it closed for a fixed back-off window -- a minute or more, long enough that a
|
|
293
|
+
permissions fix or a freed disk has a chance to land -- then let exactly one write through. If that
|
|
294
|
+
write rejects, latch again and lengthen the window; if it succeeds, clear the latch. Anything faster
|
|
295
|
+
is a consumer-side retry under another name.
|
|
296
|
+
|
|
297
|
+
This also decides what your operators can see. `log-write-recovered` (see
|
|
298
|
+
[structured stderr notices](#structured-stderr-notices)) is emitted **only** on a subsequent
|
|
299
|
+
successful write, and a consumer that latches off permanently never issues one. Its entire stderr
|
|
300
|
+
output for the episode is a single `log-write-failed` followed by silence, which is indistinguishable
|
|
301
|
+
from a failure that self-healed -- so an alert-clear rule keyed on `log-write-recovered` will never
|
|
302
|
+
fire for it. Either treat `log-write-failed` as latching on the operator side too, or use the timed
|
|
303
|
+
re-arm above, which is what makes a recovery notice reachable at all.
|
|
304
|
+
|
|
305
|
+
#### Self-Healing Retry
|
|
281
306
|
|
|
282
307
|
A cached log directory can outlive the directory it describes - for example, something deletes
|
|
283
308
|
`logs/` while your process is running. To cover that, a failure whose `err.code` is `ENOENT`,
|
|
284
309
|
`EACCES`, `EPERM` or `EROFS` drops the cached directory entry and retries the write **exactly once**.
|
|
285
|
-
Any other code
|
|
310
|
+
Any other code -- `EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE` and the rest -- rejects on the first
|
|
311
|
+
attempt, with no retry.
|
|
286
312
|
|
|
287
|
-
A
|
|
288
|
-
|
|
289
|
-
one of
|
|
313
|
+
A retry that succeeds emits no *failure* notice: the promise resolves normally and the write counts
|
|
314
|
+
as a success, closing any failure episode already open for that directory with the usual
|
|
315
|
+
`log-write-recovered`. A rejection carrying one of those four codes has therefore already spent its
|
|
316
|
+
single retry; every other code was never retryable in the first place. The retry is per call and per
|
|
317
|
+
`Log` instance -- independent instances retry independently.
|
|
290
318
|
|
|
291
|
-
#### Structured stderr
|
|
319
|
+
#### Structured stderr Notices
|
|
292
320
|
|
|
293
321
|
Alongside the rejection, **Log** emits a machine-parseable notice to `stderr` so that operators have
|
|
294
322
|
something to key on. The write-failure path must never re-enter the logger itself, so these notices
|
|
295
|
-
bypass all formatting and configuration: they are
|
|
296
|
-
line (JSONL), regardless of your color, prefix, suffix or path settings.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Never leave a write unhandled on the assumption that stderr covers it.
|
|
323
|
+
bypass all formatting and configuration: they are emitted via `console.error` as one JSON object per
|
|
324
|
+
line (JSONL), regardless of your color, prefix, suffix or path settings. Anything that wraps or
|
|
325
|
+
patches `console.error` sees them too. Notices are informational; the promise rejection is the
|
|
326
|
+
failure signal.
|
|
300
327
|
|
|
301
328
|
| Field | Value |
|
|
302
|
-
|
|
|
329
|
+
| :---: | :--- |
|
|
303
330
|
| `ts` | ISO-8601 timestamp of the notice |
|
|
304
331
|
| `surface` | Always `"stonyx-logs"` |
|
|
305
332
|
| `sessionKey` | Always `null` |
|
|
@@ -312,7 +339,7 @@ Never leave a write unhandled on the assumption that stderr covers it.
|
|
|
312
339
|
`log-write-failed` payload:
|
|
313
340
|
|
|
314
341
|
| Field | Description |
|
|
315
|
-
|
|
|
342
|
+
| :---: | :--- |
|
|
316
343
|
| `targetLog` | Resolved path of the log file that could not be written |
|
|
317
344
|
| `path` | Resolved log *directory*, with trailing separator - this is the dedupe key |
|
|
318
345
|
| `syscall` | The failing syscall, e.g. `"mkdir"` or `"open"` |
|
|
@@ -322,10 +349,12 @@ Never leave a write unhandled on the assumption that stderr covers it.
|
|
|
322
349
|
|
|
323
350
|
`log-write-recovered` carries `targetLog`, `path` and `suppressedCount` only.
|
|
324
351
|
|
|
325
|
-
**Dedupe:** notices are one per failure episode, keyed on the resolved directory
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
**Dedupe:** notices are one per failure episode, keyed on the resolved directory and scoped to the
|
|
353
|
+
`Log` instance -- the failure counters are instance state, so two instances writing to the same
|
|
354
|
+
directory emit one notice each. The first failure for a directory emits `log-write-failed`; every
|
|
355
|
+
subsequent failure for that same directory is silent. The next successful write to it emits a single
|
|
356
|
+
`log-write-recovered` carrying `suppressedCount` - the number of notices that were suppressed, i.e.
|
|
357
|
+
`N - 1` for an episode of `N` consecutive failures.
|
|
329
358
|
Every failure still rejects its own promise; only the notices are deduped.
|
|
330
359
|
|
|
331
360
|
Five consecutive failures followed by one success emit exactly these two records:
|
|
@@ -358,7 +387,7 @@ const log = new Log({
|
|
|
358
387
|
|
|
359
388
|
| Option | Type | Default | Description |
|
|
360
389
|
| :---: | :---: | :---: | :--- |
|
|
361
|
-
| `logToFileByDefault` | **Boolean** | *false* | Option to change default setting for `logToFile` parameter of logging functions. When true, every log call becomes a rejectable file write -- see [handling write failures](#handling-write-failures). |
|
|
390
|
+
| `logToFileByDefault` | **Boolean** | *false* | Option to change default setting for `logToFile` parameter of logging functions. When true, every log call except `debug()` becomes a rejectable file write -- see [handling write failures](#handling-write-failures). |
|
|
362
391
|
| `logTimestamp` | **Boolean** | *false* | Option to include timestamp in console logging. Timestamps are automatically included in file logs. |
|
|
363
392
|
| `path` | **String** | *'logs/'* | Path in which to store log files. This setting is relative to your project's root directory. |
|
|
364
393
|
| `prefix` | **String** | *''* | Prefix string to prepend all log messages for all log types with the exception of *debug*. |
|
package/dist/index.d.ts
CHANGED
|
@@ -17,21 +17,128 @@ export default class Log {
|
|
|
17
17
|
directoryCache: Map<string, Promise<void>>;
|
|
18
18
|
writeFailures: Map<string, number>;
|
|
19
19
|
[key: string]: unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Logs `content` to the console in the `info` color, and to file when `logToFile` is true.
|
|
22
|
+
*
|
|
23
|
+
* **A failed file write rejects the returned promise, and that rejection is terminal - do not retry
|
|
24
|
+
* it.** `ENOENT`, `EACCES`, `EPERM` and `EROFS` have already been retried once internally by the
|
|
25
|
+
* time one reaches you; every other code (`EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE`, ...) is never
|
|
26
|
+
* retried, because a retry cannot help it. Handle the first rejection by disabling file logging or
|
|
27
|
+
* backing off - never by writing again. The rejection value is the underlying
|
|
28
|
+
* `NodeJS.ErrnoException` with `err.code` preserved, and leaving it unhandled terminates the
|
|
29
|
+
* process.
|
|
30
|
+
*
|
|
31
|
+
* @see https://github.com/abofs/stonyx-logs#handling-write-failures
|
|
32
|
+
*/
|
|
20
33
|
info: (content: string, logToFile?: boolean, overwrite?: boolean) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Logs `content` to the console in the `warn` color, and to file when `logToFile` is true.
|
|
36
|
+
*
|
|
37
|
+
* **A failed file write rejects the returned promise, and that rejection is terminal - do not retry
|
|
38
|
+
* it.** `ENOENT`, `EACCES`, `EPERM` and `EROFS` have already been retried once internally by the
|
|
39
|
+
* time one reaches you; every other code (`EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE`, ...) is never
|
|
40
|
+
* retried, because a retry cannot help it. Handle the first rejection by disabling file logging or
|
|
41
|
+
* backing off - never by writing again. The rejection value is the underlying
|
|
42
|
+
* `NodeJS.ErrnoException` with `err.code` preserved, and leaving it unhandled terminates the
|
|
43
|
+
* process.
|
|
44
|
+
*
|
|
45
|
+
* @see https://github.com/abofs/stonyx-logs#handling-write-failures
|
|
46
|
+
*/
|
|
21
47
|
warn: (content: string, logToFile?: boolean, overwrite?: boolean) => Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Logs `content` to the console in the `error` color, and to file when `logToFile` is true.
|
|
50
|
+
*
|
|
51
|
+
* **A failed file write rejects the returned promise, and that rejection is terminal - do not retry
|
|
52
|
+
* it.** `ENOENT`, `EACCES`, `EPERM` and `EROFS` have already been retried once internally by the
|
|
53
|
+
* time one reaches you; every other code (`EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE`, ...) is never
|
|
54
|
+
* retried, because a retry cannot help it. Handle the first rejection by disabling file logging or
|
|
55
|
+
* backing off - never by writing again. The rejection value is the underlying
|
|
56
|
+
* `NodeJS.ErrnoException` with `err.code` preserved, and leaving it unhandled terminates the
|
|
57
|
+
* process.
|
|
58
|
+
*
|
|
59
|
+
* @see https://github.com/abofs/stonyx-logs#handling-write-failures
|
|
60
|
+
*/
|
|
22
61
|
error: (content: string, logToFile?: boolean, overwrite?: boolean) => Promise<void>;
|
|
23
62
|
constructor(options?: Partial<LogOptions>);
|
|
24
63
|
defineType(type: string, setting: ColorSetting, options?: Partial<LogOptions> | null): void;
|
|
25
64
|
createConvenienceMethod(type: string): void;
|
|
65
|
+
/**
|
|
66
|
+
* Validates params and applies configuration-based defaults for logging. Every convenience method
|
|
67
|
+
* created by `defineType` - including `info`, `warn` and `error` - routes through here. Note that
|
|
68
|
+
* `debug` does not: it is a direct method that never consults `logToFileByDefault`.
|
|
69
|
+
*
|
|
70
|
+
* **A failed file write rejects the returned promise, and that rejection is terminal - do not retry
|
|
71
|
+
* it.** `ENOENT`, `EACCES`, `EPERM` and `EROFS` have already been retried once internally by the
|
|
72
|
+
* time one reaches you; every other code (`EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE`, ...) is never
|
|
73
|
+
* retried, because a retry cannot help it. Handle the first rejection by disabling file logging or
|
|
74
|
+
* backing off - never by writing again. The rejection value is the underlying
|
|
75
|
+
* `NodeJS.ErrnoException` with `err.code` preserved, and leaving it unhandled terminates the
|
|
76
|
+
* process.
|
|
77
|
+
*
|
|
78
|
+
* @see https://github.com/abofs/stonyx-logs#handling-write-failures
|
|
79
|
+
*/
|
|
26
80
|
logAction(type: string, content: string, logToFile?: boolean, overwrite?: boolean): Promise<void>;
|
|
27
81
|
getOptionForType(type: string, option: keyof LogOptions): LogOptions[keyof LogOptions];
|
|
28
82
|
chalk(): ReturnType<Color['getChalkInstance']>;
|
|
83
|
+
/**
|
|
84
|
+
* Logs to console, and conditionally to file.
|
|
85
|
+
*
|
|
86
|
+
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, so
|
|
87
|
+
* the caller receives the underlying `NodeJS.ErrnoException`. Callers must handle it: see the
|
|
88
|
+
* `writeToFile` contract below.
|
|
89
|
+
*/
|
|
29
90
|
log(content: string, type: string, logToFile: boolean, overwrite: boolean): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Direct hardcoded debug method (limited file logging). Unlike the `defineType` convenience
|
|
93
|
+
* methods this does not route through `logAction`, so it never consults `logToFileByDefault` and
|
|
94
|
+
* only writes to file when `logToFile` is passed explicitly.
|
|
95
|
+
*
|
|
96
|
+
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, on
|
|
97
|
+
* exactly the same contract as `log` - see `writeToFile` below.
|
|
98
|
+
*/
|
|
30
99
|
debug(content: unknown, logToFile?: boolean, overwrite?: boolean): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Writes `content` to the resolved target for `type`, creating the target's directory on first
|
|
102
|
+
* use for that directory.
|
|
103
|
+
*
|
|
104
|
+
* The returned promise rejecting with the underlying `NodeJS.ErrnoException` (with `err.code`
|
|
105
|
+
* preserved) is the sole failure signal - callers that care must handle it. The structured
|
|
106
|
+
* stderr notice emitted alongside a failure is informational only and is deduped per episode.
|
|
107
|
+
*
|
|
108
|
+
* On `ENOENT`, `EACCES`, `EPERM` or `EROFS` the cached directory entry is dropped and the write
|
|
109
|
+
* is retried exactly once; every other code rejects on the first attempt without a retry.
|
|
110
|
+
*
|
|
111
|
+
* A rejection is terminal either way. For those four codes the self-heal has already run and
|
|
112
|
+
* failed by the time one escapes; the rest were never retryable, so repeating the call cannot
|
|
113
|
+
* change the outcome. Consumers must not layer their own retry on top - doing so either races the
|
|
114
|
+
* internal one or repeats a call that already cannot succeed, and delays the disable/back-off the
|
|
115
|
+
* rejection exists to trigger. The correct response to the first rejection is to stop writing.
|
|
116
|
+
* See README "Handling write failures".
|
|
117
|
+
*/
|
|
31
118
|
writeToFile(type: string, content: string, overwrite: boolean): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* One structured stderr notice per failure episode: an emit on the first failure, silence while
|
|
121
|
+
* it keeps failing, one recovery emit on the next success. A null payload records a success.
|
|
122
|
+
*
|
|
123
|
+
* Keyed on the directory rather than the resolved target, matching `directoryCache`: every code
|
|
124
|
+
* in the retry allowlist is a directory-level condition, so keying on the target would strand an
|
|
125
|
+
* un-reaped entry - and silently drop its suppressed count - whenever a `{date}` template rotates
|
|
126
|
+
* away from a still-failing filename.
|
|
127
|
+
*/
|
|
32
128
|
noticeWriteResult(path: string, targetLog: string, payload: Record<string, unknown> | null): void;
|
|
129
|
+
/**
|
|
130
|
+
* The write-failure path must never re-enter this logger, so notices go straight out through
|
|
131
|
+
* `console.error` as a single JSONL record, bypassing all of this package's formatting. Field
|
|
132
|
+
* order is the canonical one mandated by the framework logging schema, with the optional
|
|
133
|
+
* `schemaVersion` last.
|
|
134
|
+
*/
|
|
33
135
|
emitNotice(severity: Severity, event: string, payload: Record<string, unknown>): void;
|
|
34
136
|
resolveFilename(template: string, type: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Ensures the target's directory exists. Both write paths auto-create the file itself, so no
|
|
139
|
+
* bootstrap write is needed. `targetLog` is unused but retained for signature compatibility:
|
|
140
|
+
* resolveFilename strips separators, so the only cached invariant is the directory.
|
|
141
|
+
*/
|
|
35
142
|
validateFileAndDirectory(path: string, targetLog: string): Promise<void>;
|
|
36
143
|
sanitizePath(path: string): string;
|
|
37
144
|
}
|
package/dist/index.js
CHANGED
|
@@ -72,7 +72,21 @@ export default class Log {
|
|
|
72
72
|
createConvenienceMethod(type) {
|
|
73
73
|
this[type] = (content, logToFile, overwrite = false) => this.logAction(type, content, logToFile, overwrite);
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Validates params and applies configuration-based defaults for logging. Every convenience method
|
|
77
|
+
* created by `defineType` - including `info`, `warn` and `error` - routes through here. Note that
|
|
78
|
+
* `debug` does not: it is a direct method that never consults `logToFileByDefault`.
|
|
79
|
+
*
|
|
80
|
+
* **A failed file write rejects the returned promise, and that rejection is terminal - do not retry
|
|
81
|
+
* it.** `ENOENT`, `EACCES`, `EPERM` and `EROFS` have already been retried once internally by the
|
|
82
|
+
* time one reaches you; every other code (`EISDIR`, `ENOTDIR`, `ENOSPC`, `EMFILE`, ...) is never
|
|
83
|
+
* retried, because a retry cannot help it. Handle the first rejection by disabling file logging or
|
|
84
|
+
* backing off - never by writing again. The rejection value is the underlying
|
|
85
|
+
* `NodeJS.ErrnoException` with `err.code` preserved, and leaving it unhandled terminates the
|
|
86
|
+
* process.
|
|
87
|
+
*
|
|
88
|
+
* @see https://github.com/abofs/stonyx-logs#handling-write-failures
|
|
89
|
+
*/
|
|
76
90
|
logAction(type, content, logToFile, overwrite) {
|
|
77
91
|
// set logToFile default based on class options when not set
|
|
78
92
|
if (logToFile === undefined)
|
|
@@ -93,7 +107,7 @@ export default class Log {
|
|
|
93
107
|
chalk() {
|
|
94
108
|
return this.color.getChalkInstance();
|
|
95
109
|
}
|
|
96
|
-
|
|
110
|
+
/**
|
|
97
111
|
* Logs to console, and conditionally to file.
|
|
98
112
|
*
|
|
99
113
|
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, so
|
|
@@ -118,8 +132,10 @@ export default class Log {
|
|
|
118
132
|
return;
|
|
119
133
|
await this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
|
|
120
134
|
}
|
|
121
|
-
|
|
122
|
-
* Direct hardcoded debug method (limited file logging).
|
|
135
|
+
/**
|
|
136
|
+
* Direct hardcoded debug method (limited file logging). Unlike the `defineType` convenience
|
|
137
|
+
* methods this does not route through `logAction`, so it never consults `logToFileByDefault` and
|
|
138
|
+
* only writes to file when `logToFile` is passed explicitly.
|
|
123
139
|
*
|
|
124
140
|
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, on
|
|
125
141
|
* exactly the same contract as `log` - see `writeToFile` below.
|
|
@@ -130,7 +146,7 @@ export default class Log {
|
|
|
130
146
|
return;
|
|
131
147
|
await this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
|
|
132
148
|
}
|
|
133
|
-
|
|
149
|
+
/**
|
|
134
150
|
* Writes `content` to the resolved target for `type`, creating the target's directory on first
|
|
135
151
|
* use for that directory.
|
|
136
152
|
*
|
|
@@ -141,11 +157,12 @@ export default class Log {
|
|
|
141
157
|
* On `ENOENT`, `EACCES`, `EPERM` or `EROFS` the cached directory entry is dropped and the write
|
|
142
158
|
* is retried exactly once; every other code rejects on the first attempt without a retry.
|
|
143
159
|
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
160
|
+
* A rejection is terminal either way. For those four codes the self-heal has already run and
|
|
161
|
+
* failed by the time one escapes; the rest were never retryable, so repeating the call cannot
|
|
162
|
+
* change the outcome. Consumers must not layer their own retry on top - doing so either races the
|
|
163
|
+
* internal one or repeats a call that already cannot succeed, and delays the disable/back-off the
|
|
164
|
+
* rejection exists to trigger. The correct response to the first rejection is to stop writing.
|
|
165
|
+
* See README "Handling write failures".
|
|
149
166
|
*/
|
|
150
167
|
async writeToFile(type, content, overwrite) {
|
|
151
168
|
const path = this.getOptionForType(type, 'path');
|
|
@@ -178,7 +195,7 @@ export default class Log {
|
|
|
178
195
|
}
|
|
179
196
|
this.noticeWriteResult(path, targetLog, null);
|
|
180
197
|
}
|
|
181
|
-
|
|
198
|
+
/**
|
|
182
199
|
* One structured stderr notice per failure episode: an emit on the first failure, silence while
|
|
183
200
|
* it keeps failing, one recovery emit on the next success. A null payload records a success.
|
|
184
201
|
*
|
|
@@ -207,10 +224,11 @@ export default class Log {
|
|
|
207
224
|
});
|
|
208
225
|
}
|
|
209
226
|
}
|
|
210
|
-
|
|
211
|
-
* The write-failure path must never re-enter this logger, so notices go straight
|
|
212
|
-
* single JSONL record
|
|
213
|
-
* schema, with the optional
|
|
227
|
+
/**
|
|
228
|
+
* The write-failure path must never re-enter this logger, so notices go straight out through
|
|
229
|
+
* `console.error` as a single JSONL record, bypassing all of this package's formatting. Field
|
|
230
|
+
* order is the canonical one mandated by the framework logging schema, with the optional
|
|
231
|
+
* `schemaVersion` last.
|
|
214
232
|
*/
|
|
215
233
|
emitNotice(severity, event, payload) {
|
|
216
234
|
const ts = new Date().toISOString();
|
|
@@ -246,7 +264,7 @@ export default class Log {
|
|
|
246
264
|
// sanitize: prevent path traversal and disallow directory separators
|
|
247
265
|
return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
|
|
248
266
|
}
|
|
249
|
-
|
|
267
|
+
/**
|
|
250
268
|
* Ensures the target's directory exists. Both write paths auto-create the file itself, so no
|
|
251
269
|
* bootstrap write is needed. `targetLog` is unused but retained for signature compatibility:
|
|
252
270
|
* resolveFilename strips separators, so the only cached invariant is the directory.
|