{"version":3,"file":"logger.mjs","names":[],"sources":["../src/std/logger/helpers.ts","../src/std/logger/logger_core.ts","../src/std/logger/plugins/file_logger.ts","../src/std/logger/plugins/mina_logger.ts"],"sourcesContent":["/**\n * 日志系统工具函数。\n *\n * @internal\n */\n\nimport type { LogLevel } from './defines.ts';\n\n// #region Internal Variables\n\nconst LOG_LEVEL_INDEX: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };\n\n// #endregion\n\n/**\n * 判断给定级别是否满足最低级别要求。\n *\n * @param entryLevel - 日志条目的级别。\n * @param minLevel - 最低允许的级别。\n * @returns `entryLevel >= minLevel` 时返回 `true`。\n */\nexport function shouldLog(entryLevel: LogLevel, minLevel: LogLevel): boolean {\n    return LOG_LEVEL_INDEX[entryLevel] >= LOG_LEVEL_INDEX[minLevel];\n}\n\n/**\n * 将日志参数列表转为消息字符串。\n *\n * 单参数直接序列化，多参数用空格拼接，避免不必要的 map/join 开销。\n */\nexport function buildMessage(args: unknown[]): string {\n    return args.length === 1\n        ? stringifyArg(args[0])\n        : args.map(stringifyArg).join(' ');\n}\n\n// #region Internal Functions\n\n/**\n * 将单个参数转为字符串。\n *\n * string 原样返回；bigint 转 `` `${value}n` ``；object 尝试 JSON 序列化\n *（含 BigInt replacer fallback）；其余用 String()。\n */\nfunction stringifyArg(arg: unknown): string {\n    if (typeof arg === 'bigint') {\n        return `${arg}n`;\n    }\n\n    if (typeof arg === 'object' && arg !== null) {\n        try {\n            return JSON.stringify(arg, (_, v) =>\n                typeof v === 'bigint' ? `${v}n` : v,\n            );\n        } catch {\n            // 循环引用等，最终 fallback\n            return String(arg);\n        }\n    }\n\n    return String(arg);\n}\n\n// #endregion\n","/**\n * 日志系统核心逻辑：单例状态管理、日志流水线、插件调度。\n */\n\nimport { Lazy } from 'happy-rusty';\nimport type {\n    LogFilter,\n    LoggerConfig,\n    LoggerPlugin,\n    LogLevel,\n    PluginContext,\n} from './defines.ts';\nimport { shouldLog } from './helpers.ts';\n\n// #region Internal Variables\n\n/**\n * Console 方法映射。\n *\n * `.bind(console)` 防止直接传递方法引用时 `this` 上下文丢失。\n */\nconst CONSOLE_FN: Record<LogLevel, (...args: unknown[]) => void> = {\n    'debug': console.debug.bind(console),\n    'info': console.info.bind(console),\n    'warn': console.warn.bind(console),\n    'error': console.error.bind(console),\n};\n\n/**\n * 全局单例状态（懒初始化）。\n */\nlet lazyState = /*#__PURE__*/ Lazy(() => createDefaultState());\n\n// #endregion\n\n/**\n * 初始化日志系统。\n *\n * @param config - 日志系统配置。\n * @since 2.6.0\n * @example\n * ```ts\n * const file = fileLog({ level: 'debug' });\n * logger.init({ plugins: [file, wxLog({ level: 'warn' })] });\n * ```\n */\nexport function init(config: LoggerConfig = {}): void {\n    // 清理旧插件资源（不触发默认 state 创建）\n    lazyState.get().inspect(state => {\n        for (const plugin of state.plugins) {\n            plugin.onDestroy?.();\n        }\n    });\n\n    const level = config.level ?? 'info';\n    const plugins = config.plugins ?? [];\n\n    const ctx: PluginContext = { globalLevel: level, filter: config.filter };\n\n    // 调用每个 plugin 的 onInit\n    for (const plugin of plugins) {\n        plugin.onInit?.(ctx);\n    }\n\n    const newState: LoggerState = {\n        console: {\n            enabled: config.console?.enabled ?? true,\n            level: config.console?.level ?? level,\n            filter: config.filter,\n        },\n        plugins,\n    };\n\n    lazyState = Lazy(() => newState);\n\n    // 拦截全局 console（不提供 restore，如需 restore 请用独立 injectConsole 函数）\n    if (config.injectConsole) {\n        injectConsole();\n    }\n}\n\n/**\n * 输出 debug 级别日志。\n * @since 2.6.0\n */\nexport function debug(...args: unknown[]): void {\n    dispatchLog('debug', ...args);\n}\n\n/**\n * 输出 info 级别日志。\n * @since 2.6.0\n */\nexport function info(...args: unknown[]): void {\n    dispatchLog('info', ...args);\n}\n\n/**\n * 输出 warn 级别日志。\n * @since 2.6.0\n */\nexport function warn(...args: unknown[]): void {\n    dispatchLog('warn', ...args);\n}\n\n/**\n * 输出 error 级别日志。\n * @since 2.6.0\n */\nexport function error(...args: unknown[]): void {\n    dispatchLog('error', ...args);\n}\n\n/**\n * 拦截全局 `console` 方法，将其重定向到 logger。\n *\n * 调用后，`console.debug`/`info`/`warn`/`error`（以及 `console.log`）会经过\n * logger 的 `dispatchLog`，触发插件 pipeline（如 `fileLog`）。\n *\n * logger 自身的 console 输出使用模块加载时捕获的原始方法（`CONSOLE_FN`），\n * 不会递归。\n *\n * @returns restore 函数，调用后恢复原始 `console` 方法。\n * @since 2.6.0\n * @example\n * ```ts\n * logger.init({ plugins: [fileLog()] });\n * const restore = injectConsole();\n * // 之后所有 console.info(...) 会走 logger pipeline\n * console.info('App started'); // → file 写入 + console 输出\n * restore(); // 需要时恢复\n * ```\n */\nexport function injectConsole(): () => void {\n    const original = {\n        log: console.log,\n        debug: console.debug,\n        info: console.info,\n        warn: console.warn,\n        error: console.error,\n    };\n\n    // console.log 映射到 info 级别（console.log ≈ console.info）\n    console.log = (...args: unknown[]) => info(...args);\n    console.debug = (...args: unknown[]) => debug(...args);\n    console.info = (...args: unknown[]) => info(...args);\n    console.warn = (...args: unknown[]) => warn(...args);\n    console.error = (...args: unknown[]) => error(...args);\n\n    return () => {\n        console.log = original.log;\n        console.debug = original.debug;\n        console.info = original.info;\n        console.warn = original.warn;\n        console.error = original.error;\n    };\n}\n\n// #region Internal Types\n\n/**\n * 日志系统运行时状态。\n *\n * 封装控制台输出配置、过滤器和插件列表。通过 init 创建，仅供模块内部使用。\n */\ninterface LoggerState {\n    console: {\n        enabled: boolean;\n        level: LogLevel;\n        filter?: LogFilter;\n    };\n    plugins: LoggerPlugin[];\n}\n\n// #endregion\n\n// #region Internal Functions\n\nfunction createDefaultState(): LoggerState {\n    return {\n        console: {\n            enabled: true,\n            level: 'info',\n        },\n        plugins: [],\n    };\n}\n\n/**\n * 统一的日志分发入口。\n *\n * 控制台和插件均接收原始参数，由各消费端自行决定如何格式化或过滤。\n */\nfunction dispatchLog(level: LogLevel, ...args: unknown[]): void {\n    const state = lazyState.force();\n\n    // 1. 控制台输出\n    if (state.console.enabled\n        && shouldLog(level, state.console.level)\n        && (!state.console.filter || state.console.filter(level, ...args))) {\n        CONSOLE_FN[level](...args);\n    }\n\n    // 2. 插件回调\n    for (const plugin of state.plugins) {\n        plugin.onLog?.(level, ...args);\n    }\n}\n\n// #endregion\n","/**\n * 文件日志插件：fileLog 工厂，提供缓冲写入、日志分割（period + size）、旧文件清理。\n */\n\nimport { gzipSync } from 'fflate/browser';\nimport type { AsyncIOResult } from 'happy-rusty';\nimport { encodeUtf8 } from '../../codec/mod.ts';\nimport { addHideListener } from '../../event/mod.ts';\nimport {\n    appendFile,\n    readDir,\n    readFile,\n    remove,\n    stat,\n    writeFile,\n} from '../../fs/mod.ts';\nimport type {\n    LogEntry,\n    LogFilter,\n    LogFormatter,\n    LoggerPlugin,\n    LogLevel,\n    PluginContext,\n} from '../defines.ts';\nimport { buildMessage, shouldLog } from '../helpers.ts';\nimport type { PluginConfigBase } from './defines.ts';\n\n// #region Internal Variables\n\n/**\n * 默认日志文件根目录。\n */\nconst DEFAULT_ROOT_DIR = '/.minigame-std-logs';\n\n/**\n * 日志级别标签。\n */\nconst LEVEL_LABELS: Record<LogLevel, string> = {\n    debug: 'DEBUG',\n    info: 'INFO ',\n    warn: 'WARN ',\n    error: 'ERROR',\n};\n\n// #endregion\n\n/**\n * 日志分割（split）配置。\n *\n * @since 2.6.0\n */\nexport interface FileSplitConfig {\n    /**\n     * 日志文件分割的时间粒度（毫秒）。\n     *\n     * 同一 period 内的日志写入同一个文件，到期自动切换。\n     *\n     * @defaultValue `3600000`（1 小时）\n     */\n    period?: number;\n    /**\n     * 单个日志文件最大大小（字节）。\n     *\n     * @defaultValue `10 * 1024 * 1024`（10MB）\n     */\n    maxSize?: number;\n    /**\n     * 最多保留的日志文件数。\n     *\n     * @defaultValue `24`（period 为 1 小时时即一天的日志量）\n     */\n    maxCount?: number;\n    /**\n     * 文件最大保留时间（毫秒），创建时间超过此值的文件将被删除。\n     *\n     * 与 `maxCount` 叠加：先按时间过期删除，剩余文件若仍超 `maxCount` 再按数量删最旧的。\n     *\n     * @defaultValue `undefined`（不按时间清理）\n     */\n    maxAge?: number;\n    /**\n     * 是否使用 UTF-8 字节数计算文件大小（`true`）而非字符数（`false`）。\n     *\n     * @defaultValue `false`\n     */\n    useByteSize?: boolean;\n    /**\n     * 是否在切分时压缩旧日志文件（`.log` → `.log.gz`）。\n     *\n     * 压缩后原始 `.log` 被删除，压缩是 fire-and-forget，不阻塞日志写入。\n     * 注意：压缩后文件变为 `.log.gz`，读取/合并时需先解压。\n     * `maxCount` 为 1 时压缩产物 `.log.gz` 会临时占用额外名额，建议 `maxCount >= 2`。\n     *\n     * @defaultValue `false`\n     */\n    compress?: boolean;\n}\n\n/**\n * 文件插件配置。\n *\n * @since 2.6.0\n */\nexport interface FilePluginConfig extends PluginConfigBase {\n    /**\n     * 日志格式化器。\n     *\n     * @defaultValue `defaultFormatter`（`[时间] [级别] 消息\\n`）\n     */\n    formatter?: LogFormatter;\n    /**\n     * 日志文件根目录。\n     *\n     * @defaultValue `'/.minigame-std-logs'`\n     */\n    rootDir?: string;\n    /**\n     * 日志分割配置。\n     */\n    split?: FileSplitConfig;\n    /**\n     * 缓冲区最大条目数，达到后触发 flush。\n     *\n     * @defaultValue `100`\n     */\n    maxBufferSize?: number;\n    /**\n     * 定时 flush 间隔（毫秒），`0` 表示仅靠缓冲区阈值触发。\n     *\n     * @defaultValue `5000`\n     */\n    flushInterval?: number;\n}\n\n/**\n * 日志文件查询条件。\n *\n * @since 2.6.0\n */\nexport interface LogFileQuery {\n    /**\n     * 起始时间戳（毫秒，含）。按文件创建时间（文件名时间戳）筛选。\n     */\n    from?: number;\n    /**\n     * 结束时间戳（毫秒，含）。按文件创建时间（文件名时间戳）筛选。\n     */\n    to?: number;\n}\n\n/**\n * 文件插件 API 接口。\n *\n * @since 2.6.0\n */\nexport interface FilePluginAPI extends LoggerPlugin {\n    /**\n     * 立即将缓冲区内容写入文件，并等待所有在途写入完成。\n     *\n     * 写入失败不会 reject，也不会返回错误（错误由内部统一处理）。\n     */\n    flush(): Promise<void>;\n    /**\n     * 获取日志文件列表，可按文件创建时间筛选。\n     *\n     * 返回完整路径（`rootDir/文件名.log`），结果按文件名排序。\n     * 文件名无法解析时间戳的文件不受 `query` 过滤，始终包含在结果中。\n     */\n    getFiles(query?: LogFileQuery): AsyncIOResult<string[]>;\n    /**\n     * 获取日志文件根目录。\n     */\n    getRootDir(): string;\n}\n\n/**\n * 创建文件日志插件。\n *\n * 插件在创建时即完成初始化（恢复/创建活跃文件、启动定时 flush、注册切后台监听），\n * 因此即使不传给 `logger.init()` 也能独立工作。传给 `logger.init()` 后，\n * `onInit` 会用全局配置（`level`/`filter`）refinement 自身配置。\n *\n * **注意**：`onInit`/`onLog` 由 logger 核心调度，不应手动调用。\n *\n * @param config - 文件插件配置。\n * @returns 支持 LoggerPlugin 和文件管理 API 的插件实例。\n * @since 2.6.0\n * @example\n * ```ts\n * const file = fileLog({ level: 'debug' });\n * logger.init({ plugins: [file] });\n * await file.flush();\n * ```\n */\nexport function fileLog(config: FilePluginConfig = {}): FilePluginAPI {\n    // ── 解析配置 ──\n    const rootDir = config.rootDir ?? DEFAULT_ROOT_DIR;\n    const split = {\n        period: config.split?.period ?? 3600000,\n        maxSize: config.split?.maxSize ?? 10 * 1024 * 1024,\n        maxCount: config.split?.maxCount ?? 24,\n        maxAge: config.split?.maxAge,\n        useByteSize: config.split?.useByteSize ?? false,\n        compress: config.split?.compress ?? false,\n    };\n    const maxBufferSize = config.maxBufferSize ?? 100;\n    const flushInterval = config.flushInterval ?? 5000;\n\n    // ── 运行时状态（创建即用 config 初始化，不依赖 onInit）──\n    // buffer 存预格式化结果，避免 flush 时重复格式化\n    const buffer: BufferedEntry[] = [];\n    let level: LogLevel = config.level ?? 'info';\n    let filter: LogFilter | null | undefined = config.filter;\n    const formatter: LogFormatter = config.formatter ?? defaultFormatter;\n    // 默认用当前时间戳做文件名，resumeOrCreate 可能改为续写\n    let currentFile = newLogPath();\n    // 同步累加：writeEntry 时即计入，appendFile 失败会导致 size 偏大但不回滚\n    let currentFileSize = 0;\n    let currentFileStartTime = Date.now();\n    // 定时 flush 定时器，onDestroy 时清理\n    let flushTimer: ReturnType<typeof setInterval> | null = null;\n    // 切后台监听移除器，onDestroy 时清理\n    let hideListenerRemover: (() => void) | null = null;\n\n    // init 异步过程中暂存日志（带原始 timestamp），完成后回放\n    const pending: [LogLevel, unknown[], number][] = [];\n    let initPromise: Promise<void> | null = null;\n\n    // fire-and-forget 的在途 append，flush() 等待它们完成\n    const inFlight = new Set<Promise<void>>();\n    // 当前 prune 链的 Promise，null 表示无 prune 在跑\n    // 多次 schedulePrune 调用串行链接（chain），避免并发 prune 的重复 IO\n    let prunePromise: Promise<void> | null = null;\n\n    // 时间戳对应的 period 序号\n    function periodIndex(ts: number): number {\n        return Math.floor(ts / split.period);\n    }\n\n    // 时间戳是否在当前或未来的 period 内（>= 兼容时间回拨，保证最新日志写最新文件）\n    function inCurrentPeriod(ts: number): boolean {\n        return periodIndex(ts) >= periodIndex(Date.now());\n    }\n\n    // 当前活跃文件是否已离开 period\n    function isFileExpired(): boolean {\n        return !inCurrentPeriod(currentFileStartTime);\n    }\n\n    // 创建新日志文件路径（ISO 时间戳命名，永不碰撞）\n    function newLogPath(): string {\n        return `${rootDir}/${toLogName()}`;\n    }\n\n    // 同步切文件：重置文件路径、period 起点、size\n    function newFile(): void {\n        if (split.compress && currentFileSize > 0) {\n            // currentFile 是 string，传参时值已复制，后续 reassign 不影响\n            // Promise.all 调用时同步迭代 inFlight，调用后的新增 append 不影响\n            void compressOldFile(currentFile, inFlight)\n                .catch(() => {\n                    // 压缩失败——原始 .log 仍存在，下次 prune 清理\n                });\n        }\n        currentFile = newLogPath();\n        currentFileStartTime = Date.now();\n        currentFileSize = 0;\n        void schedulePrune();\n    }\n\n    // 触发当前 buffer 的 flush（fire-and-forget）\n    // 同步 splice + 快照 targetFile，appendFile 并发安全\n    function flushCurrent(): void {\n        if (buffer.length === 0) return;\n\n        const entries = buffer.splice(0);\n        const content = entries.map(e => e.formatted).join('');\n\n        const p = appendFile(currentFile, content)\n            .then(() => {\n                // 不关心返回值\n            })\n            .finally(() => {\n                // 无论成功失败，都从 inFlight 移除自己\n                inFlight.delete(p);\n            })\n            .catch(() => {\n                // 吞掉意外 rejection，保证 flush() 的 Promise.all 不被 reject\n            });\n\n        inFlight.add(p);\n\n        // 重置定时器，避免刚 flush 后短时间内又触发低效写入\n        if (flushTimer != null) {\n            clearInterval(flushTimer);\n            flushTimer = setInterval(flushCurrent, flushInterval);\n        }\n    }\n\n    // 调度 prune：串行链接（chain），避免并发 prune 的重复 IO\n    // 每次调用链接到前一个 prune 之后，先等在途 append 完成再读目录\n    function schedulePrune(): Promise<void> {\n        const prev = prunePromise;\n        const p = (prev ?? Promise.resolve())\n            .then(() => Promise.all(inFlight))\n            .then(tryReadLogFiles)\n            .then(pruneOldFiles)\n            .catch(() => {\n                // 吞掉意外 rejection，防止 unhandled rejection\n            })\n            .finally(() => {\n                // 只有最后一个链才清除，中间链不清除（已被新链覆盖）\n                if (prunePromise === p) {\n                    prunePromise = null;\n                }\n            });\n        prunePromise = p;\n        return p;\n    }\n\n    // 读取目录下排序后的日志文件名列表（含 .log 和 .log.gz）\n    async function readLogFiles(): AsyncIOResult<string[]> {\n        const dirResult = await readDir(rootDir);\n        return dirResult.map(files => files\n            .filter(n => n.endsWith('.log') || n.endsWith('.log.gz'))\n            .sort(),\n        );\n    }\n\n    // readLogFiles 的便利包装：错误时返回空数组（调用方按\"无文件\"处理）\n    async function tryReadLogFiles(): Promise<string[]> {\n        return (await readLogFiles()).unwrapOr([]);\n    }\n\n    // 清理超量旧文件：按文件名排序，保留最新的 maxCount 个\n    // best-effort：失败不处理，下次切文件时会重试\n    // logFiles 由调用方通过 tryReadLogFiles 预先读取（避免隐藏 IO）\n    // logFiles 已排序（oldest first），先按 maxAge 过期删除，剩余若超 maxCount 再删最旧的\n    async function pruneOldFiles(logFiles: string[]): Promise<void> {\n        if (logFiles.length === 0) return;\n\n        const { maxAge, maxCount } = split;\n\n        // currentFile 可能还没落盘（newFile 刚创建），不在 logFiles 中\n        // 但它即将写入磁盘，需要为其预留一个 maxCount 名额\n        const currentFileName = currentFile.substring(rootDir.length + 1);\n        const reserveActive = !logFiles.includes(currentFileName);\n        const effectiveMaxCount = reserveActive ? maxCount - 1 : maxCount;\n\n        // 无 maxAge 且未超 effectiveMaxCount，直接返回\n        if (maxAge == null && logFiles.length <= effectiveMaxCount) return;\n\n        const now = Date.now();\n        const toDelete: string[] = [];\n        let remaining: string[];\n\n        // 1. 按时间过期删除（maxAge）\n        if (maxAge != null) {\n            remaining = [];\n            for (const name of logFiles) {\n                const ts = parseTimestamp(name);\n                if (ts != null && now - ts > maxAge) {\n                    toDelete.push(name);\n                } else {\n                    remaining.push(name);\n                }\n            }\n        } else {\n            remaining = logFiles;\n        }\n\n        // 2. 剩余文件若仍超 effectiveMaxCount，删最旧的（remaining 保持有序）\n        if (remaining.length > effectiveMaxCount) {\n            const excess = remaining.slice(0, remaining.length - effectiveMaxCount);\n            toDelete.push(...excess);\n        }\n\n        if (toDelete.length === 0) return;\n\n        await Promise.all(toDelete.map(name => remove(`${rootDir}/${name}`)));\n    }\n\n    // 将过滤后的日志条目加入缓冲，同步决策切分 + 累加 size\n    function writeEntry(level: LogLevel, args: unknown[], timestamp: number = Date.now()): void {\n        const entry: LogEntry = { timestamp, level, message: buildMessage(args) };\n        const formatted = formatter(entry);\n        const entryLen = split.useByteSize ? encodeUtf8(formatted).length : formatted.length;\n\n        // 写入前决策：period 过期或超限 → 先 flush 旧 buffer 到旧文件，再切新文件\n        if (isFileExpired() || currentFileSize + entryLen > split.maxSize) {\n            flushCurrent();\n            newFile();\n        }\n\n        buffer.push({ entry, formatted });\n        currentFileSize += entryLen;\n\n        if (buffer.length >= maxBufferSize) {\n            flushCurrent();\n        }\n    }\n\n    // 启动定时 flush，flushInterval 为 0 时仅靠 buffer 阈值触发\n    // timer 存储到 flushTimer，onDestroy 时 clearInterval\n    function startAutoFlush(): void {\n        if (flushInterval > 0) {\n            flushTimer = setInterval(flushCurrent, flushInterval);\n        }\n    }\n\n    // 查找当前 period 内的文件续写（检查最新文件的 mtime），没有则创建新文件\n    // 同时清理超量旧文件（复用已读取的 logFiles，避免二次 readDir）\n    // 用 mtime 而非文件名时间戳：防篡改；同一 period 内创建时间和 mtime 必定同 period\n    async function resumeOrCreate(): Promise<void> {\n        const logFiles = await tryReadLogFiles();\n        // 只考虑未压缩的 .log 文件用于续写（不能 append 到 .log.gz）\n        const resumable = logFiles.filter(n => n.endsWith('.log'));\n\n        if (resumable.length === 0) {\n            newFile();\n        } else {\n            // 检查最新文件的 mtime 是否在当前 period 内\n            const latestPath = `${rootDir}/${resumable[resumable.length - 1]}`;\n            const statResult = await stat(latestPath);\n            if (statResult.isOkAnd(x => inCurrentPeriod(x.lastModifiedTime))) {\n                const s = statResult.unwrap();\n                currentFile = latestPath;\n                currentFileStartTime = s.lastModifiedTime;\n                currentFileSize = s.size;\n            } else {\n                newFile();\n            }\n        }\n\n        // 清理超量文件（复用 logFiles 含 .log.gz，避免二次 readDir）\n        await pruneOldFiles(logFiles);\n    }\n\n    // ── 启动初始化（创建即就绪，不依赖 onInit）──\n    // 查找或创建活跃文件（同时清理超量旧文件）→ 启动定时器 → 回放 pending\n    // 目录由首次 appendFile 自动创建（writeFile 内部会 mkdir parent）\n    // .finally：无论 resumeOrCreate 成功失败都继续初始化（启动定时器、回放 pending）\n    // .catch：吞掉意外 rejection，防止 unhandled rejection\n    initPromise = resumeOrCreate()\n        .finally(() => {\n            startAutoFlush();\n            hideListenerRemover = addHideListener(flushCurrent);\n            // 回放 init 期间暂存的日志\n            for (const [lvl, args, ts] of pending) {\n                writeEntry(lvl, args, ts);\n            }\n            pending.length = 0;\n            initPromise = null;\n        })\n        .catch(() => {\n            // 吞掉意外 rejection，防止 unhandled rejection\n        });\n\n    // ── API ──\n\n    return {\n        name: 'fileLog',\n\n        onInit(ctx: PluginContext): void {\n            // 用全局配置 refinement 自身配置：自身 > 全局 > 默认\n            // 创建时已用 config 初始化，这里仅补全 config 未指定时从全局继承\n            level = config.level ?? ctx.globalLevel;\n            filter = config.filter !== undefined ? config.filter : ctx.filter;\n        },\n\n        onLog(lvl: LogLevel, ...args: unknown[]): void {\n            // 级别过滤 → 自定义过滤\n            if (!shouldLog(lvl, level)) return;\n            if (filter && !filter(lvl, ...args)) return;\n\n            // init 尚未完成，暂存（带原始 timestamp）后回放\n            if (initPromise != null) {\n                pending.push([lvl, args, Date.now()]);\n                return;\n            }\n\n            writeEntry(lvl, args);\n        },\n\n        onDestroy(): void {\n            // 清理定时器和事件监听，防止 re-init 后资源泄漏\n            if (flushTimer != null) {\n                clearInterval(flushTimer);\n                flushTimer = null;\n            }\n            hideListenerRemover?.();\n            hideListenerRemover = null;\n        },\n\n        async flush(): Promise<void> {\n            // 等 init 完成，确保 pending 已回放到 buffer\n            if (initPromise != null) {\n                await initPromise;\n            }\n\n            // 触发当前 buffer flush\n            flushCurrent();\n            // 等待在途 append 完成 + 触发 prune 并等待\n            // schedulePrune 内部 Promise.all(inFlight) 等所有 append，\n            // 链式确保串行 prune，flush 返回时文件数已不超过 maxCount\n            await schedulePrune();\n        },\n\n        async getFiles(query?: LogFileQuery): AsyncIOResult<string[]> {\n            // 确保缓冲区已落盘，使文件列表包含当前活跃文件\n            flushCurrent();\n            await Promise.all(inFlight);\n\n            return (await readLogFiles()).map(logFiles => {\n                const from = query?.from;\n                const to = query?.to;\n                const needFilter = from != null || to != null;\n\n                const files: string[] = [];\n                for (const name of logFiles) {\n                    // 仅在有时间范围过滤时才解析时间戳，避免无谓的正则匹配\n                    if (needFilter) {\n                        const fileTimestamp = parseTimestamp(name);\n                        if (fileTimestamp != null) {\n                            if (from != null && fileTimestamp < from) continue;\n                            if (to != null && fileTimestamp > to) continue;\n                        }\n                    }\n\n                    files.push(`${rootDir}/${name}`);\n                }\n\n                // readLogFiles 已排序，prepend 统一前缀不改变顺序\n                return files;\n            });\n        },\n\n        getRootDir(): string {\n            return rootDir;\n        },\n    };\n}\n\n// #region Internal Types\n\n/**\n * 缓冲区条目：预格式化的 LogEntry。\n *\n * flush 时直接 join，避免重复格式化。\n */\ninterface BufferedEntry {\n    entry: LogEntry;\n    formatted: string;\n}\n\n// #endregion\n\n// #region Internal Functions\n\n/**\n * 默认日志格式化器。\n *\n * 输出格式：`[2026-06-18 14:30:00.123] [INFO ] 消息内容\\n`\n */\nfunction defaultFormatter(entry: LogEntry): string {\n    const time = formatTimestamp(entry.timestamp, 'yyyy-MM-dd HH:mm:ss.SSS');\n    return `[${time}] [${LEVEL_LABELS[entry.level]}] ${entry.message}\\n`;\n}\n\n/**\n * 格式化时间戳为指定模式。\n */\nfunction formatTimestamp(timestamp: number, pattern: string): string {\n    const date = new Date(timestamp);\n\n    const tokens: Record<string, string> = {\n        'yyyy': date.getFullYear().toString(),\n        'MM': pad2(date.getMonth() + 1),\n        'dd': pad2(date.getDate()),\n        'HH': pad2(date.getHours()),\n        'mm': pad2(date.getMinutes()),\n        'ss': pad2(date.getSeconds()),\n        'SSS': pad3(date.getMilliseconds()),\n    };\n\n    return pattern.replace(/yyyy|MM|dd|HH|mm|ss|SSS/g, matched => tokens[matched]);\n}\n\n/**\n * 用当前时间戳生成独一无二的日志文件名（本地时间）。\n *\n * 全部使用 `-` 分隔，避免 ISO 8601 的 `T` 误导（文件名是本地时间，无时区后缀）。\n * 排序友好：`localeCompare` 按年-月-日-时-分-秒.毫秒逐位递减排序。\n *\n * 跨时区场景下应以 `LogEntry.timestamp`（epoch 毫秒）为准。\n */\nfunction toLogName(): string {\n    return `${formatTimestamp(Date.now(), 'yyyy-MM-dd-HH-mm-ss.SSS')}.log`;\n}\n\n/**\n * 从日志文件名解析时间戳（按本地时间）。\n *\n * 文件名由 {@link toLogName} 生成：`yyyy-MM-dd-HH-mm-ss.SSS.log`。\n * 用 `new Date(y, mo-1, d, h, mi, s, ms)` 显式按本地时间构造，避免\n * `new Date(string)` 在不同运行时对无时区字符串的解析差异。\n */\nfunction parseTimestamp(name: string): number | null {\n    const match = name.match(/^(\\d{4})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})-(\\d{2})\\.(\\d{3})\\.log(?:\\.gz)?$/);\n    if (!match) return null;\n\n    const [, y, mo, d, h, mi, s, ms] = match.map(Number);\n    return new Date(y, mo - 1, d, h, mi, s, ms).getTime();\n}\n\nfunction pad2(n: number): string {\n    return pad(n, 2);\n}\n\nfunction pad3(n: number): string {\n    return pad(n, 3);\n}\n\nfunction pad(n: number, maxLength: number): string {\n    return n.toString().padStart(maxLength, '0');\n}\n\n/**\n * 压缩旧日志文件：等待 in-flight append 完成 → 读 → gzipSync → 写 .log.gz → 删 .log\n */\nasync function compressOldFile(filePath: string, pending: Set<Promise<void>>): Promise<void> {\n    await Promise.all(pending);\n\n    const readResult = await readFile(filePath);\n    if (readResult.isErr()) return;\n\n    const compressed = gzipSync(readResult.unwrap());\n\n    const gzPath = `${filePath}.gz`;\n    const writeResult = await writeFile(gzPath, compressed);\n    if (writeResult.isErr()) return;\n\n    // 压缩成功后才删原始文件，保证不丢数据\n    await remove(filePath);\n}\n\n// #endregion\n","/**\n * wx.getLogManager 插件：声明式创建 wxLog。\n */\n\nimport { Lazy } from 'happy-rusty';\nimport type { LogFilter, LoggerPlugin, LogLevel, PluginContext } from '../defines.ts';\nimport { buildMessage, shouldLog } from '../helpers.ts';\nimport type { PluginConfigBase } from './defines.ts';\n\n// ── Plugin Types ─────────────────────────────────────────────────\n\n/**\n * wx.getLogManager 插件配置（仅小游戏生效）。\n *\n * @since 2.6.0\n */\nexport interface WxLogPluginConfig extends PluginConfigBase {\n    /**\n     * 透传给 `wx.getLogManager` 的参数。\n     *\n     * {@link WechatMinigame.GetLogManagerOption.level}\n     *\n     * @defaultValue `0`\n     */\n    rawLevel?: 0 | 1;\n}\n\n// ── Plugin API ───────────────────────────────────────────────────\n\n/**\n * 创建 wx.getLogManager 插件（仅小游戏生效）。\n *\n * @param config - 插件配置。\n * @returns LoggerPlugin 实例。\n * @since 2.6.0\n * @example\n * ```ts\n * logger.init({ plugins: [wxLog({ level: 'warn' })] });\n * ```\n */\nexport function wxLog(config: WxLogPluginConfig = {}): LoggerPlugin {\n    const logManager = Lazy(() => wx.getLogManager({ level: config.rawLevel ?? 0 }));\n\n    let level: LogLevel;\n    let filter: LogFilter | null | undefined;\n\n    return {\n        name: 'wxLog',\n\n        onInit(ctx: PluginContext): void {\n            level = config.level ?? ctx.globalLevel;\n            filter = config.filter !== undefined ? config.filter : ctx.filter;\n        },\n\n        onLog(lvl: LogLevel, ...args: unknown[]): void {\n            if (!shouldLog(lvl, level)) return;\n            if (filter && !filter(lvl, ...args)) return;\n\n            const manager = logManager.force();\n            // 微信小游戏会将参数逐一进行 JSON.stringify 序列化, 遇到 bigint 类型会报错 `TypeError: Do not know how to serialize a BigInt`\n            // 预序列化参数为单条消息字符串（处理 BigInt 等平台无法序列化的类型）\n            const message = buildMessage(args);\n\n            switch (lvl) {\n                case 'debug':\n                    manager.debug(message);\n                    break;\n                case 'info':\n                    manager.info(message);\n                    break;\n                case 'warn':\n                    manager.warn(message);\n                    break;\n                case 'error':\n                    // wx.getLogManager 没有 error 方法，故降级为 warn\n                    manager.warn(`[ERROR] ${message}`);\n                    break;\n            }\n        },\n    };\n}\n"],"mappings":"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"}