refactor: ♻️ Rewrite logging logic into a unified package
Some checks failed
Mirror to Codeberg / Mirror (push) Failing after 0s
Test Publish / build (client) (push) Failing after 0s
Test Publish / build (sdk) (push) Failing after 0s

This commit is contained in:
Jesse Wierzbinski 2025-06-22 18:43:03 +02:00
parent e1bd389bf1
commit aff51b651c
No known key found for this signature in database
32 changed files with 479 additions and 402 deletions

View file

@ -0,0 +1,56 @@
import type { LogLevel, LogRecord } from "@logtape/logtape";
import chalk, { type ChalkInstance } from "chalk";
const levelAbbreviations: Record<LogLevel, string> = {
debug: "DBG",
info: "INF",
warning: "WRN",
error: "ERR",
fatal: "FTL",
trace: "TRC",
};
/**
* The styles for the log level in the console.
*/
const logLevelStyles: Record<LogLevel, ChalkInstance> = {
debug: chalk.white.bgGray,
info: chalk.black.bgWhite,
warning: chalk.black.bgYellow,
error: chalk.white.bgRed,
fatal: chalk.white.bgRedBright,
trace: chalk.white.bgBlue,
};
/**
* Pretty colored console formatter.
*
* @param record The log record to format.
* @returns The formatted log record, as an array of arguments for
* {@link console.log}.
*/
export function consoleFormatter(record: LogRecord): string[] {
const msg = record.message.join("");
const date = new Date(record.timestamp);
const time = `${date.getUTCHours().toString().padStart(2, "0")}:${date
.getUTCMinutes()
.toString()
.padStart(
2,
"0",
)}:${date.getUTCSeconds().toString().padStart(2, "0")}.${date
.getUTCMilliseconds()
.toString()
.padStart(3, "0")}`;
const formattedTime = chalk.gray(time);
const formattedLevel = logLevelStyles[record.level](
levelAbbreviations[record.level],
);
const formattedCategory = chalk.gray(record.category.join("\xb7"));
const formattedMsg = chalk.reset(msg);
return [
`${formattedTime} ${formattedLevel} ${formattedCategory} ${formattedMsg}`,
];
}

158
packages/logging/index.ts Normal file
View file

@ -0,0 +1,158 @@
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import { getFileSink, getRotatingFileSink } from "@logtape/file";
import {
configure,
getConsoleSink,
getLevelFilter,
getLogger,
type Sink,
withFilter,
} from "@logtape/logtape";
import { getSentrySink } from "@logtape/sentry";
import * as Sentry from "@sentry/bun";
import { config } from "@versia-server/config";
import { env } from "bun";
import pkg from "../../package.json" with { type: "json" };
import { consoleFormatter } from "./formatter.ts";
if (config.logging.file?.path) {
// config.logging.file.path is a path to a file, create the directory if it doesn't exist
await mkdir(dirname(config.logging.file.path), { recursive: true });
}
/**
* Returns all configured sinks depending on the configuration.
*/
const getSinks = (): Record<"file" | "console" | "sentry", Sink> => {
const sinks: Record<string, Sink> = {};
if (config.logging.file) {
if (config.logging.file.rotation) {
sinks.file = getRotatingFileSink(config.logging.file.path, {
maxFiles: config.logging.file.rotation.max_files,
maxSize: config.logging.file.rotation.max_size,
});
} else {
sinks.file = getFileSink(config.logging.file.path);
}
sinks.file = withFilter(
sinks.file,
getLevelFilter(config.logging.file.log_level),
);
}
if (config.logging.sentry) {
sinks.sentry = getSentrySink(
Sentry.init({
dsn: config.logging.sentry.dsn.origin,
debug: config.logging.sentry.debug,
sampleRate: config.logging.sentry.sample_rate,
maxBreadcrumbs: config.logging.sentry.max_breadcrumbs,
tracesSampleRate: config.logging.sentry.traces_sample_rate,
environment: config.logging.sentry.environment,
tracePropagationTargets:
config.logging.sentry.trace_propagation_targets,
release: env.GIT_COMMIT
? `${pkg.version}-${env.GIT_COMMIT}`
: pkg.version,
integrations: [Sentry.extraErrorDataIntegration()],
}),
);
sinks.sentry = withFilter(
sinks.sentry,
getLevelFilter(config.logging.sentry.log_level),
);
}
sinks.console = getConsoleSink({
formatter: consoleFormatter,
});
sinks.console = withFilter(
sinks.console,
getLevelFilter(config.logging.log_level),
);
return sinks;
};
const getSinkNames = (): ("file" | "console" | "sentry")[] => {
const names = [] as ("file" | "console" | "sentry")[];
if (config.logging.file) {
names.push("file");
}
if (config.logging.sentry) {
names.push("sentry");
}
names.push("console");
return names;
};
await configure({
reset: true,
sinks: getSinks(),
loggers: [
{
category: "server",
sinks: getSinkNames(),
},
{
category: ["federation", "inbox"],
sinks: getSinkNames(),
},
{
category: ["federation", "delivery"],
sinks: getSinkNames(),
},
{
category: ["federation", "bridge"],
sinks: getSinkNames(),
},
{
category: ["federation", "resolvers"],
sinks: getSinkNames(),
},
{
category: ["federation", "messaging"],
sinks: getSinkNames(),
},
{
category: "database",
sinks: getSinkNames(),
},
{
category: "webfinger",
sinks: getSinkNames(),
},
{
category: "sonic",
sinks: getSinkNames(),
},
{
category: ["logtape", "meta"],
lowestLevel: "error",
},
{
category: "plugin",
sinks: getSinkNames(),
},
],
});
export const serverLogger = getLogger("server");
export const federationInboxLogger = getLogger(["federation", "inbox"]);
export const federationDeliveryLogger = getLogger(["federation", "delivery"]);
export const federationBridgeLogger = getLogger(["federation", "bridge"]);
export const federationResolversLogger = getLogger(["federation", "resolvers"]);
export const federationMessagingLogger = getLogger(["federation", "messaging"]);
export const databaseLogger = getLogger("database");
export const webfingerLogger = getLogger("webfinger");
export const sonicLogger = getLogger("sonic");
export const pluginLogger = getLogger("plugin");

View file

@ -0,0 +1,22 @@
{
"name": "@versia-server/logging",
"module": "index.ts",
"type": "module",
"version": "0.0.1",
"private": true,
"exports": {
".": {
"import": "./index.ts",
"default": "./index.ts"
}
},
"dependencies": {
"@versia-server/config": "workspace:*",
"@logtape/logtape": "catalog:",
"@logtape/file": "catalog:",
"@logtape/sentry": "catalog:",
"@logtape/otel": "catalog:",
"@sentry/bun": "catalog:",
"chalk": "catalog:"
}
}