【Linux】程序持久化运行记录——日志<含基础封装实战代码>
本文介绍了一个基于C++17的通用日志模块设计,采用策略模式、RAII机制和流式拼接三大核心思想。该模块支持控制台和文件两种输出方式,通过智能指针自动管理资源,使用互斥锁保证线程安全。日志内容包括时间、级别、进程ID、文件名和行号等信息,支持任意类型数据的流式拼接。实现上利用析构函数自动触发日志写入,避免了手动刷新操作。模块提供简洁的宏定义接口,便于在项目中快速集成和使用。
前景引入
当我们运行多线程/进程程序的时候,由于输出操作不具有原子性,于是就会出现多线程竞态访问的现象,导致控制台打印混乱、消息重叠等现象

日志的引入可以很好的解决此类问题,并且可以对程序的运行过程进行分类记录,灵活对文件/控制台进行原子性写入。
日志的作用
日志是软件系统运行过程中产生的结构化、可追溯的运行记录,是保障系统稳定、可观测、可维护的核心基础设施。其主要作用包括:
1. 故障定位与问题排查
日志记录程序执行流程、关键变量、异常信息与错误堆栈,是线上问题排查的核心依据,能够快速定位故障发生的时间、位置、原因与上下文,大幅降低问题修复成本。
2. 系统运行状态监控
通过日志可以实时观测服务启动、接口调用、请求耗时、资源使用等运行状态,为监控告警、性能统计、健康检查提供数据支撑,保障系统稳定运行。
3. 业务流程追溯与审计
日志可完整记录用户行为、操作记录、业务流转与关键决策,实现全链路可追溯,满足业务审计、合规检查、责任界定等需求。
4. 性能分析与系统优化
基于日志统计接口耗时、并发量、调用频次、资源瓶颈等指标,帮助开发者识别性能热点、优化逻辑、提升系统吞吐量与响应速度。
5. 安全监控与风险防范
记录登录、授权、敏感操作等行为,用于识别异常访问、暴力破解、越权操作等安全风险,为安全事件分析、入侵检测、事后溯源提供证据链。
6. 辅助开发与调试
在开发与测试阶段,日志替代传统打印方式,提供结构化、分级、可持久化的调试信息,提升开发效率与代码质量。
绝大部分语言都对日志进行了库函数封装,如C++的spdlog、C语言zlog等等,我们这里进行伪代码封装自己的基础日志系统。
1.预期效果
日志的核心目标是:按照预设格式原子性地写入目标文件,其核心用途是对多线程 / 多进程环境下的程序运行事件,进行完整、有序的过程记录。

