diff --git a/README.md b/README.md index 2bbb3744..13146f91 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ - [x] Advanced Roles and Permissions API. - [x] HTTP proxy support - [x] Tor hidden service support +- [x] Sentry logging support - [x] Ability to change the domain name in a single config change, without any database edits ## Screenshots diff --git a/app.ts b/app.ts index 94cfdd23..7954b841 100644 --- a/app.ts +++ b/app.ts @@ -1,4 +1,5 @@ import { errorResponse, jsonResponse, response } from "@/response"; +import { sentry } from "@/sentry"; import { Hono } from "@hono/hono"; import { getLogger } from "@logtape/logtape"; import { config } from "config-manager"; @@ -96,6 +97,7 @@ export const appFactory = async () => { app.onError((error) => { const serverLogger = getLogger("server"); serverLogger.error`${error}`; + sentry?.captureException(error); return jsonResponse( { error: "A server error occured", diff --git a/bun.lockb b/bun.lockb index 810ed68d..dcf00f4a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/config/config.example.toml b/config/config.example.toml index 7360d170..628bd9cc 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -389,6 +389,18 @@ log_ip = false # Log all filtered objects log_filters = true +[logging.sentry] +# Whether to enable https://sentry.io error logging +enabled = false +# Sentry DSN for error logging +dsn = "" +debug = false + +sample_rate = 1.0 +traces_sample_rate = 1.0 +max_breadcrumbs = 100 +# environment = "production" + [logging.storage] # Path to logfile for requests requests = "logs/requests.log" diff --git a/index.ts b/index.ts index dfd8c83b..2490b018 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,5 @@ import { configureLoggers } from "@/loggers"; +import { sentry } from "@/sentry"; import { createServer } from "@/server"; import { config } from "config-manager"; import { appFactory } from "~/app"; @@ -6,6 +7,7 @@ import { setupDatabase } from "./drizzle/db"; if (import.meta.main) { await import("./setup"); + sentry?.captureMessage("Server started"); } await setupDatabase(); diff --git a/package.json b/package.json index 40c7f129..50519171 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ "@lysand-org/client": "^0.2.5", "@lysand-org/federation": "2.1.8", "@oclif/core": "^4.0.14", + "@sentry/bun": "^8.19.0", "@tufjs/canonical-json": "^2.0.0", "altcha-lib": "^0.4.1", "blurhash": "^2.0.5", diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index 1f9b6bcf..8487da0d 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -568,6 +568,20 @@ export const configValidator = z.object({ .default("info"), log_ip: z.boolean().default(false), log_filters: z.boolean().default(true), + sentry: z + .object({ + enabled: z.boolean().default(false), + dsn: z.string().url().or(z.literal("")).optional(), + debug: z.boolean().default(false), + sample_rate: z.number().min(0).max(1.0).default(1.0), + traces_sample_rate: z.number().min(0).max(1.0).default(1.0), + max_breadcrumbs: z.number().default(100), + environment: z.string().optional(), + }) + .refine( + (arg) => (arg.enabled ? !!arg.dsn : true), + "When sentry is enabled, DSN must be set", + ), storage: z .object({ requests: z.string().default("logs/requests.log"), @@ -582,6 +596,13 @@ export const configValidator = z.object({ log_level: "info", log_ip: false, log_filters: true, + sentry: { + enabled: false, + debug: false, + sample_rate: 1.0, + traces_sample_rate: 1.0, + max_breadcrumbs: 100, + }, storage: { requests: "logs/requests.log", }, diff --git a/utils/sentry.ts b/utils/sentry.ts new file mode 100644 index 00000000..33a56af3 --- /dev/null +++ b/utils/sentry.ts @@ -0,0 +1,18 @@ +import * as Sentry from "@sentry/bun"; +import { config } from "config-manager"; +import pkg from "~/package.json"; + +const sentryInstance = + config.logging.sentry.enabled && + Sentry.init({ + dsn: config.logging.sentry.dsn, + 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.http.bind], + release: pkg.version, + }); + +export const sentry = sentryInstance || undefined;