『 Linux 』简单日志插件

文章目录


什么是日志

日志(log)是一种记录和存储系统运行状态,事件,和操作历史的文件和数据库,这些记录通常以时间顺序排列且详细记录系统中发生的各种活动;

日志的类型常见有以下几种:

  • 系统日志

    记录操作系统级别的事件,如启动,关机,错误和警告等;

  • 应用日志:

    记录应用程序运行过程中的事件,如用户操作,错误,状态变更等;

  • 安全日志

    记录系统和应用程序的安全相关事件,如登录尝试,权限变更等;

日志的作用为以下:

  • 问题诊断和故障排查

    日志可以帮助系统管理员和开发人员诊断和解决问题;

  • 系统监控和维护

    通过分析日志,管理员可以监控系统的运行状态并且识别潜在的问题和异常;

  • 安全审计和合规性

    日志记录系统和应用程序的各种安全事件以帮助进行安全审计,同时确保系统符合安全和法规要求;

  • 性能分析和优化

    日志数据可以用于分析系统和应用程序的性能,并间接提升系统效率和响应速度;

  • 历史记录和追踪

    日志提供了系统和应用程序运行过程中的详细历史记录,可以用于追踪和回溯系统的操作和变更;

日志记录通常包含以下基本信息:

  • 时间戳

    记录事件发生的具体事件;

  • 事件类型

    记录事件的类型,如错误,警告,信息等;

  • 事件来源

    知名事件是由哪个系统组件或者是应用程序生成的;

  • 事件描述

    详细描述事件的内容和相关信息;

本文主要简单设计一个简单应用日志;


应用日志的设计要点

应用日志专门记录应用程序正在运行过程中发生的各种事件,错误,状态变化等信息;

应用日志可以帮助跟踪程序的执行过程并进行诊断从而了解具体问题;

应用日志的日志级别主要如下:

  • DEBUG

    详细的信息,一般用于诊断问题,经常用于开发过程;

  • INFO

    普通操作信息,用于记录应用程序的正常运行情况;

  • WARNING

    警告信息,标明看那会出现问题的情况,但程序仍可以正常运行;

  • ERROR

    错误信息,标明程序中的某些功能出现问题需要引起注意;

  • CRITICAL/FATAL

    严重错误(致命)信息,标明程序可能无法继续运行,需要立即解决;

日志的统一格式有助于日志的解析和分析;

常见的格式包括JSON,XML或简单的纯文本格式;

如:

bash 复制代码
2024-07-15 10:00:00 [INFO] [module_name] - This is an info message.
2024-07-15 10:01:00 [ERROR] [module_name] - An error occurred.

日志的存储通常有以下几种:

  • 文件

    将日志记录到文件中,方便查看和归档;

  • 数据块

    将日志存储到数据库中,便于查询和分析;

  • 日志管理系统

    使用专门的日志管理工具,如ELK堆栈,进行集中化管理和分析;


简单应用日志插件设计编码

主要思路为封装一个为log的类;

在类中实现根据错误编码产生的日志并使用对应的方式打印至显示器或写入文件中;

文件为:

  • log.hpp

    该文件主要封装日志插件并实现;

  • makefile

    自动化构建过程文件;

  • test.cc

    测试文件;


大概框架及所需头文件与定义

cpp 复制代码
#pragma once

#include <fcntl.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#include <iostream>

#define INFO 0     // 常规信息
#define DEBUG 1    // debug信息
#define WARNING 2  // 警告信息
#define ERROR 3    // 错误信息
#define FATAL 4    // 致命错误信息

#define SCREEN 1    // 向显示器文件写入
#define ONEFILE 2   // 向单个文件进行写入
#define CLASSIFY 3  // 分类将日志进行写入

#define LOG_BUFFER_SIZE 1024  // 日志所需大小

#define LogFile "log.txt"  // 默认日志文件文件名

class Log {
 public:
  Log() {  // 构造函数 用于初始化默认打印方式以及设置默认路径
    printMethod = SCREEN;
    path = "./log/";
  }

  ~Log() { ; }  // 析构函数 未使用

 private:
  int printMethod;   // 打印风格
  std::string path;  // 默认路径
};

构造函数用于初始化默认打印风格以及初始化默认路径;

利用 #define 定义出可能需要使用的常量同时包含所需头文件;


日志信息的输出

此处使用的日志输出格式为:

bash 复制代码
[loglevel][time] logmassage

日志的格式主要为 : 默认部分 + 自定义部分;

