【Linux C++ 日志系统实战】Logger 日志器完整实现:级别控制、宏封装、动态输出、自动崩溃退出

前言

在之前的文章中,我们实现了 Timestamp 时间戳、LogMessage 日志消息体。今天,我们实现日志系统最上层入口:Logger 日志器

它承担以下核心功能:

  1. 对外提供极简使用方式:LOG_INFO << "hello"

  2. 全局日志级别开关(TRACE/DEBUG/INFO/ERROR...)

  3. 可动态替换输出(控制台 / 文件 / 网络)

  4. 析构函数自动输出日志(无需手动 flush)

  5. FATAL 级别直接崩溃退出,便于现场保留

  6. 支持环境变量初始化日志级别

本文基于你提供的完整工程代码,逐行精讲,可直接用于 Linux 服务端项目。


一、整体架构

Logger 是整个日志系统的入口类,结构如下:

  1. LogMessage:负责拼装日志内容

  2. Logger:负责输出、级别、宏封装

  3. 宏定义 LOG_DEBUG / LOG_INFO:极简调用

  4. 输出回调 OutputFun:可替换为控制台 / 文件

  5. 刷新回调 FlushFun:刷新缓冲区


二、公共头文件 LogCommon.hpp

日志级别、缓冲区大小、级别字符串,全局共用

cpp 复制代码
#ifndef LOG_COMMON_HPP
#define LOG_COMMON_HPP

namespace tulun
{
    static const int SMALL_BUFF_LEN = 128;
    static const int MEDIAN_BUFF_LEN = 512;
    static const int LARGE_BUFF_LEN = 1024;

    // C++11 强类型枚举
    enum class LOG_LEVEL
    {
        TRACE = 0,
        DEBUG,
        INFO,
        WARN,
        ERROR,
        FATAL,
        NUM_LOG_LEVELS,
    };

    // 级别字符串映射
    static const char * LLTOSTR[]=
    {
        "TRACE",
        "DEBUG",
        "INFO",
        "WARN",
        "ERROR",
        "FATAL",
        "NUM_LOG_LEVELS",
    };
}

#endif

三、Logger 类定义(Logger.hpp)

对外提供日志宏、输出设置、级别设置、流接口

cpp 复制代码
#include <functional>
#include "LogMessage.hpp"

#ifndef LOGGER_HPP
#define LOGGER_HPP

namespace tulun
{
class Logger
{
public:
    // 输出回调:string → 输出位置
    using OutputFun = std::function<void(const std::string &)>;
    // 刷新回调
    using FlushFun = std::function<void(void)>;

private:
    static OutputFun s_output_;    // 全局输出
    static FlushFun  s_flush_;     // 全局刷新
    static LOG_LEVEL s_level_;     // 全局级别

private:
    LogMessage impl_;  // 内部日志消息对象

public:
    // 构造:级别 + 文件 + 函数 + 行号
    Logger(const LOG_LEVEL &level,
           const std::string &filename,
           const std::string &funcname,
           int line);

    // 析构:自动输出日志 + 刷新
    ~Logger();

    // 获取流,用于 << 输入
    LogMessage &stream();

    // 全局设置
    static void setOutput(const OutputFun &out);
    static void setFlush(const FlushFun &flush);
    static LOG_LEVEL getLogLevel();
    static void setLogLevel(const LOG_LEVEL &level);
};

// ====================== 日志宏定义 ======================
#define LOG_TRACE                                                \
    if (tulun::Logger::getLogLevel() <= tulun::LOG_LEVEL::TRACE) \
    tulun::Logger(tulun::LOG_LEVEL::TRACE, __FILE__, __func__, __LINE__).stream()

#define LOG_DEBUG                                                \
    if (tulun::Logger::getLogLevel() <= tulun::LOG_LEVEL::DEBUG) \
    tulun::Logger(tulun::LOG_LEVEL::DEBUG, __FILE__, __func__, __LINE__).stream()

#define LOG_INFO                                                 \
    if (tulun::Logger::getLogLevel() <= tulun::LOG_LEVEL::INFO)  \
    tulun::Logger(tulun::LOG_LEVEL::INFO, __FILE__, __func__, __LINE__).stream()

#define LOG_WARN                                                 \
    if (tulun::Logger::getLogLevel() <= tulun::LOG_LEVEL::WARN)  \
    tulun::Logger(tulun::LOG_LEVEL::WARN, __FILE__, __func__, __LINE__).stream()

#define LOG_ERROR \
    tulun::Logger(tulun::LOG_LEVEL::ERROR, __FILE__, __func__, __LINE__).stream()

#define LOG_FATAL \
    tulun::Logger(tulun::LOG_LEVEL::FATAL, __FILE__, __func__, __LINE__).stream()

#define LOG_SYSERR    LOG_ERROR
#define LOG_SYSFATAL  LOG_FATAL

}

#endif

四、Logger 核心实现(Logger.cpp)

这是日志系统的心脏

  • 自动初始化级别

  • 析构自动输出

  • 支持替换输出

  • FATAL 直接崩溃退出

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include "Logger.hpp"

