diff --git a/.github/config.workflow.toml b/.github/config.workflow.toml index a88391b8..2a933b98 100644 --- a/.github/config.workflow.toml +++ b/.github/config.workflow.toml @@ -429,31 +429,28 @@ text = "No spam" [logging] -# Available levels: debug, info, warning, error, fatal -log_level = "debug" - -log_file_path = "logs/versia.log" - -[logging.types] -# Either pass a boolean -# requests = true -# Or a table with the following keys: -# requests_content = { level = "debug", log_file_path = "logs/requests.log" } -# Available types are: requests, responses, requests_content, filters +# Available levels: trace, debug, info, warning, error, fatal +log_level = "info" # For console output +# [logging.file] +# path = "logs/versia.log" +# log_level = "info" +# +# [logging.file.rotation] +# max_size = 10_000_000 # 10 MB +# max_files = 10 # Keep 10 rotated files +# # https://sentry.io support -# Uncomment to enable # [logging.sentry] -# Sentry DSN for error logging # dsn = "https://example.com" # debug = false - # sample_rate = 1.0 # traces_sample_rate = 1.0 # Can also be regex # trace_propagation_targets = [] # max_breadcrumbs = 100 # environment = "production" +# log_level = "info" [plugins] # Whether to automatically load all plugins in the plugins directory diff --git a/benchmarks/timeline.ts b/benchmarks/timeline.ts index a0b30083..67a7cfb1 100644 --- a/benchmarks/timeline.ts +++ b/benchmarks/timeline.ts @@ -6,9 +6,6 @@ import { } from "@versia-server/tests"; import { bench, run } from "mitata"; import type { z } from "zod"; -import { configureLoggers } from "@/loggers"; - -await configureLoggers(true); const { users, tokens, deleteUsers } = await getTestUsers(5); await getTestStatuses(40, users[0]); diff --git a/bun.lock b/bun.lock index 354a316e..7c4b39df 100644 --- a/bun.lock +++ b/bun.lock @@ -14,12 +14,11 @@ "@hackmd/markdown-it-task-lists": "catalog:", "@hono/zod-validator": "catalog:", "@inquirer/confirm": "catalog:", - "@logtape/file": "catalog:", - "@logtape/logtape": "catalog:", "@scalar/hono-api-reference": "catalog:", "@sentry/bun": "catalog:", "@versia-server/config": "workspace:*", "@versia-server/kit": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia-server/tests": "workspace:*", "@versia/client": "workspace:*", "@versia/sdk": "workspace:*", @@ -91,10 +90,10 @@ "version": "0.9.0-alpha.0", "dependencies": { "@hono/zod-validator": "catalog:", - "@logtape/logtape": "catalog:", "@scalar/hono-api-reference": "catalog:", "@versia-server/config": "workspace:*", "@versia-server/kit": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia-server/tests": "workspace:*", "@versia/client": "workspace:*", "@versia/sdk": "workspace:*", @@ -145,14 +144,27 @@ "zod-validation-error": "catalog:", }, }, + "packages/logging": { + "name": "@versia-server/logging", + "version": "0.0.1", + "dependencies": { + "@logtape/file": "catalog:", + "@logtape/logtape": "catalog:", + "@logtape/otel": "catalog:", + "@logtape/sentry": "catalog:", + "@sentry/bun": "catalog:", + "@versia-server/config": "workspace:*", + "chalk": "catalog:", + }, + }, "packages/plugin-kit": { "name": "@versia-server/kit", "version": "0.0.0", "dependencies": { "@hackmd/markdown-it-task-lists": "catalog:", "@hono/zod-validator": "catalog:", - "@logtape/logtape": "catalog:", "@versia-server/config": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia/client": "workspace:*", "@versia/sdk": "workspace:*", "altcha-lib": "catalog:", @@ -199,9 +211,9 @@ "name": "@versia-server/worker", "version": "0.9.0-alpha.0", "dependencies": { - "@logtape/logtape": "catalog:", "@versia-server/config": "workspace:*", "@versia-server/kit": "workspace:*", + "@versia-server/logging": "workspace:*", "chalk": "catalog:", }, }, @@ -211,6 +223,7 @@ "esbuild", "@biomejs/biome", "msgpackr-extract", + "protobufjs", ], "catalog": { "@biomejs/biome": "^2.0.4", @@ -226,6 +239,8 @@ "@inquirer/confirm": "^5.1.12", "@logtape/file": "^1.0.0", "@logtape/logtape": "^1.0.0", + "@logtape/otel": "^1.0.0", + "@logtape/sentry": "^1.0.0", "@scalar/hono-api-reference": "^0.9.6", "@sentry/bun": "^9.29.0", "@types/bun": "^1.2.17", @@ -513,6 +528,10 @@ "@logtape/logtape": ["@logtape/logtape@1.0.0", "", {}, "sha512-GOOiaJcHSJQfFt+khrtoxfQ29klDiG8UxrgC+lPt7K6HhwEMQnB47j7V0GQ6F8bnS7JIZgYQmbXb+yGUF2pKhA=="], + "@logtape/otel": ["@logtape/otel@1.0.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.202.0", "@opentelemetry/exporter-logs-otlp-http": "^0.202.0", "@opentelemetry/otlp-exporter-base": "^0.202.0", "@opentelemetry/resources": "^2.0.1", "@opentelemetry/sdk-logs": "^0.202.0", "@opentelemetry/semantic-conventions": "^1.34.0" }, "peerDependencies": { "@logtape/logtape": "1.0.0" } }, "sha512-SMfmIFSsayszoYyOUyG7fq1FgFzdwnATzOCGRNq8JLUZ4vdO5fURqsYt4kG1LO7m2ioOsNwc0KkcVKY/s9kjiw=="], + + "@logtape/sentry": ["@logtape/sentry@1.0.0", "", { "dependencies": { "@sentry/core": "^9.28.1" }, "peerDependencies": { "@logtape/logtape": "1.0.0" } }, "sha512-GMYe0MRnumtE09sDCGmCkvuCmcZfqco8CknG5TQYILaX7IHD/TFchCNtxCEeeZUWnmwaRzWAXDm3+YDA4NZcWQ=="], + "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], @@ -533,12 +552,14 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="], + "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.202.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fTBjMqKCfotFWfLzaKyhjLvyEyq5vDKTTFfBmx21btv3gvy8Lq6N5Dh2OzqeuN4DjtpSvNT1uNVfg08eD2Rfxw=="], "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.30.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA=="], "@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="], + "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.202.0", "@opentelemetry/otlp-transformer": "0.202.0", "@opentelemetry/sdk-logs": "0.202.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-mJWLkmoG+3r+SsYQC+sbWoy1rjowJhMhFvFULeIPTxSI+EZzKPya0+NZ3+vhhgx2UTybGQlye3FBtCH3o6Rejg=="], + "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.57.2", "", { "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg=="], "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.46.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-AyXVnlCf/xV3K/rNumzKxZqsULyITJH6OVLiW6730JPRqWA7Zc9bvYoVNpN6iOpTU8CasH34SU/ksVJmObFibQ=="], @@ -585,9 +606,17 @@ "@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.10.1", "", { "dependencies": { "@opentelemetry/core": "^1.8.0", "@opentelemetry/instrumentation": "^0.57.1" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-rkOGikPEyRpMCmNu9AQuV5dtRlDmJp2dK5sw8roVshAGoB6hH/3QjDtRhdwd75SsJwgynWUNRUYe0wAkTo16tQ=="], + "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.202.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.202.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-nMEOzel+pUFYuBJg2znGmHJWbmvMbdX5/RhoKNKowguMbURhz0fwik5tUKplLcUtl8wKPL1y9zPnPxeBn65N0Q=="], + + "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.202.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5XO77QFzs9WkexvJQL9ksxL8oVFb/dfi9NWQSq7Sv0Efr9x3N+nb1iklP1TeVgxqJ7m1xWiC/Uv3wupiQGevMw=="], + "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.36.2", "", {}, "sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g=="], - "@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="], + "@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="], + + "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-pv8QiQLQzk4X909YKm0lnW4hpuQg4zHwJ4XBd5bZiXcd9urvrJNoNVKnxGHPiDVX/GiLFvr5DMYsDBQbZCypRQ=="], + + "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g=="], "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg=="], @@ -605,6 +634,26 @@ "@prisma/instrumentation": ["@prisma/instrumentation@6.8.2", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-5NCTbZjw7a+WIZ/ey6G8SY+YKcyM2zBF0hOT1muvqC9TbVtTCr5Qv3RL/2iNDOzLUHEvo4I1uEfioyfuNOGK8Q=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], + + "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], + + "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], + + "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], + + "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], + + "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], + + "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], + + "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], + + "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], + + "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.0", "", { "os": "android", "cpu": "arm" }, "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.0", "", { "os": "android", "cpu": "arm64" }, "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw=="], @@ -741,6 +790,8 @@ "@versia-server/kit": ["@versia-server/kit@workspace:packages/plugin-kit"], + "@versia-server/logging": ["@versia-server/logging@workspace:packages/logging"], + "@versia-server/tests": ["@versia-server/tests@workspace:packages/tests"], "@versia-server/worker": ["@versia-server/worker@workspace:packages/worker"], @@ -1125,6 +1176,8 @@ "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="], @@ -1287,6 +1340,8 @@ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "protobufjs": ["protobufjs@7.5.3", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw=="], + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="], @@ -1525,9 +1580,25 @@ "@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="], + "@opentelemetry/instrumentation-http/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], - "@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], + "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="], + + "@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="], + + "@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="], "@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], @@ -1537,6 +1608,8 @@ "@scalar/types/zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="], + "@sentry/node/@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="], + "@ts-morph/common/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], @@ -1649,6 +1722,8 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "@sentry/node/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], + "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "cheerio-select/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], diff --git a/classes/inbox/processor.ts b/classes/inbox/processor.ts index 0657afca..ddfe69d1 100644 --- a/classes/inbox/processor.ts +++ b/classes/inbox/processor.ts @@ -1,4 +1,3 @@ -import { getLogger, type Logger } from "@logtape/logtape"; import { EntitySorter, type JSONObject } from "@versia/sdk"; import { verify } from "@versia/sdk/crypto"; import * as VersiaEntities from "@versia/sdk/entities"; @@ -13,13 +12,13 @@ import { User, } from "@versia-server/kit/db"; import { Likes, Notes } from "@versia-server/kit/tables"; +import { federationInboxLogger } from "@versia-server/logging"; import type { SocketAddress } from "bun"; import { Glob } from "bun"; import chalk from "chalk"; import { and, eq } from "drizzle-orm"; import { matches } from "ip-matching"; import { isValidationError } from "zod-validation-error"; -import { sentry } from "@/sentry"; /** * Checks if the hostname is defederated using glob matching. @@ -65,7 +64,6 @@ export class InboxProcessor { key: CryptoKey; } | null, private authorizationHeader?: string, - private logger: Logger = getLogger(["federation", "inbox"]), private requestIp: SocketAddress | null = null, ) {} @@ -156,7 +154,7 @@ export class InboxProcessor { */ public async process(): Promise { !this.sender && - this.logger.debug`Processing request from potential bridge`; + federationInboxLogger.debug`Processing request from potential bridge`; if (this.sender && isDefederated(this.sender.instance.data.baseUrl)) { // Return 201 to avoid @@ -165,15 +163,15 @@ export class InboxProcessor { return; } - this.logger.debug`Instance ${chalk.gray( + federationInboxLogger.debug`Instance ${chalk.gray( this.sender?.instance.data.baseUrl, )} is not defederated`; const shouldCheckSignature = this.shouldCheckSignature(); shouldCheckSignature - ? this.logger.debug`Checking signature` - : this.logger.debug`Skipping signature check`; + ? federationInboxLogger.debug`Checking signature` + : federationInboxLogger.debug`Skipping signature check`; if (shouldCheckSignature) { const isValid = await this.isSignatureValid(); @@ -183,7 +181,7 @@ export class InboxProcessor { } } - shouldCheckSignature && this.logger.debug`Signature is valid`; + shouldCheckSignature && federationInboxLogger.debug`Signature is valid`; try { await new EntitySorter(this.body) @@ -596,8 +594,7 @@ export class InboxProcessor { throw new ApiError(400, "Failed to process request", e.message); } - this.logger.error`${e}`; - sentry?.captureException(e); + federationInboxLogger.error`${e}`; throw new ApiError(500, "Failed to process request", e.message); } diff --git a/classes/plugin/loader.ts b/classes/plugin/loader.ts index da9f1bde..c97e43fe 100644 --- a/classes/plugin/loader.ts +++ b/classes/plugin/loader.ts @@ -1,7 +1,7 @@ import { readdir } from "node:fs/promises"; -import { getLogger, type Logger } from "@logtape/logtape"; import { config } from "@versia-server/config"; import { type Manifest, manifestSchema, Plugin } from "@versia-server/kit"; +import { pluginLogger, serverLogger } from "@versia-server/logging"; import { file, sleep } from "bun"; import chalk from "chalk"; import { parseJSON5, parseJSONC } from "confbox"; @@ -14,8 +14,6 @@ import type { HonoEnv } from "~/types/api"; * Class to manage plugins. */ export class PluginLoader { - private logger = getLogger("plugin"); - /** * Get all directories in a given directory. * @param {string} dir - The directory to search. @@ -74,8 +72,7 @@ export class PluginLoader { throw new Error(`Unsupported manifest file type: ${manifestFile}`); } catch (e) { - this.logger - .fatal`Could not parse plugin manifest ${chalk.blue(manifestPath)} as ${manifestFile.split(".").pop()?.toUpperCase()}.`; + pluginLogger.fatal`Could not parse plugin manifest ${chalk.blue(manifestPath)} as ${manifestFile.split(".").pop()?.toUpperCase()}.`; throw e; } } @@ -129,8 +126,7 @@ export class PluginLoader { const result = await manifestSchema.safeParseAsync(manifest); if (!result.success) { - this.logger - .fatal`Plugin manifest ${chalk.blue(manifestPath)} is invalid.`; + pluginLogger.fatal`Plugin manifest ${chalk.blue(manifestPath)} is invalid.`; throw fromZodError(result.error); } @@ -154,8 +150,7 @@ export class PluginLoader { return plugin; } - this.logger - .fatal`Default export of entrypoint ${chalk.blue(entrypoint)} at ${chalk.blue(dir)} is not a Plugin.`; + pluginLogger.fatal`Default export of entrypoint ${chalk.blue(entrypoint)} at ${chalk.blue(dir)} is not a Plugin.`; throw new Error("Entrypoint is not a Plugin"); } @@ -177,8 +172,7 @@ export class PluginLoader { const disabledOn = (disabled?.length ?? 0) > 0; if (enabledOn && disabledOn) { - this.logger - .fatal`Both enabled and disabled lists are specified. Only one of them can be used.`; + pluginLogger.fatal`Both enabled and disabled lists are specified. Only one of them can be used.`; throw new Error("Invalid configuration"); } @@ -219,10 +213,9 @@ export class PluginLoader { plugin: Plugin; }[], app: Hono, - logger: Logger, ): Promise { for (const data of plugins) { - logger.info`Loading plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright(data.manifest.version)} ${chalk.gray(`[${plugins.indexOf(data) + 1}/${plugins.length}]`)}`; + serverLogger.info`Loading plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright(data.manifest.version)} ${chalk.gray(`[${plugins.indexOf(data) + 1}/${plugins.length}]`)}`; const time1 = performance.now(); @@ -232,13 +225,13 @@ export class PluginLoader { config.plugins?.config?.[data.manifest.name], ); } catch (e) { - logger.fatal`Error encountered while loading plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright(data.manifest.version)} configuration.`; - logger.fatal`This is due to invalid, missing or incomplete configuration.`; - logger.fatal`Put your configuration at ${chalk.blueBright( + serverLogger.fatal`Error encountered while loading plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright(data.manifest.version)} configuration.`; + serverLogger.fatal`This is due to invalid, missing or incomplete configuration.`; + serverLogger.fatal`Put your configuration at ${chalk.blueBright( "plugins.config.", )}`; - logger.fatal`Here is the error message, please fix the configuration file accordingly:`; - logger.fatal`${(e as ValidationError).message}`; + serverLogger.fatal`Here is the error message, please fix the configuration file accordingly:`; + serverLogger.fatal`${(e as ValidationError).message}`; await sleep(Number.POSITIVE_INFINITY); } @@ -250,7 +243,7 @@ export class PluginLoader { const time3 = performance.now(); - logger.info`Plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright( + serverLogger.info`Plugin ${chalk.blueBright(data.manifest.name)} ${chalk.blueBright( data.manifest.version, )} loaded in ${chalk.gray( `${(time2 - time1).toFixed(2)}ms`, diff --git a/classes/queues/inbox.ts b/classes/queues/inbox.ts index 2ed73f02..9df27f47 100644 --- a/classes/queues/inbox.ts +++ b/classes/queues/inbox.ts @@ -1,4 +1,3 @@ -import { getLogger } from "@logtape/logtape"; import type { JSONObject } from "@versia/sdk"; import { config } from "@versia-server/config"; import { ApiError } from "@versia-server/kit"; @@ -64,7 +63,6 @@ export const getInboxWorker = (): Worker => data, null, headers.authorization, - getLogger(["federation", "inbox"]), ip, ); @@ -160,7 +158,6 @@ export const getInboxWorker = (): Worker => key, }, undefined, - getLogger(["federation", "inbox"]), ip, ); diff --git a/classes/search/search-manager.ts b/classes/search/search-manager.ts index 32415141..65fcbb7f 100644 --- a/classes/search/search-manager.ts +++ b/classes/search/search-manager.ts @@ -3,9 +3,9 @@ * @description Sonic search integration for indexing and searching accounts and statuses */ -import { getLogger } from "@logtape/logtape"; import { config } from "@versia-server/config"; import { db, Note, User } from "@versia-server/kit/db"; +import { sonicLogger } from "@versia-server/logging"; import type { SQL, ValueOrArray } from "drizzle-orm"; import { Ingest as SonicChannelIngest, @@ -27,7 +27,6 @@ export class SonicSearchManager { private searchChannel: SonicChannelSearch; private ingestChannel: SonicChannelIngest; private connected = false; - private logger = getLogger("sonic"); /** * @param config Configuration for Sonic @@ -55,7 +54,7 @@ export class SonicSearchManager { */ public async connect(silent = false): Promise { if (!config.search.enabled) { - !silent && this.logger.info`Sonic search is disabled`; + !silent && sonicLogger.info`Sonic search is disabled`; return; } @@ -63,28 +62,24 @@ export class SonicSearchManager { return; } - !silent && this.logger.info`Connecting to Sonic...`; + !silent && sonicLogger.info`Connecting to Sonic...`; // Connect to Sonic await new Promise((resolve, reject) => { this.searchChannel.connect({ connected: (): void => { !silent && - this.logger.info`Connected to Sonic Search Channel`; + sonicLogger.info`Connected to Sonic Search Channel`; resolve(true); }, disconnected: (): void => - this.logger - .error`Disconnected from Sonic Search Channel. You might be using an incorrect password.`, + sonicLogger.error`Disconnected from Sonic Search Channel. You might be using an incorrect password.`, timeout: (): void => - this.logger - .error`Sonic Search Channel connection timed out`, + sonicLogger.error`Sonic Search Channel connection timed out`, retrying: (): void => - this.logger - .warn`Retrying connection to Sonic Search Channel`, + sonicLogger.warn`Retrying connection to Sonic Search Channel`, error: (error): void => { - this.logger - .error`Failed to connect to Sonic Search Channel: ${error}`; + sonicLogger.error`Failed to connect to Sonic Search Channel: ${error}`; reject(error); }, }); @@ -94,20 +89,17 @@ export class SonicSearchManager { this.ingestChannel.connect({ connected: (): void => { !silent && - this.logger.info`Connected to Sonic Ingest Channel`; + sonicLogger.info`Connected to Sonic Ingest Channel`; resolve(true); }, disconnected: (): void => - this.logger.error`Disconnected from Sonic Ingest Channel`, + sonicLogger.error`Disconnected from Sonic Ingest Channel`, timeout: (): void => - this.logger - .error`Sonic Ingest Channel connection timed out`, + sonicLogger.error`Sonic Ingest Channel connection timed out`, retrying: (): void => - this.logger - .warn`Retrying connection to Sonic Ingest Channel`, + sonicLogger.warn`Retrying connection to Sonic Ingest Channel`, error: (error): void => { - this.logger - .error`Failed to connect to Sonic Ingest Channel: ${error}`; + sonicLogger.error`Failed to connect to Sonic Ingest Channel: ${error}`; reject(error); }, }); @@ -119,9 +111,9 @@ export class SonicSearchManager { this.ingestChannel.ping(), ]); this.connected = true; - !silent && this.logger.info`Connected to Sonic`; + !silent && sonicLogger.info`Connected to Sonic`; } catch (error) { - this.logger.fatal`Error while connecting to Sonic: ${error}`; + sonicLogger.fatal`Error while connecting to Sonic: ${error}`; throw error; } } @@ -143,7 +135,7 @@ export class SonicSearchManager { `${user.data.username} ${user.data.displayName} ${user.data.note}`, ); } catch (error) { - this.logger.error`Failed to add user to Sonic: ${error}`; + sonicLogger.error`Failed to add user to Sonic: ${error}`; } } diff --git a/config/config.example.toml b/config/config.example.toml index ce0ebb76..ac825c0b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -435,31 +435,29 @@ text = "No spam" [logging] -# Available levels: debug, info, warning, error, fatal -log_level = "debug" - -log_file_path = "logs/versia.log" - -[logging.types] -# Either pass a boolean -# requests = true -# Or a table with the following keys: -# requests_content = { level = "debug", log_file_path = "logs/requests.log" } -# Available types are: requests, responses, requests_content, filters +# Available levels: trace, debug, info, warning, error, fatal +log_level = "info" # For console output +# [logging.file] +# path = "logs/versia.log" +# log_level = "info" +# +# [logging.file.rotation] +# max_size = 10_000_000 # 10 MB +# max_files = 10 # Keep 10 rotated files +# # https://sentry.io support -# Uncomment to enable # [logging.sentry] -# Sentry DSN for error logging # dsn = "https://example.com" # debug = false - # sample_rate = 1.0 # traces_sample_rate = 1.0 # Can also be regex # trace_propagation_targets = [] # max_breadcrumbs = 100 # environment = "production" +# log_level = "info" + [plugins] # Whether to automatically load all plugins in the plugins directory diff --git a/package.json b/package.json index 938ab571..4766f263 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ "@inquirer/confirm": "^5.1.12", "@logtape/file": "^1.0.0", "@logtape/logtape": "^1.0.0", + "@logtape/sentry": "^1.0.0", + "@logtape/otel": "^1.0.0", "@scalar/hono-api-reference": "^0.9.6", "@sentry/bun": "^9.29.0", "altcha-lib": "^1.3.0", @@ -130,6 +132,7 @@ "es5-ext", "esbuild", "msgpackr-extract", + "protobufjs", "sharp" ], "devDependencies": { @@ -162,13 +165,12 @@ "@hackmd/markdown-it-task-lists": "catalog:", "@hono/zod-validator": "catalog:", "@inquirer/confirm": "catalog:", - "@logtape/file": "catalog:", - "@logtape/logtape": "catalog:", "@scalar/hono-api-reference": "catalog:", "@sentry/bun": "catalog:", "@versia-server/config": "workspace:*", "@versia-server/kit": "workspace:*", "@versia-server/tests": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia/client": "workspace:*", "@versia/sdk": "workspace:*", "altcha-lib": "catalog:", diff --git a/packages/api/app.ts b/packages/api/app.ts index 084b38f0..98ba93a2 100644 --- a/packages/api/app.ts +++ b/packages/api/app.ts @@ -1,8 +1,8 @@ import { resolve } from "node:path"; -import { getLogger } from "@logtape/logtape"; import { Scalar } from "@scalar/hono-api-reference"; import { config } from "@versia-server/config"; import { ApiError } from "@versia-server/kit"; +import { serverLogger } from "@versia-server/logging"; import chalk from "chalk"; import { Hono } from "hono"; import { serveStatic } from "hono/bun"; @@ -13,8 +13,6 @@ import { secureHeaders } from "hono/secure-headers"; import { openAPISpecs } from "hono-openapi"; import { Youch } from "youch"; import { applyToHono } from "@/bull-board.ts"; -import { configureLoggers } from "@/loggers"; -import { sentry } from "@/sentry"; import pkg from "~/package.json" with { type: "application/json" }; import { PluginLoader } from "../../classes/plugin/loader.ts"; import type { ApiRouteExports, HonoEnv } from "../../types/api.ts"; @@ -28,9 +26,6 @@ import { routes } from "./routes.ts"; import "zod-openapi/extend"; export const appFactory = async (): Promise> => { - await configureLoggers(); - const serverLogger = getLogger("server"); - const app = new Hono({ strict: false, }); @@ -124,7 +119,7 @@ export const appFactory = async (): Promise> => { config.plugins?.overrides.disabled, ); - await PluginLoader.addToApp(plugins, app, serverLogger); + await PluginLoader.addToApp(plugins, app); const time2 = performance.now(); @@ -193,7 +188,6 @@ export const appFactory = async (): Promise> => { const youch = new Youch(); console.error(await youch.toANSI(error)); - sentry?.captureException(error); return c.json( { error: "A server error occured", diff --git a/packages/api/index.ts b/packages/api/index.ts index 150d1f0d..93cd93c8 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -1,7 +1,6 @@ import process from "node:process"; import { config } from "@versia-server/config"; import { Youch } from "youch"; -import { sentry } from "@/sentry"; import { createServer } from "@/server"; import { appFactory } from "./app.ts"; @@ -16,6 +15,5 @@ process.on("uncaughtException", async (error) => { }); await import("./setup.ts"); -sentry?.captureMessage("Server started", "info"); createServer(config, await appFactory()); diff --git a/packages/api/middlewares/ip-bans.ts b/packages/api/middlewares/ip-bans.ts index 91f2fa1d..6c0c3d32 100644 --- a/packages/api/middlewares/ip-bans.ts +++ b/packages/api/middlewares/ip-bans.ts @@ -1,10 +1,9 @@ -import { getLogger } from "@logtape/logtape"; import { config } from "@versia-server/config"; import { ApiError } from "@versia-server/kit"; +import { serverLogger } from "@versia-server/logging"; import type { SocketAddress } from "bun"; import { createMiddleware } from "hono/factory"; import { matches } from "ip-matching"; -import { sentry } from "@/sentry"; export const ipBans = createMiddleware(async (context, next) => { // Check for banned IPs @@ -22,11 +21,8 @@ export const ipBans = createMiddleware(async (context, next) => { throw new ApiError(403, "Forbidden"); } } catch (e) { - const logger = getLogger("server"); - - logger.error`Error while parsing banned IP "${ip}" `; - logger.error`${e}`; - sentry?.captureException(e); + serverLogger.error`Error while parsing banned IP "${ip}" `; + serverLogger.error`${e}`; return context.json( { error: `A server error occured: ${(e as Error).message}` }, diff --git a/packages/api/middlewares/logger.ts b/packages/api/middlewares/logger.ts index eff6bd55..4a310eea 100644 --- a/packages/api/middlewares/logger.ts +++ b/packages/api/middlewares/logger.ts @@ -1,37 +1,26 @@ -import { getLogger } from "@logtape/logtape"; -import { config } from "@versia-server/config"; +import { serverLogger } from "@versia-server/logging"; import { SHA256 } from "bun"; import chalk from "chalk"; import { createMiddleware } from "hono/factory"; export const logger = createMiddleware(async (context, next) => { - if (config.logging.types.requests) { - const serverLogger = getLogger("server"); - const body = await context.req.raw.clone().text(); + const body = await context.req.raw.clone().text(); - const urlAndMethod = `${chalk.green(context.req.method)} ${chalk.blue(context.req.url)}`; + const urlAndMethod = `${chalk.green(context.req.method)} ${chalk.blue(context.req.url)}`; - const hash = `${chalk.bold("Hash")}: ${chalk.yellow( - new SHA256().update(body).digest("hex"), - )}`; + const hash = `${chalk.bold("Hash")}: ${chalk.yellow( + new SHA256().update(body).digest("hex"), + )}`; - const headers = `${chalk.bold("Headers")}:\n${Array.from( - context.req.raw.headers.entries(), - ) - .map( - ([key, value]) => - ` - ${chalk.cyan(key)}: ${chalk.white(value)}`, - ) - .join("\n")}`; + const headers = `${chalk.bold("Headers")}:\n${Array.from( + context.req.raw.headers.entries(), + ) + .map(([key, value]) => ` - ${chalk.cyan(key)}: ${chalk.white(value)}`) + .join("\n")}`; - const bodyLog = `${chalk.bold("Body")}: ${chalk.gray(body)}`; + const bodyLog = `${chalk.bold("Body")}: ${chalk.gray(body)}`; - if (config.logging.types.requests_content) { - serverLogger.debug`${urlAndMethod}\n${hash}\n${headers}\n${bodyLog}`; - } else { - serverLogger.debug`${urlAndMethod}`; - } - } + serverLogger.debug`${urlAndMethod}\n${hash}\n${headers}\n${bodyLog}`; await next(); }); diff --git a/packages/api/package.json b/packages/api/package.json index 2742029e..afd83d01 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -45,9 +45,9 @@ "@versia-server/config": "workspace:*", "@versia-server/tests": "workspace:*", "@versia-server/kit": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia/client": "workspace:*", "@versia/sdk": "workspace:*", - "@logtape/logtape": "catalog:", "youch": "catalog:", "hono": "catalog:", "hono-openapi": "catalog:", diff --git a/packages/api/routes/messaging/index.ts b/packages/api/routes/messaging/index.ts index 81776514..ecc383dd 100644 --- a/packages/api/routes/messaging/index.ts +++ b/packages/api/routes/messaging/index.ts @@ -1,5 +1,5 @@ -import { getLogger } from "@logtape/logtape"; import { apiRoute } from "@versia-server/kit/api"; +import { federationMessagingLogger } from "@versia-server/logging"; import chalk from "chalk"; import { describeRoute } from "hono-openapi"; @@ -19,8 +19,7 @@ export default apiRoute((app) => async (context) => { const content = await context.req.text(); - getLogger(["federation", "messaging"]) - .info`Received message via ${chalk.bold("Instance Messaging")}:\n${content}`; + federationMessagingLogger.info`Received message via ${chalk.bold("Instance Messaging")}:\n${content}`; return context.text("", 200); }, diff --git a/packages/api/routes/well-known/webfinger/index.ts b/packages/api/routes/well-known/webfinger/index.ts index b520a165..d34cdc23 100644 --- a/packages/api/routes/well-known/webfinger/index.ts +++ b/packages/api/routes/well-known/webfinger/index.ts @@ -1,4 +1,3 @@ -import { getLogger } from "@logtape/logtape"; import { FederationRequester } from "@versia/sdk/http"; import { WebFingerSchema } from "@versia/sdk/schemas"; import { config } from "@versia-server/config"; @@ -8,6 +7,7 @@ import { User } from "@versia-server/kit/db"; import { parseUserAddress } from "@versia-server/kit/parsers"; import { uuid, webfingerMention } from "@versia-server/kit/regex"; import { Users } from "@versia-server/kit/tables"; +import { federationBridgeLogger } from "@versia-server/logging"; import { and, eq, isNull } from "drizzle-orm"; import { describeRoute } from "hono-openapi"; import { resolver, validator } from "hono-openapi/zod"; @@ -90,8 +90,7 @@ export default apiRoute((app) => } catch (e) { const error = e as ApiError; - getLogger(["federation", "bridge"]) - .error`Error from bridge: ${error.message}`; + federationBridgeLogger.error`Error from bridge: ${error.message}`; } } diff --git a/packages/api/setup.ts b/packages/api/setup.ts index 99188e6d..09571c79 100644 --- a/packages/api/setup.ts +++ b/packages/api/setup.ts @@ -1,16 +1,11 @@ -import { getLogger } from "@logtape/logtape"; import { config } from "@versia-server/config"; import { Note, setupDatabase } from "@versia-server/kit/db"; import { connection } from "@versia-server/kit/redis"; -import { configureLoggers } from "@/loggers"; +import { serverLogger } from "@versia-server/logging"; import { searchManager } from "../../classes/search/search-manager.ts"; const timeAtStart = performance.now(); -await configureLoggers(); - -const serverLogger = getLogger("server"); - console.info(` ██╗ ██╗███████╗██████╗ ███████╗██╗ █████╗ ██║ ██║██╔════╝██╔══██╗██╔════╝██║██╔══██╗ diff --git a/packages/config/schema.ts b/packages/config/schema.ts index 18a19000..65b8bd17 100644 --- a/packages/config/schema.ts +++ b/packages/config/schema.ts @@ -716,34 +716,35 @@ export const ConfigSchema = z admin: z.array(z.nativeEnum(RolePermission)).default(ADMIN_ROLES), }), logging: z.strictObject({ - types: z.record( - z.enum([ - "requests", - "responses", - "requests_content", - "filters", - ]), - z - .boolean() - .default(false) - .or( - z.strictObject({ - level: z - .enum([ - "debug", - "info", - "warning", - "error", - "fatal", - ]) - .default("info"), - log_file_path: z.string().optional(), - }), - ), - ), - log_level: z - .enum(["debug", "info", "warning", "error", "fatal"]) - .default("info"), + file: z + .strictObject({ + path: z.string().default("logs/versia.log"), + rotation: z + .strictObject({ + max_size: z + .number() + .int() + .nonnegative() + .default(10_000_000), // 10 MB + max_files: z + .number() + .int() + .nonnegative() + .default(10), + }) + .optional(), + log_level: z + .enum([ + "trace", + "debug", + "info", + "warning", + "error", + "fatal", + ]) + .default("info"), + }) + .optional(), sentry: z .strictObject({ dsn: url, @@ -753,9 +754,21 @@ export const ConfigSchema = z trace_propagation_targets: z.array(z.string()).default([]), max_breadcrumbs: z.number().default(100), environment: z.string().optional(), + log_level: z + .enum([ + "trace", + "debug", + "info", + "warning", + "error", + "fatal", + ]) + .default("info"), }) .optional(), - log_file_path: z.string().default("logs/versia.log"), + log_level: z + .enum(["trace", "debug", "info", "warning", "error", "fatal"]) + .default("info"), }), debug: z .strictObject({ diff --git a/packages/logging/formatter.ts b/packages/logging/formatter.ts new file mode 100644 index 00000000..075d3cca --- /dev/null +++ b/packages/logging/formatter.ts @@ -0,0 +1,56 @@ +import type { LogLevel, LogRecord } from "@logtape/logtape"; +import chalk, { type ChalkInstance } from "chalk"; + +const levelAbbreviations: Record = { + debug: "DBG", + info: "INF", + warning: "WRN", + error: "ERR", + fatal: "FTL", + trace: "TRC", +}; + +/** + * The styles for the log level in the console. + */ +const logLevelStyles: Record = { + 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}`, + ]; +} diff --git a/packages/logging/index.ts b/packages/logging/index.ts new file mode 100644 index 00000000..56079dec --- /dev/null +++ b/packages/logging/index.ts @@ -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 = {}; + + 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"); diff --git a/packages/logging/package.json b/packages/logging/package.json new file mode 100644 index 00000000..703a3154 --- /dev/null +++ b/packages/logging/package.json @@ -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:" + } +} diff --git a/packages/plugin-kit/api.ts b/packages/plugin-kit/api.ts index e3dd5636..a77309b9 100644 --- a/packages/plugin-kit/api.ts +++ b/packages/plugin-kit/api.ts @@ -1,7 +1,7 @@ import type { Hook } from "@hono/zod-validator"; -import { getLogger } from "@logtape/logtape"; import type { RolePermission } from "@versia/client/schemas"; import { config } from "@versia-server/config"; +import { serverLogger } from "@versia-server/logging"; import { extractParams, verifySolution } from "altcha-lib"; import chalk from "chalk"; import { eq, type SQL } from "drizzle-orm"; @@ -418,7 +418,6 @@ export const jsonOrForm = (): MiddlewareHandler => { export const debugResponse = async (res: Response): Promise => { const body = await res.clone().text(); - const logger = getLogger("server"); const status = `${chalk.bold("Status")}: ${chalk.green(res.status)}`; @@ -430,9 +429,5 @@ export const debugResponse = async (res: Response): Promise => { const bodyLog = `${chalk.bold("Body")}: ${chalk.gray(body)}`; - if (config.logging.types.requests_content) { - logger.debug`${status}\n${headers}\n${bodyLog}`; - } else { - logger.debug`${status}`; - } + serverLogger.debug`${status}\n${headers}\n${bodyLog}`; }; diff --git a/packages/plugin-kit/db/instance.ts b/packages/plugin-kit/db/instance.ts index 897e3e5b..cb83accc 100644 --- a/packages/plugin-kit/db/instance.ts +++ b/packages/plugin-kit/db/instance.ts @@ -1,9 +1,12 @@ -import { getLogger } from "@logtape/logtape"; import * as VersiaEntities from "@versia/sdk/entities"; import { config } from "@versia-server/config"; import { ApiError } from "@versia-server/kit"; import { db } from "@versia-server/kit/db"; import { Instances } from "@versia-server/kit/tables"; +import { + federationMessagingLogger, + federationResolversLogger, +} from "@versia-server/logging"; import { randomUUIDv7 } from "bun"; import chalk from "chalk"; import { @@ -175,9 +178,6 @@ export class Instance extends BaseInterface { const wellKnownUrl = new URL("/.well-known/nodeinfo", origin); // Go to endpoint, then follow the links to the actual metadata - - const logger = getLogger(["federation", "resolvers"]); - try { const { json, ok, status } = await fetch(wellKnownUrl, { // @ts-expect-error Bun extension @@ -185,7 +185,7 @@ export class Instance extends BaseInterface { }); if (!ok) { - logger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( + federationResolversLogger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( origin, )} - HTTP ${status}`; return null; @@ -196,7 +196,7 @@ export class Instance extends BaseInterface { }; if (!wellKnown.links) { - logger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( + federationResolversLogger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( origin, )} - No links found`; return null; @@ -209,7 +209,7 @@ export class Instance extends BaseInterface { ); if (!metadataUrl) { - logger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( + federationResolversLogger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( origin, )} - No metadata URL found`; return null; @@ -225,7 +225,7 @@ export class Instance extends BaseInterface { }); if (!ok2) { - logger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( + federationResolversLogger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( origin, )} - HTTP ${status2}`; return null; @@ -264,7 +264,7 @@ export class Instance extends BaseInterface { }, }); } catch (error) { - logger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( + federationResolversLogger.error`Failed to fetch ActivityPub metadata for instance ${chalk.bold( origin, )} - Error! ${error}`; return null; @@ -312,14 +312,12 @@ export class Instance extends BaseInterface { } public async updateFromRemote(): Promise { - const logger = getLogger(["federation", "resolvers"]); - const output = await Instance.fetchMetadata( new URL(`https://${this.data.baseUrl}`), ); if (!output) { - logger.error`Failed to update instance ${chalk.bold( + federationResolversLogger.error`Failed to update instance ${chalk.bold( this.data.baseUrl, )}`; throw new Error("Failed to update instance"); @@ -341,12 +339,10 @@ export class Instance extends BaseInterface { } public async sendMessage(content: string): Promise { - const logger = getLogger(["federation", "messaging"]); - if ( !this.data.extensions?.["pub.versia:instance_messaging"]?.endpoint ) { - logger.info`Instance ${chalk.gray( + federationMessagingLogger.info`Instance ${chalk.gray( this.data.baseUrl, )} does not support Instance Messaging, skipping message`; diff --git a/packages/plugin-kit/db/user.ts b/packages/plugin-kit/db/user.ts index 9ff50fdc..3a7b76ae 100644 --- a/packages/plugin-kit/db/user.ts +++ b/packages/plugin-kit/db/user.ts @@ -1,4 +1,3 @@ -import { getLogger } from "@logtape/logtape"; import type { Account, Mention as MentionSchema, @@ -29,6 +28,10 @@ import { Users, UserToPinnedNotes, } from "@versia-server/kit/tables"; +import { + federationDeliveryLogger, + federationResolversLogger, +} from "@versia-server/logging"; import { password as bunPassword, randomUUIDv7 } from "bun"; import chalk from "chalk"; import { @@ -49,7 +52,6 @@ import { htmlToText } from "html-to-text"; import type { z } from "zod"; import { getBestContentType } from "@/content_types"; import { randomString } from "@/math"; -import { sentry } from "@/sentry"; import { searchManager } from "~/classes/search/search-manager"; import type { HttpVerb, KnownEntity } from "~/types/api.ts"; import { @@ -1165,8 +1167,7 @@ export class User extends BaseInterface { } public static async resolve(uri: URL): Promise { - getLogger(["federation", "resolvers"]) - .debug`Resolving user ${chalk.gray(uri)}`; + federationResolversLogger.debug`Resolving user ${chalk.gray(uri)}`; // Check if user not already in database const foundUser = await User.fromSql(eq(Users.uri, uri.href)); @@ -1187,8 +1188,7 @@ export class User extends BaseInterface { return await User.fromId(userUuid[0]); } - getLogger(["federation", "resolvers"]) - .debug`User not found in database, fetching from remote`; + federationResolversLogger.debug`User not found in database, fetching from remote`; return User.fromVersia(uri); } @@ -1419,11 +1419,10 @@ export class User extends BaseInterface { entity, ); } catch (e) { - getLogger(["federation", "delivery"]).error`Federating ${chalk.gray( + federationDeliveryLogger.error`Federating ${chalk.gray( entity.data.type, )} to ${user.uri} ${chalk.bold.red("failed")}`; - getLogger(["federation", "delivery"]).error`${e}`; - sentry?.captureException(e); + federationDeliveryLogger.error`${e}`; return { ok: false }; } diff --git a/packages/plugin-kit/package.json b/packages/plugin-kit/package.json index 0ee45f8b..0815c3a9 100644 --- a/packages/plugin-kit/package.json +++ b/packages/plugin-kit/package.json @@ -41,9 +41,9 @@ "chalk": "catalog:", "@versia/client": "workspace:*", "@versia-server/config": "workspace:*", + "@versia-server/logging": "workspace:*", "@versia/sdk": "workspace:*", "html-to-text": "catalog:", - "@logtape/logtape": "catalog:", "sharp": "catalog:", "magic-regexp": "catalog:", "altcha-lib": "catalog:", diff --git a/packages/plugin-kit/tables/db.ts b/packages/plugin-kit/tables/db.ts index 8b554839..bed83b5d 100644 --- a/packages/plugin-kit/tables/db.ts +++ b/packages/plugin-kit/tables/db.ts @@ -1,5 +1,5 @@ -import { getLogger } from "@logtape/logtape"; import { config } from "@versia-server/config"; +import { databaseLogger } from "@versia-server/logging"; import { SQL } from "bun"; import chalk from "chalk"; import { type BunSQLDatabase, drizzle } from "drizzle-orm/bun-sql"; @@ -40,8 +40,6 @@ export const db = : drizzle(primaryDb, { schema }); export const setupDatabase = async (info = true): Promise => { - const logger = getLogger("database"); - for (const dbPool of [primaryDb, ...replicas]) { try { await dbPool.connect(); @@ -53,7 +51,7 @@ export const setupDatabase = async (info = true): Promise => { return; } - logger.fatal`Failed to connect to database ${chalk.bold( + databaseLogger.fatal`Failed to connect to database ${chalk.bold( // Index of the database in the array replicas.indexOf(dbPool) === -1 ? "primary" @@ -65,17 +63,17 @@ export const setupDatabase = async (info = true): Promise => { } // Migrate the database - info && logger.info`Migrating database...`; + info && databaseLogger.info`Migrating database...`; try { await migrate(db, { migrationsFolder: "./packages/plugin-kit/tables/migrations", }); } catch (e) { - logger.fatal`Failed to migrate database. Please check your configuration.`; + databaseLogger.fatal`Failed to migrate database. Please check your configuration.`; throw e; } - info && logger.info`Database migrated`; + info && databaseLogger.info`Database migrated`; }; diff --git a/packages/worker/index.ts b/packages/worker/index.ts index ef958fc5..5170c038 100644 --- a/packages/worker/index.ts +++ b/packages/worker/index.ts @@ -1,7 +1,6 @@ import process from "node:process"; -import { getLogger } from "@logtape/logtape"; +import { serverLogger } from "@versia-server/logging"; import chalk from "chalk"; -import { sentry } from "@/sentry"; import { workers } from "./workers.ts"; process.on("SIGINT", () => { @@ -9,9 +8,6 @@ process.on("SIGINT", () => { }); await import("./setup.ts"); -sentry?.captureMessage("Server started", "info"); - -const serverLogger = getLogger("server"); for (const [worker, fn] of Object.entries(workers)) { serverLogger.info`Starting ${worker} Worker...`; diff --git a/packages/worker/package.json b/packages/worker/package.json index 51a73d6a..5fd4d9bc 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -40,7 +40,7 @@ "dependencies": { "@versia-server/config": "workspace:*", "@versia-server/kit": "workspace:*", - "chalk": "catalog:", - "@logtape/logtape": "catalog:" + "@versia-server/logging": "workspace:*", + "chalk": "catalog:" } } diff --git a/packages/worker/setup.ts b/packages/worker/setup.ts index bd2471fb..595d0121 100644 --- a/packages/worker/setup.ts +++ b/packages/worker/setup.ts @@ -1,17 +1,12 @@ -import { getLogger } from "@logtape/logtape"; import { config } from "@versia-server/config"; import { Note, setupDatabase } from "@versia-server/kit/db"; import { connection } from "@versia-server/kit/redis"; +import { serverLogger } from "@versia-server/logging"; import chalk from "chalk"; -import { configureLoggers } from "@/loggers"; import { searchManager } from "../../classes/search/search-manager.ts"; const timeAtStart = performance.now(); -await configureLoggers(); - -const serverLogger = getLogger("server"); - console.info(` ██╗ ██╗███████╗██████╗ ███████╗██╗ █████╗ ██║ ██║██╔════╝██╔══██╗██╔════╝██║██╔══██╗ diff --git a/utils/loggers.ts b/utils/loggers.ts deleted file mode 100644 index f4f66679..00000000 --- a/utils/loggers.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { mkdir } from "node:fs/promises"; -import { dirname } from "node:path"; -import { getRotatingFileSink } from "@logtape/file"; -import { - configure, - getConsoleSink, - getLevelFilter, - type LogLevel, - type LogRecord, -} from "@logtape/logtape"; -import { config } from "@versia-server/config"; -import chalk from "chalk"; - -// config.logging.log_file_path is a path to a file, create the directory if it doesn't exist -await mkdir(dirname(config.logging.log_file_path), { recursive: true }); - -const levelAbbreviations: Record = { - debug: "DBG", - info: "INF", - warning: "WRN", - error: "ERR", - fatal: "FTL", - trace: "TRC", -}; - -/** - * The styles for the log level in the console. - */ -const logLevelStyles: Record string> = { - debug: chalk.white.bgGray, - info: chalk.black.bgWhite, - warning: chalk.black.bgYellow, - error: chalk.white.bgRed, - fatal: chalk.white.bgRedBright, - trace: chalk.white.bgBlue, -}; - -/** - * The default 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 defaultConsoleFormatter(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}`, - ]; -} - -export const configureLoggers = (silent = false): Promise => - configure({ - reset: true, - sinks: { - console: getConsoleSink({ - formatter: defaultConsoleFormatter, - }), - file: getRotatingFileSink(config.logging.log_file_path, { - maxFiles: 10, - maxSize: 10 * 1024 * 1024, - }), - }, - filters: { - configFilter: silent - ? getLevelFilter(null) - : getLevelFilter(config.logging.log_level), - }, - loggers: [ - { - category: "server", - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["federation", "inbox"], - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["federation", "delivery"], - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["federation", "bridge"], - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["federation", "resolvers"], - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["federation", "messaging"], - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: "database", - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: "webfinger", - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: "sonic", - sinks: ["console", "file"], - filters: ["configFilter"], - }, - { - category: ["logtape", "meta"], - lowestLevel: "error", - }, - { - category: "plugin", - sinks: ["console", "file"], - filters: ["configFilter"], - }, - ], - }); diff --git a/utils/sentry.ts b/utils/sentry.ts deleted file mode 100644 index df9716a7..00000000 --- a/utils/sentry.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from "@sentry/bun"; -import { config } from "@versia-server/config"; -import { env } from "bun"; -import pkg from "~/package.json" with { type: "json" }; - -const sentryInstance = - config.logging.sentry && - 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()], - }); - -export const sentry = sentryInstance || undefined; diff --git a/utils/server.ts b/utils/server.ts index 3304abe1..39aa4058 100644 --- a/utils/server.ts +++ b/utils/server.ts @@ -24,9 +24,7 @@ export const createServer = ( async fetch(req, server): Promise { const output = await app.fetch(req, { ip: server.requestIP(req) }); - if (config.logging.types.responses) { - await debugResponse(output.clone()); - } + await debugResponse(output.clone()); return output; },