@stonyx/logs 1.0.1-alpha.16 → 1.0.1-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import Color, { type ColorSetting } from './color.js';
2
+ export type Severity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
2
3
  export interface LogOptions {
3
4
  logToFileByDefault: boolean;
4
5
  logTimestamp: boolean;
@@ -28,8 +29,8 @@ export default class Log {
28
29
  log(content: string, type: string, logToFile: boolean, overwrite: boolean): Promise<void>;
29
30
  debug(content: unknown, logToFile?: boolean, overwrite?: boolean): Promise<void>;
30
31
  writeToFile(type: string, content: string, overwrite: boolean): Promise<void>;
31
- noticeWriteResult(targetLog: string, payload: Record<string, unknown> | null): void;
32
- emitNotice(severity: string, event: string, payload: Record<string, unknown>): void;
32
+ noticeWriteResult(path: string, targetLog: string, payload: Record<string, unknown> | null): void;
33
+ emitNotice(severity: Severity, event: string, payload: Record<string, unknown>): void;
33
34
  resolveFilename(template: string, type: string): string;
34
35
  validateFileAndDirectory(path: string, targetLog: string): Promise<void>;
35
36
  sanitizePath(path: string): string;
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ export default class Log {
25
25
  typeOptions = {};
26
26
  // resolved directory -> in-flight or settled mkdir; instance-scoped so it cannot outlive its directory
27
27
  directoryCache = new Map();
28
- // resolved target -> consecutive write failures, used to dedupe the operator notice
28
+ // resolved directory -> consecutive write failures, used to dedupe the operator notice
29
29
  writeFailures = new Map();
30
30
  constructor(options = {}) {
31
31
  const merged = {
@@ -93,7 +93,7 @@ export default class Log {
93
93
  chalk() {
94
94
  return this.color.getChalkInstance();
95
95
  }
96
- // logs to console, and conditionally to file
96
+ // logs to console, and conditionally to file; propagates any writeToFile rejection to the caller
97
97
  async log(content, type, logToFile, overwrite) {
98
98
  const logTimestamp = this.getOptionForType(type, 'logTimestamp');
99
99
  const timestamp = `[${new Date().toLocaleString('en-US')}]`;
@@ -112,13 +112,24 @@ export default class Log {
112
112
  return;
113
113
  await this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
114
114
  }
115
- // direct hardcoded debug method (log to file functionality is limited)
115
+ // direct hardcoded debug method (limited file logging); propagates any writeToFile rejection
116
116
  async debug(content, logToFile = false, overwrite = true) {
117
117
  console.dir(content, { depth: 6 }); // eslint-disable-line no-console
118
118
  if (!logToFile)
119
119
  return;
120
120
  await this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
121
121
  }
122
+ /*
123
+ * Writes `content` to the resolved target for `type`, creating the target's directory on first
124
+ * use for that directory.
125
+ *
126
+ * The returned promise rejecting with the underlying `NodeJS.ErrnoException` (with `err.code`
127
+ * preserved) is the sole failure signal - callers that care must handle it. The structured
128
+ * stderr notice emitted alongside a failure is informational only and is deduped per episode.
129
+ *
130
+ * On `ENOENT`, `EACCES`, `EPERM` or `EROFS` the cached directory entry is dropped and the write
131
+ * is retried exactly once; every other code rejects on the first attempt without a retry.
132
+ */
122
133
  async writeToFile(type, content, overwrite) {
123
134
  const path = this.getOptionForType(type, 'path');
124
135
  const filenameTemplate = this.getOptionForType(type, 'filename');
@@ -139,29 +150,63 @@ export default class Log {
139
150
  }
140
151
  catch (error) {
141
152
  const { code, syscall } = error;
142
- this.noticeWriteResult(targetLog, { targetLog, path, syscall, code, cached });
153
+ this.noticeWriteResult(path, targetLog, {
154
+ targetLog,
155
+ path,
156
+ syscall,
157
+ code,
158
+ cached,
159
+ });
143
160
  throw error;
144
161
  }
145
- this.noticeWriteResult(targetLog, null);
162
+ this.noticeWriteResult(path, targetLog, null);
146
163
  }
147
- // One structured stderr notice per failure episode: an emit on the first failure, silence while
148
- // it keeps failing, one recovery emit on the next success. A null payload records a success.
149
- noticeWriteResult(targetLog, payload) {
150
- const failures = this.writeFailures.get(targetLog) ?? 0;
164
+ /*
165
+ * One structured stderr notice per failure episode: an emit on the first failure, silence while
166
+ * it keeps failing, one recovery emit on the next success. A null payload records a success.
167
+ *
168
+ * Keyed on the directory rather than the resolved target, matching `directoryCache`: every code
169
+ * in the retry allowlist is a directory-level condition, so keying on the target would strand an
170
+ * un-reaped entry - and silently drop its suppressed count - whenever a `{date}` template rotates
171
+ * away from a still-failing filename.
172
+ */
173
+ noticeWriteResult(path, targetLog, payload) {
174
+ const failures = this.writeFailures.get(path) ?? 0;
151
175
  if (payload) {
152
- this.writeFailures.set(targetLog, failures + 1);
153
- if (!failures)
154
- this.emitNotice('error', 'log-write-failed', { ...payload, suppressedCount: 0 });
176
+ this.writeFailures.set(path, failures + 1);
177
+ if (!failures) {
178
+ this.emitNotice('error', 'log-write-failed', {
179
+ ...payload,
180
+ suppressedCount: 0,
181
+ });
182
+ }
155
183
  }
156
184
  else if (failures) {
157
- this.writeFailures.delete(targetLog);
158
- this.emitNotice('warn', 'log-write-recovered', { targetLog, suppressedCount: failures - 1 });
185
+ this.writeFailures.delete(path);
186
+ this.emitNotice('warn', 'log-write-recovered', {
187
+ targetLog,
188
+ path,
189
+ suppressedCount: failures - 1,
190
+ });
159
191
  }
160
192
  }
161
- // the write-failure path must never re-enter this logger, so notices go straight to stderr
193
+ /*
194
+ * The write-failure path must never re-enter this logger, so notices go straight to stderr as a
195
+ * single JSONL record. Field order is the canonical one mandated by the framework logging
196
+ * schema, with the optional `schemaVersion` last.
197
+ */
162
198
  emitNotice(severity, event, payload) {
163
199
  const ts = new Date().toISOString();
164
- console.error(JSON.stringify({ ts, schemaVersion: 1, surface: 'stonyx-logs', sessionKey: null, project: null, severity, event, payload }));
200
+ console.error(JSON.stringify({
201
+ ts,
202
+ surface: 'stonyx-logs',
203
+ sessionKey: null,
204
+ project: null,
205
+ severity,
206
+ event,
207
+ payload,
208
+ schemaVersion: 1,
209
+ }));
165
210
  }
166
211
  // resolves template variables in a filename string
167
212
  resolveFilename(template, type) {
@@ -184,9 +229,11 @@ export default class Log {
184
229
  // sanitize: prevent path traversal and disallow directory separators
185
230
  return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
186
231
  }
187
- // Ensures the target's directory exists. Both write paths auto-create the file itself, so no
188
- // bootstrap write is needed. `targetLog` is unused but retained for signature compatibility:
189
- // resolveFilename strips separators, so the only cached invariant is the directory.
232
+ /*
233
+ * Ensures the target's directory exists. Both write paths auto-create the file itself, so no
234
+ * bootstrap write is needed. `targetLog` is unused but retained for signature compatibility:
235
+ * resolveFilename strips separators, so the only cached invariant is the directory.
236
+ */
190
237
  async validateFileAndDirectory(path, targetLog) {
191
238
  let pending = this.directoryCache.get(path);
192
239
  if (!pending) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonyx/logs",
3
- "version": "1.0.1-alpha.16",
3
+ "version": "1.0.1-alpha.17",
4
4
  "description": "Simplified logging for node applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",