Small logger class [closed]
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
开发者_运维知识库Closed 6 months ago.
The community reviewed whether to reopen this question 6 months ago and left it closed:
Improve this questionOriginal close reason(s) were not resolved
I am looking for a small lightweight logging system in c++. I have found some existing frameworks but I don't need all of their features at this point in time. I primarily am looking for a small system that can for example configure the log level output file. I am looking for an existing solution as I don't want to reinvent the wheel.
I strongly recommend this simple logging system: http://www.drdobbs.com/cpp/201804215. It is composed of a single header file. I have successfully used it on Linux, Windows and Mac OS X.
You write to the log like this:
FILE_LOG(logWARNING) << "Ops, variable x should be " << expectedX << "; is " << realX;
I really like the stream syntax. It is unobtrusive, typesafe and expressive. The logging framework automatically adds a \n
at the end of the line, plus date, time and indentation.
Configuring the logs is pretty easy:
FILELog::ReportingLevel() = logDEBUG3;
FILE* log_fd = fopen( "mylogfile.txt", "w" );
Output2FILE::Stream() = log_fd;
This framework is also easy to extend. At work, we have recently made some adaptations to it so that it now uses an std::ofstream
instead of a FILE*
. As a result, we are now able to add nice features such as encrypting the logs, by chaining the streams.
For anyone wanting a simple solution, I recommend: easylogging++
Single header only C++ logging library. It is extremely light-weight, robust, fast performing, thread and type safe and consists of many built-in features. It provides ability to write logs in your own customized format. It also provide support for logging your classes, third-party libraries, STL and third-party containers etc.
This library has everything built-in to prevent usage of external libraries.
Simple example: (more advanced examples available on the link above).
#include "easylogging++.h"
INITIALIZE_EASYLOGGINGPP
int main(int argv, char* argc[]) {
LOG(INFO) << "My first info log using default logger";
return 0;
}
Example output inside a class:
2015-08-28 10:38:45,900 DEBUG [default] [user@localhost] [Config::Config(const string)] [src/Config.cpp:7] Reading config file: 'config.json'
I tried log4cpp and boost::log but they are not as easy as this one.
EXTRA CONTENT: Minimal version - LOG header
I created a small code for even simpler applications based on easylogging but requires no initialization (be aware that it is probably not thread safe). Here is the code:
/*
* File: Log.h
* Author: Alberto Lepe <dev@alepe.com>
*
* Created on December 1, 2015, 6:00 PM
*/
#ifndef LOG_H
#define LOG_H
#include <iostream>
using namespace std;
enum typelog {
DEBUG,
INFO,
WARN,
ERROR
};
struct structlog {
bool headers = false;
typelog level = WARN;
};
extern structlog LOGCFG;
class LOG {
public:
LOG() {}
LOG(typelog type) {
msglevel = type;
if(LOGCFG.headers) {
operator << ("["+getLabel(type)+"]");
}
}
~LOG() {
if(opened) {
cout << endl;
}
opened = false;
}
template<class T>
LOG &operator<<(const T &msg) {
if(msglevel >= LOGCFG.level) {
cout << msg;
opened = true;
}
return *this;
}
private:
bool opened = false;
typelog msglevel = DEBUG;
inline string getLabel(typelog type) {
string label;
switch(type) {
case DEBUG: label = "DEBUG"; break;
case INFO: label = "INFO "; break;
case WARN: label = "WARN "; break;
case ERROR: label = "ERROR"; break;
}
return label;
}
};
#endif /* LOG_H */
Usage:
#include "Log.h"
int main(int argc, char** argv) {
//Config: -----(optional)----
structlog LOGCFG = {};
LOGCFG.headers = false;
LOGCFG.level = DEBUG;
//---------------------------
LOG(INFO) << "Main executed with " << (argc - 1) << " arguments";
}
This code print the message using "cout", but you can change it to use "cerr" or append a file, etc. I hope its useful to someone. (Note: I'm not C++ expert in any way, so this code may explode in extreme cases).
I recommend to try plog library (I'm the author). It's about 1000 lines of code, header only and easy to use:
#include <plog/Log.h>
int main()
{
plog::init(plog::debug, "Sample.log");
LOGD << "Hello log!";
LOGD_IF(true) << "conditional logging";
return 0;
}
all of the mentioned loggers so far make use of macros for logging calls. To me, that is so ugly, I don't care about what performance boost that gives, I won't go near it.
https://github.com/gabime/spdlog is what I like. Clean syntax, handles all typical usages. Fast and small. e.g. for a file logger it is:
auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic.txt");
my_logger->info("Some log message");
This question has my attempt with some fanciness. It is completely Standard C++ and makes no platform assumptions whatsoever. It basically consists of a temporary object used like this:
Debug(5) << "This is level 5 debug info.\n";
I'm sure you can figure out how to specify different files and other stuff when you have the basic layout. I tried to keep the class structured so that in a release build, every form of Debug output is removed as good as possible.
Mind you: if you specify a filename each time you construct it, and open the file and close it again, performance will suffer. In the case of multiple output files, it would certainly be best to have several static data members that open the different files when the program is run or if they are opened for the first time.
If you don't have size limitations on the project and you expect it to live a long time, I would suggest looking at Apache Log4cxx. It's not a small library, but it supports just about everything you ever wanted (including some things you didn't even knew you wanted) in logging, and it's portable.
In any larger project sooner or later you'll want your logging solution to do more than a "small logger class", so indeed why reinvent the wheel.
An update to Dr. Dobb's "A Lightweight Logger for C++": There are actually a couple of loggers referred to in Dr. Dobb's. The first one Logging In C++ which is listed in one of the answers. I tried to use this one but the source is no longer available on the Dr. Dobb's site.
The second one that worked for me and that I recommend is A Lightweight Logger for C++ by Filip Janiszewski working at Nokia Siemens Networks. At first I had some problems getting this code to run so as I was searching for solutions, I ran across an update by the original author at: GitHub: fjanisze/logger. I found this code to be easy to understand, modify, and to use. It is thread safe and works with Visual Studio with Windows.
Another logger mentioned above is easylogging++ . When I first tried this one it looked promising. But when I added threading and sockets2 under Windows, it crashed. I did have the defines set for threading and Sock2 but I still couldn't get it to work so I can't recommend this one. The source code is also very complex so I had no chance to modify and fix it within a reasonable amount of time.
The above answers are all great.
Doubt anyone will ever see this answer, but this is what I use https://github.com/asn10038/Cpp_Logger
Easy to set up after a configuration of 4-5 variable names in the .h file, and implemented without non standard dependencies.
Not header only but could be pretty easily.
Maybe this helps someone.
I, as well as many others, also answered this question with some code.
This isn't really "ready" in all ways, but it could be easily modified:
#pragma once
#include <codecvt>
#include <condition_variable>
#include <fstream>
#include <iostream>
#include <locale>
#include <memory>
#include <mutex>
#include <ostream>
#include <queue>
#include <sstream>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
#include <string.h>
#define LOGL(level, msg) \
if (Loggy::isLevel(level)) { \
Loggy::writer(level, __FILE__, __LINE__) << msg; \
Loggy::queue(); \
}
#define LOG_FLUSH() \
{ \
Loggy::wait_queues(); \
}
#define LOGT(msg) LOGL(Loggy::LTRACE, msg)
#define LOGD(msg) LOGL(Loggy::LDEBUG, msg)
#define LOGI(msg) LOGL(Loggy::LINFO, msg)
#define LOGE(msg) LOGL(Loggy::LERROR, msg)
namespace Loggy {
using namespace std;
constexpr int DEFAULT_BUF_CNT = 1000;
constexpr const char *DEFAULT_TIME_FMT = "%Y%m%d.%H%M%S";
constexpr double DROP_NOTIFY_SECONDS = 5.0;
constexpr double FLUSH_SECONDS = 1.0;
enum {
LINVALID = 0,
LTRACE = 9,
LDEBUG = 10,
LINFO = 20,
LERROR = 40,
LWARN = 30,
LCRITICAL = 50,
LMAX = 50,
};
unordered_map<int, string> levelNames_ = {
{ LINVALID, "INVALID" },
{ LTRACE, "TRACE" },
{ LDEBUG, "DEBUG" },
{ LINFO, "INFO" },
{ LERROR, "ERROR" },
{ LWARN, "WARN" },
{ LCRITICAL, "CRITICAL" },
};
wstring str2w(const string &in)
{
#ifdef _WIN32
if (in.empty())
return std::wstring();
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.size(), NULL, 0);
std::wstring wstrTo(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.size(), &wstrTo[0], size_needed);
return wstrTo;
#else
thread_local std::wstring_convert<std::codecvt_utf8<wchar_t>> wcu16;
return wcu16.from_bytes(in);
#endif
}
string w2str(const wstring &in)
{
#ifdef _WIN32
if (in.empty())
return std::string();
int size_needed
= WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.size(), NULL, 0, NULL, NULL);
std::string strTo(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.size(), &strTo[0], size_needed, NULL, NULL);
return strTo;
#else
thread_local std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wcu8;
return wcu8.to_bytes( in );
#endif
}
template <class T> class SafeQueue {
public:
SafeQueue(void)
: q()
, m()
, c()
, x()
{
}
~SafeQueue(void) { lock_guard<mutex> lock(m); }
// Add an element to the queue.
void push(T t)
{
lock_guard<mutex> lock(m);
q.push(t);
c.notify_one();
}
// Get the "front"-element.
// If the queue is empty, wait till a element is avaiable.
T pop(void)
{
unique_lock<mutex> lock(m);
while (!x && q.empty()) {
// release lock as long as the wait and reaquire it afterwards.
c.wait(lock);
}
if (x) {
return T();
};
T val = q.front();
q.pop();
if (q.empty()) {
c.notify_all();
}
return val;
}
size_t size() { return q.size(); }
void join(void)
{
unique_lock<mutex> lock(m);
while (!q.empty()) {
c.wait(lock);
}
}
size_t drain(void)
{
unique_lock<mutex> lock(m);
std::queue<T> empty;
swap(q, empty);
c.notify_all();
return empty.size();
}
size_t quit()
{
x = true;
return drain();
}
private:
queue<T> q;
mutable mutex m;
condition_variable c;
bool x;
};
static string timestamp(const char format[], const time_t &rawtime)
{
struct tm timeinfo;
char buffer[120];
#ifdef _WIN32
localtime_s(&timeinfo, &rawtime);
#else
localtime_r(&rawtime, &timeinfo);
#endif
strftime(buffer, sizeof(buffer), format, &timeinfo);
return string(buffer);
}
#ifdef _WIN32
#define _LOGGY_CVT_FILENAME(s) s
#else
#define _LOGGY_CVT_FILENAME(s) Loggy::w2str(s)
#endif
class Output {
SafeQueue<wstring> queue_; // this should be first
wofstream fstream_;
wostream &wstream_;
size_t max_;
int level_;
size_t dropped_ = 0;
bool alive_ = true;
time_t firstDrop_ = 0;
std::thread thread_; // this must be last
public:
Output(wostream &s, int level, int max)
: wstream_(s)
, level_(level)
, max_(max)
, thread_(&Output::worker, this)
{
}
Output(const wstring &s, int level, size_t max)
: fstream_(_LOGGY_CVT_FILENAME(s), std::wofstream::out | std::wofstream::app)
, wstream_(fstream_)
, level_(level)
, max_(max)
, thread_(&Output::worker, this)
{
}
~Output()
{
alive_ = false;
dropped_ += queue_.quit();
if (dropped_) {
logDropped();
}
thread_.join();
}
void wait() { queue_.join(); wstream_.flush(); }
void logDropped()
{
wstringstream ws;
time_t t;
time(&t);
ws << Loggy::timestamp(DEFAULT_TIME_FMT, t).c_str();
ws << " dropped " << dropped_ << " entries";
queue_.push(ws.str());
dropped_ = 0;
}
void add(wstring &str, time_t &t)
{
if (alive_) {
if (max_ == 0 || queue_.size() < max_) {
queue_.push(str);
} else {
++dropped_;
if (dropped_ == 1) {
firstDrop_ = t;
} else if (difftime(t, firstDrop_) > DROP_NOTIFY_SECONDS) {
logDropped();
}
}
}
}
void worker()
{
int written = 0;
time_t lastFlush = 0;
while (alive_) {
if (!queue_.size() && written > 0) {
time_t t;
time(&t);
if (difftime(t, lastFlush) > FLUSH_SECONDS) {
wstream_.flush();
lastFlush = t;
written = 0;
}
}
auto t = queue_.pop();
if (alive_) {
wstream_ << t << std::endl;
written += 1;
}
}
}
};
class Log {
public:
~Log() { resetOutput(); };
int level_ = LINFO;
int trigFrom_ = LINVALID;
int trigTo_ = LINVALID;
int trigCnt_ = LINVALID;
string timeFormat_ = DEFAULT_TIME_FMT;
mutex mutex_;
deque<Output> outputs_;
Output default_output_;
vector<wstring> buffer_;
Log()
: default_output_(wcout, LINFO, 1) {};
bool isLevel(int level) { return level >= level_; }
void resetOutput()
{
lock_guard<mutex> lock(mutex_);
outputs_.clear();
}
void addOutput(const wstring &path, int level, int bufferSize)
{
lock_guard<mutex> lock(mutex_);
outputs_.emplace_back(path, level, bufferSize);
}
void addOutput(wostream &stream, int level, int bufferSize)
{
lock_guard<mutex> lock(mutex_);
outputs_.emplace_back(stream, level, bufferSize);
}
std::vector<const char *> getFiles()
{
std::vector<const char *> ret;
return ret;
}
void setTrigger(int levelFrom, int levelTo, int lookbackCount)
{
trigFrom_ = levelFrom;
trigTo_ = levelTo;
trigCnt_ = lookbackCount;
}
void setLevel(int level) { level_ = level; }
struct LastLog {
wstringstream ws;
time_t tm = 0;
};
static LastLog &lastLog()
{
thread_local LastLog ll_;
return ll_;
}
static const char *basename(const char *file)
{
const char *b = strrchr(file, '\\');
if (!b)
b = strrchr(file, '/');
return b ? b + 1 : file;
}
static const char *levelname(int level) { return levelNames_[level].c_str(); }
wostream &writer(int level, const char *file, int line)
{
auto &ll = lastLog();
time(&ll.tm);
ll.ws.clear();
ll.ws.str(L"");
return ll.ws << timestamp(timeFormat_.c_str(), ll.tm).c_str() << " " << basename(file)
<< ":" << line << " " << levelname(level) << " ";
}
void queue()
{
lock_guard<mutex> lock(mutex_);
auto &ll = lastLog();
auto s = ll.ws.str();
if (outputs_.empty()) {
default_output_.add(s, ll.tm);
} else {
for (auto &out : outputs_) {
out.add(s, ll.tm);
}
}
}
void wait_queues()
{
if (outputs_.empty()) {
default_output_.wait();
} else {
for (auto &out : outputs_) {
out.wait();
}
}
}
};
static Log &getInstance()
{
static Log l;
return l;
}
void resetOutput() { getInstance().resetOutput(); }
void addOutput(const wstring &path, int level = LDEBUG, int bufferSize = DEFAULT_BUF_CNT)
{
getInstance().addOutput(path, level, bufferSize);
}
void addOutput(wostream &stream, int level = LDEBUG, int bufferSize = DEFAULT_BUF_CNT)
{
getInstance().addOutput(stream, level, bufferSize);
}
void setTrigger(int levelFrom, int levelTo, int lookbackCount)
{
getInstance().setTrigger(levelFrom, levelTo, lookbackCount);
}
std::vector<const char *> getFiles() { return getInstance().getFiles(); }
void setLevel(int level) { getInstance().setLevel(level); }
bool isLevel(int level) { return getInstance().isLevel(level); }
wostream &writer(int level, const char *file, int line)
{
return getInstance().writer(level, file, line);
}
void queue() { getInstance().queue(); }
void wait_queues() { getInstance().wait_queues(); }
} // end namespace Loggy
Features:
- writing to the log doesn't block on i/o
- similar macros to other solutions (
LOGE(blah << stream)
) - prefers discarding log entries to slowing down
- lazy flushing
- header only, very small, stl classes only
- tested on osx/win/nix
- time format is configurable
Missing stuff:
- easy, flexible log formatting (predefining a macro would be fine)
- triggers have an interface but don't work yet
- microseconds aren't working yet
If anyone actually likes this solution in any way, lmk and I'll make a real repo out of it with tests, etc. It's pretty fast. Probably not as fast as speedlogger (a heavier feature complete library), but not sure.
Original gist: https://gist.github.com/earonesty/977b14c93358fe9b9ee674baac5d42d7
i created a small logging class, cuz i had issues to include other examples in VSCode while compiling, here for a one file header:
#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <ctime>
#include <sstream>
using namespace std;
class logging
{
private:
ofstream myfile;
std::string get_time()
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream oss;
//2047-03-11 20:18:26
oss << std::put_time(&tm, "%Y-%m-%d-%H:%M:%S");
auto str = oss.str();
return str;
}
public:
logging(string filepath)
{
myfile.open (filepath);
}
~logging()
{
myfile.close();
}
void write(string line)
{
myfile << get_time() << " " << line <<std::endl;
}
};
精彩评论