@timber-js/app 0.2.0-alpha.38 → 0.2.0-alpha.39

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.
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Logger — structured logging with environment-aware formatting.
3
3
  *
4
- * timber.js does not ship a logger. Users export any object with
5
- * info/warn/error/debug methods from instrumentation.ts and the framework
6
- * picks it up. Silent if no logger export is present.
4
+ * timber.js ships a DefaultLogger that writes human-readable lines to stderr
5
+ * in production. Users can export a custom logger from instrumentation.ts to
6
+ * replace it with pino, winston, or any TimberLogger-compatible object.
7
7
  *
8
8
  * See design/17-logging.md §"Production Logging"
9
9
  */
@@ -16,14 +16,15 @@ export interface TimberLogger {
16
16
  }
17
17
  /**
18
18
  * Set the user-provided logger. Called by the instrumentation loader
19
- * when it finds a `logger` export in instrumentation.ts.
19
+ * when it finds a `logger` export in instrumentation.ts. Replaces
20
+ * the DefaultLogger entirely.
20
21
  */
21
22
  export declare function setLogger(logger: TimberLogger): void;
22
23
  /**
23
- * Get the current logger, or null if none configured.
24
- * Framework-internal used at framework event points to emit structured logs.
24
+ * Get the current logger. Always non-null returns DefaultLogger when
25
+ * no custom logger is configured.
25
26
  */
26
- export declare function getLogger(): TimberLogger | null;
27
+ export declare function getLogger(): TimberLogger;
27
28
  /** Log a completed request. Level: info. */
