Utilisation et extension de easylogging + +

Temps écoulé 2021-08-19 23:51:07 阅读数:790

utilisation et extension easylogging

Introduction

Easylogging++ C'est pour C++ Une bibliothèque de journaux efficace à un seul en - tête pour l'application.C'est très puissant,Très extensible et configurable à la demande de l'utilisateur.githubLiens:https://github.com/amrayn/easyloggingpp.

Easylogging++ Inv9.89L'édition n'a qu'un seul fichier d'en - tête,Puis changez - le en fichier d'en - tête、Un fichier source,La dernière version estv9.97(La version utilisée dans cet article).

Utiliser

Utiliser Easylogging++Il suffit de trois étapes simples:

  • Télécharger la dernière version
  • Oui.easylogging++.hEteasylogging++.cc Inclus dans le projet
  • Initialisation avec une seule macro
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(int argc, char* argv[]) {
LOG(INFO) << "My first info log using default logger";
return 0;
}

Extension

Easylogging++ Le journal par défaut est écrit dans un fichier , Et il n'y a pas de fonction pour créer un nouveau journal par date , J'ai besoin de m'agrandir . Les fonctions étendues sont les suivantes :

  • Les fichiers journaux sont placés sur une base annuelle 、 Dans le dossier généré par le mois , Un fichier journal distinct par niveau de journal ,Par exemple:“Log\2021\202108\20210818_INFO.log”
  • Générer de nouveaux fichiers journaux tous les jours , C'est - à - dire que les fichiers journaux défilent par date
  • Supprimer automatiquement en fonction de la dernière modification du fichier journal n Le fichier journal de l'autre jour ,Prise en charge uniquementWindowsSystème

Je vais essayer d'utiliser la Bibliothèque standard et Easylogging++ Il y a déjà des fonctions à l'intérieur pour réaliser des fonctions étendues , Réduire les dépendances externes , Il est également pratique de fusionner des espaces de noms plus tard .

Configurer le chemin du Journal

Easylogging++ Prise en charge des profils 、 Le Code du programme configure le chemin du Journal de deux façons , Le chemin du journal est configuré ici comme un code de programme ,Les codes sont les suivants::

static std::string LogRootPath = "D:\\Log";
static el::base::SubsecondPrecision LogSsPrec(3);
static std::string LoggerToday = el::base::utils::DateTime::getDateTime("%Y%M%d", &LogSsPrec);
static void ConfigureLogger()
{
std::string datetimeY = el::base::utils::DateTime::getDateTime("%Y", &LogSsPrec);
std::string datetimeYM = el::base::utils::DateTime::getDateTime("%Y%M", &LogSsPrec);
std::string datetimeYMd = el::base::utils::DateTime::getDateTime("%Y%M%d", &LogSsPrec);
std::string filePath = LogRootPath + "\\" + datetimeY + "\\" + datetimeYM + "\\";
std::string filename;
el::Configurations defaultConf;
defaultConf.setToDefault();
//Utilisation recommandéesetGlobally
defaultConf.setGlobally(el::ConfigurationType::Format, "%datetime %msg");
defaultConf.setGlobally(el::ConfigurationType::Enabled, "true");
defaultConf.setGlobally(el::ConfigurationType::ToFile, "true");
defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
defaultConf.setGlobally(el::ConfigurationType::SubsecondPrecision, "6");
defaultConf.setGlobally(el::ConfigurationType::PerformanceTracking, "true");
defaultConf.setGlobally(el::ConfigurationType::LogFlushThreshold, "1");
// Limiter la configuration de la taille du fichier
//defaultConf.setGlobally(el::ConfigurationType::MaxLogFileSize, "2097152");
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Global)+".log";
defaultConf.set(el::Level::Global, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Debug) + ".log";
defaultConf.set(el::Level::Debug, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Error) + ".log";
defaultConf.set(el::Level::Error, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Fatal) + ".log";
defaultConf.set(el::Level::Fatal, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Info) + ".log";
defaultConf.set(el::Level::Info, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Trace) + ".log";
defaultConf.set(el::Level::Trace, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Warning) + ".log";
defaultConf.set(el::Level::Warning, el::ConfigurationType::Filename, filePath + filename);
el::Loggers::reconfigureLogger("default", defaultConf);
// Limiter la taille du fichier activer
//el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
}

