diff --git a/db.ts b/db.ts index 7bd3ff1..770dd8d 100644 --- a/db.ts +++ b/db.ts @@ -1,4 +1,4 @@ -import { Db, QueryParameter } from "./utils/db_interface.ts"; +import { Db, QueryParameter, SqliteMaster } from "./utils/db_interface.ts"; import { compare as compare_ver, format as format_ver, @@ -19,14 +19,8 @@ import { import { Task, TaskType } from "./task.ts"; import { generate as randomstring } from "randomstring"; import type { GalleryMetadataSingle } from "./page/GalleryMetadata.ts"; +import { base_logger } from "./utils/logger.ts"; -type SqliteMaster = { - type: string; - name: string; - tbl_name: string; - rootpage: number; - sql: string; -}; export enum SqliteTransactionType { DEFERRED = "DEFERRED", IMMEDIATE = "IMMEDIATE", @@ -335,6 +329,7 @@ const SHARED_TOKEN_TABLE = `CREATE TABLE shared_token ( type INT, info TEXT );`; +const logger = base_logger.get_logger("db"); function escape_fields(fields: string, namespace: string) { const fs = fields.split(","); @@ -470,7 +465,7 @@ export class EhDb { this.add_file(g as EhFile, false); } else { try_remove_sync(f.path); - console.log("Deleted ", f.path); + logger.debug("Deleted ", f.path); } }); offset += files.length; @@ -659,7 +654,7 @@ export class EhDb { ofiles.forEach((o) => { if (o.path !== f.path) { try_remove_sync(o.path); - console.log("Deleted ", o.path); + logger.debug("Deleted ", o.path); } }); } @@ -1026,7 +1021,7 @@ export class EhDb { this.db.query("DELETE FROM filemeta WHERE token = ?;", [token]); files.forEach((f) => { try_remove_sync(f.path); - console.log("Deleted ", f.path); + logger.debug("Deleted ", f.path); }); } delete_gallery(gid: number | bigint) { diff --git a/main.ts b/main.ts index 55db4e4..b2cdc15 100644 --- a/main.ts +++ b/main.ts @@ -7,6 +7,7 @@ import { sure_dir, try_remove_sync } from "./utils.ts"; import { EhDb } from "./db.ts"; import { load_eht_file, update_database_tag } from "./eh_translation.ts"; import { get_abort_signal } from "./signal_handler.ts"; +import { base_logger } from "./utils/logger.ts"; function show_help() { console.log("Usage: main.ts [options] Command"); @@ -73,6 +74,7 @@ if (cmd == CMD.Unknown) { } const settings = await load_settings(args.config); +await base_logger.init(settings.db_path || settings.base); if (!check_file_permissions(settings.base)) { throw Error("Can not aceess download loaction."); } diff --git a/meilisearch.ts b/meilisearch.ts index 11ce3e3..0dfff3f 100644 --- a/meilisearch.ts +++ b/meilisearch.ts @@ -7,6 +7,7 @@ import { import { sleep, toJSON } from "./utils.ts"; import type { EhDb } from "./db.ts"; import isEqual from "lodash/isEqual"; +import { base_logger } from "./utils/logger.ts"; const GMetaSettings: Record = { displayedAttributes: ["*"], @@ -32,6 +33,7 @@ const GMetaSettings: Record = { ], sortableAttributes: ["filecount", "filesize", "gid", "posted", "rating"], }; +const logger = base_logger.get_logger("meilisearch"); export class MeiliSearchServer { client; @@ -61,13 +63,13 @@ export class MeiliSearchServer { #gallery_remove(e: Event) { const ev = e as CustomEvent; this.removeGallery(ev.detail).catch((e) => { - console.log(e); + logger.warn("Failed to remove gallery:", e); }); } #gallery_update(e: Event) { const ev = e as CustomEvent; this.updateGallery(ev.detail).catch((e) => { - console.log(e); + logger.warn("Failed to update gallery", e); }); } async #updateGMetaSettings() { @@ -82,7 +84,7 @@ export class MeiliSearchServer { } }); if (need_update) { - console.log(u); + logger.log(u); await this.waitTask(this.#gmeta.updateSettings(u)); } } @@ -96,7 +98,7 @@ export class MeiliSearchServer { return await this.client.getIndex(uid); } catch (e) { if (e instanceof MeiliSearchApiError) { - if (e.code === "index_not_found") { + if (e.name === "index_not_found") { await this.waitTask( this.client.createIndex(uid, { primaryKey }), ); diff --git a/routes/api/config.ts b/routes/api/config.ts index 143008b..b162372 100644 --- a/routes/api/config.ts +++ b/routes/api/config.ts @@ -10,6 +10,7 @@ import { return_json } from "../../server/utils.ts"; import { ExitTarget } from "../../signal_handler.ts"; import type { User } from "../../db.ts"; import { toJSON } from "../../utils.ts"; +import { base_logger } from "../../utils/logger.ts"; const UNSAFE_TYPE: (keyof ConfigType)[] = [ "base", @@ -21,6 +22,7 @@ const UNSAFE_TYPE: (keyof ConfigType)[] = [ "meili_update_api_key", ]; const UNSAFE_TYPE2 = UNSAFE_TYPE as string[]; +const logger = base_logger.get_logger("api-config"); export const handler: Handlers = { async GET(req, ctx) { @@ -56,7 +58,7 @@ export const handler: Handlers = { }; socket.onerror = () => { removeListener(); - console.error("WebSocket error."); + logger.error("WebSocket error."); }; socket.onmessage = (e) => { try { diff --git a/routes/api/file/upload.ts b/routes/api/file/upload.ts index 1065a4b..f825181 100644 --- a/routes/api/file/upload.ts +++ b/routes/api/file/upload.ts @@ -8,6 +8,9 @@ import { sure_dir } from "../../../utils.ts"; import mime from "mime"; import { extname, join, resolve } from "@std/path"; import { UserPermission } from "../../../db.ts"; +import { base_logger } from "../../../utils/logger.ts"; + +const logger = base_logger.get_logger("api-file-upload"); export const handler: Handlers = { async POST(req, ctx) { @@ -76,7 +79,7 @@ export const handler: Handlers = { throw e; } } catch (e) { - console.error(e); + logger.error(e); return return_error(500, "Internal Server Error."); } }, diff --git a/routes/api/task.ts b/routes/api/task.ts index 1acb188..5d41590 100644 --- a/routes/api/task.ts +++ b/routes/api/task.ts @@ -14,6 +14,9 @@ import { User, UserPermission } from "../../db.ts"; import { toJSON } from "../../utils.ts"; import { ImportConfig } from "../../tasks/import.ts"; import { UpdateTagTranslationConfig } from "../../tasks/update_tag_translation.ts"; +import { base_logger } from "../../utils/logger.ts"; + +const logger = base_logger.get_logger("api-task"); export const handler: Handlers = { GET(req, ctx) { @@ -61,7 +64,7 @@ export const handler: Handlers = { socket.onerror = () => { removeListener(); clearInterval(interval); - console.error("WebSocket error."); + logger.error("WebSocket error."); }; socket.onmessage = (e) => { try { diff --git a/server.ts b/server.ts index 7992bb1..c57eeeb 100644 --- a/server.ts +++ b/server.ts @@ -5,6 +5,7 @@ import { AlreadyClosedError, TaskManager } from "./task_manager.ts"; import twindPlugin from "$fresh/plugins/twind.ts"; import twindConfig from "./twind.config.ts"; import { load_translation } from "./server/i18ns.ts"; +import { base_logger } from "./utils/logger.ts"; let task_manager: TaskManager | undefined = undefined; let cfg_path: string | undefined = undefined; @@ -29,6 +30,7 @@ const renderFn: RenderFunction = (ctx, render) => { export async function startServer(path: string) { cfg_path = path; const cfg = await load_settings(path); + await base_logger.init(cfg.db_path || cfg.base); task_manager = new TaskManager(cfg); await task_manager.init(); task_manager.run(true).catch((e) => { diff --git a/task_manager.ts b/task_manager.ts index 5a5af84..8de35d3 100644 --- a/task_manager.ts +++ b/task_manager.ts @@ -31,6 +31,7 @@ import { sleep, toJSON, } from "./utils.ts"; +import { base_logger } from "./utils/logger.ts"; export class AlreadyClosedError extends Error { } @@ -56,6 +57,8 @@ type RunningTask = { base: Task; }; +const logger = base_logger.get_logger("task_manager"); + export class TaskManager extends EventTarget { #closed = false; cfg; @@ -122,7 +125,7 @@ export class TaskManager extends EventTarget { this.#check_closed(); const otask = await this.db.check_download_task(gid, token); if (otask !== undefined) { - console.log("The task is already in list."); + logger.log("The task is already in list."); return mark_already ? null : otask; } const task: Task = { @@ -150,7 +153,7 @@ export class TaskManager extends EventTarget { this.#check_closed(); const otask = await this.db.check_fix_gallery_page_task(); if (otask !== undefined) { - console.log("The task is already in list."); + logger.log("The task is already in list."); return otask; } const task: Task = { @@ -172,7 +175,7 @@ export class TaskManager extends EventTarget { this.#check_closed(); const otask = await this.db.check_download_task(gid, token); if (otask !== undefined) { - console.log("The task is already in list."); + logger.log("The task is already in list."); return mark_already ? null : otask; } const task: Task = { @@ -189,7 +192,7 @@ export class TaskManager extends EventTarget { this.#check_closed(); const otask = await this.db.check_update_meili_search_data_task(gid); if (otask !== undefined) { - console.log("The task is already in list."); + logger.log("The task is already in list."); return otask; } const task: Task = { @@ -209,7 +212,7 @@ export class TaskManager extends EventTarget { this.#check_closed(); const otask = await this.db.check_update_tag_translation_task(); if (otask !== undefined) { - console.log("The task is already in list."); + logger.log("The task is already in list."); return mark_already ? null : otask; } const task: Task = { @@ -258,7 +261,7 @@ export class TaskManager extends EventTarget { this.dispatchEvent("task_finished", status.value); } else if (status.status == PromiseStatus.Rejected) { if (status.reason && !this.aborted) { - console.log(status.reason); + logger.warn(status.reason); const fatal = !(status.reason instanceof RecoverableError); this.dispatchEvent("task_error", { task: task.base, @@ -279,7 +282,7 @@ export class TaskManager extends EventTarget { } close() { if (this.#closed) { - console.trace("Manager closed multiple times."); + logger.trace("Manager closed multiple times."); return; } this.#closed = true; diff --git a/tasks/download.ts b/tasks/download.ts index baee540..d7f4199 100644 --- a/tasks/download.ts +++ b/tasks/download.ts @@ -24,6 +24,7 @@ import { import { basename, extname, join, resolve } from "@std/path"; import { exists } from "@std/fs/exists"; import { ProgressReadable } from "../utils/progress_readable.ts"; +import { base_logger } from "../utils/logger.ts"; export type DownloadConfig = { max_download_img_count?: number; @@ -38,6 +39,8 @@ export const DEFAULT_DOWNLOAD_CONFIG: DownloadConfig = {}; const PROGRESS_UPDATE_INTERVAL = 200; +const logger = base_logger.get_logger("download-task"); + class DownloadManager { #abort: AbortSignal; #force_abort: AbortSignal; @@ -78,7 +81,7 @@ class DownloadManager { async (t) => { const s = await promiseState(t); if (s.status === PromiseStatus.Rejected) { - if (!this.#force_abort.aborted) console.log(s.reason); + if (!this.#force_abort.aborted) logger.log(s.reason); this.#progress.failed_page += 1; this.#sendEvent(); } else if (s.status === PromiseStatus.Fulfilled) { @@ -219,7 +222,7 @@ export async function download_task( manager: TaskManager, dcfg: DownloadConfig, ) { - console.log("Started to download gallery", task.gid); + logger.log("Started to download gallery", task.gid); const gdatas = await client.fetchGalleryMetadataByAPI([ task.gid, task.token, @@ -288,7 +291,7 @@ export async function download_task( } } } - console.log("Already download page", i.index); + logger.log("Already download page", i.index); return; } } @@ -301,7 +304,7 @@ export async function download_task( if (load_times >= max_retry_count) reject(errors); i.load().then(resolve).catch((e) => { if (force_abort.aborted) { - console.log("Aborted when loading image:", i); + logger.log("Aborted when loading image:", i); errors.push(e); reject(errors); return; @@ -326,7 +329,7 @@ export async function download_task( ); if (names[i.name] > 1) { path = add_suffix_to_path(path, i.page_token); - console.log("Changed path to", path); + logger.debug("Changed path to", path); } const f = download_original ? i.get_original_file(path) @@ -428,12 +431,12 @@ export async function download_task( try { hash = getHashFromUrl(url); } catch (e) { - console.warn(e); + logger.warn(e); } if (hash) { const fhash = await calFileSha1(path); if (hash != fhash) { - console.warn( + logger.warn( `Hash not matched: file hash ${fhash}, original hash ${hash}, url ${url}`, ); throw new HashError(); @@ -450,7 +453,7 @@ export async function download_task( } download().then(resolve).catch((e) => { if (force_abort.aborted) { - console.log( + logger.log( "Aborted when downloading image:", i, ); @@ -495,7 +498,7 @@ export async function download_task( reject(e); return; } - console.log("Failed to download, retry: ", e); + logger.warn("Failed to download, retry: ", e); retry += 1; if (retry >= max_retry_count) { reject(e); @@ -512,7 +515,7 @@ export async function download_task( } function try2_() { deal_with_img().then(resolve).catch((e) => { - console.log("Failed to download, retry: ", e); + logger.warn("Failed to download, retry: ", e); retry += 1; if (retry >= max_retry_count) { reject(e); @@ -584,7 +587,7 @@ export async function download_task( replaced_gallery.forEach((g) => { const gmeta = db.get_gmeta_by_gid(g.gid); if (!gmeta) return; - console.log("Remove gallery ", g.gid); + logger.debug("Remove gallery ", g.gid); if (manager.meilisearch) { manager.meilisearch.target.dispatchEvent( new CustomEvent("gallery_remove", { detail: gmeta.gid }), diff --git a/tasks/import.ts b/tasks/import.ts index 69c0547..cdb1bea 100644 --- a/tasks/import.ts +++ b/tasks/import.ts @@ -19,6 +19,7 @@ import { ImportMethod, ImportSize } from "../config.ts"; import { fb_get_size } from "../thumbnail/ffmpeg_binary.ts"; import { EhFile, PMeta } from "../db.ts"; import type { ReadonlyZip } from "../utils/readonly_zip.ts"; +import { base_logger } from "../utils/logger.ts"; export type ImportConfig = { max_import_img_count?: number; @@ -41,6 +42,8 @@ const VALID_EXTS = [".jpg", ".png", ".gif"]; const PROGRESS_UPDATE_INTERVAL = 200; +const logger = base_logger.get_logger("import-task"); + class ImportManager { #abort: AbortSignal; #force_abort: AbortSignal; @@ -78,7 +81,7 @@ class ImportManager { async (t) => { const s = await promiseState(t); if (s.status === PromiseStatus.Rejected) { - if (!this.#force_abort.aborted) console.log(s.reason); + if (!this.#force_abort.aborted) logger.log(s.reason); this.#progress.failed_page += 1; this.#sendEvent(); } else if (s.status === PromiseStatus.Fulfilled) { @@ -262,7 +265,7 @@ class FileLoader { export async function import_task(task: Task, manager: TaskManager) { if (!task.details) throw Error("Task details are needed."); - console.log("Started to import gallery", task.gid); + logger.log("Started to import gallery", task.gid); const icfg: ImportConfig = JSON.parse(task.details); const cfg = manager.cfg; const client = manager.client; @@ -331,12 +334,12 @@ export async function import_task(task: Task, manager: TaskManager) { async function import_img(i: Page) { const opath = f.get_file(i.name, i.index); if (!opath) { - console.log("File not found"); + logger.log("File not found"); return; } const size = await fb_get_size(opath); if (!size) { - console.log("Failed to get file size for", opath); + logger.log("Failed to get file size for", opath); throw Error("Failed to get file size."); } const ofiles = db.get_files(i.token); @@ -373,7 +376,7 @@ export async function import_task(task: Task, manager: TaskManager) { } } } - console.log("Already has page", i.index); + logger.log("Already has page", i.index); return; } } @@ -394,7 +397,7 @@ export async function import_task(task: Task, manager: TaskManager) { let path = join(base_path, is_original ? i.name : i.sampled_name); if (import_method != ImportMethod.Keep && names[i.name] > 1) { path = add_suffix_to_path(path, i.token); - console.log("Changed path to", path); + logger.debug("Changed path to", path); } if (import_method == ImportMethod.Move) { await Deno.rename(opath, path); @@ -406,7 +409,7 @@ export async function import_task(task: Task, manager: TaskManager) { if (cfg.check_file_hash && is_original) { const sha = await calFileSha1(path); if (sha.slice(0, i.token.length) != i.token) { - console.warn( + logger.warn( `Hash not matched: file hash ${sha}, token ${i.token}`, ); return; @@ -429,7 +432,7 @@ export async function import_task(task: Task, manager: TaskManager) { async function import_zip_img(i: Page) { const opath = f.get_zip(i.name, i.index); if (!opath) { - console.log("File not found"); + logger.log("File not found"); return; } const ofiles = db.get_files(i.token); @@ -466,7 +469,7 @@ export async function import_task(task: Task, manager: TaskManager) { } } } - console.log("Already has page", i.index); + logger.log("Already has page", i.index); return; } } @@ -478,11 +481,11 @@ export async function import_task(task: Task, manager: TaskManager) { ); if (names[i.name] > 1) { path = add_suffix_to_path(path, i.token); - console.log("Changed path to", path); + logger.log("Changed path to", path); } const zf = f.open_zip_file(opath); if (!zf) { - console.log("File not found"); + logger.log("File not found"); return; } try { @@ -505,7 +508,7 @@ export async function import_task(task: Task, manager: TaskManager) { } const size = await fb_get_size(path); if (!size) { - console.log("Failed to get file size for", path); + logger.error("Failed to get file size for", path); throw Error("Failed to get file size."); } const is_original = icfg.size == ImportSize.Original || @@ -514,7 +517,7 @@ export async function import_task(task: Task, manager: TaskManager) { if (cfg.check_file_hash && is_original) { const sha = await calFileSha1(path); if (sha.slice(0, i.token.length) != i.token) { - console.warn( + logger.warn( `Hash not matched: file hash ${sha}, token ${i.token}`, ); return; @@ -574,7 +577,7 @@ export async function import_task(task: Task, manager: TaskManager) { replaced_gallery.forEach((g) => { const gmeta = db.get_gmeta_by_gid(g.gid); if (!gmeta) return; - console.log("Remove gallery ", g.gid); + logger.debug("Remove gallery ", g.gid); if (manager.meilisearch) { manager.meilisearch.target.dispatchEvent( new CustomEvent("gallery_remove", { diff --git a/thumbnail/ffmpeg_api.ts b/thumbnail/ffmpeg_api.ts index 1bb05f4..7794730 100644 --- a/thumbnail/ffmpeg_api.ts +++ b/thumbnail/ffmpeg_api.ts @@ -1,6 +1,9 @@ /// import { Struct } from "pwn/mod.ts"; import { ThumbnailAlign, ThumbnailConfig, ThumbnailGenMethod } from "./base.ts"; +import { base_logger } from "../utils/logger.ts"; + +const logger = base_logger.get_logger("thumbnail-ffmpeg-api"); let libSuffix = ""; let libPrefix = "lib"; @@ -49,7 +52,7 @@ const _Result = new Struct({ e: "s32", fferr: "s32" }); function get_error(fferr: Uint8Array) { const u = new Uint8Array(64); - lib.symbols.thumbnail_error(fferr, u, u.length); + lib.symbols.thumbnail_error(fferr, u, BigInt(u.length)); let len = u.findIndex((i) => i === 0); if (len === -1) len = u.length; return (new TextDecoder()).decode(u.slice(0, len)); @@ -100,7 +103,7 @@ export async function fa_generate_thumbnail( cfg.quality, ); if (re) { - console.error(re); + logger.error(re); } return re === undefined; } diff --git a/thumbnail/ffmpeg_binary.ts b/thumbnail/ffmpeg_binary.ts index 65a8dcf..6c2c4b5 100644 --- a/thumbnail/ffmpeg_binary.ts +++ b/thumbnail/ffmpeg_binary.ts @@ -1,6 +1,9 @@ import { ThumbnailFormat } from "../config.ts"; import { ThumbnailAlign } from "./base.ts"; import { type ThumbnailConfig, ThumbnailGenMethod } from "./base.ts"; +import { base_logger } from "../utils/logger.ts"; + +const logger = base_logger.get_logger("thumbnail-ffmpeg-binary"); export async function check_ffmpeg_binary(p: string) { const cmd = new Deno.Command(p, { @@ -114,9 +117,9 @@ export async function fb_generate_thumbnail( if (s.code !== 0) { try { const d = (new TextDecoder()).decode(s.stderr); - console.log(d); + logger.warn(d); } catch (_) { - console.log(s.stderr); + logger.warn(s.stderr); } } return s.code === 0; diff --git a/utils.ts b/utils.ts index 2481e9b..9e2662e 100644 --- a/utils.ts +++ b/utils.ts @@ -377,3 +377,8 @@ export function replaceExtname(path: string, ext: string) { } return path.slice(0, path.length - extname(path).length) + ext; } + +export function stackTrace(skip: number = 0) { + const err = new Error(); + return err.stack?.split("\n").slice(2 + skip).join("\n"); +} diff --git a/utils/db_interface.ts b/utils/db_interface.ts index d16fb43..7d5bb4c 100644 --- a/utils/db_interface.ts +++ b/utils/db_interface.ts @@ -12,6 +12,13 @@ export type QueryParameter = export type QueryParameterSet = | Record | Array; +export type SqliteMaster = { + type: string; + name: string; + tbl_name: string; + rootpage: number; + sql: string; +}; export interface Db { close(force?: boolean): void; diff --git a/utils/logger.ts b/utils/logger.ts new file mode 100644 index 0000000..7dc4f1c --- /dev/null +++ b/utils/logger.ts @@ -0,0 +1,221 @@ +import { join } from "@std/path"; +import { format as format_ver, parse as parse_ver } from "@std/semver"; +import { parse_bool, stackTrace } from "../utils.ts"; +import { Db, SqliteMaster } from "./db_interface.ts"; + +const ALL_TABLES = [ + "version", + "log", +]; +const VERSION_TABLE = `CREATE TABLE version ( + id TEXT, + ver TEXT, + PRIMARY KEY (id) +);`; +const LOG_TABLE = `CREATE TABLE log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + time INT, + message TEXT, + level INT, + type TEXT, + stack TEXT +);`; + +export const TRACE_LEVEL = 1; +export const DEBUG_LEVEL = 2; +export const LOG_LEVEL = 3; +export const INFO_LEVEL = 4; +export const WARN_LEVEL = 5; +export const ERROR_LEVEL = 6; + +export function format_message( + message: unknown[], + options?: Deno.InspectOptions, +) { + return message.map((x) => Deno.inspect(x, options)).join(" "); +} + +class BaseLogger { + db?: Db; + #exist_table: Set = new Set(); + #use_ffi = false; + readonly version = parse_ver("1.0.0-0"); + async init(base_path: string) { + const db_path = join(base_path, "logs.db"); + this.#use_ffi = parse_bool(Deno.env.get("DB_USE_FFI") ?? "false"); + if (this.#use_ffi) { + const DB = (await import("./db_ffi.ts")).DbFfi; + this.db = new DB(db_path, { int64: true }); + } else { + const DB = (await import("./db_wasm.ts")).DbWasm; + this.db = new DB(db_path); + } + if (!this.#check_database()) this.#create_table(); + } + #check_database() { + if (!this.db) throw new Error("Database not initialized"); + this.#update_exists_table(); + const v = this.#read_version(); + if (!v) return false; + if ( + ALL_TABLES.length !== this.#exist_table.size || + !ALL_TABLES.every((x) => this.#exist_table.has(x)) + ) return false; + return true; + } + #create_table() { + if (!this.db) return; + if (!this.#exist_table.has("version")) { + this.db.execute(VERSION_TABLE); + this.#write_version(); + } + if (!this.#exist_table.has("log")) { + this.db.execute(LOG_TABLE); + } + this.#update_exists_table(); + } + #read_version() { + if (!this.db) return null; + if (!this.#exist_table.has("version")) return null; + const cur = this.db.query<[string]>( + "SELECT ver FROM version WHERE id = ?;", + ["logs"], + ); + for (const i of cur) { + return parse_ver(i[0]); + } + return null; + } + #update_exists_table() { + if (!this.db) return; + const cur = this.db.queryEntries( + "SELECT * FROM main.sqlite_master;", + ); + this.#exist_table.clear(); + for (const i of cur) { + if (i.type == "table") { + this.#exist_table.add(i.name); + } + } + } + #write_version() { + if (!this.db) return; + this.db.transaction(() => { + if (!this.db) return; + this.db.query("INSERT OR REPLACE INTO version VALUES (?, ?);", [ + "logs", + format_ver(this.version), + ]); + }); + } + add(type: string, level: number, ...messages: unknown[]) { + this.#fallback(type, level, ...messages); + if (!this.db) return; + const message = format_message(messages); + const stack = + (level >= TRACE_LEVEL && level < DEBUG_LEVEL) || level >= WARN_LEVEL + ? stackTrace(2) + : undefined; + this.db.query( + "INSERT INTO log (time, message, level, type, stack) VALUES (?, ?, ?, ?, ?);", + [ + Date.now(), + message, + level, + type, + stack === undefined ? null : stack, + ], + ); + } + close() { + this.db?.close(); + this.db = undefined; + } + debug(type: string, ...messages: unknown[]) { + this.add(type, DEBUG_LEVEL, ...messages); + } + error(type: string, ...messages: unknown[]) { + this.add(type, ERROR_LEVEL, ...messages); + } + #fallback(type: string, level: number, ...messages: unknown[]) { + if (type === "default") { + if (level >= ERROR_LEVEL) { + console.error(...messages, "\n" + stackTrace(3)); + } else if (level >= WARN_LEVEL) { + console.warn(...messages, "\n" + stackTrace(3)); + } else if (level >= INFO_LEVEL) { + console.info(...messages); + } else if (level >= LOG_LEVEL) { + console.log(...messages); + } else if (level >= DEBUG_LEVEL) { + console.debug(...messages); + } else if (level >= TRACE_LEVEL) { + console.log("Trace:", ...messages, "\n" + stackTrace(3)); + } + return; + } + if (level >= ERROR_LEVEL) { + console.error(type + ":", ...messages, "\n" + stackTrace(3)); + } else if (level >= WARN_LEVEL) { + console.warn(type + ":", ...messages, "\n" + stackTrace(3)); + } else if (level >= INFO_LEVEL) { + console.info(type + ":", ...messages); + } else if (level >= LOG_LEVEL) { + console.log(type + ":", ...messages); + } else if (level >= DEBUG_LEVEL) { + console.debug(type + ":", ...messages); + } else if (level >= TRACE_LEVEL) { + console.log( + "Trace:", + type + ":", + ...messages, + "\n" + stackTrace(3), + ); + } + } + get_logger(type: string) { + return new Logger(this, type); + } + info(type: string, ...messages: unknown[]) { + this.add(type, INFO_LEVEL, ...messages); + } + log(type: string, ...messages: unknown[]) { + this.add(type, LOG_LEVEL, ...messages); + } + trace(type: string, ...messages: unknown[]) { + this.add(type, TRACE_LEVEL, ...messages); + } + warn(type: string, ...messages: unknown[]) { + this.add(type, WARN_LEVEL, ...messages); + } +} + +class Logger { + #base: BaseLogger; + #type: string; + constructor(base: BaseLogger, type: string) { + this.#base = base; + this.#type = type; + } + debug(...messages: unknown[]) { + this.#base.add(this.#type, DEBUG_LEVEL, ...messages); + } + error(...messages: unknown[]) { + this.#base.add(this.#type, ERROR_LEVEL, ...messages); + } + info(...messages: unknown[]) { + this.#base.add(this.#type, INFO_LEVEL, ...messages); + } + log(...messages: unknown[]) { + this.#base.add(this.#type, LOG_LEVEL, ...messages); + } + trace(...messages: unknown[]) { + this.#base.add(this.#type, TRACE_LEVEL, ...messages); + } + warn(...messages: unknown[]) { + this.#base.add(this.#type, WARN_LEVEL, ...messages); + } +} + +export const base_logger = new BaseLogger(); +export const logger = base_logger.get_logger("default");