refactor: migrate to lily for logging refactor: migrate to lily use singleton for lily finish lily migration
willow hai@wlo.moe
Sun, 27 Jul 2025 00:26:44 +0100
17 files changed,
50 insertions(+),
230 deletions(-)
jump to
M
src/config.ts
→
src/config.ts
@@ -1,6 +1,9 @@
import fs from "node:fs"; import { z } from "zod"; +import lily from "@/utils/logging"; +const logger = lily.child("config"); + const discordSchema = z.object({ token: z.string(), app_id: z.string(),@@ -88,9 +91,9 @@ );
const conf = schema.safeParse(runtimeConfig); if (conf.success) return conf.data; - console.error("invalid environment variables"); + logger.error("invalid environment variables"); for (const err of conf.error.errors) - console.log(` ${err.message}: ${err.path}`); + logger.error(` ${err.message}: ${err.path}`); process.exit(1); }
M
src/database/connection.ts
→
src/database/connection.ts
@@ -1,8 +1,8 @@
import mongoose from "mongoose"; import cfg from "../config.ts"; -import { Logger } from "../utils/logging.ts"; +import lily from "../utils/logging.ts"; -const logger = new Logger("database"); +const logger = lily.child("database"); export async function connectDatabase() { try {
M
src/events/interactionCreate.ts
→
src/events/interactionCreate.ts
@@ -1,8 +1,8 @@
import { type BaseInteraction, type Client, Events } from "discord.js"; import { commands } from "../handlers/interactions"; -import { logger as eventLogger } from "../handlers/events"; -const logger = eventLogger.child("interactionCreate"); +import { loggers } from "@/utils/logging"; +const logger = loggers.interactions; export default { event: Events.InteractionCreate,
M
src/events/messageCreate.ts
→
src/events/messageCreate.ts
@@ -1,6 +1,6 @@
import config from "@/config"; import { UserModel } from "@/database/schemas"; -import { logger as _logger } from "@/utils/logging"; +import lily from "@/utils/logging"; import vision from "@google-cloud/vision"; import { type Attachment,@@ -15,7 +15,7 @@ image: Attachment;
isCat: boolean; }; -const logger = _logger.child(["google-cloud", "vision"]); +const logger = lily.child(["google-cloud", "vision"]); type ImageResults = Record<string, ImageResult>; const client = new vision.ImageAnnotatorClient({
M
src/handlers/events.ts
→
src/handlers/events.ts
@@ -1,9 +1,9 @@
import { type Client, Collection } from "discord.js"; import type { IEvent } from "../types.ts"; -import { Logger } from "../utils/logging.ts"; +import lily from "../utils/logging.ts"; import { crawlDirectory, getHandlerPath } from "./common.ts"; -const logger = new Logger("events"); +const logger = lily.child("events"); async function getEvents(): Promise<Collection<string, IEvent>> { const eventFiles = new Collection<string, IEvent>();
M
src/handlers/interactions.ts
→
src/handlers/interactions.ts
@@ -1,12 +1,12 @@
import type { ApplicationCommandData, Client, Snowflake } from "discord.js"; import { Collection, REST, Routes } from "discord.js"; -import cfg from "../config.ts"; -import type { ICommand } from "../types.ts"; -import { Logger } from "../utils/logging.ts"; +import cfg from "@/config.ts"; +import type { ICommand } from "@/types.ts"; +import lily from "@/utils/logging.ts"; import { crawlDirectory, getHandlerPath } from "./common.ts"; -const logger = new Logger("interactions"); +const logger = lily.child("interactions"); export const commands = new Collection<string, ICommand>(); async function getCommands(): Promise<ICommand[]> {
M
src/index.ts
→
src/index.ts
@@ -6,13 +6,8 @@ import registerInteractions from "./handlers/interactions.ts";
import cfg from "./config.ts"; import { watchForum } from "./utils/forumWatcher.ts"; -import { COLOURS, Logger } from "./utils/logging.ts"; +import logger from "./utils/logging.ts"; -const logger = new Logger("bot", { - timestamp: true, - colorize: true, - scopeColor: COLOURS.FG_CYAN, -}); const client = new Client({ intents: [ GatewayIntentBits.GuildMessages,@@ -27,7 +22,7 @@
await connectDatabase(); await Promise.all([registerEvents(client), registerInteractions(client)]); logger.info("events and interactions registered!"); - logger.newLine(); + console.log(); const forumConfig = cfg.data.roblox?.forumWatcher; if (!forumConfig || !forumConfig.enabled) return;
M
src/interactions/commands/bug/index.ts
→
src/interactions/commands/bug/index.ts
@@ -1,6 +1,6 @@
import config from "@/config.ts"; +import type { ICommand } from "@/types.ts"; import { type ApplicationCommandData, SlashCommandBuilder } from "discord.js"; -import type { ICommand } from "../../../types.ts"; import { buttonCommand } from "./button.ts"; import { reportCommand } from "./report.ts";
M
src/interactions/commands/bug/report.ts
→
src/interactions/commands/bug/report.ts
@@ -1,4 +1,7 @@
import config from "@/config.ts"; +import { BugModel, GuildModel, getNextBugId } from "@/database/schemas.ts"; +import { createUserIfNotExists } from "@/utils/exists.ts"; +import { hasManagerPermissions } from "@/utils/permissions.ts"; import { ActionRowBuilder, ButtonBuilder,@@ -18,16 +21,13 @@ TextInputBuilder,
TextInputStyle, } from "discord.js"; import { - BugModel, - GuildModel, - getNextBugId, -} from "../../../database/schemas.ts"; -import { createUserIfNotExists } from "../../../utils/exists.ts"; -import { Logger } from "../../../utils/logging.ts"; -import { hasManagerPermissions } from "../../../utils/permissions.ts"; -import { PROJECT_MAP, getProjectName, updateBugEmbed } from "./shared.ts"; + PROJECT_MAP, + getProjectName, + logger as lily, + updateBugEmbed, +} from "./shared.ts"; -const logger = new Logger("bug-report"); +const logger = lily.child("report"); export function getProjectChoices() { return Object.entries(PROJECT_MAP).map(([key, project]) => ({
M
src/interactions/commands/config.ts
→
src/interactions/commands/config.ts
@@ -1,6 +1,6 @@
import config from "@/config.ts"; import { GuildModel, type GuildType } from "@/database/schemas.ts"; -import { Logger } from "@/utils/logging.ts"; +import lily from "@/utils/logging.ts"; import { ChannelType, type ChatInputCommandInteraction,@@ -9,7 +9,7 @@ PermissionFlagsBits,
SlashCommandBuilder, } from "discord.js"; -const logger = new Logger("config-command"); +const logger = lily.child("configCommand"); const commandData = new SlashCommandBuilder() .setName("config")
M
src/interactions/commands/search.ts
→
src/interactions/commands/search.ts
@@ -13,7 +13,6 @@ TextDisplayBuilder,
escapeMarkdown, } from "discord.js"; -import config from "@/config"; import { getRobloxUser, searchRobloxUsers } from "@/utils/roblox"; import { formatUserInfo } from "@/utils/userInfo";
M
src/utils/bans.ts
→
src/utils/bans.ts
@@ -1,7 +1,9 @@
import config from "@/config"; import type { GetUserResponse } from "@/types/roblox"; +import lily from "@/utils/logging"; import { ContainerBuilder, TextDisplayBuilder } from "discord.js"; +const logger = lily.child("bans"); const projects = Object.values(config.data.projects); const BASE_URL = "https://apis.roblox.com/user/cloud/v2/universes";@@ -38,7 +40,7 @@ },
}); if (!bans.ok) { - console.error( + logger.error( `failed to fetch bans for user ${userId} in project ${project.name}:`, bans.statusText, );
M
src/utils/exists.ts
→
src/utils/exists.ts
@@ -1,7 +1,7 @@
import { UserModel } from "../database/schemas"; -import { Logger } from "../utils/logging"; +import lily from "../utils/logging"; -const logger = new Logger("exists"); +const logger = lily.child("exists"); export async function createUserIfNotExists(userId: string, guildId: string) { let user = await UserModel.findOne({
M
src/utils/forumWatcher.ts
→
src/utils/forumWatcher.ts
@@ -1,6 +1,6 @@
import config from "@/config"; import type { Post } from "@/types/forums"; -import { Logger } from "@sillowww/lily"; +import lily from "@/utils/logging"; import { ActionRowBuilder, ButtonBuilder,@@ -19,7 +19,7 @@
const startTime = Date.now(); const seenPosts = new Set<string>(); const forumConfig = config.data.roblox?.forumWatcher; -const logger = new Logger("ForumWatcher"); +const logger = lily.child("forumWatcher"); export function isNewPost(post: Post): boolean { const postId = post.id.toString();
M
src/utils/logging.ts
→
src/utils/logging.ts
@@ -1,189 +1,10 @@
import { Logger as Lily } from "@sillowww/lily"; - -const COLOURS = { - RESET: "\x1b[0m", - BRIGHT: "\x1b[1m", - DIM: "\x1b[2m", - UNDERSCORE: "\x1b[4m", - BLINK: "\x1b[5m", - REVERSE: "\x1b[7m", - HIDDEN: "\x1b[8m", - - FG_BLACK: "\x1b[30m", - FG_RED: "\x1b[31m", - FG_GREEN: "\x1b[32m", - FG_YELLOW: "\x1b[33m", - FG_BLUE: "\x1b[34m", - FG_MAGENTA: "\x1b[35m", - FG_CYAN: "\x1b[36m", - FG_WHITE: "\x1b[37m", +const lily = new Lily("bot"); - BG_BLACK: "\x1b[40m", - BG_RED: "\x1b[41m", - BG_GREEN: "\x1b[42m", - BG_YELLOW: "\x1b[43m", - BG_BLUE: "\x1b[44m", - BG_MAGENTA: "\x1b[45m", - BG_CYAN: "\x1b[46m", - BG_WHITE: "\x1b[47m", +const loggers = { + events: lily.child("Events"), + interactions: lily.child("Interactions"), }; -enum LogLevel { - DEBUG = 0, - INFO = 1, - WARN = 2, - ERROR = 3, - FATAL = 4, - OFF = 5, -} - -interface LogOptions { - timestamp?: boolean; - colorize?: boolean; - scopeColor?: string; -} - -class Logger { - private static logLevel: LogLevel = LogLevel.INFO; - private scope: string[]; - private options: LogOptions; - private static readonly MAX_LEVEL_LENGTH = Math.max( - ...Object.keys(LogLevel) - .filter((k) => Number.isNaN(Number(k))) - .map((k) => k.length), - ); - - private static readonly SCOPE_COLOURS = [ - COLOURS.FG_RED, - COLOURS.FG_GREEN, - COLOURS.FG_YELLOW, - COLOURS.FG_BLUE, - COLOURS.FG_MAGENTA, - COLOURS.FG_CYAN, - ]; - - private static scopeColours: { [scopeName: string]: string } = {}; - - constructor(scope: string | string[] = [], options: LogOptions = {}) { - this.scope = Array.isArray(scope) ? scope : [scope]; - this.options = { - timestamp: true, - colorize: true, - scopeColor: this.getScopeColor(this.scope.join("/")), - ...options, - }; - - if (process.env.LOG_LEVEL) { - try { - const envLogLevel = process.env.LOG_LEVEL.toUpperCase(); - if (envLogLevel in LogLevel) { - Logger.setLogLevel(LogLevel[envLogLevel as keyof typeof LogLevel]); - } else { - console.warn( - `Invalid LOG_LEVEL environment variable: ${process.env.LOG_LEVEL}. Using default: INFO.`, - ); - } - } catch (e) { - console.warn(`Error parsing LOG_LEVEL: ${e}. Using default: INFO.`); - } - } - } - - static setLogLevel(level: LogLevel): void { - Logger.logLevel = level; - } - - private getLevelColor(level: LogLevel): string { - switch (level) { - case LogLevel.DEBUG: - return COLOURS.FG_WHITE; - case LogLevel.INFO: - return COLOURS.FG_GREEN; - case LogLevel.WARN: - return COLOURS.FG_YELLOW; - case LogLevel.ERROR: - return COLOURS.FG_RED; - case LogLevel.FATAL: - return COLOURS.BG_RED + COLOURS.FG_WHITE; - default: - return COLOURS.RESET; - } - } - - private getScopeColor(scopeName: string): string { - if (Logger.scopeColours[scopeName]) { - return Logger.scopeColours[scopeName]; - } - - const colourIndex = - Object.keys(Logger.scopeColours).length % Logger.SCOPE_COLOURS.length; - const colour = Logger.SCOPE_COLOURS[colourIndex]; - Logger.scopeColours[scopeName] = colour; - return colour; - } - - private formatMessage(level: LogLevel, message: string): string { - const now = new Date(); - const timestamp = this.options.timestamp - ? `${COLOURS.DIM}[${now.toLocaleString()}]${COLOURS.RESET} ` - : ""; - const levelString = LogLevel[level]; - const scopeString = - this.scope.length > 0 ? `[${this.scope.join("/")}] ` : ""; - const levelColor = this.getLevelColor(level); - const COLOURStart = this.options.colorize ? levelColor : ""; - const colorEnd = this.options.colorize ? COLOURS.RESET : ""; - - const formattedMessage = `${timestamp}${COLOURStart}[${levelString}]${colorEnd} ${this.options.colorize && this.options.scopeColor ? this.options.scopeColor : ""}${scopeString}${COLOURS.RESET}${message}`; - return formattedMessage; - } - - private log(level: LogLevel, message: string, ...args: unknown[]): void { - if (level >= Logger.logLevel) { - const formattedMessage = this.formatMessage(level, message); - console.log(formattedMessage, ...args); - } - } - - debug(message: string, ...args: unknown[]): void { - this.log(LogLevel.DEBUG, message, ...args); - } - - info(message: string, ...args: unknown[]): void { - this.log(LogLevel.INFO, message, ...args); - } - - warn(message: string, ...args: unknown[]): void { - this.log(LogLevel.WARN, message, ...args); - } - - error(message: string, ...args: unknown[]): void { - this.log(LogLevel.ERROR, message, ...args); - } - - fatal(message: string, ...args: unknown[]): void { - this.log(LogLevel.FATAL, message, ...args); - } - - newLine(): void { - console.log(); - } - - child(scope: string | string[], options: LogOptions = {}): Logger { - const newScope = Array.isArray(scope) - ? [...this.scope, ...scope] - : [...this.scope, scope]; - return new Logger(newScope, { ...this.options, ...options }); - } -} - -const singleton = new Logger("global", { - timestamp: true, - colorize: true, - scopeColor: COLOURS.FG_MAGENTA, -}); - -const logger = new Lily("root"); - -export default singleton; -export { Logger, LogLevel, COLOURS, logger }; +export default lily; +export { loggers };