【Linux取經路】一個簡單的日志模塊

文章目錄

  • 一、可變參數的使用
  • 二、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 置空。

image-20240305154610604

二、Log

2.1 日志打印

日志一般包括:日志的時間、日志的等級、日志的內容、文件名稱和行號。

2.1.1 時間獲取

時間獲取介紹time 返回值是時間戳、gettimeofday 系統調用接口、localtime 將一個時間戳轉化成我們看得懂的格式。

gettimeofday:

image-20240305163715736

localtime:

image-20240305170401246

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;
};

三、結語

今天的分享到這里就結束啦!如果覺得文章還不錯的話,可以三連支持一下,春人的主頁還有很多有趣的文章,歡迎小伙伴們前去點評,您的支持就是春人前進的動力!

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/13864.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/13864.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/13864.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

為什么以太網適配器不是192.168而是196.254【筆記】

為什么以太網適配器不是192.168而是196.254【筆記】 前言版權為什么以太網適配器不是192.168而是196.254最后 前言 2024-03-12 22:55:34 公開發布于 2024-5-22 00:20:35 以下內容源自《【筆記】》 僅供學習交流使用 版權 禁止其他平臺發布時刪除以下此話 本文首次發布于CS…

Linux: tools: crash: not a supported file format

這個原因是,通過比對每個format的magic數值,或者其他的信息,來看是否屬于某個format,如果都不符合,就會出現這個錯誤。說明kernel的coredump文件,dump的有些問題。 main (argc=3, argv=0x7fffffffda88) at main.c:496 496 } else if (is_compressed…

Java高級面試精粹:問題與解答集錦(一)

Java 面試問題及答案 1. 什么是Java中的多態&#xff0c;它是如何實現的&#xff1f; 答案&#xff1a; 多態是Java中的一個核心概念&#xff0c;它允許不同類的對象對同一消息做出響應&#xff0c;但具體的行為會根據對象的實際類型而有所不同。多態主要通過以下兩種方式實現…

git命令行指引

命令行指引 您還可以按照以下說明從計算機中上傳現有文件。 Git 全局設置 git config --global user.name "lizhijun" git config --global user.email "oldgunqfhotmail.com"創建一個新倉庫 git clone gitfiles.tfedu.net:aigk985-gaokao/Folder-watc…

計算機畢業設計 | node.js(Express)+vue影院售票商城 電影放映購物系統(附源碼+論文)

1&#xff0c;緒論 1.1 項目背景 最近幾年&#xff0c;我國影院企業發展迅猛&#xff0c;各大電影院不斷建設新的院線&#xff0c;每年新投入使用的熒幕數目逐年顯著上升。這離不開人們的觀影需求及對觀影的過程要求的不斷進步。廣大觀影消費者需要知道自己的空閑時間&#x…

Django中使用Celery(通用方案、官方方案)

Django中使用Celery&#xff08;通用方案、官方方案&#xff09; 目錄 Django中使用Celery&#xff08;通用方案、官方方案&#xff09;通用方案場景前置準備完整代碼 Celery官方方案【1】注冊celery配置【2】創建celery文件【3】init注冊【4】添加任務【5】啟動worker異步任務…

設計模式六大原則之依賴倒置原則

文章目錄 概念邏輯關系 小結 概念 依賴倒置原則指在設計代碼架構時&#xff0c;高層模塊不應該依賴底層模塊&#xff0c;二者都應該依賴抽象。抽象不應該依賴于細節&#xff0c;細節應該依賴于抽象。 邏輯關系 如上圖所示&#xff0c;邏輯應該就是這樣&#xff0c;高層依賴于…

解決Wordpress中Cravatar頭像無法訪問問題

一、什么是Cravatar Gravatar是WordPress母公司Automattic推出的一個公共頭像服務&#xff0c;也是WordPress默認的頭像服務。但因為長城防火墻的存在&#xff0c;Gravatar在中國時不時就會被墻一下&#xff0c;比如本次從2021年2月一直到8月都是不可訪問狀態。 在以往的時候&…

Java_IO流學習

IO流 概念 I – in – 輸入(讀) O – out – 輸出(寫) 流 – 一點一點的像水流一樣去傳輸數據 注意&#xff1a;站在程序的角度去看待輸入還是輸出 分類 按照方向分流&#xff1a;輸入流、輸出流 按照單位分流&#xff1a;字節流、字符流 按照功能分流&#xff1a;基礎流/節點…

R語言:單細胞pcoa降維和去批次

#生成隨機顏色 > randomColor <- function() { paste0("#",paste0(sample(c(0:9, letters[1:6]), 6, replace TRUE),collapse "")) } # 生成100個隨機顏色 > randomColors <- replicate(100,randomColor()) > seuratreadRDS("seu…

RAG系統(四)手撕基于向量檢索的 RAG

RAG系統&#xff08;一&#xff09;系統介紹與向量檢索 RAG系統&#xff08;二&#xff09;文檔的加載與分段 RAG系統&#xff08;三&#xff09;向量數據庫 完整代碼需要依賴前邊三章中的代碼&#xff0c;本節主要展示整合后的RAG系統&#xff0c;及運行示例。 from MyVec…

前端javascript包管理,npm升級用pnpm

一 pnpm 介紹 pnpm&#xff08;Package Manager&#xff09;是一個快速、節省磁盤空間的 JavaScript 包管理器&#xff0c;它是 Node.js 生態系統中 npm 的一個替代品。pnpm 解決了傳統包管理工具在處理依賴時的一些痛點&#xff0c;特別是關于存儲空間使用和依賴地獄的問題。…

如何將Google Search Console添加到WordPress和GA4

您想知道如何將 Google Search Console 添加到您的 Google Analytics 帳戶和 WordPress 網站嗎&#xff1f; 作為網站主&#xff0c;Google Search Console 是一款不能不使用的工具。對于任何想要確保其網站在 Google 搜索結果中表現良好的人來說&#xff0c;這絕對是一個必不…

leetCode-hot100-數組專題之區間問題

數組專題之區間問題 知識點&#xff1a;解決思路&#xff1a;例題56.合并區間57.插入區間253.會議室 Ⅱ485.無重疊區間 數組區間問題是算法中常見的一類問題&#xff0c;它們通常涉及對數組中的區間進行排序、合并、插入或刪除操作。無論是合并區間、插入區間還是刪除重復空間&…

【HarmonyOS嘗鮮課】- 下載、安裝DevEco Studio以及配置環境、創建運行HarmonyOS項目

下載、安裝開發工具 進入DevEco Studio下載官網&#xff0c;單擊“立即下載”進入下載頁面。 這里以Windows為例進行安裝&#xff0c;可以根據操作系統選擇對應的版本進行下載。 下載完成后解壓一下&#xff0c;進入文件里&#xff0c;雙擊應用程序&#xff0c;打開安裝向導&a…

Redis主從、哨兵、集群講解

一、Redis主從 大家在面試中可能經常會被問到Redis的高可用問題。Redis高可用回答包括兩個層面&#xff0c;一個就是數據不能丟失&#xff0c;或者說盡量減少丟失 ;另外一個就是保證Redis服務不中斷 。 對于盡量減少數據丟失&#xff0c;可以通過AOF和RDB保證。 對于保證服務…

2024年中青杯數學建模思路+建模過程+視頻講解

A 題&#xff1a;人工智能視域下養老輔助系統的構建 隨著全球人口老齡化的加劇&#xff0c;養老問題已經成為一個世界性的社會問題。它不僅 關系到每個家庭、每個個體的未來福祉&#xff0c;也關系到國家的社會穩定和可持續發展。根 據聯合國的相關數據顯示&#xff0c;全球 65…

linux---線程控制

線程和進程 以前我們要同時跑多個程序&#xff0c;可以通過fork()多個子進程&#xff0c;然后通過系統函數進行程序的替換&#xff0c;但是創建進程代價大&#xff0c;不僅要拷貝一份父進程的地址空間&#xff0c;頁表&#xff0c;文件表述符表等。但是線程不需要因為是進程的…

搜索引擎新特性與未來展望

多元化大模型支持: 本搜索引擎具備卓越的兼容性和靈活性,支持多種大模型(LLMs)。無論是本地部署的llama3、gemma、mistral等模型,還是云服務提供的OpenAI/gpt4-o、Groq/Llama3等,都能輕松接入,為用戶提供豐富的智能搜索體驗。現代化技術棧構建: 項目采用業界領先的Next…

windows docker desktop 更換鏡像存儲目錄

windows docker desktop 更換鏡像存儲目錄 方法&#xff1a;如圖&#xff0c;Browse瀏覽一個新的目錄并選中&#xff0c;確定后&#xff0c;程序會開始stop&#xff0c;在stop完成前&#xff0c;會持續遷移原有鏡像到新的位置&#xff0c;你會發現目標位置的磁盤占用空間越來越…