2.使用接口与大致思路
头文件及接口
C++17原生文件系统库:
头文件<filesystem>
检查路径是否存在:std::filesystem::exists(path)判断文件 / 目录是否存在
创建多级目录:std::filesystem::create_directories(path)递归创建多级目录(如/home/uer/...)
C++原生内存管理库:
头文件<memory>
std::unique_ptr独占式智能指针:一个指针独占一个对象,自动释放内存(析构时调用 delete)
std::make_unique安全创建 std::unique_ptr(C++14 引入):避免裸 new,减少异常安全问题
字符串流处理库:
头文件<sstream>
std::stringstream可读写的字符串流:既可以往里面写数据(拼接),也可以从里面读数据(解析)。
文件流操作库:
std::ofstream输出文件流:仅用于写文件。
大致思路
程序采用 “策略模式 + RAII 机制 + 流式拼接” 三大核心设计思想,实现 “可切换输出目标、自动释放资源、支持任意类型日志拼接” 的通用日志模块:
- 策略模式:把 “控制台输出” 和 “文件输出” 封装成独立的策略类,通过统一接口切换,扩展新输出方式(如网络日志)时无需修改核心逻辑;
- RAII 机制:利用
Logmessage临时对象的析构函数自动输出日志,无需手动调用 “刷新 / 写入” 接口; - 流式拼接:重载
operator<<运算符,支持像cout一样拼接任意类型的日志内容; - 线程安全:所有输出操作加互斥锁,避免多线程下日志乱序 / 残缺;
- 自动资源管理:用
std::unique_ptr管理策略对象、std::ofstream管理文件句柄,避免内存 / 文件句柄泄漏。
3.完整代码展示
策略模式
// 基类
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &info) = 0;
};
// 子类,显示器打印
class ScreenStratey : public LogStrategy
{
public:
void SyncLog(const std::string &info) override
{
/* 输出逻辑控制代码块 */
}
private:
};
// 子类,指定文件打印
std::string defaultpath = "./Log";
std::string defaultfile = "Mylog.log";
class FileStrategy : public LogStrategy
{
public:
FileStrategy() {};
void SyncLog(const std::string &message) override
{
/* 输出逻辑控制代码块 */
}
~FileStrategy() {};
private:
};
RAII资源管理模式
class Logger
{
/* ... */
class Logmessage
{
/* ... */
~Logmessage()
{
_logger.ffulsh_strategy->SyncLog(finfo); // 析构函数调用输出策略
};
}
operator()(LEVEL level, std::string name, int line) // 返回临时变量,结束自动调用析构
{
return Logmessage(level, name, line, *this);
}
};
流式拼接
template<typename T> // 利用模版支持不同参数
Logmessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
finfo += ss.str();
return *this; // 返回指针,实现链式调用实现可变参数
}
完整代码
#include <string>
#include <iostream>
#include "Mutex.hpp"
#include <cstdio>
#include <sstream>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <memory>
#include <filesystem>
using namespace MutexModule;
namespace LogModule
{
#define gsep "\r\n"
class LogStrategy
{
public:
~LogStrategy() = default;
virtual void SyncLog(const std::string &info) = 0;
};
// 显示器打印
class ScreenStratey : public LogStrategy
{
public:
void SyncLog(const std::string &info) override
{
LockGuard _lock(_mutex);
std::cout << info << gsep;
}
private:
Mutex _mutex;
};
// 指定文件打印
std::string defaultpath = "./Log";
std::string defaultfile = "Mylog.log";
class FileStrategy : public LogStrategy
{
public:
FileStrategy(const std::string path = defaultpath, const std::string filename = defaultfile)
: _path(path),
_filename(filename)
{
if (std::filesystem::exists(_path))
{
return;
}
try
{
std::filesystem::create_directories(_path);
}
catch (const std::filesystem::filesystem_error &e)
{
std::cerr << e.what() << '\n';
}
}
void SyncLog(const std::string &message) override
{
LockGuard lockguard(_mutex);
std::string filename = _path + (_path.back() == '/' ? "" : "/") + _filename; // "./log/" + "my.log"
std::ofstream out(filename, std::ios::app); // 追加写入的 方式打开
if (!out.is_open())
{
return;
}
out << message << gsep;
out.close();
}
~FileStrategy() {};
private:
std::string _path;
std::string _filename;
Mutex _mutex;
};
enum class LEVEL
{
DEBUG,
INFO,
WARNING,
ERROR,
FATAL
};
std::string leveltos(const LEVEL &level)
{
switch (level)
{
case LEVEL::DEBUG:
return "DEBUG";
case LEVEL::INFO:
return "INFO";
case LEVEL::WARNING:
return "WARNING";
case LEVEL::ERROR:
return "ERROR";
case LEVEL::FATAL:
return "FATAL";
default:
return "UNKNOW";
}
}
std::string GetTime()
{
time_t curr = time(nullptr);
struct tm curr_tm;
localtime_r(&curr, &curr_tm);
char timebuffer[128];
snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d:%02d:%02d",
curr_tm.tm_year + 1900,
curr_tm.tm_mon + 1,
curr_tm.tm_mday,
curr_tm.tm_hour,
curr_tm.tm_min,
curr_tm.tm_sec);
return timebuffer;
}
// 默认执行类
class Logger
{
public:
Logger()
{
Enable_Screen_log_Strategy();
}
void Enable_Screen_log_Strategy()
{
ffulsh_strategy = std::make_unique<ScreenStratey>();
}
void Enable_File_log_Strategy()
{
ffulsh_strategy = std::make_unique<FileStrategy>();
}
class Logmessage
{
public:
Logmessage(const LEVEL &level, std::string filename, int line, Logger &logger)
: _ctime(GetTime()),
_level(level),
_pid(getpid()),
_filename(filename),
_line(line),
_logger(logger)
{
std::stringstream ss;
ss << "[" << _ctime << "] "
<< "[" << leveltos(_level) << "] "
<< "[" << _pid << "] "
<< "[" << _filename << "] "
<< "[" << _line << "] "
<< "- ";
finfo = ss.str();
}
template <typename T>
Logmessage &operator<<(const T &info)
{
std::stringstream ss;
ss << info;
finfo += ss.str();
return *this;
}
~Logmessage()
{
_logger.ffulsh_strategy->SyncLog(finfo);
}
private:
std::string _ctime;
LEVEL _level;
pid_t _pid;
std::string _filename;
int _line;
Logger &_logger;
std::string finfo; // 完整信息
};
Logmessage operator()(LEVEL level, std::string name, int line)
{
return Logmessage(level, name, line, *this);
}
~Logger()
{
}
private:
std::unique_ptr<LogStrategy> ffulsh_strategy;
};
Logger logger;
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Screen_log_Strategy logger.Enable_Screen_log_Strategy()
#define Enable_File_log_Strategy logger.Enable_File_log_Strategy()
}
// 日志格式:[ctime][level][pid][filename][line]
注:编译时需要添加-std=c++17选项
补充:互斥锁封装MUTEX.hpp
// Mutex.hpp
#pragma once
#include <iostream>
#include <pthread.h>
namespace MutexModule
{
class Mutex
{
public:
Mutex()
{
pthread_mutex_init(&_mutex, nullptr);
}
void Lock()
{
int n = pthread_mutex_lock(&_mutex);
(void)n;
}
void Unlock()
{
int n = pthread_mutex_unlock(&_mutex);
(void)n;
}
~Mutex()
{
pthread_mutex_destroy(&_mutex);
}
private:
pthread_mutex_t _mutex;
};
class LockGuard
{
public:
LockGuard(Mutex &mutex) : _mutex(mutex)
{
_mutex.Lock();
}
~LockGuard()
{
_mutex.Unlock();
}
private:
Mutex &_mutex;
};
}
更多推荐



所有评论(0)