简介:CuteLogger 是一套面向 Qt 开发者的轻量级日志库,用于替代 qDebug 等原生输出,解决多线程工程中日志信息不全、格式不统一、扩展性不足等问题。它几乎能记录文件名、源代码行与函数签名,并通过附加程序机制灵活输出到文件、控制台甚至 Android logcat,也支持自定义附加程序与输出格式;同时内置日志分类、耗时统计,线程安全,可直接替换现有 qDebug 调用,适合中高级 Qt 工程师集成到桌面端或移动端项目。压缩包共 20 个文件,以 10 个头文件与 9 个实现文件为主,再配合 1 个 pri 工程文件完成模块组织,整体仅 29KB,结构清晰,便于引入现有工程后按需裁剪。资源已有 925 人学习下载,源码中附加程序接口、字符串格式化基类与管理器的实现,能帮助读者理解日志框架的扩展点,并据此定制符合自己项目的输出格式与分类策略。对希望摆脱调试输出、建立规范日志体系的 Qt 开发者而言,这是一份小巧实用的参考实现。
1. 拿到 CuteLogger.rar 之后,先搞清楚它替你解决了什么
第一次拿到 CuteLogger.rar 这个压缩包时,大多数人和我一样,以为只是又一个“写日志更漂亮”的库——用起来才发现,它真正解决的是日志这条链路的确定性:谁、在哪个线程、什么时候、以什么级别、写到了哪里。某次排查一个多线程采集程序的崩溃现场,旧日志只有时间戳和一行文字,线程交错完全看不出竞态,换上 CuteLogger 后,线程 ID、分类名、滚动文件同时到位,崩在哪个调用立刻清晰。它适合三类人:被 Qt 项目日志乱序折磨的开发、需要在嵌入式与服务端保留现场的人、以及想把日志输出做成可插拔组件而不是焊死在代码里的团队。说白了,它解决的不是“有没有日志”,而是“日志能不能当证据用”。
2. 编译与集成:把 CuteLogger 接进 Qt 项目的最小步骤
2.1 解压后先认目录:哪些文件是必须的
不管你手上的 CuteLogger.rar 是从哪个渠道拿到的、压缩包内部目录长什么样,第一件事永远是先解压、再看文件清单。Linux 或 macOS 下我一般用 unrar 或 unar;Windows 直接右键解压也能处理。解压命令如下:
unrar x CuteLogger.rar # 如果系统没有 unrar,可以用 unar 替代,输出结果相同 ls -la解压后你会看到一堆 .h 和 .cpp。这套包里装的是源码工程,不是编译好的二进制,所以“解压即用”是不存在的,必须先参与编译。需要认出的核心文件大致是这样几类:
| 文件 | 作用 |
|---|---|
| Logger.h / Logger.cpp | 日志入口,对外提供流式写日志接口 |
| LogManager.h / LogManager.cpp | 管理所有 Logger 实例,按名字复用 |
| AbstractAppender.h / .cpp | 所有输出端的抽象基类 |
| AbstractStringAppender.h / .cpp | 支持格式字符串的输出基类 |
| ConsoleAppender.h / .cpp | 控制台输出端 |
| FileAppender.h / .cpp | 固定文件输出端 |
| RollingFileAppender.h / .cpp | 按大小滚动的文件输出端 |
| CMakeLists.txt 或 .pri | 构建脚本,决定怎么把库编出来 |
我见过有人拿到包后直接朝 main.cpp 里 include 了一堆 Logger.h,然后发现链接失败——因为他只拷贝了头文件,没让源文件参与编译。这个包的集成本质是:把上面这些文件里的核心代码编译进你的程序,或者编成独立库再链进来。先认清文件归属,后面才不容易乱。
2.2 CMake 接入与源码拷贝:两种集成方式的取舍
最常见的做法是把解压出来的整个目录放进你工程的 third_party 或 3rdparty 下,然后在 CMakeLists.txt 里通过 add_subdirectory 把它作为子目录编进来。前提是包里自带可用的 CMakeLists.txt,构建目标和目标名需要看一眼再写,通常会有一个库目标,名字类似 CuteLogger。完整写法如下:
cmake_minimum_required(VERSION 3.16) project(Demo) set(CMAKE_CXX_STANDARD 17) set(CMAKE_AUTOMOC ON) # 关键:Logger 继承自 QObject,必须开启自动 moc find_package(Qt5 COMPONENTS Core QUIET) # Qt6 改成 find_package(Qt6 ...) add_subdirectory(third_party/CuteLogger) add_executable(Demo main.cpp) target_link_libraries(Demo PRIVATE CuteLogger Qt::Core)这里有两处容易翻车。第一,CMAKE_AUTOMOC 必须打开。CuteLogger 内部有 QObject 子类,如果你自己把源码拷进 target 而不开 AUTOMOC,编译会报一堆“未定义虚表”或 moc 文件找不到的错误;走 add_subdirectory 时,如果子目录 CMakeLists 里自己开了,你这边不用管,但直接混合编译时一定要显式打开。第二,target_link_libraries 里的 Qt::Core 要和你的 find_package 版本对应,Qt5 就链 Qt5::Core,Qt6 就链 Qt6::Core,混用会链接失败。
如果你不想引入一个子目录工程,也可以直接把 src 下的 .cpp 全部加进自己的可执行文件 target,相当于“源码拷贝”集成。这种方式少一次库封装,改动直观,但会让你的构建脚本变得啰嗦,而且每次升级这个日志库都要手动替换文件。我会这么写:
set(CUTE_LOGGER_SRC third_party/CuteLogger/Logger.cpp third_party/CuteLogger/LogManager.cpp third_party/CuteLogger/AbstractAppender.cpp third_party/CuteLogger/AbstractStringAppender.cpp third_party/CuteLogger/ConsoleAppender.cpp third_party/CuteLogger/FileAppender.cpp third_party/CuteLogger/RollingFileAppender.cpp ) add_executable(Demo main.cpp ${CUTE_LOGGER_SRC}) target_include_directories(Demo PRIVATE third_party/CuteLogger)要点是 target_include_directories 必须指到放头文件的那个目录,否则 include "Logger.h" 找不到。两种方式我的建议是:只在一个项目里用,选第一种 add_subdirectory,干净、方便升级;需要同时给三四个工程共用,就先把 CuteLogger 编成静态库,再用 find_package 或直接 link 进来,避免每个工程重复编译同一批源码。
2.3 与 Qt 的耦合:AUTOMOC、构建器和导出宏的边界
CuteLogger 是围绕 Qt 核心类设计的库,QString、QDateTime、QFile 是它的底层依赖,所以不要试图在纯标准库环境里硬编它。实际集成时,你要么用 Qt 5 的某个 LTS 版本,要么用 Qt 6,CMake 里通过 find_package 把对应模块找出来。编译器的选择上,Windows 下常见的是 MSVC 或 MinGW,两者都能编,但有个细节:如果你这侧程序用 MSVC 编,CuteLogger 也必须用同编译器,混用 MinGW 编的静态库再让 MSVC 链接,会出现符号兼容问题,这类问题通常到运行期才爆,不好查。
这里还需要留意导出宏。Windows 上动态库会通过导出宏控制哪些符号对外可见,CuteLogger 的头文件里通常有 CUTE_LOGGER_EXPORT 之类的宏定义,如果你选择静态编译,记得把对应的宏开关关掉或按包内说明改成空定义,否则链接期会要求你额外带一堆导入库。具体宏名以你拿到的包为准,我习惯是打开它的全局头文件搜 EXPORT 关键字确认一遍,别猜。
如果你是从 qmake 工程接它,包里可能自带 .pri 文件,在 .pro 里 include 对应 .pri 路径即可。遇到过问题:.pri 里的源码路径是相对路径,你把包放到别的目录层级,路径不匹配会导致 file(GLOB) 拿不到文件。处理办法是调整 include 的相对路径,或者干脆改用上面 CMake 方式,路径关系由你自己控制。
3. 核心协作关系:Logger、LogManager、Appender 如何各司其职
3.1 一条日志消息的完整旅程:从流式写入到文件落盘
先看一段最常见的调用代码,然后跟着它把链路走一遍:
#include "Logger.h" #include "ConsoleAppender.h" #include "FileAppender.h" void handleLogin(int uid) { Logger *logger = Logger::logger("login"); logger->info() << "user login, uid=" << uid; }这段代码里你只看到 logger->info() 一个调用,实际上背后发生了四层动作。第一层,Logger::info() 返回一个临时的流式对象,operator<< 把你的参数逐个拼接起来;第二层,这个临时对象在析构时把整条消息连同当前时间戳、线程 ID、级别、文件名、行号、函数名、Logger 名字组装成一个待写入的消息体;第三层,Logger 遍历自己已经注册的 Appender 列表,逐个调用 write;第四层,AbstractStringAppender 按你设置的格式串把消息体的字段替换成最终字符串,再写到控制台或文件。
这个设计的价值在于,业务代码只负责“我要记一条日志”,至于记到哪、用什么格式、断没断文件,都是后话,由 Appender 决定。你需要理解的核心机制是:LoggerStream 生命周期结束才真正触发写动作。所以不要写出这样的代码:
logger->info() << "begin"; // 这里构造完临时对象,析构还没发生,某些极端场景下行为会变得 tricky写成常规一条语句一个日志即可。消息组装发生在析构阶段,意味着如果那条语句所在的函数异常返回,临时对象依然会被析构,日志还是会写出去,这点比手动拼字符串安全得多。
3.2 流式接口与级别过滤:为什么 logger->debug() 可能什么都不打印
CuteLogger 的级别体系一共六档:Trace、Debug、Info、Warning、Error、Fatal。每个 Logger 实例可以设置自己的最低输出级别,低于这个级别的日志直接不进入 Appender 分发。这是很多人第一次用时最容易误解的地方:你以为集成好了就能看到全部日志,结果 debug() 和 trace() 一条都不出来,原因就是 Logger 的默认级别过滤把它们拦掉了。
处理方式很直接:
Logger *logger = Logger::logger("main"); logger->setLevel(Logger::Trace); // 全量输出,适合调试期 // logger->setLevel(Logger::Warning); // 生产环境只留警告和错误级别过滤发生在消息组装完成之后、分发到 Appender 之前,而不是发生在 Appender 内部。这意味着即使你挂了十个 Appender,级别判断也只做一次,不会因为输出端多而重复计算。CuteLogger 也提供了一组宏,用起来能自动带上文件名和行号:
LOG_TRACE(logger) << "trace message"; LOG_DEBUG(logger) << "debug message"; LOG_INFO(logger) << "info message"; LOG_WARNING(logger) << "warning message"; LOG_ERROR(logger) << "error message"; LOG_FATAL(logger) << "fatal message";流式接口的好处是不需要做字符串格式化,QString 的数字转换、指针地址输出都很自然。要注意的是这些宏和 logger->info() 成员函数是两条平行路径,宏内部会补全 file/line/function 信息,成员函数路径在某些版本里不会补全函数名。所以我自己的习惯是:代码里一律用 LOG_xxx 宏,统一拿文件名和行号,不依赖成员函数的行为差异。
3.3 用 Logger 名字做分类:一套日志体系如何分片管理
LogManager 按名字维护着 Logger 实例。同样一个名字,Logger::logger("network") 在程序里调用一百次,返回的是同一个实例。这带来两个实用效果。第一,Appender 注册一次,所有通过 Logger::logger("network") 写日志的地方都会复用同一组输出端;第二,不同名字的 Logger 拥有独立的 Appender 列表,互相不影响。
我的常用做法是给每个业务模块分配独立 Logger 名字,比如 ui、network、db、scheduler。每个模块日志可以挂不同的 Appender,实现“网络模块单独落一个文件,业务日志进滚动文件,告警日志直接走控制台”这种细粒度管理。示例:
Logger::logger("ui")->registerAppender(consoleAppender); Logger::logger("network")->registerAppender(networkFileAppender); Logger::logger("db")->registerAppender(dbFileAppender); Logger::logger("scheduler")->registerAppender(rollFileAppender);这样当线上出问题时,我只需要盯 network.log 或 db 对应的文件,不用在总日志里大海捞针。你也可以把分类名挂在格式串里,用 %{category} 占位符打印出来,这样即使所有日志都落同一个文件,也能用文本工具按分类名过滤。分类这套东西很轻,但它是日志体系能否用于事后分析的关键,不要省。
4. 落地输出:控制台、文件、滚动文件的参数与格式定制
4.1 ConsoleAppender:调试窗口里的第一现场
ConsoleAppender 是把日志打印到标准输出流的输出端,适合开发调试期、以及没有持久化需求的服务端简单场景。注册方式相当简单:
#include "ConsoleAppender.h" ConsoleAppender *console = new ConsoleAppender; console->setFormat("%{timestamp} [%{level}] %{category} %{message}"); logger->registerAppender(console);setFormat 里的占位符会在每次写日志时被替换成实际内容。第一次接入时我建议只保留 timestamp、level、message 三项,字段越少,肉眼扫日志越快。控制台输出的典型限制是:没有持久化、重启即丢失、大量输出时会拖慢终端,所以它适合“调试期看现场”,不适合“生产环境留证据”。如果程序是 daemon 方式跑在后台,控制台日志基本等于没用,这种情况直接把 ConsoleAppender 从注册列表里去掉,或者让它和文件 Appender 并存,不要只挂控制台。
还有个细节值得注意:ConsoleAppender 内部用的是标准输出流,而某些框架会把 stdout 重定向到日志文件,这样你注册了控制台 Appender 但日志却出现在别的文件里。遇到这种“控制台没东西但程序没报错”的情况,先检查进程的标准输出被谁接管了,别急着怀疑 Appender 失效。
4.2 FileAppender:固定文件写入与打开方式
FileAppender 把日志写进一个固定文件,不切割、不轮转。代码写法:
#include "FileAppender.h" FileAppender *file = new FileAppender; file->setFileName("/var/log/app/runtime.log"); file->setFormat("%{timestamp} [%{level}] %{category} %{threadid} %{message}"); logger->registerAppender(file);写文件时的注意点集中在路径上。第一,目录必须提前存在,FileAppender 不会帮你递归创建目录,目录不存在时它会静默失败,你在业务代码里完全看不到错误提示,日志文件也不会生成。第二,建议写绝对路径,相对路径受工作目录影响,程序被 service 拉起时工作目录往往不是你预期的那个。第三,文件权限要够,跑在系统服务账户下时,目标目录可能不可写,同样表现为静默失败。
我一般会在程序启动后主动探测一下文件 Appender 是否真的写出来了,用一个非 debug 级别的日志写一条启动标记,然后立刻去文件系统确认。这样的主动验证 30 秒就能做完,能省掉后面几小时的排查。固定文件适合短期调试或临时任务,长时间跑的系统别只用 FileAppender,否则单文件会无限膨胀,后面要用编辑器打开一个 2GB 的日志文件,光加载就够受的。
4.3 RollingFileAppender:按大小滚动才是长跑方案
长跑程序建议用 RollingFileAppender,它会按文件大小自动切分并保留有限个历史文件。常见用法:
#include "RollingFileAppender.h" RollingFileAppender *rolling = new RollingFileAppender; rolling->setFileName("/var/log/app/service.log"); rolling->setFormat("%{timestamp} [%{level}] %{category} %{function} %{message}"); rolling->setMaxFileSize(10 * 1024 * 1024); // 单文件最大 10MB rolling->setMaxLogFiles(5); // 保留 5 个历史文件 logger->registerAppender(rolling);关键参数是两个:maxFileSize 控制单个文件的上限,maxLogFiles 控制保留的历史文件数量。写满上限后,库会把当前文件改成 service.log.1,再新开 service.log 继续写,旧文件依次后移,超过保留数量的历史文件会被删除。这样磁盘占用被锁在一个可控范围:10MB 乘 5 个,加当前文件,最多 60MB。
这两个参数的设置要看你的日志量和磁盘空间。日志量大、又要长期排查问题时,单文件 100MB、保留 10 个,能覆盖更长时间线;日志只是留痕用,单文件 5MB、保留 3 个就够。我的习惯是先用一个月的数据量估算:如果一天产生约 200MB 日志,单文件 100MB、保留 5 个能覆盖约两天半的现场,再配一个定期归档任务,避免重要日志被滚动删除。
4.4 Format 字符串与常用占位符的匹配关系
格式串是 CuteLogger 定制输出最常用、也最需要确认的一项能力。不同小版本对占位符的支持可能有细微差异,拿包后以 AbstractStringAppender 源码为准,但下面这些是绝大多数版本通用的:
| 占位符 | 含义 | 示例输出 |
|---|---|---|
| %{timestamp} | 日志产生时间 | 2025-05-18 14:23:01.123 |
| %{level} | 日志级别 | INFO |
| %{category} | Logger 分类名 | network |
| %{threadid} | 线程 ID | 0x16d8 |
| %{file} | 源文件名 | handleLogin.cpp |
| %{line} | 行号 | 37 |
| %{function} | 函数名 | handleLogin(int) |
| %{message} | 日志正文 | user login, uid=123 |
实际排格式串时,我建议固定顺序:时间、级别、分类、线程 ID、文件名行号、正文。时间在前方便按时间线排序检索,线程 ID 放在前面方便定位并发问题,正文放最后避免被前面的长字段挤到不可读。格式串里不建议塞太多占位符,尤其是 file/line/function 三者同时打满时,一行日志会变得很长,终端里看还好,落到文件里会增加不少体积,分析时也不利于一眼抓住重点。
还有一个习惯值得养成:把 format 抽成一个公共函数或常量,所有 Appender 复用,不要每个 Appender 各写一份。这样后续要加字段,只改一处,所有输出端都同步更新,不会出现某个文件缺字段、某个文件多字段的混乱局面。
5. 避坑:CuteLogger 常见的 5 个集成交警及排查思路
5.1 编译过了,但日志一条都没有
现象:程序编译链接全通过,运行也不报错,控制台和文件里都看不到任何日志输出。
原因:大概率不是库坏了,而是级别过滤或 Appender 注册时机出了问题。Logger 默认级别可能把 debug 以下全部过滤;或者你注册 Appender 的代码写在某次条件分支里,那个分支没执行;再或者你拿到的 logger 实例和写日志时用的不是同一个名字,注册挂在 "main" 上,写日志却用 "network",两套实例互不相通。
解决:先做最小验证——在 main 最开始,用 LOG_ERROR 打一条固定消息,级别最高不会被过滤;同时确认注册代码一定早于所有写日志代码。如果 ERROR 能出、INFO 不出,就是级别问题,把 Logger 级别调到 Trace 再看。如果 ERROR 都不出,优先怀疑 Appender 没注册成功,直接打印 Appender 数量或检查注册返回值。这里的关键是“分步排除”,把级别过滤、实例复用、注册时机三个变量逐个固定住。
5.2 中文日志乱码或者干脆是问号
现象:程序里写的中文日志在控制台显示乱码,在文件里打开看是问号或空白。
原因:没有直接证据指向 CuteLogger 本身有编码 bug,绝大多数情况是三层编码不一致:源文件编码、Qt 内部 QString 编码、输出端落盘时的编码。Qt 的 QString 内部是 UTF-16,你的中文字符串字面量如果源文件是 GBK 编码,被 Qt 读进来时转码结果就是错的,后面再怎么设置格式串都救不回来。
解决:所有源文件统一 UTF-8 编码,且在编译器里指定 UTF-8 处理;输出文件侧,FileAppender 写出时把它配置成 UTF-8,文本编辑器也用 UTF-8 打开。Windows 控制台乱码是另一个独立问题,它和系统代码页有关,不要和文件乱码混在一起排查。先确认文件里写出来的字节流是否正确,再去看控制台显示层,否则会在错误的方向上浪费很多时间。优先保证落盘文件编码正确——那是证据,控制台只是临时看的。
5.3 滚动文件没有按预期切分,文件无限增大
现象:明明设置了 maxFileSize,日志文件却持续增长,始终没有出现 .1、.2 这类滚动文件。
原因:最常见的是参数理解错误——maxFileSize 的单位可能导致你填的数偏大,比如你以为填的是 MB,实际库按字节处理,填了 10 结果上限是 10 字节,或者反过来填了 10MB 但库按 KB 解释,上限变成 10GB。另一种情况是文件被其他进程占用了,Windows 下日志文件被杀软或另一个打开句柄锁住,库尝试重命名文件时失败,只能继续往原文件写。
解决:先做对照试验,把 maxFileSize 设成一个很小的值,比如 1024 字节,写几十条日志看它滚不滚。能滚说明机制正常,问题在参数数值;不滚说明库本身或文件占用有问题,换目录、确认没有其他进程打开该文件后再试。别在生产日志上做这个实验,单独开一个调试目录,日志轮转失败的原因还能顺带在目录里查到迹象。
5.4 高并发下日志丢失或顺序错乱
现象:多线程同时写日志,跑一段时间后发现总数对不上,或者日志行之间的先后顺序和实际执行顺序不一致。
原因:先区分“丢”和“乱”是两种问题。丢日志常见于你把日志写操作放在一个会提前返回的路径里,临时对象析构前程序就退出了;也可能是 Appender 内部有队列,队列满时策略是丢弃而不是阻塞。顺序乱则通常是多线程下锁的粒度问题,不同线程的消息在竞争同一把锁时,先获得锁的线程反而可能后进入日志调用,导致记录的先后顺序不等于实际执行顺序。
解决:给每条日志带一个自增序号或时间戳到毫秒级,才能在分析时还原真实顺序。如果确认是同一 logger 实例并发写入,先做压力测试,比如八个线程各写一万条,结束后统计文件总行数是否等于八万,用这个手段确认有没有丢。丢的话,检查是否把 logger 局部化了——每个线程各自创建了 Logger 实例,导致 Appender 重复注册或互斥失效。并发量再往上走,就要考虑自定义异步 Appender,把写入丢到独立线程处理,避免业务线程阻塞在日志写盘上。
5.5 链接期符号冲突或运行期双份实例
现象:程序里既有你集成进来的 CuteLogger,又通过某个依赖库间接带了一份 CuteLogger,链接时出现符号重定义,或者运行时日志行为诡异——你注册的 Appender 不生效,但另一个模块的日志在打印。
原因:两份源码编译进了同一个进程,导出符号完全一样,链接器随机选择其中一个实现,导致你调用的和你配置的不是同一份代码。这个问题在大型项目里很隐蔽,因为不是每次构建都会报错,有时候只是运行期行为不一致。
解决:在项目里维护且只维护一份 CuteLogger 源码,统一通过同一个构建目标引用。集成前先搜一下依赖树里有没有其他模块也 include 了 Logger.h,发现重复就删掉一方,把它们的引用指向同一份。如果条件不允许动依赖库,可以给其中一份源码整体加命名空间或改导出宏,但这种方式后续升级维护成本高,只能算最后一招。检查方法也简单:程序里主动查询 Logger 实例个数和 Appender 注册数量,和预期核对,数量对不上基本就是双份了。
6. 进阶:定制 Appender 与验证你的日志体系
当你在几个输出端之间反复切换、觉得仍然不够用时,就该动手定制 Appender 了。CuteLogger 的输出端全部继承自 AbstractAppender,你只需要实现一个 write 虚函数,就能定义任意输出行为。下面是一个只放行 Warning 及以上级别的告警 Appender,可以把关键日志额外送进单独的告警文件或推给上层逻辑:
#include "AbstractAppender.h" class AlertAppender : public AbstractAppender { protected: void write(const QDateTime ×tamp, Logger::LogLevel level, const char *file, int line, const char *function, const QString &category, const QString &message) override { if (level < Logger::Warning) { return; // 只关心告警级别以上 } // 这里可以把 message 推到内存环形缓冲、上报服务或写独立告警文件 QFile alertFile("/var/log/app/alert.log"); if (alertFile.open(QIODevice::Append | QIODevice::Text)) { QTextStream stream(&alertFile); stream << timestamp.toString(Qt::ISODate) << " [" << category << "] " << message << "\n"; } } };使用方式和内置 Appender 一致,注册给对应 Logger 即可。这里有一个性能教训:write 回调是在 Logger 的锁保护下执行的,如果你在里面对一个远程服务做同步上报,所有共享同一个 Logger 的线程都会被拖住。走自定义 Appender 且涉及耗时操作时,正确做法是只做内存写入或投递到自己的工作线程队列,不要在 write 里阻塞。
定制完别急着上线,先验证功能正确性。我的验证习惯是:写一个多线程压力小用例,N 个线程各写 M 条带编号的日志,结束之后统计文件行数,确认等于 N 乘 M;再把每行末尾的编号取出来排序查重,确认没有丢失也没有重复。命令行里一行 awk 就能完成统计:
wc -l /var/log/app/stress.log awk -F'|' '{print $NF}' /var/log/app/stress.log | sort -n | uniq | wc -l两个数字相等,说明日志链路在并发下是可靠的,这时候才敢把日志体系真正交到生产环境。我自己的习惯是:每次集成一个像 CuteLogger 这样的外部库,先搭一个最小可运行工程验证生命周期,再做并发压测,最后才接进业务代码。为了一点日志功能,不值得在生产环境里试错。这套路子走通之后,你手里就不只是“一个日志库”,而是一套有分类、有滚动、有告警、经得起并发验证的日志基础设施。希望帮到你。
本文还有配套的精品资源,点击获取