log-publisher.ts64 lines · main
1import { env } from './env.js';
2import { getRedis } from './lib/redis.js';
3import type { LogEntry } from './log-collector.js';
4
5export interface InvocationEnvelope {
6 projectId: string;
7 deploymentId: string;
8 invocationId: string;
9 functionName: string;
10 status: 'ok' | 'err';
11 durationMs: number;
12 touchedTables: readonly string[];
13 userLogs: LogEntry[];
14 errCode?: string;
15 errMessage?: string;
16 ts: string;
17}
18
19/**
20 * Publish an invocation envelope to the project's Redis stream
21 * (`logs:{projectId}`). The stream is MAXLEN-capped so it doesn't grow
22 * unbounded; durability lives in `function_logs` (apps/api fan-out).
23 *
24 * Silent no-op when Redis isn't configured — invocations still succeed,
25 * `briven logs --tail` just has nothing to show.
26 */
27export async function publishInvocation(envelope: InvocationEnvelope): Promise<void> {
28 const redis = getRedis();
29 if (!redis) return;
30 const key = `logs:${envelope.projectId}`;
31 const fields: Array<string> = [
32 'kind',
33 'invocation',
34 'deploymentId',
35 envelope.deploymentId,
36 'invocationId',
37 envelope.invocationId,
38 'functionName',
39 envelope.functionName,
40 'status',
41 envelope.status,
42 'durationMs',
43 String(envelope.durationMs),
44 'touchedTables',
45 envelope.touchedTables.join(','),
46 'logs',
47 JSON.stringify(envelope.userLogs),
48 'ts',
49 envelope.ts,
50 ];
51 if (envelope.errCode) fields.push('errCode', envelope.errCode);
52 if (envelope.errMessage) fields.push('errMessage', envelope.errMessage);
53
54 try {
55 // ioredis xadd signature: xadd(key, 'MAXLEN', '~', cap, '*', ...fields)
56 await redis.xadd(key, 'MAXLEN', '~', String(env.BRIVEN_LOGS_STREAM_MAX), '*', ...fields);
57 } catch (err) {
58 // Don't let a flaky Redis kill the invocation path. Log to real stderr.
59 console.warn(
60 '[runtime] logs publish failed:',
61 err instanceof Error ? err.message : String(err),
62 );
63 }
64}