开发日志251206

date
Dec 6, 2025
slug
开发日志251206
status
Published
tags
计算机
AI
summary
基本的脚本写完后 只用控制台看日志有点不方便了 今天主要处理完善日志读写功能的实现 最好还能实现 分级日志输出 考虑让页面异步发送请求到node.js 然后让node.js写日志到本地
type
Post
基本的脚本写完后 只用控制台看日志有点不方便了
今天主要处理完善日志读写功能的实现 最好还能实现 分级日志输出 考虑让页面异步发送请求到node.js 然后让node.js写日志到本地
由于日志分为主机日志和设备日志 此外除了写日志之外 可能还要写报表内容 例如每天总结等 因此需要设置两个模块
  1. 文件读写模块
  1. 日志传入模块
文件读写模块可以提供各类接口 方便各种形式的文件写入 日志传入模块可以专用于日志读写调用文件读写模块
后期如果有需要在主机执行的内容也可以通过这个接口读写日志等

const logInfo = {
    config: {
        enableFileLog: true,// 是否启用文件日志
        logLevel: 0 //
    },

    levelMap: { ERROR: 4, WARNING: 3, SUCCESS: 2, INFO: 1, DEBUG: 0 },

    // 直接用 fetch 发送(页面关闭也能尽量送达)
    _send(payload) {
        if (!this.config.enableFileLog) return;
        post(`/api/log`, payload);
    },

    // 获取调用者函数名
    _getCallerFunctionName() {
        try {
            const error = new Error();
            const stack = error.stack.split('\n');
            if (stack.length > 3) {
                const callerLine = stack[4].trim();
                // 匹配函数名,处理不同格式
                const match = callerLine.match(/at\s+([^\s(]+)\s+\(/);
                return match ? match[1] : 'anonymous';
            }
        } catch (e) {
            // 忽略错误
        }
        return 'unknown';
    },
    // 私有日志方法
    _log(levelEmoji, level, tag, message, deviceId = null) {
        if (!tag) {
            tag = this._getCallerFunctionName();
        }

        const time = timeStr('time');// 获取当前时间字符串
        const tagPart = tag ? `[${tag}]` : '';// 处理标签部分
        const logMessage = `[${time}][${levelEmoji}${level}]${tagPart}${message}\n`;// 组成日志消息
        if (this.levelMap[level] >= this.config.logLevel) {
            console.log(logMessage);// 输出日志到控制台
            log.innerText += "→ " + logMessage;
            log.scrollTop = log.scrollHeight;
        }

        // 发送到后端落盘
        this._send({
            message: logMessage,
            deviceId: deviceId
        });
    },
    i(message, deviceId, tag) { this._log('ℹ️', 'INFO', tag, message, deviceId); },
    s(message, deviceId, tag) { this._log('✅', 'SUCCESS', tag, message, deviceId); },
    e(message, deviceId, tag) { this._log('🔴', 'ERROR', tag, message, deviceId); },
    w(message, deviceId, tag) { this._log('⚠️', 'WARNING', tag, message, deviceId); },
    d(message, deviceId, tag) { this._log('🛠️', 'DEBUG', tag, message, deviceId); },
};
// ==================== 统一文件服务核心 ====================
// 所有文件操作都走这里(日志、配置、缓存、全部!)
const FileService = {
    // 根目录配置(可扩展多个)
    roots: {
        LOG: path.join(__dirname, "LOG"),
        CONFIG: path.join(__dirname, "CONFIG"),
        CACHE: path.join(__dirname, "CACHE")
    },

    // 通用写文件(自动创建目录 + 追加模式)
    async write(rootKey, relativePath, content, options = {}) {
        const root = this.roots[rootKey];
        if (!root) throw new Error(`Invalid root: ${rootKey}`);

        const fullPath = path.join(root, relativePath);
        await fs.mkdir(path.dirname(fullPath), { recursive: true });

        if (options.append) {
            await fs.appendFile(fullPath, content, "utf-8");
        } else {
            await fs.writeFile(fullPath, content, "utf-8");
        }
        return fullPath;
    },

    // 通用读文件
    async read(rootKey, relativePath) {
        const fullPath = path.join(this.roots[rootKey], relativePath);
        if (fs.exists(fullPath)) {
            return fs.readFile(fullPath, "utf-8");
        }
        return null;
    }
};

// ==================== 统一 API ====================
// 写文件(日志、配置都用这一个接口)
app.post("/api/file/write", (req, res) => {
    try {
        const { root, path: relPath, content, append = false } = req.body;

        // 验证参数是否存在
        if (!root || !relPath || !content) {
            return res.status(400).json({
                error: "Missing required parameters",
                required: ["root", "path"]
            });
        }

        FileService.write(root, relPath, content, { append });
        res.send("ok");
    } catch (err) {
        console.error("写文件失败:", err.message);
        res.status(500).send(err.message);
    }
});

// 读文件
app.get("/api/file/read", (req, res) => {
    try {
        const { root, path: relPath } = req.query;

        // 验证参数是否存在
        if (!root || !relPath) {
            return res.status(400).json({
                error: "Missing required parameters",
                required: ["root", "path"]
            });
        }

        const content = FileService.read(root, relPath);
        if (content === null) return res.status(404).send("not found");
        res.send(content);
    } catch (err) {
        res.status(500).send(err.message);
    }
});

// ==================== 专用日志接口(极简封装)================
// 保留这个接口只是为了前端调用方便,内部直接转发
app.post("/api/log", (req, res) => {
    const { message, deviceId } = req.body;
    if (!message) return res.status(400).send("missing message");

    const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD

    try {
        if (deviceId) {
            // 设备日志
            FileService.write("LOG", `设备${deviceId}/${today} 设备${deviceId}.log`, message, { append: true });
        } else {
            // 主机日志
            FileService.write("LOG", `HOST/${today}.log`, message, { append: true });
        }
        res.send("ok");
    } catch (err) {
        res.status(500).send("write failed");
    }
});

© Dominic Hodpel 2022 - 2026