默认部分一般为 日志等级 与 日志时间 ;

  • 日志等级

    日志等级需要以字符串的形式进行显示,则可以使用switch() case:对日志等级进行返回;

    cpp 复制代码
      const std::string levelToString(int level) {  // 以字符串形式获取日志等级
        switch (level) {
          case INFO:
            return "INFO";
          case DEBUG:
            return "DEBUG";
          case WARNING:
            return "WARNING";
          case ERROR:
            return "ERROR";
          case FATAL:
            return "FATAL";
          default:
            return "NONE";
        }
      }

    需要注意此处返回的是std::string类型,若是需要打印字符串或是当做字符串使用需要调用其string::c_str();

  • 日志时间

    日志时间一般采用时间戳的方式获取;

    常见的系统调用接口有time()系统调用接口与gettimeofday()系统调用接口,此处使用time();

    bash 复制代码
    NAME
           time - get time in seconds
    
    SYNOPSIS
           #include <time.h>
    
           time_t time(time_t *t);
    
    DESCRIPTION
           time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
    
           If t is non-NULL, the return value is also stored in the memory pointed to by t.
    
    RETURN VALUE
           On  success,  the  value  of  time in seconds since the Epoch is returned.  On error, ((time_t) -1) is returned, and errno is set
           appropriately.

    调用time()系统调用接口将会返回time_t类型,实际打印的是未格式化的时间戳;

    若是需要格式化需要调用localtime()接口将时间戳进行实例化;

    bash 复制代码
    NAME
           asctime,  ctime,  gmtime,  localtime,  mktime, asctime_r, ctime_r, gmtime_r, localtime_r - transform date and time to broken-down
           time or ASCII
    
    SYNOPSIS
           #include <time.h>
    
           struct tm *localtime(const time_t *timep);

    其会返回一个struct tm类型的结构体,其结构体构造如下:

    cpp 复制代码
               struct tm {
                   int tm_sec;         /* seconds */
                   int tm_min;         /* minutes */
                   int tm_hour;        /* hours */
                   int tm_mday;        /* day of the month */
                   int tm_mon;         /* month */
                   int tm_year;        /* year */
                   int tm_wday;        /* day of the week */
                   int tm_yday;        /* day in the year */
                   int tm_isdst;       /* daylight saving time */
               };

    可根据需要获取结构中数据;

    可用printf打印进行debug显示数据是否正确:

    cpp 复制代码
     printf("%d-%d-%d %d:%d:%d\n", ctime->tm_year + 1900, ctime->tm_mon + 1,
             ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    time()系统调用接口中介绍了时间戳的开始时间,为了显示正确时间需要加上对应的数值;

自定义部分需要使用可变参数列表即...;

需要调用vsnprintf()来获取用户自定义的内容:

bash 复制代码
NAME
       printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - formatted output conversion

SYNOPSIS
     
       #include <stdarg.h>

       int vsnprintf(char *str, size_t size, const char *format, va_list ap);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       snprintf(), vsnprintf():
           _BSD_SOURCE || _XOPEN_SOURCE >= 500 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L;
           or cc -std=c99

DESCRIPTION
       The  functions  in  the  printf()  family  produce  output  according to a format as described below.  The functions printf() and
       vprintf() write output to stdout, the standard output stream; fprintf() and vfprintf() write output to the given  output  stream;
       sprintf(), snprintf(), vsprintf() and vsnprintf() write to the character string str.

在使用可变参数的时候需要定义va_list变量来表示可变参数;

并且使用va_start()来初始化一个va_list类型的变量,这个变量用来访问传递给函数的可变参数;

结束使用后需要用va_end()结束对可变参数的访问;

cpp 复制代码
    va_list s;
    va_start(s, format);
    char custombuffer[LOG_BUFFER_SIZE];
    vsnprintf(custombuffer, sizeof(custombuffer), format, s);
    va_end(s);

当获取结束默认部分与自定义部分后调用snprintf()将两个信息组合成一条信息,并根据接下来的需求输出日志信息;

  • 输出日志信息代码

    cpp 复制代码
      void logmessage(int level, const char* format, ...) {
        // 获取时间
        time_t currentTime = time(nullptr);
        struct tm* ctime = localtime(&currentTime);
        // 格式: 默认部分 + 自定义部分
        // 默认部分
        char defaultbuffer[LOG_BUFFER_SIZE];
    
        snprintf(defaultbuffer, sizeof(defaultbuffer), "[%s][%d-%d-%d %d:%d:%d]",
                 levelToString(level).c_str(), ctime->tm_year + 1900,
                 ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour,
                 ctime->tm_min, ctime->tm_sec);
    
        // 自定义部分
        va_list s;
        va_start(s, format);
        char custombuffer[LOG_BUFFER_SIZE];
        vsnprintf(custombuffer, sizeof(custombuffer), format, s);
        va_end(s);
    
        // 组合
        char logtxt[LOG_BUFFER_SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultbuffer, custombuffer);
    
        // 组合完毕后可直接打印也可直接写到文件中
        // std::cout << logtxt << std::endl; - 暂时打印至显示器中;
    
        printLog(level, logtxt);
      }

日志的输出方式

可根据已定义的日志输出方式使用switch()case:分类进行输出;

cpp 复制代码
  void Enable(int method) { printMethod = method; }  // 更改日志输出方式

Enable()函数用来根据用户需求更改日志的打印方式;

cpp 复制代码
  void printLog(int level, const std::string& logtxt) {  // 不同方式输出日志
    switch (printMethod) {
      case SCREEN:
        /* code */
        std::cout << logtxt << std::endl;
        break;
      case ONEFILE:
        printOneFile(LogFile, logtxt);
        break;
      case CLASSIFY:
        printClassify(level, logtxt);
        break;
      default:
        break;
    }
  }

printLog()通过使用switch()case:对日志信息输出进行控制,输出至屏幕时直接std::cout打印至屏幕即可;

  • 将日志信息写入至同一日志文件log.txt

    思路为:

    打开文件,将日志信息进行写入最后关闭文件;

    cpp 复制代码
      void printOneFile(const std::string& logname,
                        const std::string& logtxt) {  // 单个文件输出日志
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_CREAT | O_APPEND | O_WRONLY, 0666);
        if (fd < 0) {
          std::cerr << "open fail" << std::endl;
          return;
        }
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
      }
  • 将日志信息进行分类归档

    思路与写进同一文件思路相同;

    创建一个std::string对象为默认的文件名,并调用levelToString()对文件名进行修改;

    最后调用printOneFile()函数即可;

    cpp 复制代码
     void printClassify(int level,
        const std::string& logtxt) {  // 分类形式输出日志
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level);
        printOneFile(filename, logtxt);
      }

