refactor(backend): route startup logs through logger
This commit is contained in:
parent
21aefb589d
commit
7016fc1ad5
@ -1,5 +1,6 @@
|
|||||||
import { initializeAllContainers, registerContainers } from '@bytelyst/cosmos';
|
import { initializeAllContainers, registerContainers } from '@bytelyst/cosmos';
|
||||||
import type { ContainerConfig } from '@bytelyst/cosmos';
|
import type { ContainerConfig } from '@bytelyst/cosmos';
|
||||||
|
import type { Logger } from '@bytelyst/logger';
|
||||||
import { config } from './config.js';
|
import { config } from './config.js';
|
||||||
|
|
||||||
const CONTAINER_DEFS: Record<string, ContainerConfig> = {
|
const CONTAINER_DEFS: Record<string, ContainerConfig> = {
|
||||||
@ -27,7 +28,9 @@ const CONTAINER_DEFS: Record<string, ContainerConfig> = {
|
|||||||
palace_diaries: { partitionKeyPath: '/userId' },
|
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);
|
registerContainers(CONTAINER_DEFS);
|
||||||
|
|
||||||
const shouldInit = config.NODE_ENV !== 'production' || process.env.COSMOS_AUTO_INIT === 'true';
|
const shouldInit = config.NODE_ENV !== 'production' || process.env.COSMOS_AUTO_INIT === 'true';
|
||||||
@ -37,9 +40,8 @@ export async function initCosmosIfNeeded(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await initializeAllContainers();
|
await initializeAllContainers();
|
||||||
process.stdout.write('[notelett-backend] Cosmos containers ensured\n');
|
logger?.info('Cosmos containers ensured');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
logger?.error('Cosmos init failed', err);
|
||||||
process.stderr.write(`[notelett-backend] Cosmos init failed: ${msg}\n`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,16 @@ import { stripHtmlForEmbedding } from '../../lib/embeddings.js';
|
|||||||
|
|
||||||
// ── Types ──────────────────────────────────────────────────────────
|
// ── 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 {
|
export interface PromptScheduleDoc {
|
||||||
id: string;
|
id: string;
|
||||||
productId: 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 });
|
trackEvent('scheduled_action_fired', schedule.userId, { scheduleId: schedule.id, templateSlug: template?.slug ?? schedule.templateId });
|
||||||
ran++;
|
ran++;
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const msg = err instanceof Error ? err.message : 'Unknown scheduler error';
|
schedulerLogger.error({ err, scheduleId: schedule.id }, 'Failed to run scheduled prompt');
|
||||||
process.stderr.write(`[scheduler] Failed to run schedule ${schedule.id}: ${msg}\n`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ran;
|
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;
|
if (schedulerInterval) return;
|
||||||
schedulerInterval = setInterval(() => {
|
schedulerInterval = setInterval(() => {
|
||||||
void runSchedulerTick();
|
void runSchedulerTick();
|
||||||
|
|||||||
@ -6,12 +6,18 @@ const startServiceMock = vi.fn(async () => undefined);
|
|||||||
const initCosmosIfNeededMock = vi.fn(async () => undefined);
|
const initCosmosIfNeededMock = vi.fn(async () => undefined);
|
||||||
const initDatastoreMock = vi.fn(() => undefined);
|
const initDatastoreMock = vi.fn(() => undefined);
|
||||||
const diagnosticsRoutesMock = vi.fn(async () => undefined);
|
const diagnosticsRoutesMock = vi.fn(async () => undefined);
|
||||||
|
const startSchedulerLoopMock = vi.fn();
|
||||||
|
const stopSchedulerLoopMock = vi.fn();
|
||||||
|
|
||||||
const appMock = {
|
const appMock = {
|
||||||
register: vi.fn(async () => undefined),
|
register: vi.fn(async () => undefined),
|
||||||
get: vi.fn(),
|
get: vi.fn(),
|
||||||
post: vi.fn(),
|
post: vi.fn(),
|
||||||
addHook: vi.fn(),
|
addHook: vi.fn(),
|
||||||
|
log: {
|
||||||
|
error: vi.fn(),
|
||||||
|
info: vi.fn(),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.mock('@bytelyst/fastify-core', () => ({
|
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/routes.js', () => ({ notePromptRoutes: vi.fn() }));
|
||||||
vi.mock('./modules/note-prompts/scheduler.js', () => ({
|
vi.mock('./modules/note-prompts/scheduler.js', () => ({
|
||||||
promptSchedulerRoutes: vi.fn(),
|
promptSchedulerRoutes: vi.fn(),
|
||||||
startSchedulerLoop: vi.fn(),
|
startSchedulerLoop: startSchedulerLoopMock,
|
||||||
stopSchedulerLoop: vi.fn(),
|
stopSchedulerLoop: stopSchedulerLoopMock,
|
||||||
}));
|
}));
|
||||||
vi.mock('./modules/intake/routes.js', () => ({ intakeRoutes: vi.fn() }));
|
vi.mock('./modules/intake/routes.js', () => ({ intakeRoutes: vi.fn() }));
|
||||||
vi.mock('./modules/note-collaborators/routes.js', () => ({ noteCollaboratorRoutes: vi.fn() }));
|
vi.mock('./modules/note-collaborators/routes.js', () => ({ noteCollaboratorRoutes: vi.fn() }));
|
||||||
@ -51,6 +57,7 @@ vi.mock('./lib/config.js', () => ({
|
|||||||
PORT: 4016,
|
PORT: 4016,
|
||||||
HOST: '0.0.0.0',
|
HOST: '0.0.0.0',
|
||||||
JWT_SECRET: 'test-secret',
|
JWT_SECRET: 'test-secret',
|
||||||
|
NODE_ENV: 'test',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
vi.mock('./lib/product-config.js', () => ({
|
vi.mock('./lib/product-config.js', () => ({
|
||||||
@ -78,12 +85,16 @@ describe('server bootstrap', () => {
|
|||||||
it('initializes app, routes, and starts service', async () => {
|
it('initializes app, routes, and starts service', async () => {
|
||||||
await import('./server.js');
|
await import('./server.js');
|
||||||
|
|
||||||
expect(initCosmosIfNeededMock).toHaveBeenCalledOnce();
|
expect(initCosmosIfNeededMock).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
error: expect.any(Function),
|
||||||
|
info: expect.any(Function),
|
||||||
|
}));
|
||||||
expect(initDatastoreMock).toHaveBeenCalledOnce();
|
expect(initDatastoreMock).toHaveBeenCalledOnce();
|
||||||
expect(createServiceAppMock).toHaveBeenCalledOnce();
|
expect(createServiceAppMock).toHaveBeenCalledOnce();
|
||||||
expect(registerOptionalJwtContextMock).toHaveBeenCalledOnce();
|
expect(registerOptionalJwtContextMock).toHaveBeenCalledOnce();
|
||||||
expect(appMock.register).toHaveBeenCalledTimes(14);
|
expect(appMock.register).toHaveBeenCalledTimes(14);
|
||||||
expect(diagnosticsRoutesMock).toHaveBeenCalledWith(appMock);
|
expect(diagnosticsRoutesMock).toHaveBeenCalledWith(appMock);
|
||||||
|
expect(startSchedulerLoopMock).toHaveBeenCalledWith(60_000, appMock.log);
|
||||||
expect(startServiceMock).toHaveBeenCalledWith(appMock, { port: 4016, host: '0.0.0.0' });
|
expect(startServiceMock).toHaveBeenCalledWith(appMock, { port: 4016, host: '0.0.0.0' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { createServiceApp, registerOptionalJwtContext, startService } from '@bytelyst/fastify-core';
|
import { createServiceApp, registerOptionalJwtContext, startService } from '@bytelyst/fastify-core';
|
||||||
|
import { createLogger } from '@bytelyst/logger';
|
||||||
import { jwtVerify } from 'jose';
|
import { jwtVerify } from 'jose';
|
||||||
import { noteAgentActionRoutes } from './modules/note-agent-actions/routes.js';
|
import { noteAgentActionRoutes } from './modules/note-agent-actions/routes.js';
|
||||||
import { ecosystemPhase1Routes } from './modules/ecosystem-phase1/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';
|
import * as noteRepo from './modules/notes/repository.js';
|
||||||
|
|
||||||
const jwtSecret = new TextEncoder().encode(config.JWT_SECRET);
|
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();
|
initDatastore();
|
||||||
|
|
||||||
const app = await createServiceApp({
|
const app = await createServiceApp({
|
||||||
@ -74,7 +79,7 @@ await registerApiPlugin(noteCollaboratorRoutes);
|
|||||||
await registerApiPlugin(palaceRoutes);
|
await registerApiPlugin(palaceRoutes);
|
||||||
|
|
||||||
// ── Start scheduler loop (F25) ────────────────────────────────────
|
// ── Start scheduler loop (F25) ────────────────────────────────────
|
||||||
startSchedulerLoop();
|
startSchedulerLoop(60_000, app.log);
|
||||||
initWebhookSubscriber();
|
initWebhookSubscriber();
|
||||||
app.addHook('onClose', async () => { stopSchedulerLoop(); stopWebhookSubscriber(); });
|
app.addHook('onClose', async () => { stopSchedulerLoop(); stopWebhookSubscriber(); });
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user