diff --git a/config/config.example.toml b/config/config.example.toml index 449ec39a..5c1651ad 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -42,10 +42,10 @@ tos_url = "https://my-site.com/tos" # Whether to enable registrations or not registration = true rules = [ - "Do not harass others", - "Be nice to people", - "Don't spam", - "Don't post illegal content", + "Do not harass others", + "Be nice to people", + "Don't spam", + "Don't post illegal content", ] # Delete this section if you don't want to use custom OAuth providers @@ -72,8 +72,8 @@ bind_port = "8080" banned_ips = [] # Banned user agents, regex format banned_user_agents = [ - # "curl\/7.68.0", - # "wget\/1.20.3", + # "curl\/7.68.0", + # "wget\/1.20.3", ] [http.bait] @@ -156,32 +156,32 @@ max_field_name_size = 1000 max_field_value_size = 1000 # Forbidden usernames, defaults are from Akkoma username_blacklist = [ - ".well-known", - "~", - "about", - "activities", - "api", - "auth", - "dev", - "inbox", - "internal", - "main", - "media", - "nodeinfo", - "notice", - "oauth", - "objects", - "proxy", - "push", - "registration", - "relay", - "settings", - "status", - "tag", - "users", - "web", - "search", - "mfa", + ".well-known", + "~", + "about", + "activities", + "api", + "auth", + "dev", + "inbox", + "internal", + "main", + "media", + "nodeinfo", + "notice", + "oauth", + "objects", + "proxy", + "push", + "registration", + "relay", + "settings", + "status", + "tag", + "users", + "web", + "search", + "mfa", ] # Whether to blacklist known temporary email providers blacklist_tempmail = false @@ -189,57 +189,57 @@ blacklist_tempmail = false email_blacklist = [] # Valid URL schemes, otherwise the URL is parsed as text url_scheme_whitelist = [ - "http", - "https", - "ftp", - "dat", - "dweb", - "gopher", - "hyper", - "ipfs", - "ipns", - "irc", - "xmpp", - "ircs", - "magnet", - "mailto", - "mumble", - "ssb", - "gemini", + "http", + "https", + "ftp", + "dat", + "dweb", + "gopher", + "hyper", + "ipfs", + "ipns", + "irc", + "xmpp", + "ircs", + "magnet", + "mailto", + "mumble", + "ssb", + "gemini", ] # Only allow those MIME types of data to be uploaded # This can easily be spoofed, but if it is spoofed it will appear broken # to normal clients until despoofed enforce_mime_types = false allowed_mime_types = [ - "image/jpeg", - "image/png", - "image/gif", - "image/heic", - "image/heif", - "image/webp", - "image/avif", - "video/webm", - "video/mp4", - "video/quicktime", - "video/ogg", - "audio/wave", - "audio/wav", - "audio/x-wav", - "audio/x-pn-wave", - "audio/vnd.wave", - "audio/ogg", - "audio/vorbis", - "audio/mpeg", - "audio/mp3", - "audio/webm", - "audio/flac", - "audio/aac", - "audio/m4a", - "audio/x-m4a", - "audio/mp4", - "audio/3gpp", - "video/x-ms-asf", + "image/jpeg", + "image/png", + "image/gif", + "image/heic", + "image/heif", + "image/webp", + "image/avif", + "video/webm", + "video/mp4", + "video/quicktime", + "video/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wave", + "audio/vnd.wave", + "audio/ogg", + "audio/vorbis", + "audio/mpeg", + "audio/mp3", + "audio/webm", + "audio/flac", + "audio/aac", + "audio/m4a", + "audio/x-m4a", + "audio/mp4", + "audio/3gpp", + "video/x-ms-asf", ] [defaults] @@ -294,8 +294,8 @@ banner = "" # Note contents note_content = [ - # "(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+", - # "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+", + # "(https?://)?(www\\.)?youtube\\.com/watch\\?v=[a-zA-Z0-9_-]+", + # "(https?://)?(www\\.)?youtu\\.be/[a-zA-Z0-9_-]+", ] emoji = [] # These will drop users matching the filters @@ -308,6 +308,8 @@ bio = [] log_requests = false # Log request and their contents (warning: this is a lot of data) log_requests_verbose = false +# Available levels: debug, info, warning, error, critical +log_level = "info" # For GDPR compliance, you can disable logging of IPs log_ip = false diff --git a/database/entities/Status.ts b/database/entities/Status.ts index acbefdb0..2043738b 100644 --- a/database/entities/Status.ts +++ b/database/entities/Status.ts @@ -35,6 +35,8 @@ import { statusToMentions, user, } from "~drizzle/schema"; +import { dualLogger } from "@loggers"; +import { LogLevel } from "~packages/log-manager"; import type { Note } from "~types/lysand/Object"; import type { Attachment as APIAttachment } from "~types/mastodon/attachment"; import type { Status as APIStatus } from "~types/mastodon/status"; @@ -601,7 +603,11 @@ export const resolveStatus = async ( for (const attachment of note.attachments ?? []) { const resolvedAttachment = await attachmentFromLysand(attachment).catch( (e) => { - console.error(e); + dualLogger.logError( + LogLevel.ERROR, + "Federation.StatusResolver", + e, + ); return null; }, ); @@ -616,7 +622,7 @@ export const resolveStatus = async ( for (const emoji of note.extensions?.["org.lysand:custom_emojis"]?.emojis ?? []) { const resolvedEmoji = await fetchEmoji(emoji).catch((e) => { - console.error(e); + dualLogger.logError(LogLevel.ERROR, "Federation.StatusResolver", e); return null; }); @@ -1010,8 +1016,14 @@ export const federateStatus = async (status: StatusWithRelations) => { const response = await fetch(request); if (!response.ok) { - console.error(await response.text()); - throw new Error( + dualLogger.log( + LogLevel.DEBUG, + "Federation.Status", + await response.text(), + ); + dualLogger.log( + LogLevel.ERROR, + "Federation.Status", `Failed to federate status ${status.id} to ${user.uri}`, ); } diff --git a/database/entities/User.ts b/database/entities/User.ts index 3f5d534e..34619033 100644 --- a/database/entities/User.ts +++ b/database/entities/User.ts @@ -12,6 +12,8 @@ import { relationship, user, } from "~drizzle/schema"; +import { dualLogger } from "@loggers"; +import { LogLevel } from "~packages/log-manager"; import type { Account as APIAccount } from "~types/mastodon/account"; import type { Source as APISource } from "~types/mastodon/source"; import { @@ -191,8 +193,15 @@ export const followRequestUser = async ( const response = await fetch(request); if (!response.ok) { - console.error(await response.text()); - console.error( + dualLogger.log( + LogLevel.DEBUG, + "Federation.FollowRequest", + await response.text(), + ); + + dualLogger.log( + LogLevel.ERROR, + "Federation.FollowRequest", `Failed to federate follow request from ${follower.id} to ${followee.uri}`, ); @@ -230,8 +239,15 @@ export const sendFollowAccept = async (follower: User, followee: User) => { const response = await fetch(request); if (!response.ok) { - console.error(await response.text()); - throw new Error( + dualLogger.log( + LogLevel.DEBUG, + "Federation.FollowAccept", + await response.text(), + ); + + dualLogger.log( + LogLevel.ERROR, + "Federation.FollowAccept", `Failed to federate follow accept from ${followee.id} to ${follower.uri}`, ); } @@ -249,8 +265,15 @@ export const sendFollowReject = async (follower: User, followee: User) => { const response = await fetch(request); if (!response.ok) { - console.error(await response.text()); - throw new Error( + dualLogger.log( + LogLevel.DEBUG, + "Federation.FollowReject", + await response.text(), + ); + + dualLogger.log( + LogLevel.ERROR, + "Federation.FollowReject", `Failed to federate follow reject from ${followee.id} to ${follower.uri}`, ); } diff --git a/index.ts b/index.ts index 4719d9cf..7690584f 100644 --- a/index.ts +++ b/index.ts @@ -1,28 +1,15 @@ +import { dualLogger } from "@loggers"; import { connectMeili } from "@meilisearch"; import { config } from "config-manager"; import { count } from "drizzle-orm"; -import { LogLevel, LogManager, MultiLogManager } from "log-manager"; +import { LogLevel } from "log-manager"; import { db, setupDatabase } from "~drizzle/db"; import { status } from "~drizzle/schema"; import { createServer } from "~server"; const timeAtStart = performance.now(); -const requests_log = Bun.file(config.logging.storage.requests); -const isEntry = import.meta.path === Bun.main; - -const noColors = process.env.NO_COLORS === "true"; -const noFancyDates = process.env.NO_FANCY_DATES === "true"; - // If imported as a module, redirect logs to /dev/null to not pollute console (e.g. in tests) -const logger = new LogManager(isEntry ? requests_log : Bun.file("/dev/null")); -const consoleLogger = new LogManager( - isEntry ? Bun.stdout : Bun.file("/dev/null"), - !noColors, - !noFancyDates, -); -const dualLogger = new MultiLogManager([logger, consoleLogger]); - await dualLogger.log(LogLevel.INFO, "Lysand", "Starting Lysand..."); await setupDatabase(dualLogger); @@ -43,11 +30,9 @@ try { )[0].count; } catch (e) { const error = e as Error; - await logger.logError(LogLevel.CRITICAL, "Database", error); - await consoleLogger.logError(LogLevel.CRITICAL, "Database", error); + await dualLogger.logError(LogLevel.CRITICAL, "Database", error); process.exit(1); } - const server = createServer(config, dualLogger, true); await dualLogger.log( diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index 8042ea8d..85a486c9 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -336,6 +336,9 @@ export interface Config { /** @default false */ log_requests_verbose: boolean; + /** @default "info" */ + log_level: "info" | "debug" | "warning" | "error" | "critical"; + /** @default false */ log_ip: boolean; @@ -591,6 +594,7 @@ export const defaultConfig: Config = { logging: { log_requests: false, log_requests_verbose: false, + log_level: "info", log_ip: false, log_filters: true, storage: { diff --git a/packages/log-manager/index.ts b/packages/log-manager/index.ts index 080478c8..2e2b9056 100644 --- a/packages/log-manager/index.ts +++ b/packages/log-manager/index.ts @@ -2,6 +2,7 @@ import { appendFile, exists, mkdir } from "node:fs/promises"; import { dirname } from "node:path"; import type { BunFile } from "bun"; import chalk from "chalk"; +import { config } from "config-manager"; export enum LogLevel { DEBUG = "debug", @@ -11,6 +12,14 @@ export enum LogLevel { CRITICAL = "critical", } +const logOrder = [ + LogLevel.DEBUG, + LogLevel.INFO, + LogLevel.WARNING, + LogLevel.ERROR, + LogLevel.CRITICAL, +]; + /** * Class for handling logging to disk or to stdout * @param output BunFile of output (can be a normal file or something like Bun.stdout) @@ -67,6 +76,12 @@ export class LogManager { message: string, showTimestamp = true, ) { + if ( + logOrder.indexOf(level) < + logOrder.indexOf(config.logging.log_level as LogLevel) + ) + return; + if (this.enableColors) { await this.write( `${ @@ -113,6 +128,7 @@ export class LogManager { * @param error Error to log */ async logError(level: LogLevel, entity: string, error: Error) { + error.stack && (await this.log(LogLevel.DEBUG, entity, error.stack)); await this.log(level, entity, error.message); } diff --git a/packages/server-handler/index.ts b/packages/server-handler/index.ts index 15159f8e..d4e8dea9 100644 --- a/packages/server-handler/index.ts +++ b/packages/server-handler/index.ts @@ -164,6 +164,11 @@ export const processRoute = async ( return output; } catch (err) { + await logger.log( + LogLevel.DEBUG, + "Server.RouteHandler", + (err as Error).toString(), + ); await logger.logError( LogLevel.ERROR, "Server.RouteHandler", diff --git a/server.ts b/server.ts index 8ebcd158..553fb37d 100644 --- a/server.ts +++ b/server.ts @@ -24,8 +24,21 @@ export const createServer = ( return errorResponse("Forbidden", 403); } } catch (e) { - console.error(`[-] Error while parsing banned IP "${ip}" `); - throw e; + logger.log( + LogLevel.ERROR, + "Server.IPCheck", + `Error while parsing banned IP "${ip}" `, + ); + logger.logError( + LogLevel.ERROR, + "Server.IPCheck", + e as Error, + ); + + return errorResponse( + `A server error occured: ${(e as Error).message}`, + 500, + ); } } @@ -57,10 +70,21 @@ export const createServer = ( ); } } catch (e) { - console.error( - `[-] Error while parsing bait IP "${ip}" `, + logger.log( + LogLevel.ERROR, + "Server.IPCheck", + `Error while parsing bait IP "${ip}" `, + ); + logger.logError( + LogLevel.ERROR, + "Server.IPCheck", + e as Error, + ); + + return errorResponse( + `A server error occured: ${(e as Error).message}`, + 500, ); - throw e; } } diff --git a/server/api/api/v1/accounts/[id]/index.test.ts b/server/api/api/v1/accounts/[id]/index.test.ts index a66d6957..00073c85 100644 --- a/server/api/api/v1/accounts/[id]/index.test.ts +++ b/server/api/api/v1/accounts/[id]/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestStatuses, diff --git a/server/api/api/v1/accounts/lookup/index.test.ts b/server/api/api/v1/accounts/lookup/index.test.ts index f447053f..f06f4b1d 100644 --- a/server/api/api/v1/accounts/lookup/index.test.ts +++ b/server/api/api/v1/accounts/lookup/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestUsers, diff --git a/server/api/api/v1/accounts/lookup/index.ts b/server/api/api/v1/accounts/lookup/index.ts index 4cef6613..87b137d7 100644 --- a/server/api/api/v1/accounts/lookup/index.ts +++ b/server/api/api/v1/accounts/lookup/index.ts @@ -17,6 +17,8 @@ import { resolveWebFinger, userToAPI, } from "~database/entities/User"; +import { dualLogger } from "@loggers"; +import { LogLevel } from "~packages/log-manager"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -68,7 +70,11 @@ export default apiRoute( const [username, domain] = accountMatches[0].split("@"); const foundAccount = await resolveWebFinger(username, domain).catch( (e) => { - console.error(e); + dualLogger.logError( + LogLevel.ERROR, + "WebFinger.Resolve", + e as Error, + ); return null; }, ); diff --git a/server/api/api/v1/accounts/search/index.test.ts b/server/api/api/v1/accounts/search/index.test.ts index 41ccc8c4..15d16ff2 100644 --- a/server/api/api/v1/accounts/search/index.test.ts +++ b/server/api/api/v1/accounts/search/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestUsers, diff --git a/server/api/api/v1/statuses/[id]/favourited_by.test.ts b/server/api/api/v1/statuses/[id]/favourited_by.test.ts index 969025b1..00affb12 100644 --- a/server/api/api/v1/statuses/[id]/favourited_by.test.ts +++ b/server/api/api/v1/statuses/[id]/favourited_by.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestStatuses, diff --git a/server/api/api/v1/statuses/[id]/reblogged_by.test.ts b/server/api/api/v1/statuses/[id]/reblogged_by.test.ts index 18de5ab3..e2b8a783 100644 --- a/server/api/api/v1/statuses/[id]/reblogged_by.test.ts +++ b/server/api/api/v1/statuses/[id]/reblogged_by.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestStatuses, diff --git a/server/api/api/v1/statuses/index.test.ts b/server/api/api/v1/statuses/index.test.ts index c6823470..1dd97eb0 100644 --- a/server/api/api/v1/statuses/index.test.ts +++ b/server/api/api/v1/statuses/index.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestUsers, diff --git a/server/api/api/v1/timelines/home.test.ts b/server/api/api/v1/timelines/home.test.ts index bebfcc47..f8f5370c 100644 --- a/server/api/api/v1/timelines/home.test.ts +++ b/server/api/api/v1/timelines/home.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestStatuses, diff --git a/server/api/api/v1/timelines/public.test.ts b/server/api/api/v1/timelines/public.test.ts index 382d23b6..68dc2530 100644 --- a/server/api/api/v1/timelines/public.test.ts +++ b/server/api/api/v1/timelines/public.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, test } from "bun:test"; -import { config } from "~index"; +import { config } from "config-manager"; import { deleteOldTestUsers, getTestStatuses, diff --git a/server/api/api/v2/search/index.ts b/server/api/api/v2/search/index.ts index 9487dcc8..65f1d919 100644 --- a/server/api/api/v2/search/index.ts +++ b/server/api/api/v2/search/index.ts @@ -12,6 +12,8 @@ import { } from "~database/entities/User"; import { db } from "~drizzle/db"; import { instance, user } from "~drizzle/schema"; +import { dualLogger } from "@loggers"; +import { LogLevel } from "~packages/log-manager"; export const meta = applyConfig({ allowedMethods: ["GET"], @@ -117,7 +119,11 @@ export default apiRoute( username, domain, ).catch((e) => { - console.error(e); + dualLogger.logError( + LogLevel.ERROR, + "WebFinger.Resolve", + e, + ); return null; }); diff --git a/server/api/users/[uuid]/inbox/index.ts b/server/api/users/[uuid]/inbox/index.ts index 24a20d83..2f9d1c06 100644 --- a/server/api/users/[uuid]/inbox/index.ts +++ b/server/api/users/[uuid]/inbox/index.ts @@ -11,6 +11,8 @@ import { } from "~database/entities/User"; import { db } from "~drizzle/db"; import { notification, relationship } from "~drizzle/schema"; +import { dualLogger } from "@loggers"; +import { LogLevel } from "~packages/log-manager"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -126,7 +128,11 @@ export default apiRoute(async (req, matchedRoute, extraData) => { const newStatus = await resolveStatus(undefined, note).catch( (e) => { - console.error(e); + dualLogger.logError( + LogLevel.ERROR, + "Inbox.NoteResolve", + e as Error, + ); return null; }, ); diff --git a/utils/loggers.ts b/utils/loggers.ts new file mode 100644 index 00000000..e7e420a9 --- /dev/null +++ b/utils/loggers.ts @@ -0,0 +1,18 @@ +import { LogManager, MultiLogManager } from "log-manager"; +import { config } from "config-manager"; + +const noColors = process.env.NO_COLORS === "true"; +const noFancyDates = process.env.NO_FANCY_DATES === "true"; + +const requests_log = Bun.file(config.logging.storage.requests); + +export const logger = new LogManager( + true ? requests_log : Bun.file("/dev/null"), +); +export const consoleLogger = new LogManager( + true ? Bun.stdout : Bun.file("/dev/null"), + !noColors, + !noFancyDates, +); + +export const dualLogger = new MultiLogManager([logger, consoleLogger]);