Si vous voulez que chaque module fonctionnel du logiciel génère son propre journal , Vous pouvez vous référer au code ci - dessus pour l'implémenter vous - même , Notez les deux points suivants lors de la mise en œuvre :

  • Utiliser“%Y%M” Lors de la configuration du chemin de fichier ,Easylogging++ Seul le premier formateur sera reconnu ,Par exemple:“\%datetime{%Y%M}\%datetime{%Y%M}” Le chemin généré est “\202108\%datetime{%Y%M}”.
  • Easylogging++ L'ajout d'un niveau de log dans un nom de fichier n'est pas actuellement pris en charge , Besoin de se réaliser soi - même ,Par exemple:“\%datetime{%Y%M}%level.log” Le chemin généré est “\202108%level.log”.

Ces questions peuvent être évitées comme je l'ai fait ci - dessus , Ou modifier le code source pour corriger , Les parties modifiées du code source seront placées à la fin de l'article .

Journal de défilement du temps

Easylogging++ Il n'y a pas de fonction pour faire défiler les journaux dans le temps , Cette fonction doit vérifier l'heure actuelle et décider si un nouveau fichier journal doit être généré ( Le nom du fichier doit contenir des informations temporelles ), Il n'y a que deux questions clés :

  • Vérifiez l'heure : Sélectionnez vérifier une fois avant chaque écriture de journal , Il est donc nécessaire de surveiller l'écriture de chaque journal .
  • Générer un nouveau fichier journal : Appelez directement “ConfigureLogger()” La méthode écrase la configuration du Journal .

Note:: Si vous utilisez un minuteur pour vérifier l'heure actuelle , Le fichier journal n'a pas pu être mis à jour en temps opportun lorsque l'heure du système a été modifiée .

Pour surveiller l'écriture de chaque journal, il faut mettre en oeuvre un héritage LogDispatchCallbackLa classe de,Les codes sont les suivants::

class LogDispatcher : public el::LogDispatchCallback
{
protected:
void handle(const el::LogDispatchData* data) noexcept override {
m_data = data;
// Utilisez le générateur de journaux par défaut de l'enregistreur pour programmer
dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(),
m_data->dispatchAction() == el::base::DispatchAction::NormalLog));
// Vous pouvez également écrire dans la base de données ici
}
private:
const el::LogDispatchData* m_data;
void dispatch(el::base::type::string_t&& logLine) noexcept
{
el::base::SubsecondPrecision ssPrec(3);
static std::string now = el::base::utils::DateTime::getDateTime("%Y%M%d", &ssPrec);
if (now != LoggerToday)
{
LoggerToday= now;
ConfigureLogger();
}
}
};

LogDispatcherEst utilisé comme suit::

el::Helpers::installLogDispatchCallback<LogDispatcher>("LogDispatcher");
LogDispatcher* dispatcher = el::Helpers::logDispatchCallback<LogDispatcher>("LogDispatcher");
dispatcher->setEnabled(true);

Supprimer automatiquement le journal

La dernière modification sous le dossier supprimer automatiquement les journaux a eu lieu le n Le journal de l'autre jour ,Les codes sont les suivants::

