@steve31415/baselib 3.14.0 → 3.15.0

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,7 +1,9 @@
1
1
  export declare function requestStarted(): void;
2
2
  export declare function requestEnded(): void;
3
- /** The fields a diagnostic event carries: requests in flight right now and
4
- * how long ago the last one ended (undefined before the first request). */
3
+ /** The fields an event carries: requests in flight right now and how long
4
+ * ago the last one ended. Before the first request has ended there is no
5
+ * such moment, so `sinceLastRequestMs` is omitted rather than invented;
6
+ * `inFlightRequests` alone marks a server that has not finished one yet. */
5
7
  export declare function activitySnapshot(): {
6
8
  inFlightRequests: number;
7
9
  sinceLastRequestMs?: number;
@@ -10,5 +12,5 @@ export declare function activitySnapshot(): {
10
12
  * request ends when the handler returns its Response; a streaming body
11
13
  * that outlives that is not tracked. */
12
14
  export declare function trackActivity<A extends unknown[]>(fetch: (...args: A) => Response | Promise<Response>): (...args: A) => Promise<Response>;
13
- /** Test hook: forget everything. */
15
+ /** Test hook: forget everything, including the logger registration. */
14
16
  export declare function resetActivity(): void;
package/dist/activity.js CHANGED
@@ -1,5 +1,5 @@
1
- // Request activity of this process, for log events that need to know
2
- // whether Cloud Run had CPU allocated at the time: with request-based
1
+ // Request activity of this process, carried by every server log event so
2
+ // any of them can say whether Cloud Run had CPU allocated at the time: with request-based
3
3
  // billing (the fleet default) an instance's CPU is throttled to almost
4
4
  // nothing between requests, and timers, connection handshakes and
5
5
  // credential refreshes crawl. A connection failure logged with
@@ -7,10 +7,13 @@
7
7
  // throttled tail; one with `inFlightRequests: 1` did not.
8
8
  //
9
9
  // `startServer` tracks every HTTP request automatically. A custom server
10
- // wraps its fetch handler with `trackActivity`. WebSocket sessions are not
10
+ // wraps its fetch handler with `trackActivity`. Wrapping is also what
11
+ // registers this module as the logger's process-meta source (process-meta.ts), so a
12
+ // process that serves nothing logs no activity fields. WebSocket sessions are not
11
13
  // counted (they hold CPU on Cloud Run, but the upgrade bypasses fetch), so
12
14
  // on a WebSocket-heavy app zero in-flight requests does not prove the
13
15
  // instance was idle.
16
+ import { setProcessMetaSource } from './process-meta.js';
14
17
  let inFlight = 0;
15
18
  let lastRequestEndedAt = 0;
16
19
  export function requestStarted() {
@@ -20,8 +23,10 @@ export function requestEnded() {
20
23
  inFlight = Math.max(0, inFlight - 1);
21
24
  lastRequestEndedAt = Date.now();
22
25
  }
23
- /** The fields a diagnostic event carries: requests in flight right now and
24
- * how long ago the last one ended (undefined before the first request). */
26
+ /** The fields an event carries: requests in flight right now and how long
27
+ * ago the last one ended. Before the first request has ended there is no
28
+ * such moment, so `sinceLastRequestMs` is omitted rather than invented;
29
+ * `inFlightRequests` alone marks a server that has not finished one yet. */
25
30
  export function activitySnapshot() {
26
31
  return {
27
32
  inFlightRequests: inFlight,
@@ -32,6 +37,7 @@ export function activitySnapshot() {
32
37
  * request ends when the handler returns its Response; a streaming body
33
38
  * that outlives that is not tracked. */
34
39
  export function trackActivity(fetch) {
40
+ setProcessMetaSource(activitySnapshot);
35
41
  return async (...args) => {
36
42
  requestStarted();
37
43
  try {
@@ -42,8 +48,9 @@ export function trackActivity(fetch) {
42
48
  }
43
49
  };
44
50
  }
45
- /** Test hook: forget everything. */
51
+ /** Test hook: forget everything, including the logger registration. */
46
52
  export function resetActivity() {
53
+ setProcessMetaSource(undefined);
47
54
  inFlight = 0;
48
55
  lastRequestEndedAt = 0;
49
56
  }
package/dist/db.d.ts CHANGED
@@ -100,9 +100,9 @@ export declare const FLEET_POOL_DEFAULTS: {
100
100
  * any transaction or advisory lock it held is gone.
101
101
  * Each event carries `elapsedMs` (time in that phase: the connect
102
102
  * attempt, the wait, the idle spell, the checkout), the pg `code`, the
103
- * pool's counts, `inFlightRequests` + `sinceLastRequestMs` (`activity.ts`:
104
- * was Cloud Run CPU allocated?) and, given a Cloud SQL credential reader,
105
- * `credentialRemainingMs`. Query:
103
+ * pool's counts and, given a Cloud SQL credential reader,
104
+ * `credentialRemainingMs`; the logger adds request activity, as on every
105
+ * server event (`activity.ts`: was Cloud Run CPU allocated?). Query:
106
106
  * `['apps'] | where kind == 'db-connection-loss' | summarize count() by app, tostring(meta.phase)`.
107
107
  *
108
108
  * The checked-out path is the one that used to crash the process: pg-pool
package/dist/db.js CHANGED
@@ -5,8 +5,8 @@
5
5
  import { readdir, readFile } from 'node:fs/promises';
6
6
  import { join } from 'node:path';
7
7
  import pg from 'pg';
8
- import { activitySnapshot } from './activity.js';
9
8
  import { requireEnv } from './config.js';
9
+ import { processMeta } from './process-meta.js';
10
10
  import { serializeError } from './log-core.js';
11
11
  // DATE columns come back as 'YYYY-MM-DD' strings, not JS Dates: calendar
12
12
  // dates are timezone-free by meaning, and a Date object smears them across
@@ -106,9 +106,9 @@ const instrumented = new WeakSet();
106
106
  * any transaction or advisory lock it held is gone.
107
107
  * Each event carries `elapsedMs` (time in that phase: the connect
108
108
  * attempt, the wait, the idle spell, the checkout), the pg `code`, the
109
- * pool's counts, `inFlightRequests` + `sinceLastRequestMs` (`activity.ts`:
110
- * was Cloud Run CPU allocated?) and, given a Cloud SQL credential reader,
111
- * `credentialRemainingMs`. Query:
109
+ * pool's counts and, given a Cloud SQL credential reader,
110
+ * `credentialRemainingMs`; the logger adds request activity, as on every
111
+ * server event (`activity.ts`: was Cloud Run CPU allocated?). Query:
112
112
  * `['apps'] | where kind == 'db-connection-loss' | summarize count() by app, tostring(meta.phase)`.
113
113
  *
114
114
  * The checked-out path is the one that used to crash the process: pg-pool
@@ -134,7 +134,6 @@ export function logPoolErrors(pool, logger, options = {}) {
134
134
  ...(since === undefined ? {} : { elapsedMs: Date.now() - since }),
135
135
  code: err.code,
136
136
  pool: { total: pool.totalCount, idle: pool.idleCount, waiting: pool.waitingCount },
137
- ...activitySnapshot(),
138
137
  ...(options.credentialExpiry
139
138
  ? expiry === undefined
140
139
  ? { credentialUnknown: true }
@@ -144,8 +143,9 @@ export function logPoolErrors(pool, logger, options = {}) {
144
143
  };
145
144
  if (log)
146
145
  log.warn(message, meta);
146
+ // No logger to add the activity fields, so add them here.
147
147
  else
148
- console.warn(message, { kind: CONNECTION_LOSS_KIND, ...meta, error: serializeError(err) });
148
+ console.warn(message, { kind: CONNECTION_LOSS_KIND, ...processMeta(), ...meta, error: serializeError(err) });
149
149
  };
150
150
  // Per-client bookkeeping across one checkout: the borrower-side error
151
151
  // listener (pg-pool drops its own at checkout) and when the client was
package/dist/http.js CHANGED
@@ -55,8 +55,9 @@ export function startServer(opts) {
55
55
  assertBootSafety();
56
56
  logUncaughtErrors(opts.logger);
57
57
  const port = opts.port ?? Number(process.env.PORT ?? 8080);
58
- // Every request counts as activity (activity.ts) so connection-loss
59
- // events can say whether Cloud Run had CPU allocated at the time.
58
+ // Every request counts as activity (activity.ts), and wrapping registers
59
+ // the logger's activity source: every event from here on says whether
60
+ // Cloud Run had CPU allocated at the time.
60
61
  const server = serve({ fetch: trackActivity(opts.app.fetch), port }, (info) => {
61
62
  opts.logger.info('server started', { port: info.port });
62
63
  });
@@ -121,6 +121,10 @@ export interface EventFactoryOptions {
121
121
  app: string;
122
122
  source: 'server' | 'browser';
123
123
  context?: Record<string, unknown>;
124
+ /** Process-level fields merged under `meta` on every event, read at event
125
+ * time (the server logger passes process-meta.ts's request-activity source).
126
+ * Ignored for browser events. The caller's own meta wins on a name clash. */
127
+ processMeta?: () => Record<string, unknown> | undefined;
124
128
  }
125
129
  export declare function makeEvent(opts: EventFactoryOptions, level: LogLevel, message: string, meta?: Record<string, unknown>): LogEvent;
126
130
  /** The event for one logger call plus, the first time a site's meta breaks
package/dist/log-core.js CHANGED
@@ -361,6 +361,11 @@ function buildEvent(opts, level, message, meta) {
361
361
  if (rewrites.length > 0)
362
362
  m = { ...m, logShape: rewrites.join('+') };
363
363
  }
364
+ // Added after shaping: a fixed vocabulary that must not count against the
365
+ // site's leaf budget.
366
+ const processMeta = opts.source === 'server' ? opts.processMeta?.() : undefined;
367
+ if (processMeta)
368
+ m = { ...processMeta, ...m };
364
369
  const event = {
365
370
  _time: new Date().toISOString(),
366
371
  app: opts.app,
package/dist/log.js CHANGED
@@ -9,6 +9,7 @@
9
9
  // log-core's key-shape guard rewrites offenders and WARNs once per site.
10
10
  import { writeSync } from 'node:fs';
11
11
  import { LogShipper, makeEvents, truncate, } from './log-core.js';
12
+ import { processMeta } from './process-meta.js';
12
13
  // makeEvent/makeEvents are exported for app test fakes: routing a recording
13
14
  // logger's calls through makeEvents applies the key-shape guard (strict under
14
15
  // vitest) to fake-logged sites too.
@@ -147,7 +148,7 @@ export function createLogger(opts) {
147
148
  process.once('beforeExit', () => void shipper.finalFlush());
148
149
  }
149
150
  function build(context) {
150
- const factory = { app: opts.app, source: 'server', context };
151
+ const factory = { app: opts.app, source: 'server', context, processMeta };
151
152
  const emit = (level, message, meta) => {
152
153
  for (const event of makeEvents(factory, level, message, meta)) {
153
154
  if (stdout)
@@ -0,0 +1,3 @@
1
+ export declare function setProcessMetaSource(next: (() => Record<string, unknown>) | undefined): void;
2
+ /** The registered source's fields now; undefined when none is registered. */
3
+ export declare function processMeta(): Record<string, unknown> | undefined;
@@ -0,0 +1,16 @@
1
+ // The one process-wide source of fields the server logger merges under
2
+ // `meta` on every event. Today that is request activity (activity.ts),
3
+ // registered by whatever serves HTTP (trackActivity, so startServer and
4
+ // custom servers alike): a process that never registers — a Cloud Run Job,
5
+ // a CLI, a test — emits no such fields at all, so absence means "not a
6
+ // server" and 0 means "idle server". Internal: not part of the package's
7
+ // public surface, and kept apart from log.ts so neither the logger nor
8
+ // activity.ts depends on the other.
9
+ let source;
10
+ export function setProcessMetaSource(next) {
11
+ source = next;
12
+ }
13
+ /** The registered source's fields now; undefined when none is registered. */
14
+ export function processMeta() {
15
+ return source?.();
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve31415/baselib",
3
- "version": "3.14.0",
3
+ "version": "3.15.0",
4
4
  "description": "Plasticine new-world shared platform library: logging, auth, service-to-service auth, db, HTTP, sync, app updates",
5
5
  "type": "module",
6
6
  "license": "MIT",