专栏首页YuanXin日志库的实现机制与优化方法

日志库的实现机制与优化方法

Keywords:堆栈、容器存储、Lazy Log、异步日志、缓存周期

概述

规范化的日志输出和存留,可以用来:开发调试、行为留存、程序状态记录

对于日志,一般需要 4 个要素:时间、级别、位置、内容、上下文信息。对于集群或者多台机器来说,日志还需要区分不同机器的唯一标识

基本原理:堆栈信息

自己封了个包,日志报错信息的格式为: 。下面就是一条日志:

[1:17:27 PM]  (/root/tcb-console-node/dist/controllers/auth.js:auth:38:15) smart-proxy signature check fail

time、level、info 元素非常容易获得,但是对于 loc 元素来说,它包含了调用日志的文件、函数、行数和列数。这些是通过堆栈信息来获得的。

所以,获取 loc 的原理是:调用日志模块接口时,接口内部生成一个 Error;根据堆栈信息,按照规范撰写正则表达式,匹配出文件、函数、行数和列数。

// Node Error Stack: https://github.com/v8/v8/wiki/Stack%20Trace%20API
const stackReg1 = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/i;
const stackReg2 = /at\s+()(.*):(\d*):(\d*)/i;

export function print(msg: string, level?: LogLevel) {
    const time = new Date().toLocaleTimeString();
    const error = new Error(); // 主动生成错误
    const stackList = error.stack.split("\n").slice(2); // 处理报错堆栈
    const sp = stackReg1.exec(stackList[0]) || stackReg2.exec(stackList[0]); // 从堆栈中匹配信息
    if (!sp) {
        return;
    }

    const log = {
        time,
        func: sp[1],
        filepath: sp[2],
        line: sp[3],
        pos: sp[4],
        stack: error.stack,
        msg,
        level
    };
    // ...
}

⚠️ 注意:在error.stack.split('\n').slice(2)这句逻辑中,对于不同的调用层级关系,切片的位置不一样。上面暴露的 print 函数,外界是直接调用。如果是外界调用的接口 a,接口 a 调用 b,接口 b 中生成的 Error。那么,堆栈会变长。但根据 Nodejs 的文档,堆栈最多是 10 层。

日志存储

日志可以根据级别,写入指定文件。比如: info 级别 => /data/my-logs/info.log。

程序应该自动识别环境,开发环境下,可以只吐到控制台,无需写入磁盘。

优化方法

1. Lazy Log

主要体现:根据不同环境、不同级别中,节省 IO

对于开发环境,日志直接输出控制台即可,没必要向磁盘写入。

对于 log、info 等日志级别,日志直接输出控制台,开发/生产环境均没必要向磁盘写入。

2. 异步打印日志

对于高并发服务,每次均向控制台/磁盘采用同时策略吐出日志,会造成 IO 过高。

可以自己封装个方法,将日志存放在队列中,每隔 1000ms 打印/磁盘 io 一次,再清空队列。

let queue = [];
let lock = false;
const interval = 1000;

setTimeout(() => {
    if (!queue.length || lock) return;
    lock = true; // 根据实际情况,决定是否用锁
    let copyQueue = queue;
    queue = []; // 申请新的内存空间
    copyQueue.forEach(item => console.log(item)); // 控制台/磁盘io
    copyQueue = null; // 放置内存泄漏
    lock = false;
}, interval);

export function print(msg: string, level?: LogLevel) {
    const error = new Error(); // 主动生成错误
    const stackList = error.stack.split("\n").slice(2); // 处理报错堆栈
    const sp = stackReg1.exec(stackList[0]) || stackReg2.exec(stackList[0]); // 从堆栈中匹配信息
    if (!sp) return;

    queue.push({
        time: new Date().toLocaleTimeString(),
        func: sp[1],
        filepath: sp[2],
        line: sp[3],
        pos: sp[4],
        stack: error.stack,
        msg,
        level
    });
}

⚠️ 可以使用消息队列。云厂商的日志服务就是这个思路,开启脚本监听对应日志文件,异步将数据放上云端。

3. 缓存周期

对于程序日志来说,可以设置 15 天自动清理。对于敏感接口访问留存,可以持久存储在 DB 中。

4. ELK

用于日志可视化,以及日志快捷查询。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • LeetCode 233.数字1的个数 - JavaScript

    题目描述:给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。

    心谭博客
  • 玩转 Nodejs 命令行

    在做 cli 工具的时候,非常需要命令行相关的第三方库。一个比较稳健成熟的命令行应该考虑以下 4 种需求:

    心谭博客
  • NodeJS模块研究 - crypto

    这次研究下 nodejs 的 crypto 模块,它提供了各种各样加密算法的 API。这篇文章记录了常用加密算法的种类、特点、用途和代码实现。其中涉及算法较多,...

    心谭博客
  • Log4j2 + SLF4j打造日志系统

    java 界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,许多应用程序的日志部分都交给了 log4j,不过作为组件开发者,他们希望自己的组件不要...

    匠心Java
  • 基于Egg框架的日志链路追踪实践

    实现全链路日志追踪,便于日志监控、问题排查、接口响应耗时数据统计等,首先 API 接口服务接收到调用方请求,根据调用方传的 traceId,在该次调用链中处理业...

    五月君
  • Java基础系列(三十二):断言 + 日志入门

    不应该使用断言向程序的其他部分通告发生了可恢复性的错误,或者,不应该作为程序向用户通告问题的手段,断言只应该用于在测试阶段确定程序内部的错误信息。

    山禾说
  • Raft 协议学习笔记

    好久没有更新博客了,最近研究了Raft 协议,谈谈自己对 Raft 协议的理解。希望这篇文章能够帮助大家理解 Raft 论文。

    用户2060079
  • iKcamp|基于Koa2搭建Node.js实战(含视频)☞ 记录日志

    沪江CCtalk视频地址:https://www.cctalk.com/v/15114923883523 log 日志中间件 最困难的事情就是认识自己。 在一...

    iKcamp
  • 网站数据统计分析之二:前端日志采集是与非

    在上一篇《网站数据统计分析之一:日志收集原理及其实现》中,咱们详细的介绍了整个日志采集的原理与流程。但是不是这样在真实的业务环境中就万事大吉了呢?事实往往并非如...

    用户1177713
  • Logback文件这么配置,TPS提高至少10倍

    SpringBoot工程自带logback和slf4j的依赖,所以重点放在编写配置文件上,需要引入什么依赖,日志依赖冲突统统都不需要我们管了。

    物流IT圈

扫码关注云+社区

领取腾讯云代金券