文章目錄
- 一、可變參數的使用
- 二、Log
- 2.1 日志打印
- 2.1.1 時間獲取
- 2.1.2 日志分塊打印
- 2.2 打印模式選擇
- 2.3 Log 使用樣例
- 2.4 Log 完整源碼
- 三、結語
一、可變參數的使用
int sum(int n, ...)
{va_list s; // va_list 本質上就是一個指針va_start(s, n); int sum = 0;while(n){sum += va_arg(s, int);n--;}va_end(s);return sum;
}
va_list
本質上就是一個 char *
類型,va_start
的作用就是讓指針 s
指向函數棧幀中第一個非可變形參。因為函數的實參是按照從右向左的順序進行壓棧的,因此只要知道了第一個非可變形參的位置,就可以找到第一個可變參數的位置,進而去解析所有的可變參數。這就要求:可變參數的左邊必須要有一個具體的參數。va_arg
是根據第二個參數所確定的類型來提取可變參數,va_end
是將 s
置空。
二、Log
2.1 日志打印
日志一般包括:日志的時間、日志的等級、日志的內容、文件名稱和行號。
2.1.1 時間獲取
時間獲取介紹:time
返回值是時間戳、gettimeofday
系統調用接口、localtime
將一個時間戳轉化成我們看得懂的格式。
gettimeofday:
localtime:
void logmessage(int level, const char *format, ...)
{time_t t = time(nullptr);struct tm *ctime = localtime(&t);printf("%d-%d-%d %d:%d:%d", ctime->tm_year+1900, ctime->tm_mon+1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}
2.1.2 日志分塊打印
根據日志的內容,可以將一條日志信息分成兩部分:默認部分和自定義部分。
-
默認部分:日志的時間、等級。
-
自定義部分:日志的內容,需要進行格式化控制的內容。
日志等級轉字符串模塊:
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";}
}
默認部分代碼:通過 snprintf
函數將默認部分的信息寫入到一個字符數組中。
void DefaultMessage(int level, char *defaultbuffer, int size)
{time_t t = time(nullptr);struct tm *ctime = localtime(&t);snprintf(defaultbuffer, size, "[%s][%d-%d-%d %d:%d:%d]", LevelToString(level), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
}
自定義部分代碼:通過 vsnprintf
函數來幫助我們解析用戶的格式化輸入,將用戶自定義的信息寫入到一個字符數組中。
void logmessage(int level, const char *format, ...)
{char defaultbuffer[SIZE]; // 存儲默認內容DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));char userbuffer[SIZE]; // 存儲自定義內容va_list s;va_start(s, format);vsnprintf(userbuffer, sizeof(userbuffer), format, s);va_end(s);
}
將默認內容和自定義內容合并:
void logmessage(int level, const char *format, ...)
{char defaultbuffer[SIZE]; // 存儲默認內容DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));char userbuffer[SIZE]; // 存儲自定義內容va_list s;va_start(s, format);vsnprintf(userbuffer, sizeof(userbuffer), format, s);va_end(s);char logtxt[SIZE*2];snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);std::cout << logtxt << std::endl;
}
2.2 打印模式選擇
可以通過設置選項,將日志信息打印到:**顯示器、一個文件、多個文件(同等級的在一個文件)。**抽象一個 Log
類,在里面設置一個成員變量來選擇日志的輸出。
模式設置:
void SetStyle(int style)
{_outputstyle = style;
}
模式選擇:
void OutPutLog(int level, const std::string &message)
{switch (_outputstyle){case Screen: // 向顯示器打印OutPutToScreen(message);return;case Onefile: // 向一個文件中打印OutPutToOnefile(_logpath, message);return;case Classfile: // 向多個文件中打印OutPutToClassfile(level, message);return;}
}
向顯示器打印:
// 將日志信息打印到屏幕
void OutPutToScreen(const std::string &message)
{std::cout << message << std::endl;
}
向一個文件中寫入:
// 將日志信息保存到一個文件中
void OutPutToOnefile(const std::string &path, const std::string &message)
{// 打開文件std::fstream fp;fp.open(path, std::ios::app);if (!fp.is_open()){std::cout << path << " open faile" << std::endl;}// 向文件寫入fp << message << std::endl;// 關閉文件fp.close();
}
向多個文件中寫入:
// 將日志信息按照等級保存到不同文件中
void OutPutToClassfile(int level, const std::string &message)
{switch (level){case Info:OutPutToOnefile(INFO_LOG_PATH, message);break;case Debug:OutPutToOnefile(DEBUG_LOG_PATH, message);break;case Warning:OutPutToOnefile(WARING_LOG_PATH, message);break;case Error:OutPutToOnefile(ERROR_LOG_PATH, message);break;case Fatal:OutPutToOnefile(FATAL_LOG_PATH, message);break;default:break;}return;
}
operator() 讓調用顯得更加優雅:
void operator()(int level, const char *format, ...)
{char defaultbuffer[SIZE]; // 存儲默認內容DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));char userbuffer[SIZE]; // 存儲自定義內容va_list s;va_start(s, format);vsnprintf(userbuffer, sizeof(userbuffer), format, s);va_end(s);char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);// std::cout << logtxt << std::endl;OutPutLog(level, logtxt);
}
2.3 Log 使用樣例
#include "log.hpp"
#include <stdlib.h>
#include <unistd.h>int main()
{Log log;int cnt = 10;while (cnt--){if(cnt == 5) log.SetStyle(Classfile);log(Info, "I am %d %s %f", 2, "wuchengyang", 3.14);log(Debug, "I am %d %s %f", 3, "wuchengyang", 4.78);log(Fatal, "I am %d %s %f", 4, "wuchengyang", 5.32);sleep(1);}return 0;
}
2.4 Log 完整源碼
#pragma once
#include <stdarg.h>
#include <iostream>
#include <time.h>
#include <fstream>#define SIZE 1024
// 定義日志等級
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4// 日志輸出
#define Screen 1
#define Onefile 2
#define Classfile 3// 存儲日志信息的目錄
#define LOG_PATH "./Log/log.txt"
#define INFO_LOG_PATH "./Log/InfoLog.txt"
#define DEBUG_LOG_PATH "./Log/DebugLog.txt"
#define WARING_LOG_PATH "./Log/WaringLog.txt"
#define ERROR_LOG_PATH "./Log/ErrorLog.txt"
#define FATAL_LOG_PATH "./Log/FatalLog.txt"class Log
{
public:Log(const std::string &logpath = LOG_PATH, int style = Onefile): _logpath(logpath),_outputstyle(style){}private: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";}}void DefaultMessage(int level, char *defaultbuffer, int size){time_t t = time(nullptr);struct tm *ctime = localtime(&t);snprintf(defaultbuffer, size, "[%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);}// 將日志信息打印到屏幕void OutPutToScreen(const std::string &message){std::cout << message << std::endl;}// 將日志信息保存到一個文件中void OutPutToOnefile(const std::string &path, const std::string &message){// 打開文件std::fstream fp;fp.open(path, std::ios::app);if (!fp.is_open()){std::cout << path << " open faile" << std::endl;}// 向文件寫入fp << message << std::endl;// 關閉文件fp.close();}// 將日志信息按照等級保存到不同文件中void OutPutToClassfile(int level, const std::string &message){switch (level){case Info:OutPutToOnefile(INFO_LOG_PATH, message);break;case Debug:OutPutToOnefile(DEBUG_LOG_PATH, message);break;case Warning:OutPutToOnefile(WARING_LOG_PATH, message);break;case Error:OutPutToOnefile(ERROR_LOG_PATH, message);break;case Fatal:OutPutToOnefile(FATAL_LOG_PATH, message);break;default:break;}return;}void OutPutLog(int level, const std::string &message){switch (_outputstyle){case Screen: // 向顯示器打印OutPutToScreen(message);return;case Onefile: // 向一個文件中打印OutPutToOnefile(_logpath, message);return;case Classfile: // 向多個文件中打印OutPutToClassfile(level, message);return;}}public:void SetStyle(int style){_outputstyle = style;}// void logmessage(int level, const char *format, ...)// {// char defaultbuffer[SIZE]; // 存儲默認內容// DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));// char userbuffer[SIZE]; // 存儲自定義內容// va_list s;// va_start(s, format);// vsnprintf(userbuffer, sizeof(userbuffer), format, s);// va_end(s);// char logtxt[SIZE * 2];// snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);// // std::cout << logtxt << std::endl;// OutPutLog(level, logtxt);// }void operator()(int level, const char *format, ...){char defaultbuffer[SIZE]; // 存儲默認內容DefaultMessage(level, defaultbuffer, sizeof(defaultbuffer));char userbuffer[SIZE]; // 存儲自定義內容va_list s;va_start(s, format);vsnprintf(userbuffer, sizeof(userbuffer), format, s);va_end(s);char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", defaultbuffer, userbuffer);// std::cout << logtxt << std::endl;OutPutLog(level, logtxt);}~Log(){}private:int _outputstyle;std::string _logpath;
};
三、結語
今天的分享到這里就結束啦!如果覺得文章還不錯的話,可以三連支持一下,春人的主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是春人前進的動力!