@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.
- package/dist/activity.d.ts +5 -3
- package/dist/activity.js +13 -6
- package/dist/db.d.ts +3 -3
- package/dist/db.js +6 -6
- package/dist/http.js +3 -2
- package/dist/log-core.d.ts +4 -0
- package/dist/log-core.js +5 -0
- package/dist/log.js +2 -1
- package/dist/process-meta.d.ts +3 -0
- package/dist/process-meta.js +16 -0
- package/package.json +1 -1
package/dist/activity.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare function requestStarted(): void;
|
|
2
2
|
export declare function requestEnded(): void;
|
|
3
|
-
/** The fields
|
|
4
|
-
*
|
|
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,
|
|
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`.
|
|
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
|
|
24
|
-
*
|
|
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,
|
|
104
|
-
*
|
|
105
|
-
* `
|
|
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,
|
|
110
|
-
*
|
|
111
|
-
* `
|
|
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)
|
|
59
|
-
//
|
|
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
|
});
|
package/dist/log-core.d.ts
CHANGED
|
@@ -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,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