refactor(logs): move to core package (#4586)

This commit is contained in:
Meier Lukas
2025-12-16 23:37:44 +01:00
committed by GitHub
parent d86af072bf
commit d348abfe4a
145 changed files with 971 additions and 708 deletions

View File

@@ -1,8 +1,8 @@
import { AxiosError } from "axios";
import { createTask, validate } from "node-cron";
import { Stopwatch } from "@homarr/common";
import type { MaybePromise } from "@homarr/common/types";
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
import { db } from "@homarr/db";
import type { Logger } from "./logger";
@@ -33,33 +33,39 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
return (callback: () => MaybePromise<void>) => {
const catchingCallbackAsync = async () => {
try {
creatorOptions.logger.logDebug(`The callback of '${name}' cron job started`);
creatorOptions.logger.logDebug("The callback of cron job started", {
name,
});
const stopwatch = new Stopwatch();
await creatorOptions.beforeCallback?.(name);
const beforeCallbackTook = stopwatch.getElapsedInHumanWords();
await callback();
const callbackTook = stopwatch.getElapsedInHumanWords();
creatorOptions.logger.logDebug(
`The callback of '${name}' cron job succeeded (before callback took ${beforeCallbackTook}, callback took ${callbackTook})`,
);
creatorOptions.logger.logDebug("The callback of cron job succeeded", {
name,
beforeCallbackTook,
callbackTook,
});
const durationInMillis = stopwatch.getElapsedInMilliseconds();
if (durationInMillis > expectedMaximumDurationInMillis) {
creatorOptions.logger.logWarning(
`The callback of '${name}' succeeded but took ${(durationInMillis - expectedMaximumDurationInMillis).toFixed(2)}ms longer than expected (${expectedMaximumDurationInMillis}ms). This may indicate that your network performance, host performance or something else is too slow. If this happens too often, it should be looked into.`,
);
creatorOptions.logger.logWarning("The callback of cron job took longer than expected", {
name,
durationInMillis,
expectedMaximumDurationInMillis,
});
}
await creatorOptions.onCallbackSuccess?.(name);
} catch (error) {
// Log AxiosError in a less detailed way to prevent very long output
if (error instanceof AxiosError) {
creatorOptions.logger.logError(
`Failed to run job '${name}': [AxiosError] ${error.message} ${error.response?.status} ${error.response?.config.url}\n${error.stack}`,
);
} else {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
creatorOptions.logger.logError(`Failed to run job '${name}': ${error}`);
}
creatorOptions.logger.logError(
new ErrorWithMetadata(
"The callback of cron job failed",
{
name,
},
{ cause: error },
),
);
await creatorOptions.onCallbackError?.(name, error);
}
};
@@ -80,21 +86,28 @@ const createCallback = <TAllowedNames extends string, TName extends TAllowedName
timezone: creatorOptions.timezone,
},
);
creatorOptions.logger.logDebug(
`The cron job '${name}' was created with expression ${defaultCronExpression} in timezone ${creatorOptions.timezone} and runOnStart ${options.runOnStart}`,
);
creatorOptions.logger.logDebug("The scheduled task for cron job was created", {
name,
cronExpression: defaultCronExpression,
timezone: creatorOptions.timezone,
runOnStart: options.runOnStart,
});
return scheduledTask;
},
async onStartAsync() {
if (options.beforeStart) {
creatorOptions.logger.logDebug(`Running beforeStart for job: ${name}`);
creatorOptions.logger.logDebug("Running beforeStart for job", {
name,
});
await options.beforeStart();
}
if (!options.runOnStart) return;
creatorOptions.logger.logDebug(`The cron job '${name}' is running because runOnStart is set to true`);
creatorOptions.logger.logDebug("The cron job is configured to run on start, executing callback", {
name,
});
await catchingCallbackAsync();
},
async executeAsync() {
@@ -117,11 +130,17 @@ export const createCronJobCreator = <TAllowedNames extends string = string>(
defaultCronExpression: TExpression,
options: CreateCronJobOptions = { runOnStart: false },
) => {
creatorOptions.logger.logDebug(`Validating cron expression '${defaultCronExpression}' for job: ${name}`);
creatorOptions.logger.logDebug("Validating cron expression for cron job", {
name,
cronExpression: defaultCronExpression,
});
if (!validate(defaultCronExpression)) {
throw new Error(`Invalid cron expression '${defaultCronExpression}' for job '${name}'`);
}
creatorOptions.logger.logDebug(`Cron job expression '${defaultCronExpression}' for job ${name} is valid`);
creatorOptions.logger.logDebug("Cron job expression for cron job is valid", {
name,
cronExpression: defaultCronExpression,
});
const returnValue = {
withCallback: createCallback<TAllowedNames, TName>(name, defaultCronExpression, options, creatorOptions),

View File

@@ -19,11 +19,15 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
options: CreateCronJobGroupCreatorOptions,
) => {
return <TJobs extends Jobs<TAllowedNames>>(jobs: TJobs) => {
options.logger.logDebug(`Creating job group with ${Object.keys(jobs).length} jobs.`);
options.logger.logDebug("Creating job group.", {
jobCount: Object.keys(jobs).length,
});
for (const [key, job] of objectEntries(jobs)) {
if (typeof key !== "string" || typeof job.name !== "string") continue;
options.logger.logDebug(`Added job ${job.name} to the job registry.`);
options.logger.logDebug("Registering job in the job registry.", {
name: job.name,
});
jobRegistry.set(key, {
...job,
name: job.name,
@@ -54,7 +58,9 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
if (!job) return;
if (!tasks.has(job.name)) return;
options.logger.logInfo(`Starting schedule cron job ${job.name}.`);
options.logger.logInfo("Starting schedule of cron job.", {
name: job.name,
});
await job.onStartAsync();
await tasks.get(name as string)?.start();
},
@@ -64,7 +70,9 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
continue;
}
options.logger.logInfo(`Starting schedule of cron job ${job.name}.`);
options.logger.logInfo("Starting schedule of cron job.", {
name: job.name,
});
await job.onStartAsync();
await tasks.get(job.name)?.start();
}
@@ -76,19 +84,25 @@ export const createJobGroupCreator = <TAllowedNames extends string = string>(
throw new Error(`The job "${job.name}" can not be executed manually.`);
}
options.logger.logInfo(`Running schedule cron job ${job.name} manually.`);
options.logger.logInfo("Running schedule cron job manually.", {
name: job.name,
});
await tasks.get(name as string)?.execute();
},
stopAsync: async (name: keyof TJobs) => {
const job = jobRegistry.get(name as string);
if (!job) return;
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
options.logger.logInfo("Stopping schedule of cron job.", {
name: job.name,
});
await tasks.get(name as string)?.stop();
},
stopAllAsync: async () => {
for (const job of jobRegistry.values()) {
options.logger.logInfo(`Stopping schedule cron job ${job.name}.`);
options.logger.logInfo("Stopping schedule of cron job.", {
name: job.name,
});
await tasks.get(job.name)?.stop();
}
},

View File

@@ -1,10 +1,9 @@
import type { CreateCronJobCreatorOptions } from "./creator";
import { createCronJobCreator } from "./creator";
import { createJobGroupCreator } from "./group";
import { ConsoleLogger } from "./logger";
export const createCronJobFunctions = <TAllowedNames extends string>(
options: CreateCronJobCreatorOptions<TAllowedNames> = { logger: new ConsoleLogger() },
options: CreateCronJobCreatorOptions<TAllowedNames>,
) => {
return {
createCronJob: createCronJobCreator<TAllowedNames>(options),

View File

@@ -1,24 +1,7 @@
export interface Logger {
logDebug(message: string): void;
logInfo(message: string): void;
logDebug(message: string, metadata?: Record<string, unknown>): void;
logInfo(message: string, metadata?: Record<string, unknown>): void;
logError(message: string, metadata?: Record<string, unknown>): void;
logError(error: unknown): void;
logWarning(message: string): void;
}
export class ConsoleLogger implements Logger {
public logDebug(message: string) {
console.log(message);
}
public logInfo(message: string) {
console.log(message);
}
public logError(error: unknown) {
console.error(error);
}
public logWarning(message: string) {
console.warn(message);
}
logWarning(message: string, metadata?: Record<string, unknown>): void;
}