|-------------------------------------------------------|
| 需要注意,尽量在makefile中使用mkdir命令来优先创建log目录,否则会打开文件失败; |


operator()重载

调用过麻烦的话可将void logmessage(int level, const char\* format, ...)函数封装为void operator()(int level, const char* format,...)以减少调用繁琐;

cpp 复制代码
void operator()(int level, const char* format,
                  ...) {  // 重载()操作符并将原本接口进行封装
    // 获取时间
    time_t currentTime = time(nullptr);
    struct tm* ctime = localtime(&currentTime);
    // printf("%d-%d-%d %d:%d:%d\n", ctime->tm_year + 1900, ctime->tm_mon + 1,
    //        ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
    //        //打印 debug

    // 格式: 默认部分 + 自定义部分
    // 默认部分
    char defaultbuffer[LOG_BUFFER_SIZE];

    snprintf(defaultbuffer, sizeof(defaultbuffer),
             "[%s][%d-%02d-%02d %02d:%02d:%02d]", levelToString(level).c_str(),
             ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
             ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    // 自定义部分
    va_list s;
    va_start(s, format);
    char custombuffer[LOG_BUFFER_SIZE];
    vsnprintf(custombuffer, sizeof(custombuffer), format, s);
    va_end(s);

    // 组合
    char logtxt[LOG_BUFFER_SIZE * 2];
    snprintf(logtxt, sizeof(logtxt), "%s %s\n", defaultbuffer, custombuffer);

    // 组合完毕后可直接打印也可直接写到文件中
    // std::cout << logtxt << std::endl;

    printLog(level, logtxt);
  }

完整代码(供参考):

[参考代码(gitee) - DIo夹心小面包 (半介莽夫)]

相关推荐
獨枭几秒前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风3 分钟前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵4 分钟前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier
荒Huang4 分钟前
Linux挖矿病毒(kswapd0进程使cpu爆满)
linux·运维·服务器
断墨先生7 分钟前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
何曾参静谧39 分钟前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
AI街潜水的八角1 小时前
基于C++的决策树C4.5机器学习算法(不调包)
c++·算法·决策树·机器学习
JSU_曾是此间年少1 小时前
数据结构——线性表与链表
数据结构·c++·算法
无极程序员2 小时前
PHP常量
android·ide·android studio
hjjdebug2 小时前
linux 下 signal() 函数的用法,信号类型在哪里定义的?
linux·signal