chore: reduce polling log noise across backend and nginx (#336)

This commit is contained in:
Daniel Volz
2026-02-27 00:54:21 +01:00
committed by GitHub
parent 19ba4bb7d2
commit 6b27d234d9
13 changed files with 226 additions and 28 deletions
+121
View File
@@ -33,6 +33,7 @@
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^4.0.18",
"drizzle-kit": "^0.31.9",
"pino-pretty": "^13.1.3",
"supertest": "^7.2.2",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
@@ -3017,6 +3018,13 @@
"node": ">=18"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -3161,6 +3169,16 @@
"node": ">= 12"
}
},
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -3888,6 +3906,16 @@
"safe-buffer": "^5.0.1"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -4025,6 +4053,13 @@
"node": ">=12.0.0"
}
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
"integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-decode-uri-component": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
@@ -4500,6 +4535,13 @@
"node": ">=18.0.0"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"dev": true,
"license": "MIT"
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
@@ -4611,6 +4653,16 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/joycon": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/js-base64": {
"version": "3.7.8",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz",
@@ -4852,6 +4904,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -5117,6 +5179,41 @@
"split2": "^4.0.0"
}
},
"node_modules/pino-pretty": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
"integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
"dev": true,
"license": "MIT",
"dependencies": {
"colorette": "^2.0.7",
"dateformat": "^4.6.3",
"fast-copy": "^4.0.0",
"fast-safe-stringify": "^2.1.1",
"help-me": "^5.0.0",
"joycon": "^3.1.1",
"minimist": "^1.2.6",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pump": "^3.0.0",
"secure-json-parse": "^4.0.0",
"sonic-boom": "^4.0.1",
"strip-json-comments": "^5.0.2"
},
"bin": {
"pino-pretty": "bin.js"
}
},
"node_modules/pino-pretty/node_modules/pino-abstract-transport": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"split2": "^4.0.0"
}
},
"node_modules/pino-std-serializers": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
@@ -5174,6 +5271,17 @@
"integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==",
"license": "ISC"
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"dev": true,
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
@@ -5630,6 +5738,19 @@
"reusify": "^1.0.0"
}
},
"node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/superagent": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
+1
View File
@@ -42,6 +42,7 @@
"@types/supertest": "^6.0.2",
"@vitest/coverage-v8": "^4.0.18",
"drizzle-kit": "^0.31.9",
"pino-pretty": "^13.1.3",
"supertest": "^7.2.2",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
-4
View File
@@ -78,10 +78,6 @@ async function runMigrations() {
const migrateResult = await runDrizzleMigrations(db);
if (!migrateResult.success) {
log.error(`[DB] Migration error: ${migrateResult.error}`);
} else if (migrateResult.warning) {
log.warn(`[DB] Migration warning: ${migrateResult.warning}`);
} else {
log.debug(`[DB] Drizzle migrations completed`);
}
// Run ALTER TABLE migrations for backward compatibility
+5 -6
View File
@@ -88,13 +88,12 @@ export async function runDrizzleMigrations(
await migrate(database, { migrationsFolder });
return { success: true };
} catch (err: unknown) {
// If the error is about existing schema objects, the DB is already up-to-date
// This happens when ALTER migrations in client.ts have already added the columns,
// or when tables were created before drizzle migrations were introduced
if ((err as Error).message?.includes("duplicate column") || (err as Error).message?.includes("already exists")) {
return { success: true, warning: `Schema already up-to-date: ${(err as Error).message}` };
const msg = (err as Error).message ?? "";
// Duplicate column / already exists = DB is already up-to-date (expected for existing DBs)
if (msg.includes("duplicate column") || msg.includes("already exists")) {
return { success: true };
}
return { success: false, error: (err as Error).message };
return { success: false, error: msg };
}
}
+9 -1
View File
@@ -58,10 +58,18 @@ function sanitizeCorrelationId(headers: IncomingHttpHeaders): string | null {
}
function buildLoggerOptions(level: string) {
return {
const base = {
level,
timestamp: () => `,"time":"${new Date().toISOString()}"`,
};
// Human-readable logs in development; structured JSON in production/test
if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {
return {
...base,
transport: { target: "pino-pretty", options: { translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l" } },
};
}
return base;
}
/** Create and configure Fastify app (without starting) */
+4 -2
View File
@@ -137,8 +137,9 @@ async function validateShareDoseId(share: typeof shareTokens.$inferSelect, doseI
export async function doseRoutes(app: FastifyInstance) {
// ---------------------------------------------------------------------------
// GET /doses/taken - PROTECTED: Get all taken doses for the user
// Suppress request logs — polled every 5s by frontend
// ---------------------------------------------------------------------------
app.get("/doses/taken", { preHandler: requireAuth }, async (request, reply) => {
app.get("/doses/taken", { preHandler: requireAuth, logLevel: "warn" }, async (request, reply) => {
const userId = await getUserId(request, reply);
// Get all taken doses for this user (no time limit)
@@ -304,8 +305,9 @@ export async function doseRoutes(app: FastifyInstance) {
// ---------------------------------------------------------------------------
// GET /share/:token/doses - PUBLIC: Get taken doses for a share link
// Suppress request logs — polled every 5s by SharedSchedule
// ---------------------------------------------------------------------------
app.get<{ Params: { token: string } }>("/share/:token/doses", async (request, reply) => {
app.get<{ Params: { token: string } }>("/share/:token/doses", { logLevel: "warn" }, async (request, reply) => {
const { token } = request.params;
const { share, reason } = await getActiveShareToken(token);
+2 -3
View File
@@ -10,11 +10,10 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
const backendVersion = packageJson.version || "unknown";
export async function healthRoutes(app: FastifyInstance) {
// Exempt from rate limit - lightweight health check
app.get("/health", { config: { rateLimit: false } }, async () => ({
// Exempt from rate limit + suppress request logs (called every 30s by Docker healthcheck)
app.get("/health", { config: { rateLimit: false }, logLevel: "warn" }, async () => ({
status: "ok",
version: backendVersion,
smtpConfigured: Boolean(process.env.SMTP_HOST),
shoutrrrConfigured: Boolean(process.env.SHOUTRRR_URL),
}));
}
+2 -1
View File
@@ -284,7 +284,8 @@ export async function settingsRoutes(app: FastifyInstance) {
}
// Get settings for current user
app.get("/settings", async (request, reply) => {
// Suppress request logs — polled every 30s for reminder status refresh
app.get("/settings", { logLevel: "warn" }, async (request, reply) => {
const userId = await getUserId(request, reply);
const settings = await getOrCreateUserSettings(userId);
-2
View File
@@ -868,7 +868,6 @@ describe("E2E Tests with Real Routes", () => {
const json = response.json();
expect(json.status).toBe("ok");
expect(typeof json.smtpConfigured).toBe("boolean");
expect(typeof json.shoutrrrConfigured).toBe("boolean");
});
});
@@ -1289,7 +1288,6 @@ describe("E2E Tests with Real Routes", () => {
const json = response.json();
expect(json.status).toBe("ok");
expect(typeof json.smtpConfigured).toBe("boolean");
expect(typeof json.shoutrrrConfigured).toBe("boolean");
});
});
+8 -4
View File
@@ -23,18 +23,22 @@ function shouldLog(level: string): boolean {
return LOG_LEVELS[level] >= getLevel();
}
function ts(): string {
return new Date().toISOString();
}
export const log = {
debug(msg: string): void {
if (shouldLog("debug")) console.log(msg);
if (shouldLog("debug")) console.log(`[${ts()}] [DEBUG] ${msg}`);
},
info(msg: string): void {
if (shouldLog("info")) console.log(msg);
if (shouldLog("info")) console.log(`[${ts()}] [INFO] ${msg}`);
},
warn(msg: string): void {
if (shouldLog("warn")) console.warn(msg);
if (shouldLog("warn")) console.warn(`[${ts()}] [WARN] ${msg}`);
},
error(msg: string): void {
if (shouldLog("error")) console.error(msg);
if (shouldLog("error")) console.error(`[${ts()}] [ERROR] ${msg}`);
},
};