namespace tulun
{

// ====================== 默认输出/刷新 ======================
void defaultOutput(const std::string &msg)
{
    fwrite(msg.c_str(), 1, msg.size(), stdout);
}

void defaultFlush()
{
    fflush(stdout);
}

// 全局静态成员
typename Logger::OutputFun Logger::s_ouptut_ = defaultOutput;
typename Logger::FlushFun Logger::s_flush_ = defaultFlush;
tulun::LOG_LEVEL Logger::s_level_ = InitLogLevel();

// ====================== 全局设置接口 ======================
void Logger::setOutput(const OutputFun &out)
{
    s_output_ = out;
}

void Logger::setFlush(const FlushFun &flush)
{
    s_flush_ = flush;
}

void Logger::setLogLevel(const LOG_LEVEL &level)
{
    s_level_ = level;
}

LOG_LEVEL Logger::getLogLevel()
{
    return s_level_;
}

// ====================== 环境变量初始化级别 ======================
LOG_LEVEL InitLogLevel()
{
    if (::getenv("TULUN_LOG_TRACE")) {
        return LOG_LEVEL::TRACE;
    } else if (::getenv("TULUN_LOG_DEBUG")) {
        return LOG_LEVEL::DEBUG;
    } else {
        return LOG_LEVEL::INFO;
    }
}

// ====================== 构造/析构 ======================
Logger::Logger(const LOG_LEVEL &level,
               const std::string &filename,
               const std::string &funcname,
               int line)
    : impl_{level, filename, funcname, line}
{
}

// 核心:析构时自动输出日志
Logger::~Logger()
{
    // 换行
    impl_ << '\n';

    // 调用输出回调
    s_output_(impl_.toString());

    // 刷新缓冲区
    s_flush_();

    // FATAL 级别直接崩溃退出
    if (impl_.getLogLevel() == LOG_LEVEL::FATAL) {
        fprintf(stderr, "Process exit due to FATAL log\n");
        exit(EXIT_FAILURE);
    }
}

// 获取流,支持 << 操作
LogMessage &Logger::stream()
{
    return impl_;
}

} // namespace tulun

五、使用示例(Test04_01_Log.cpp)

支持:

  • 极简调用:LOG_ERROR << "hello"

  • 动态设置级别

  • 动态输出到文件

  • 自动崩溃

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include "Logger.hpp"

using namespace std;
using namespace tulun;

void func()
{
    LOG_TRACE << " 1 ";
    LOG_DEBUG << " 2 ";
    LOG_INFO  << " 3 ";
    LOG_WARN  << " 4 ";
    LOG_ERROR << " 5 ";
    LOG_FATAL << " 6 ";
    LOG_ERROR << "hello";
}

// 输出到文件
FILE *fp = fopen("yhping.log", "w");

void outputFile(const std::string &msg)
{
    fwrite(msg.c_str(), 1, msg.size(), fp);
}

void FlushFile()
{
    fflush(fp);
}

int main()
{
    // 设置只输出 ERROR 及以上级别
    Logger::setLogLevel(LOG_LEVEL::ERROR);

    // 设置输出到文件
    Logger::setOutput(outputFile);
    Logger::setFlush(FlushFile);

    func();

    fclose(fp);
    fp = nullptr;
    return 0;
}

六、输出日志格式示例

bash 复制代码
2026/04/03-18:55:00.609469Z 4831 ERROR Test04_01_Log.cpp func 16  :  5 : 
2026/04/03-18:55:00.609823Z 4831 FATAL Test04_01_Log.cpp func 17  :  6  : 
Process exit

七、核心技术点精讲

1. 为什么要在析构函数里输出日志?

这是C++ 日志系统经典设计

  • 利用临时对象生命周期

  • 一行写完 LOG_INFO << "xxx" 后,语句结束,临时对象析构

  • 自动输出、自动 flush

  • 无需手动 append() / write()

2. 日志宏为什么要加 if 判断?

cpp 复制代码
#define LOG_DEBUG \
if (level <= DEBUG) \
Logger(...).stream()
  • 级别不够时不构造 Logger / LogMessage

  • 低级别日志零开销

  • 线上可直接关闭 TRACE/DEBUG,无性能损耗

3. 为什么用 function 回调输出?

  • 动态替换输出:控制台、文件、网络、kafka

  • 不修改 Logger 代码,符合开闭原则

  • 灵活适配各种环境

4. FATAL 日志为什么直接 exit?

  • 致命错误必须立即停止

  • 防止程序异常继续运行导致数据损坏

  • 便于 coredump 抓现场

5. 环境变量初始化日志级别好处?

  • 启动程序前设置环境变量即可开启调试

  • 无需改代码、无需重启

  • 线上定位问题极快


八、总结

Logger 是整个日志系统的顶层入口,实现了企业级日志必备的全部能力:

极简流式日志:LOG_INFO << "hello"

全局级别控制,运行时可切换

低级别日志零开销

输出 / 刷新可动态替换

析构自动输出,无需手动管理

FATAL 级别自动崩溃退出

支持环境变量初始化级别

Linux 服务端生产可用

相关推荐
小政同学3 小时前
【NFS故障】共享的文件无法执行
linux·运维·服务器
AI木马人4 小时前
3.【Prompt工程实战】如何设计一个可复用的Prompt系统?(避免每次手写提示词)
linux·服务器·人工智能·深度学习·prompt
ch3nyuyu4 小时前
Ubuntu(乌班图)基础指令
linux·运维·网络
minglie14 小时前
gcc编译器汇总
linux
白菜欣6 小时前
Linux —《开发三件套:gcc/g++、gdb、make/Makefile 全解析》
linux·运维
万法若空6 小时前
C++ <memory> 库全方位详解
开发语言·c++
代码中介商6 小时前
C++ 类型转换深度解析:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
青小莫6 小时前
C++之string(OJ练习)
开发语言·c++·stl
senijusene6 小时前
基于 imx6ull平台按键驱动开发:input子系统+中断子系统+platform总线
linux·驱动开发
6Hzlia7 小时前
【Hot 100 刷题计划】 LeetCode 199. 二叉树的右视图 | C++ DFS 逆序遍历
c++·leetcode·深度优先