refactor(backend): route startup logs through logger

This commit is contained in:
Saravana Achu Mac 2026-05-05 11:00:38 -07:00
parent 21aefb589d
commit 7016fc1ad5
4 changed files with 42 additions and 12 deletions

View File

@ -1,5 +1,6 @@
import { initializeAllContainers, registerContainers } from '@bytelyst/cosmos';
import type { ContainerConfig } from '@bytelyst/cosmos';
import type { Logger } from '@bytelyst/logger';
import { config } from './config.js';
const CONTAINER_DEFS: Record<string, ContainerConfig> = {
@ -27,7 +28,9 @@ const CONTAINER_DEFS: Record<string, ContainerConfig> = {
palace_diaries: { partitionKeyPath: '/userId' },
};
export async function initCosmosIfNeeded(): Promise<void> {
type CosmosInitLogger = Pick<Logger, 'error' | 'info'>;
export async function initCosmosIfNeeded(logger?: CosmosInitLogger): Promise<void> {
registerContainers(CONTAINER_DEFS);
const shouldInit = config.NODE_ENV !== 'production' || process.env.COSMOS_AUTO_INIT === 'true';
@ -37,9 +40,8 @@ export async function initCosmosIfNeeded(): Promise<void> {
try {
await initializeAllContainers();
process.stdout.write('[notelett-backend] Cosmos containers ensured\n');
logger?.info('Cosmos containers ensured');
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
process.stderr.write(`[notelett-backend] Cosmos init failed: ${msg}\n`);
logger?.error('Cosmos init failed', err);
}
}

View File

@ -22,6 +22,16 @@ import { stripHtmlForEmbedding } from '../../lib/embeddings.js';
// ── Types ──────────────────────────────────────────────────────────
type SchedulerLogger = {
error: (details: Record<string, unknown>, message: string) => void;
};
let schedulerLogger: SchedulerLogger = { error: () => undefined };
export function setSchedulerLogger(logger: SchedulerLogger): void {
schedulerLogger = logger;
}
export interface PromptScheduleDoc {
id: string;
productId: string;
@ -216,15 +226,17 @@ export async function runSchedulerTick(): Promise<number> {
trackEvent('scheduled_action_fired', schedule.userId, { scheduleId: schedule.id, templateSlug: template?.slug ?? schedule.templateId });
ran++;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Unknown scheduler error';
process.stderr.write(`[scheduler] Failed to run schedule ${schedule.id}: ${msg}\n`);
schedulerLogger.error({ err, scheduleId: schedule.id }, 'Failed to run scheduled prompt');
}
}
return ran;
}
export function startSchedulerLoop(intervalMs = 60_000): void {
export function startSchedulerLoop(intervalMs = 60_000, logger?: SchedulerLogger): void {
if (logger) {
setSchedulerLogger(logger);
}
if (schedulerInterval) return;
schedulerInterval = setInterval(() => {
void runSchedulerTick();

View File

@ -6,12 +6,18 @@ const startServiceMock = vi.fn(async () => undefined);
const initCosmosIfNeededMock = vi.fn(async () => undefined);
const initDatastoreMock = vi.fn(() => undefined);
const diagnosticsRoutesMock = vi.fn(async () => undefined);
const startSchedulerLoopMock = vi.fn();
const stopSchedulerLoopMock = vi.fn();
const appMock = {
register: vi.fn(async () => undefined),
get: vi.fn(),
post: vi.fn(),
addHook: vi.fn(),
log: {
error: vi.fn(),
info: vi.fn(),
},
};
vi.mock('@bytelyst/fastify-core', () => ({
@ -36,8 +42,8 @@ vi.mock('./modules/ecosystem-phase3/routes.js', () => ({ ecosystemPhase3Routes:
vi.mock('./modules/note-prompts/routes.js', () => ({ notePromptRoutes: vi.fn() }));
vi.mock('./modules/note-prompts/scheduler.js', () => ({
promptSchedulerRoutes: vi.fn(),
startSchedulerLoop: vi.fn(),
stopSchedulerLoop: vi.fn(),
startSchedulerLoop: startSchedulerLoopMock,
stopSchedulerLoop: stopSchedulerLoopMock,
}));
vi.mock('./modules/intake/routes.js', () => ({ intakeRoutes: vi.fn() }));
vi.mock('./modules/note-collaborators/routes.js', () => ({ noteCollaboratorRoutes: vi.fn() }));
@ -51,6 +57,7 @@ vi.mock('./lib/config.js', () => ({
PORT: 4016,
HOST: '0.0.0.0',
JWT_SECRET: 'test-secret',
NODE_ENV: 'test',
},
}));
vi.mock('./lib/product-config.js', () => ({
@ -78,12 +85,16 @@ describe('server bootstrap', () => {
it('initializes app, routes, and starts service', async () => {
await import('./server.js');
expect(initCosmosIfNeededMock).toHaveBeenCalledOnce();
expect(initCosmosIfNeededMock).toHaveBeenCalledWith(expect.objectContaining({
error: expect.any(Function),
info: expect.any(Function),
}));
expect(initDatastoreMock).toHaveBeenCalledOnce();
expect(createServiceAppMock).toHaveBeenCalledOnce();
expect(registerOptionalJwtContextMock).toHaveBeenCalledOnce();
expect(appMock.register).toHaveBeenCalledTimes(14);
expect(diagnosticsRoutesMock).toHaveBeenCalledWith(appMock);
expect(startSchedulerLoopMock).toHaveBeenCalledWith(60_000, appMock.log);
expect(startServiceMock).toHaveBeenCalledWith(appMock, { port: 4016, host: '0.0.0.0' });
});
});

View File

@ -1,4 +1,5 @@
import { createServiceApp, registerOptionalJwtContext, startService } from '@bytelyst/fastify-core';
import { createLogger } from '@bytelyst/logger';
import { jwtVerify } from 'jose';
import { noteAgentActionRoutes } from './modules/note-agent-actions/routes.js';
import { ecosystemPhase1Routes } from './modules/ecosystem-phase1/routes.js';
@ -27,8 +28,12 @@ import { findShareByToken } from './modules/note-shares/repository.js';
import * as noteRepo from './modules/notes/repository.js';
const jwtSecret = new TextEncoder().encode(config.JWT_SECRET);
const startupLogger = createLogger({
service: config.SERVICE_NAME,
isDev: config.NODE_ENV !== 'production',
});
await initCosmosIfNeeded();
await initCosmosIfNeeded(startupLogger);
initDatastore();
const app = await createServiceApp({
@ -74,7 +79,7 @@ await registerApiPlugin(noteCollaboratorRoutes);
await registerApiPlugin(palaceRoutes);
// ── Start scheduler loop (F25) ────────────────────────────────────
startSchedulerLoop();
startSchedulerLoop(60_000, app.log);
initWebhookSubscriber();
app.addHook('onClose', async () => { stopSchedulerLoop(); stopWebhookSubscriber(); });