28
29
  export declare function logRequestCompleted(data: {
29
30
  method: string;
@@ -69,6 +70,22 @@ export declare function logRenderError(data: {
69
70
  export declare function logProxyError(data: {
70
71
  error: unknown;
71
72
  }): void;
73
+ /** Log unhandled error in server action. Level: error. */
74
+ export declare function logActionError(data: {
75
+ method: string;
76
+ path: string;
77
+ error: unknown;
78
+ }): void;
79
+ /** Log unhandled error in route handler. Level: error. */
80
+ export declare function logRouteError(data: {
81
+ method: string;
82
+ path: string;
83
+ error: unknown;
84
+ }): void;
85
+ /** Log SSR streaming error (post-shell). Level: error. */
86
+ export declare function logStreamingError(data: {
87
+ error: unknown;
88
+ }): void;
72
89
  /** Log waitUntil() adapter missing (once at startup). Level: warn. */
73
90
  export declare function logWaitUntilUnsupported(): void;
74
91
  /** Log waitUntil() promise rejection. Level: warn. */
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAEpD;AAED;;;GAGG;AACH,wBAAgB,SAAS,IAAI,YAAY,GAAG,IAAI,CAE/C;AAsBD,4CAA4C;AAC5C,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAEP;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAE/E;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,IAAI,CAEP;AAED,kDAAkD;AAClD,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,IAAI,CAEP;AAED,6DAA6D;AAC7D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAM/F;AAED,sDAAsD;AACtD,wBAAgB,cAAc,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAQ3F;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAM5D;AAED,sEAAsE;AACtE,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEnE;AAED,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAEpF;AAED,oCAAoC;AACpC,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAE7D"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/server/logger.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAiBxC;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CA+ClE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE,yBAAyB,GAClC,SAAS,CAuJX;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAwBtE;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,OAAO,EACvB,eAAe,EAAE,OAAO,GACvB,SAAS,GAAG,IAAI,CA8BlB"}
1
+ {"version":3,"file":"node-stream-transforms.d.ts","sourceRoot":"","sources":["../../src/server/node-stream-transforms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAkBxC;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CA+ClE;AAID;;;;;;;;;;;;;;;;GAgBG;AACH;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE,yBAAyB,GAClC,SAAS,CAuJX;AAOD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAwBtE;AAoBD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,OAAO,EACvB,eAAe,EAAE,OAAO,GACvB,SAAS,GAAG,IAAI,CA8BlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
1
+ {"version":3,"file":"route-handler.d.ts","sourceRoot":"","sources":["../../src/server/route-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;AAE/E,wEAAwE;AACxE,MAAM,MAAM,WAAW,GAAG;KACvB,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,YAAY;CACjC,CAAC;AAOF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,EAAE,CAyBpE;AAID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyC/F"}
@@ -1 +1 @@
1
- {"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAsEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,CAkFzC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,aAAa,EAAE,QAAQ,GACvC,cAAc,CAAC,UAAU,CAAC,CAE5B;AAgFD;;;;;;;;;;;;GAYG;AACH,2CAA2C;AAC3C,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,UAAU,CAAC,CA2B5B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,GACvB,QAAQ,CASV"}
1
+ {"version":3,"file":"ssr-render.d.ts","sourceRoot":"","sources":["../../src/server/ssr-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAsEvC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAErC;AAED,wEAAwE;AACxE,eAAO,MAAM,cAAc,SAAkB,CAAC;AAU9C;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,SAAS,EAClB,OAAO,CAAC,EAAE;IACR,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GACA,OAAO,CAAC,OAAO,aAAa,EAAE,QAAQ,CAAC,CAsFzC;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,aAAa,EAAE,QAAQ,GACvC,cAAc,CAAC,UAAU,CAAC,CAE5B;AAoFD;;;;;;;;;;;;GAYG;AACH,2CAA2C;AAC3C,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,UAAU,CAAC,CA2B5B;AAWD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,cAAc,CAAC,UAAU,CAAC,EACtC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,GACvB,QAAQ,CASV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timber-js/app",
3
- "version": "0.2.0-alpha.38",
3
+ "version": "0.2.0-alpha.39",
4
4
  "description": "Vite-native React framework built for Servers and Serverless Platforms — correct HTTP semantics, real status codes, pages that work without JavaScript",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -50,16 +50,24 @@ export function createSingleflight(opts?: SingleflightOptions): Singleflight {
50
50
  () => reject(new SingleflightTimeoutError(key, timeoutMs)),
51
51
  timeoutMs
52
52
  );
53
- fn().then(
54
- (value) => {
55
- clearTimeout(timer);
56
- resolve(value);
57
- },
58
- (err) => {
59
- clearTimeout(timer);
60
- reject(err);
61
- }
62
- );
53
+ // Wrap in try/catch so a synchronous throw from fn()
54
+ // (e.g. argument validation) still clears the timer.
55
+ // Without this, the timer leaks until expiry.
56
+ try {
57
+ fn().then(
58
+ (value) => {
59
+ clearTimeout(timer);
60
+ resolve(value);
61
+ },
62
+ (err) => {
63
+ clearTimeout(timer);
64
+ reject(err);
65
+ }
66
+ );
67
+ } catch (err) {
68
+ clearTimeout(timer);
69
+ reject(err);
70
+ }
63
71
  });
64
72
  } else {
65
73
  promise = fn();
@@ -32,6 +32,7 @@ import { enforceBodyLimits, enforceFieldLimit, type BodyLimitsConfig } from './b
32
32
  import { parseFormData } from './form-data.js';
33
33
  import type { FormFlashData } from './form-flash.js';
34
34
  import { checkVersionSkew, applyReloadHeaders } from './version-skew.js';
35
+ import { logActionError } from './logger.js';
35
36
 
36
37
  // ─── Types ────────────────────────────────────────────────────────────────
37
38
 
@@ -193,7 +194,7 @@ async function handleRscAction(
193
194
  });
194
195
  } catch (error) {
195
196
  // Log full error server-side for debugging
196
- console.error('[timber] server action error:', error);
197
+ logActionError({ method: req.method, path: new URL(req.url).pathname, error });
197
198
 
198
199
  // Return structured error response — ActionError gets its code/data,
199
200
  // unexpected errors get sanitized { code: 'INTERNAL_ERROR' }
@@ -309,7 +310,7 @@ async function handleFormAction(
309
310
  renderer: config.revalidateRenderer,
310
311
  });
311
312
  } catch (error) {
312
- console.error('[timber] server action error:', error);
313
+ logActionError({ method: req.method, path: new URL(req.url).pathname, error });
313
314
 
314
315
  // Return the error as flash data for re-render.
315
316
  // handleActionError produces { serverError } for ActionErrors
@@ -0,0 +1,95 @@
1
+ /**
2
+ * DefaultLogger — human-readable stderr logging when no custom logger is configured.
3
+ *
4
+ * Ships as the fallback so production deployments always have error visibility,
5
+ * even without an `instrumentation.ts` logger export. Output is one line per
6
+ * event, designed for `fly logs`, `kubectl logs`, Cloudflare dashboard tails, etc.
7
+ *
8
+ * Format:
9
+ * [timber] ERROR message key=value key=value trace_id=4bf92f35
10
+ * [timber] WARN message key=value key=value trace_id=4bf92f35
11
+ * [timber] INFO message method=GET path=/dashboard status=200 durationMs=43 trace_id=4bf92f35
12
+ *
13
+ * Behavior:
14
+ * - Suppressed entirely in dev mode (dev logging handles all output)
15
+ * - `debug` suppressed unless TIMBER_DEBUG is set
16
+ * - Replaced entirely when a custom logger is set via `setLogger()`
17
+ *
18
+ * See design/17-logging.md §"DefaultLogger"
19
+ */
20
+
21
+ import { isDevMode, isDebug } from './debug.js';
22
+ import { formatSsrError } from './error-formatter.js';
23
+ import type { TimberLogger } from './logger.js';
24
+
25
+ /**
26
+ * Format data fields as `key=value` pairs for human-readable output.
27
+ * - `error` key is serialized via formatSsrError for stack trace cleanup
28
+ * - `trace_id` is truncated to 8 chars for readability (full ID in OTEL)
29
+ * - Other values are stringified inline
30
+ */
31
+ function formatDataFields(data?: Record<string, unknown>): string {
32
+ if (!data) return '';
33
+
34
+ const parts: string[] = [];
35
+ let traceId: string | undefined;
36
+
37
+ for (const [key, value] of Object.entries(data)) {
38
+ if (key === 'trace_id') {
39
+ // Defer trace_id to the end
40
+ traceId = typeof value === 'string' ? value : String(value);
41
+ continue;
42
+ }
43
+ if (key === 'error') {
44
+ // Serialize errors with formatSsrError for clean output
45
+ parts.push(`error=${formatSsrError(value)}`);
46
+ continue;
47
+ }
48
+ if (value === undefined || value === null) continue;
49
+ parts.push(`${key}=${value}`);
50
+ }
51
+
52
+ // trace_id always last, truncated to 8 chars for readability
53
+ if (traceId) {
54
+ parts.push(`trace_id=${traceId.slice(0, 8)}`);
55
+ }
56
+
57
+ return parts.length > 0 ? ' ' + parts.join(' ') : '';
58
+ }
59
+
60
+ /** Pad level string to fixed width for alignment. */
61
+ function padLevel(level: string): string {
62
+ return level.padEnd(5);
63
+ }
64
+
65
+ export function createDefaultLogger(): TimberLogger {
66
+ return {
67
+ error(msg: string, data?: Record<string, unknown>): void {
68
+ if (isDevMode()) return;
69
+ const fields = formatDataFields(data);
70
+ // Use process.stderr.write for consistent output (no extra newline handling)
71
+ process.stderr.write(`[timber] ${padLevel('ERROR')} ${msg}${fields}\n`);
72
+ },
73
+
74
+ warn(msg: string, data?: Record<string, unknown>): void {
75
+ if (isDevMode()) return;
76
+ const fields = formatDataFields(data);
77
+ process.stderr.write(`[timber] ${padLevel('WARN')} ${msg}${fields}\n`);
78
+ },
79
+
80
+ info(msg: string, data?: Record<string, unknown>): void {
81
+ if (isDevMode()) return;
82
+ const fields = formatDataFields(data);
83
+ process.stderr.write(`[timber] ${padLevel('INFO')} ${msg}${fields}\n`);
84
+ },
85
+
86
+ debug(msg: string, data?: Record<string, unknown>): void {
87
+ // debug is suppressed in dev (dev logger handles it) and in
88
+ // production unless TIMBER_DEBUG is explicitly set.
89
+ if (isDevMode()) return;
90
+ if (!isDebug()) return;
91
+ const fields = formatDataFields(data);
92
+ process.stderr.write(`[timber] ${padLevel('DEBUG')} ${msg}${fields}\n`);
93
+ },
94
+ };
95
+ }
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import { DenySignal, RedirectSignal, RenderError } from './primitives.js';
12
+ import { logRenderError } from './logger.js';
12
13
 
13
14
  // ─── Types ───────────────────────────────────────────────────────────────────
14
15
 
@@ -169,7 +170,7 @@ function handleSignal(error: unknown, responseHeaders: Headers): FlushResult {
169
170
  }
170
171
 
171
172
  // Unknown error → HTTP 500
172
- console.error('[timber] Unhandled render-phase error:', error);
173
+ logRenderError({ method: '', path: '', error });
173
174
  return {
174
175
  response: new Response(null, {
175
176
  status: 500,
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * Logger — structured logging with environment-aware formatting.
3
3
  *
4
- * timber.js does not ship a logger. Users export any object with
5
- * info/warn/error/debug methods from instrumentation.ts and the framework
6
- * picks it up. Silent if no logger export is present.
4
+ * timber.js ships a DefaultLogger that writes human-readable lines to stderr
5
+ * in production. Users can export a custom logger from instrumentation.ts to
6
+ * replace it with pino, winston, or any TimberLogger-compatible object.
7
7
  *
8
8
  * See design/17-logging.md §"Production Logging"
9
9
  */
10
10
 
11
11
  import { getTraceStore } from './tracing.js';
12
- import { formatSsrError } from './error-formatter.js';
13
- import { isDebug } from './debug.js';
12
+ import { createDefaultLogger } from './default-logger.js';
14
13
 
15
14
  // ─── Logger Interface ─────────────────────────────────────────────────────
16
15
 
@@ -24,21 +23,24 @@ export interface TimberLogger {
24
23
 
25
24
  // ─── Logger Registry ──────────────────────────────────────────────────────
26
25
 
27
- let _logger: TimberLogger | null = null;
26
+ // Initialize with DefaultLogger so production errors are never silent.
27
+ // Replaced when setLogger() is called from instrumentation.ts.
28
+ let _logger: TimberLogger = createDefaultLogger();
28
29
 
29
30
  /**
30
31
  * Set the user-provided logger. Called by the instrumentation loader
31
- * when it finds a `logger` export in instrumentation.ts.
32
+ * when it finds a `logger` export in instrumentation.ts. Replaces
33
+ * the DefaultLogger entirely.
32
34
  */
33
35
  export function setLogger(logger: TimberLogger): void {
34
36
  _logger = logger;
35
37
  }
36
38
 
37
39
  /**
38
- * Get the current logger, or null if none configured.
39
- * Framework-internal used at framework event points to emit structured logs.
40
+ * Get the current logger. Always non-null returns DefaultLogger when
41
+ * no custom logger is configured.
40
42
  */
41
- export function getLogger(): TimberLogger | null {
43
+ export function getLogger(): TimberLogger {
42
44
  return _logger;
43
45
  }
44
46
 
@@ -71,12 +73,12 @@ export function logRequestCompleted(data: {
71
73
  /** Number of concurrent in-flight requests (including this one) at completion time. */
72
74
  concurrency?: number;
73
75
  }): void {
74
- _logger?.info('request completed', withTraceContext(data));
76
+ _logger.info('request completed', withTraceContext(data));
75
77
  }
76
78
 
77
79
  /** Log request received. Level: debug. */
78
80
  export function logRequestReceived(data: { method: string; path: string }): void {
79
- _logger?.debug('request received', withTraceContext(data));
81
+ _logger.debug('request received', withTraceContext(data));
80
82
  }
81
83
 
82
84
  /** Log a slow request warning. Level: warn. */
@@ -88,7 +90,7 @@ export function logSlowRequest(data: {
88
90
  /** Number of concurrent in-flight requests at the time the slow request completed. */
89
91
  concurrency?: number;
90
92
  }): void {
91
- _logger?.warn('slow request exceeded threshold', withTraceContext(data));
93
+ _logger.warn('slow request exceeded threshold', withTraceContext(data));
92
94
  }
93
95
 
94
96
  /** Log middleware short-circuit. Level: debug. */
@@ -97,54 +99,55 @@ export function logMiddlewareShortCircuit(data: {
97
99
  path: string;
98
100
  status: number;
99
101
  }): void {
100
- _logger?.debug('middleware short-circuited', withTraceContext(data));
102
+ _logger.debug('middleware short-circuited', withTraceContext(data));
101
103
  }
102
104
 
103
105
  /** Log unhandled error in middleware phase. Level: error. */
104
106
  export function logMiddlewareError(data: { method: string; path: string; error: unknown }): void {
105
- if (_logger) {
106
- _logger.error('unhandled error in middleware phase', withTraceContext(data));
107
- } else if (isDebug()) {
108
- console.error('[timber] middleware error', data.error);
109
- }
107
+ _logger.error('unhandled error in middleware phase', withTraceContext(data));
110
108
  }
111
109
 
112
110
  /** Log unhandled render-phase error. Level: error. */
113
111
  export function logRenderError(data: { method: string; path: string; error: unknown }): void {
114
- if (_logger) {
115
- _logger.error('unhandled render-phase error', withTraceContext(data));
116
- } else if (isDebug()) {
117
- // No logger configured — fall back to console.error in dev with
118
- // cleaned-up error messages (vendor paths rewritten, hints added).
119
- console.error('[timber] render error:', formatSsrError(data.error));
120
- }
112
+ _logger.error('unhandled render-phase error', withTraceContext(data));
121
113
  }
122
114
 
123
115
  /** Log proxy.ts uncaught error. Level: error. */
124
116
  export function logProxyError(data: { error: unknown }): void {
125
- if (_logger) {
126
- _logger.error('proxy.ts threw uncaught error', withTraceContext(data));
127
- } else if (isDebug()) {
128
- console.error('[timber] proxy error', data.error);
129
- }
117
+ _logger.error('proxy.ts threw uncaught error', withTraceContext(data));
118
+ }
119
+
120
+ /** Log unhandled error in server action. Level: error. */
121
+ export function logActionError(data: { method: string; path: string; error: unknown }): void {
122
+ _logger.error('unhandled server action error', withTraceContext(data));
123
+ }
124
+
125
+ /** Log unhandled error in route handler. Level: error. */
126
+ export function logRouteError(data: { method: string; path: string; error: unknown }): void {
127
+ _logger.error('unhandled route handler error', withTraceContext(data));
128
+ }
129
+
130
+ /** Log SSR streaming error (post-shell). Level: error. */
131
+ export function logStreamingError(data: { error: unknown }): void {
132
+ _logger.error('SSR streaming error (post-shell)', withTraceContext(data));
130
133
  }
131
134
 
132
135
  /** Log waitUntil() adapter missing (once at startup). Level: warn. */
133
136
  export function logWaitUntilUnsupported(): void {
134
- _logger?.warn('adapter does not support waitUntil()');
137
+ _logger.warn('adapter does not support waitUntil()');
135
138
  }
136
139
 
137
140
  /** Log waitUntil() promise rejection. Level: warn. */
138
141
  export function logWaitUntilRejected(data: { error: unknown }): void {
139
- _logger?.warn('waitUntil() promise rejected', withTraceContext(data));
142
+ _logger.warn('waitUntil() promise rejected', withTraceContext(data));
140
143
  }
141
144
 
142
145
  /** Log staleWhileRevalidate refetch failure. Level: warn. */
143
146
  export function logSwrRefetchFailed(data: { cacheKey: string; error: unknown }): void {
144
- _logger?.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
147
+ _logger.warn('staleWhileRevalidate refetch failed', withTraceContext(data));
145
148
  }
146
149
 
147
150
  /** Log cache miss. Level: debug. */
148
151
  export function logCacheMiss(data: { cacheKey: string }): void {
149
- _logger?.debug('timber.cache MISS', withTraceContext(data));
152
+ _logger.debug('timber.cache MISS', withTraceContext(data));
150
153
  }
@@ -31,6 +31,7 @@ import {
31
31
  type FlightInjectionEvent,
32
32
  } from './flight-injection-state.js';
33
33
  import { withTimeout, RenderTimeoutError } from './render-timeout.js';
34
+ import { logStreamingError } from './logger.js';
34
35
 
35
36
  // ─── Head Injection ──────────────────────────────────────────────────────────
36
37
 
@@ -308,7 +309,7 @@ export function createNodeErrorHandler(signal?: AbortSignal): Transform {
308
309
  return;
309
310
  }
310
311
 
311
- console.error('[timber] SSR streaming error (post-shell):', error.message || error);
312
+ logStreamingError({ error });
312
313
  transform.push(Buffer.from(NOINDEX_SCRIPT, 'utf-8'));
313
314
  transform.end();
314
315
  });
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { RouteContext } from './types.js';
12
+ import { logRouteError } from './logger.js';
12
13
 
13
14
  // ─── Types ───────────────────────────────────────────────────────────────
14
15
 
@@ -122,7 +123,7 @@ async function runHandler(handler: RouteHandler, ctx: RouteContext): Promise<Res
122
123
  const res = await handler(ctx);
123
124
  return mergeResponseHeaders(res, ctx.headers);
124
125
  } catch (error) {
125
- console.error('[timber] Uncaught error in route.ts handler:', error);
126
+ logRouteError({ method: ctx.req.method, path: new URL(ctx.req.url).pathname, error });
126
127
  return new Response(null, { status: 500 });
127
128
  }
128
129
  }
@@ -23,8 +23,8 @@
23
23
  import type { ReactNode } from 'react';
24
24
  import { renderToReadableStream } from 'react-dom/server';
25
25
 
26
- import { formatSsrError } from './error-formatter.js';
27
26
  import { createRenderTimeout, RenderTimeoutError } from './render-timeout.js';
27
+ import { logRenderError, logStreamingError } from './logger.js';
28
28
 
29
29
  /**
30
30
  * Inline script that injects <meta name="robots" content="noindex"> into <head>.
@@ -187,7 +187,7 @@ export async function renderSsrNodeStream(
187
187
 
188
188
  onError(error: unknown) {
189
189
  if (isAbortError(error) || signal?.aborted) return;
190
- console.error('[timber] SSR render error:', formatSsrError(error));
190
+ logRenderError({ method: '', path: '', error });
191
191
  },
192
192
  });
193
193
 
@@ -207,10 +207,14 @@ export async function renderSsrNodeStream(
207
207
  renderTimeout.signal.addEventListener(
208
208
  'abort',
209
209
  () => {
210
- console.error(
211
- `[timber] SSR render timed out after ${timeoutMs}ms — aborting. ` +
212
- 'A Suspense component or downstream fetch may be hanging.'
213
- );
210
+ logRenderError({
211
+ method: '',
212
+ path: '',
213
+ error: new Error(
214
+ `SSR render timed out after ${timeoutMs}ms — aborting. ` +
215
+ 'A Suspense component or downstream fetch may be hanging.'
216
+ ),
217
+ });
214
218
  abort(renderTimeout.signal.reason);
215
219
  },
216
220
  { once: true }
@@ -269,13 +273,17 @@ async function renderViaReadableStream(
269
273
  onError(error: unknown) {
270
274
  if (isAbortError(error) || effectiveSignal?.aborted) return;
271
275
  if (error instanceof RenderTimeoutError) {
272
- console.error(
273
- `[timber] SSR render timed out after ${timeoutMs}ms — aborting. ` +
274
- 'A Suspense component or downstream fetch may be hanging.'
275
- );
276
+ logRenderError({
277
+ method: '',
278
+ path: '',
279
+ error: new Error(
280
+ `SSR render timed out after ${timeoutMs}ms — aborting. ` +
281
+ 'A Suspense component or downstream fetch may be hanging.'
282
+ ),
283
+ });
276
284
  return;
277
285
  }
278
- console.error('[timber] SSR render error:', formatSsrError(error));
286
+ logRenderError({ method: '', path: '', error });
279
287
  },
280
288
  });
281
289
  } catch (error) {
@@ -346,7 +354,7 @@ export function wrapStreamWithErrorHandling(
346
354
  controller.close();
347
355
  return;
348
356
  }
349
- console.error('[timber] SSR streaming error (post-shell):', formatSsrError(error));
357
+ logStreamingError({ error });
350
358
  controller.enqueue(encoder.encode(NOINDEX_SCRIPT));
351
359
  controller.close();
352
360
  }