// Supprimer sous le chemin du fichier n Le fichier journal de l'autre jour , Les dossiers vides dus à la suppression des fichiers journaux sont supprimés la prochaine fois
//isRootPourtrueHeure, Seuls les sous - dossiers vides seront nettoyés
void DeleteOldFiles(std::string path, int oldDays, bool isRoot)
{
// Basé sur la date actuelle du système actuel /Temps
time_t nowTime = time(0);
//Gestion de fichiers
intptr_t hFile = 0;
//Informations sur le document
struct _finddata_t fileinfo;
// Extension de fichier
std::string extName = ".log";
std::string str;
// Est - ce un dossier vide
bool isEmptyFolder = true;
if ((hFile = _findfirst(str.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//Si c'est un catalogue, Itératif
//Si ce n'est pas le cas,, Vérifier les fichiers
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
{
isEmptyFolder = false;
DeleteOldFiles(str.assign(path).append("\\").append(fileinfo.name), oldDays, false);
}
}
else
{
isEmptyFolder = false;
str.assign(fileinfo.name);
if ((str.size() >= extName.size()) && (str.substr(str.size() - extName.size()) == extName))
{
// C'est un fichier journal
if ((nowTime - fileinfo.time_write) / (24 * 3600) > oldDays)
{
str.assign(path).append("\\").append(fileinfo.name);
system(("attrib -H -R " + str).c_str());
system(("del/q " + str).c_str());
}
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
if (isEmptyFolder && (!isRoot))
{
system(("attrib -H -R " + path).c_str());
system(("rd/q " + path).c_str());
}
}
}

L'opération de suppression à l'intérieur est réalisée en appelant la commande Batch , Il y a une commande par lots complète sur le Web pour supprimer automatiquement les fichiers périmés , Mais je n'ai jamais réussi .
La méthode delete peut être appelée chaque jour lors de la création d'un nouveau fichier journal , La suppression d'un fichier peut prendre un certain temps , Il vaut mieux rouvrir un fil ,Les codes sont les suivants::

static int LogCleanDays = 30;
std::thread task(el::DeleteOldFiles, LogRootPath, LogCleanDays, true);

Encapsulé dans un fichier d'en - tête

Le code ci - dessus est assez fragmenté , Vous pouvez tout mettre sur “easylogginghelper.h” Dans le fichier d'en - tête , Ensuite, dans le projet, il est fait référence à . Le fichier d'en - tête fournit une fonction d'initialisation “InitEasylogging()” Pour initialiser toutes les configurations , Le Code du fichier d'en - tête est le suivant :

#pragma once
#ifndef EASYLOGGINGHELPER_H
#define EASYLOGGINGHELPER_H
#include "easylogging++.h"
#include <io.h>
#include <thread>
INITIALIZE_EASYLOGGINGPP
namespace el
{
static int LogCleanDays = 30;
static std::string LogRootPath = "D:\\Log";
static el::base::SubsecondPrecision LogSsPrec(3);
static std::string LoggerToday = el::base::utils::DateTime::getDateTime("%Y%M%d", &LogSsPrec);
// Supprimer sous le chemin du fichier n Le fichier journal de l'autre jour , Les dossiers vides dus à la suppression des fichiers journaux sont supprimés la prochaine fois
//isRootPourtrueHeure, Seuls les sous - dossiers vides seront nettoyés
void DeleteOldFiles(std::string path, int oldDays, bool isRoot)
{
// Basé sur la date actuelle du système actuel /Temps
time_t nowTime = time(0);
//Gestion de fichiers
intptr_t hFile = 0;
//Informations sur le document
struct _finddata_t fileinfo;
// Extension de fichier
std::string extName = ".log";
std::string str;
// Est - ce un dossier vide
bool isEmptyFolder = true;
if ((hFile = _findfirst(str.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
{
do
{
//Si c'est un catalogue, Itératif
//Si ce n'est pas le cas,, Vérifier les fichiers
if ((fileinfo.attrib & _A_SUBDIR))
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
{
isEmptyFolder = false;
DeleteOldFiles(str.assign(path).append("\\").append(fileinfo.name), oldDays, false);
}
}
else
{
isEmptyFolder = false;
str.assign(fileinfo.name);
if ((str.size() > extName.size()) && (str.substr(str.size() - extName.size()) == extName))
{
// C'est un fichier journal
if ((nowTime - fileinfo.time_write) / (24 * 3600) > oldDays)
{
str.assign(path).append("\\").append(fileinfo.name);
system(("attrib -H -R " + str).c_str());
system(("del/q " + str).c_str());
}
}
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile);
if (isEmptyFolder && (!isRoot))
{
system(("attrib -H -R " + path).c_str());
system(("rd/q " + path).c_str());
}
}
}
static void ConfigureLogger()
{
std::string datetimeY = el::base::utils::DateTime::getDateTime("%Y", &LogSsPrec);
std::string datetimeYM = el::base::utils::DateTime::getDateTime("%Y%M", &LogSsPrec);
std::string datetimeYMd = el::base::utils::DateTime::getDateTime("%Y%M%d", &LogSsPrec);
std::string filePath = LogRootPath + "\\" + datetimeY + "\\" + datetimeYM + "\\";
std::string filename;
el::Configurations defaultConf;
defaultConf.setToDefault();
//Utilisation recommandéesetGlobally
defaultConf.setGlobally(el::ConfigurationType::Format, "%datetime %msg");
defaultConf.setGlobally(el::ConfigurationType::Enabled, "true");
defaultConf.setGlobally(el::ConfigurationType::ToFile, "true");
defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "true");
defaultConf.setGlobally(el::ConfigurationType::SubsecondPrecision, "6");
defaultConf.setGlobally(el::ConfigurationType::PerformanceTracking, "true");
defaultConf.setGlobally(el::ConfigurationType::LogFlushThreshold, "1");
// Limiter la configuration de la taille du fichier
//defaultConf.setGlobally(el::ConfigurationType::MaxLogFileSize, "2097152");
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Global)+".log";
defaultConf.set(el::Level::Global, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Debug) + ".log";
defaultConf.set(el::Level::Debug, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Error) + ".log";
defaultConf.set(el::Level::Error, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Fatal) + ".log";
defaultConf.set(el::Level::Fatal, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Info) + ".log";
defaultConf.set(el::Level::Info, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Trace) + ".log";
defaultConf.set(el::Level::Trace, el::ConfigurationType::Filename, filePath + filename);
filename = datetimeYMd + "_" + el::LevelHelper::convertToString(el::Level::Warning) + ".log";
defaultConf.set(el::Level::Warning, el::ConfigurationType::Filename, filePath + filename);
el::Loggers::reconfigureLogger("default", defaultConf);
// Limiter la taille du fichier activer
//el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
}
class LogDispatcher : public el::LogDispatchCallback
{
protected:
void handle(const el::LogDispatchData* data) noexcept override {
m_data = data;
// Utilisez le générateur de journaux par défaut de l'enregistreur pour programmer
dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(),
m_data->dispatchAction() == el::base::DispatchAction::NormalLog));
// Vous pouvez également écrire dans la base de données ici
}
private:
const el::LogDispatchData* m_data;
void dispatch(el::base::type::string_t&& logLine) noexcept
{
el::base::SubsecondPrecision ssPrec(3);
static std::string now = el::base::utils::DateTime::getDateTime("%Y%M%d", &ssPrec);
if (now != LoggerToday)
{
LoggerToday = now;
ConfigureLogger();
std::thread task(el::DeleteOldFiles, LogRootPath, LogCleanDays, true);
}
}
};
static void InitEasylogging()
{
ConfigureLogger();
el::Helpers::installLogDispatchCallback<LogDispatcher>("LogDispatcher");
LogDispatcher* dispatcher = el::Helpers::logDispatchCallback<LogDispatcher>("LogDispatcher");
dispatcher->setEnabled(true);
}
}
#endif

Vous n'avez besoin d'appeler qu'une seule fois “el::InitEasylogging();”C'est tout.,Les codes sont les suivants::

#include "easylogging++.h"
#include "easylogginghelper.h"
int main()
{
el::InitEasylogging();
for (size_t i = 0; i < 10000; i++)
{
LOG(TRACE) << "***** trace log *****" << i;
LOG(DEBUG) << "***** debug log *****" << i;
LOG(ERROR) << "***** error log *****" << i;
LOG(WARNING) << "***** warning log *****" << i;
LOG(INFO) << "***** info log *****" << i;
// Ne pas utiliser facilement , Le programme va sortir
//LOG(FATAL) << "***** fatal log *****" << i;
Sleep(100);
}
}

Optimisation du code source(Non recommandé)

Ça dit:Easylogging++ Seul le premier caractère de format temporel est reconnu et le caractère de format hiérarchique n'est pas reconnu ,Il suffit de modifierTypedConfigurations::resolveFilename L'implémentation de la fonction est juste ,Les codes sont les suivants::

std::string TypedConfigurations::resolveFilename(Level level,const std::string& filename)
{
std::string resultingFilename = filename;
std::size_t dateIndex = std::string::npos;
std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename);
//ifLire comme suit:while
while ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) {
while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) {
dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1);
}
if (dateIndex != std::string::npos) {
const char* ptr = resultingFilename.c_str() + dateIndex;
// Goto end of specifier
ptr += dateTimeFormatSpecifierStr.size();
std::string fmt;
if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) {
// User has provided format for date/time
++ptr;
int count = 1; // Start by 1 in order to remove starting brace
std::stringstream ss;
for (; *ptr; ++ptr, ++count) {
if (*ptr == '}') {
++count; // In order to remove ending brace
break;
}
ss << *ptr;
}
// Commentez cette déclaration
//resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count);
fmt = ss.str();
} else {
fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename);
}
base::SubsecondPrecision ssPrec(3);
std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec);
base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename
base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr + "{"+ fmt+"}", now);
}
}
// Niveau de remplacement
base::utils::Str::replaceAll(resultingFilename, base::consts::kSeverityLevelFormatSpecifier, LevelHelper::convertToString(level));
base::utils::Str::replaceAll(resultingFilename, base::consts::kSeverityLevelShortFormatSpecifier, LevelHelper::convertToShortString(level));
return resultingFilename;
}

ModifierTypedConfigurations::resolveFilename Quand la fonction est implémentée , .N'oubliez pas de modifier la définition dans le fichier d'en - tête et tous les appels à cette fonction . La modification directe du code source n'est pas recommandée , Modifier le code source n'est pas bon pour les mises à jour de version ultérieures .

Annexe

Copyright:Cet article est[Temps écoulé]Établi,Veuillez apporter le lien original pour réimprimer,remercier。 https://fra.fheadline.com/2021/08/20210819235101238M.html