@hydranium/protocol 1.0.0-next.267 → 1.0.0-next.269

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/src/logger.ts CHANGED
@@ -39,15 +39,15 @@ let fileLevel: LogThreshold | undefined;
39
39
  * and every `for` / `sub` / `with` derivation preserves the implementation type.
40
40
  */
41
41
  export interface Logger {
42
- /** Log a message at error level. */
42
+ /** Requested work failed; logged once, by the code that ends the failure, not by one that rethrows it. */
43
43
  error(message?: string, ...args: unknown[]): this;
44
- /** Log a message at warn level. */
44
+ /** An anomaly that was handled, or work declined by design, such as a conflict or a cap; a clean run logs none. */
45
45
  warn(message?: string, ...args: unknown[]): this;
46
- /** Log a message at info level. */
46
+ /** A milestone the person running the server would recognise without knowing the internals. */
47
47
  info(message?: string, ...args: unknown[]): this;
48
- /** Log a message at debug level. */
48
+ /** Diagnosis; its level decides whether the line is built, never which code runs. */
49
49
  debug(message?: string, ...args: unknown[]): this;
50
- /** High-volume diagnostic lines, only emitted at the `'trace'` threshold. */
50
+ /** High-volume diagnosis, emitted only at the `'trace'` threshold; as for `debug`, no code runs only for it. */
51
51
  trace(message?: string, ...args: unknown[]): this;
52
52
  /** Log a message at info-level threshold with a `Log` label (rather than `Info`). */
53
53
  log(message?: string, ...args: unknown[]): this;
@@ -236,11 +236,41 @@ export function resolvedFromResponseError(error: ResponseError<unknown>): Resolv
236
236
  * to pass as a parameter for the same reason a number is: it is not itself
237
237
  * translatable text, so it needs no code of its own. A PROSE fragment is not,
238
238
  * and must become one code per value instead.
239
+ *
240
+ * For a reader of the client; the log takes {@link describeErrorWithStack}.
239
241
  */
240
242
  export function describeError(error: unknown): string {
241
243
  return error instanceof Error ? error.message : String(error);
242
244
  }
243
245
 
246
+ /**
247
+ * An error for the log: its stack where it has one, which names where it was
248
+ * thrown, and its causes, which name what failed when an error wraps another.
249
+ * Never for the client, whose reader cannot act on a stack.
250
+ */
251
+ export function describeErrorWithStack(error: unknown): string {
252
+ // A cause chain may loop back on itself.
253
+ return describeChain(error, new Set());
254
+ }
255
+
256
+ function describeChain(error: unknown, seen: Set<unknown>): string {
257
+ const lines: string[] = [];
258
+ let current: unknown = error;
259
+ let prefix = '';
260
+ do {
261
+ seen.add(current);
262
+ lines.push(prefix + (current instanceof Error ? (current.stack ?? current.message) : String(current)));
263
+ // Node fails a connect to several addresses with one, whose own message is empty.
264
+ if (current instanceof AggregateError) {
265
+ const errors: unknown[] = current.errors;
266
+ lines.push(...errors.filter(inner => !seen.has(inner)).map(inner => `Including: ${describeChain(inner, seen)}`));
267
+ }
268
+ current = current instanceof Error ? current.cause : undefined;
269
+ prefix = 'Caused by: ';
270
+ } while (current !== undefined && current !== null && !seen.has(current));
271
+ return lines.join('\n');
272
+ }
273
+
244
274
  /**
245
275
  * Recognises a declaration among a barrel's exports. The `format` check is what
246
276
  * discriminates: a `code` + `text` pair alone admits any object that happens to
package/src/tracer.ts CHANGED
@@ -12,6 +12,7 @@ import { Format, Logger, type LogThreshold } from './logger';
12
12
  import { NoopLogger } from './noop-logger';
13
13
  import { DefaultProfileSession, type ProfileSession } from './profile-session';
14
14
  import { Disposable } from './util';
15
+ import { describeErrorWithStack } from './messages/primitives';
15
16
 
16
17
  /** Module-global so nested timers don't reuse ids. */
17
18
  let nextTimerId = 0;
@@ -183,7 +184,7 @@ export class DefaultTracer implements Tracer {
183
184
  if (status === 'failed') {
184
185
  // Reporting the failure is the caller's, who receives it: a line at
185
186
  // warn here would exist only while `logLevel` is enabled.
186
- const detail = error instanceof Error ? (error.stack ?? error.message) : String(error);
187
+ const detail = describeErrorWithStack(error);
187
188
  this.logger.debug(`${label} [#${id}] failed: ${detail}`);
188
189
  }
189
190
  };