sjunges
9 years ago
19 changed files with 1124 additions and 142 deletions
-
10CMakeLists.txt
-
13resources/3rdparty/CMakeLists.txt
-
21resources/3rdparty/l3pp/LICENSE
-
107resources/3rdparty/l3pp/Readme.md
-
158resources/3rdparty/l3pp/formatter.h
-
106resources/3rdparty/l3pp/impl/formatter.h
-
123resources/3rdparty/l3pp/impl/logger.h
-
32resources/3rdparty/l3pp/impl/logging.h
-
18resources/3rdparty/l3pp/impl/sink.h
-
165resources/3rdparty/l3pp/l3pp.h
-
179resources/3rdparty/l3pp/logger.h
-
108resources/3rdparty/l3pp/sink.h
-
2src/builder/ExplicitDFTModelBuilder.cpp
-
7src/cli/cli.cpp
-
2src/settings/modules/DebugSettings.cpp
-
33src/utility/initialize.cpp
-
7src/utility/initialize.h
-
23src/utility/logging.h
-
122src/utility/macros.h
@ -0,0 +1,21 @@ |
|||||
|
The MIT License (MIT) |
||||
|
|
||||
|
Copyright (c) 2016 nafur |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
of this software and associated documentation files (the "Software"), to deal |
||||
|
in the Software without restriction, including without limitation the rights |
||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
copies of the Software, and to permit persons to whom the Software is |
||||
|
furnished to do so, subject to the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be included in all |
||||
|
copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
SOFTWARE. |
@ -0,0 +1,107 @@ |
|||||
|
L3++: Lightweight Logging Library for C++ |
||||
|
===== |
||||
|
|
||||
|
L3++ is a self-contained, single-header, cross-platform logging library for C++. |
||||
|
|
||||
|
The main goals for this library are simplicity, modularity and ease of use. |
||||
|
This library is released under the MIT License. |
||||
|
|
||||
|
Copyright (C) 2015 Gereon Kremer |
||||
|
|
||||
|
|
||||
|
Concepts |
||||
|
===== |
||||
|
|
||||
|
L3++ is based on the following conceptual components: |
||||
|
|
||||
|
* RecordInfo: A record info stores auxiliary information of a log message like the filename, line number and function name where the log message was emitted. |
||||
|
* Logger: A logger categorizes log messages, usually according to logical components or modules in the source code. |
||||
|
* Sink: A sink represents a logging output, for example the terminal or a log file. |
||||
|
* Formatter: A formatter is associated with a sink and converts a log message into an actual string. |
||||
|
|
||||
|
|
||||
|
Log levels |
||||
|
----- |
||||
|
The following log levels exist (from `l3pp::LogLevel`): |
||||
|
* ALL |
||||
|
* TRACE |
||||
|
* DEBUG |
||||
|
* INFO |
||||
|
* WARN |
||||
|
* ERR |
||||
|
* FATAL |
||||
|
* OFF |
||||
|
|
||||
|
Hierarchical Loggers |
||||
|
----- |
||||
|
Loggers are hierarchically structured strings like `"app.module.submodule"`. In this example, `submodule` is considered a sublogger of `module` and `app` the parent of `module`. A sublogger implicitly inherits all sinks and the log level of the parent, unless explicitly configured otherwise. |
||||
|
|
||||
|
The hierarchical tree of loggers always contains the root logger at the top. The root logger itself does not have a parent, nor a name. It can be accessed via `l3pp::Logging::getRootLogger()`. |
||||
|
|
||||
|
Each logger is assigned a log level, or is configured to inherit the log level of the parent (with the special log level `l3pp::LogLevel::INHERIT`). Note that the root logger cannot inherit a log level. Any log entry with a lower level is filtered out and will not be logged. |
||||
|
|
||||
|
A logger can also be assigned one or more sinks. By default, a logger will log to both the sinks of its parent, as well as its own sinks. Should a logger only use it own sinks, it should be set to non-additive (using `Logger::setAdditive(false)`). |
||||
|
|
||||
|
Sinks |
||||
|
----- |
||||
|
A sink provides an output for loggers. Loggers may define multiple sinks, and sinks may be shared between loggers. Sinks are associated with a formatter and a log level. The log level specifies the minimum level of a message for it to be output (independent of the log level of a logger), and by default permits all log messages. A formatter formats the log messages before being output. By default, a simple formatter is used which prints the log level, message and a newline, but other formatters can be specified. |
||||
|
|
||||
|
Formatters |
||||
|
----- |
||||
|
A formatter shapes a log message before being sent to its final destination. A non-configurable simple formatter exists, as well as a template-based formatter. The latter specifies the format of a message by means of its template arguments, see `l3pp::makeTemplateFormatter`. |
||||
|
|
||||
|
|
||||
|
Basic Usage |
||||
|
===== |
||||
|
|
||||
|
A logger object can be accessed via `l3pp::Logger::getLogger()` or `l3pp::Logger::getRootLogger()`. By default, the root logger does not output anywhere. Therefore, a sink should be added. An initial configuration may look like this: |
||||
|
|
||||
|
l3pp::Logger::initialize(); |
||||
|
l3pp::SinkPtr sink = log4carl::StreamSink::create(std::clog); |
||||
|
l3pp::Logger::getRootLogger()->addSink(sink); |
||||
|
l3pp::Logger::getRootLogger()->setLevel(log4carl::LogLevel::INFO); |
||||
|
|
||||
|
In this demo, a single sink is created that passes log messages to the standard logging stream `std::clog`. All messages must have at least level `LVL_INFO` before being printed. |
||||
|
|
||||
|
The actual logging is performed using a handful of macros. |
||||
|
These macros |
||||
|
|
||||
|
|
||||
|
Considerations |
||||
|
===== |
||||
|
|
||||
|
Performance |
||||
|
----- |
||||
|
While the use of hierarchical loggers and multiple sinks with associated formatters gives a lot of flexibility, it comes at a certain price. As the configuration is done at runtime (and may even change at runtime), the question whether a certain message is printed can only be answered at runtime. Therefore, every message, whether you will ever see it or not, has to pass through the logger and cost runtime. |
||||
|
|
||||
|
To mitigate this, we suggest the following: |
||||
|
|
||||
|
Create a preprocessor flag (like `ENABLE_LOGGING`) and define your own set of logging macros. |
||||
|
If this flag is defined, make your macros forward to the `L3PP_LOG_*` macros. |
||||
|
If this flag is not defined, make your macros do nothing. |
||||
|
|
||||
|
|
||||
|
Multiple usages in the same project |
||||
|
----- |
||||
|
Assume you have an application that uses L3++ for logging as well as some other library that also uses L3++. |
||||
|
L3++ will play nicely in this scenario (partly, it was designed for this case). |
||||
|
|
||||
|
However, you should take care of a few things: |
||||
|
* Colliding loggers: Prefix your loggers with some unique prefix. |
||||
|
* Colliding macros: If you implement the aforementioned `ENABLE_LOGGING` macro, prefix your macros with your project name. Otherwise, these macros will collide. |
||||
|
|
||||
|
|
||||
|
Implementation Details |
||||
|
===== |
||||
|
|
||||
|
Sinks |
||||
|
----- |
||||
|
A sink is a class that provides some `log` method. Any class that inherits from `l3pp::Sink` can be used. |
||||
|
|
||||
|
As of now, two implementations are available: |
||||
|
* FileSink: Writes to a output file. |
||||
|
* StreamSink: Writes to any given `std::ostream`, for example to `std::cout`. |
||||
|
|
||||
|
Formatters |
||||
|
----- |
||||
|
A formatter is a functor that given a log entry, provides a formatted string. The base class `Formatter` provides some very simple formatting, whereas `TemplateFormatter` provides more control over the shape. Internally, a `TemplateFormatter` streams its arguments to a stream before constructing the string. The special types `FieldStr` and `TimeStr` can be used to format particular attributes of a log entry. |
@ -0,0 +1,158 @@ |
|||||
|
/** |
||||
|
* @file formatter.h |
||||
|
* @author Harold Bruintjes <h.bruintjes@cs.rwth-aachen.de> |
||||
|
* |
||||
|
* Define the Formatter class, used to format output of a sink |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
#include <tuple> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
/** |
||||
|
* Formats a log messages. This is a base class that simply print the message |
||||
|
* with the log level prefix, see derived classes such as TemplatedFormatter |
||||
|
* for more interesting data. |
||||
|
*/ |
||||
|
class Formatter { |
||||
|
friend class Logger; |
||||
|
|
||||
|
static void initialize(); |
||||
|
|
||||
|
virtual std::string format(EntryContext const& context, std::string const& msg) const; |
||||
|
public: |
||||
|
virtual ~Formatter() {} |
||||
|
|
||||
|
std::string operator()(EntryContext const& context, std::string const& msg) { |
||||
|
return format(context, msg); |
||||
|
} |
||||
|
}; |
||||
|
typedef std::shared_ptr<Formatter> FormatterPtr; |
||||
|
|
||||
|
/** |
||||
|
* Possible fields for FieldStr instance |
||||
|
*/ |
||||
|
enum class Field { |
||||
|
/// Name of the file (everything following the last path separator) |
||||
|
FileName, |
||||
|
/// Full path of the file |
||||
|
FilePath, |
||||
|
/// Line number |
||||
|
Line, |
||||
|
/// Name of function currently executed |
||||
|
Function, |
||||
|
/// Name of the logger |
||||
|
LoggerName, |
||||
|
/// Message to be logged |
||||
|
Message, |
||||
|
/// Level of the log entry |
||||
|
LogLevel, |
||||
|
/// Number of milliseconds since the logger was initialized |
||||
|
WallTime, |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Controls justification of formatted log fields. |
||||
|
*/ |
||||
|
enum class Justification { |
||||
|
/// Left align field |
||||
|
LEFT, |
||||
|
/// Right align field |
||||
|
RIGHT |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* Formatter for log entry fields, with the exception of time stamp formatting |
||||
|
* (see TimeStr for that). The Field template argument determines which field |
||||
|
* is printed, see logging::Field. |
||||
|
* The other template arguments control the alignment of the output string. |
||||
|
*/ |
||||
|
template<Field field, int Width = 0, Justification j = Justification::RIGHT, char Fill = ' '> |
||||
|
class FieldStr { |
||||
|
public: |
||||
|
void stream(std::ostream& os, EntryContext const& context, std::string const& msg) const; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Formatter for log time stamps. The constructor expects a single string |
||||
|
* argument which is a formatter for the time stamp. For the specification of |
||||
|
* this format string see the documentation for std::put_time . You can use for |
||||
|
* example "%c" or "%T". |
||||
|
* The template arguments control the alignment of the output string. |
||||
|
*/ |
||||
|
class TimeStr { |
||||
|
std::string formatStr; |
||||
|
|
||||
|
public: |
||||
|
TimeStr(char const* format) : formatStr(format) { |
||||
|
} |
||||
|
TimeStr(std::string const& format) : formatStr(format) { |
||||
|
} |
||||
|
|
||||
|
void stream(std::ostream& os, EntryContext const& context, std::string const&) const; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Formatter which formats the output based on the (templated) arguments given. |
||||
|
* The arguments can be anything that implements the stream operator <<, but |
||||
|
* more interestingly also the various FormatField subclasses. These classes |
||||
|
* can output the various fields associated with a log entry. |
||||
|
*/ |
||||
|
template<typename ... Formatters> |
||||
|
class TemplateFormatter : public Formatter { |
||||
|
std::tuple<Formatters...> formatters; |
||||
|
|
||||
|
template <int N> |
||||
|
typename std::enable_if<N < (sizeof...(Formatters))>::type |
||||
|
formatTuple(EntryContext const& context, std::string const& msg, std::ostream& os) const { |
||||
|
formatElement(std::get<N>(formatters), os, context, msg); |
||||
|
formatTuple<N+1>(context, msg, os); |
||||
|
} |
||||
|
|
||||
|
template <int N> |
||||
|
typename std::enable_if<(N >= sizeof...(Formatters))>::type |
||||
|
formatTuple(EntryContext const&, std::string const&, std::ostream&) const { |
||||
|
} |
||||
|
|
||||
|
template<Field field, int Width, Justification j, char Fill> |
||||
|
void formatElement(FieldStr<field, Width, j, Fill> const& t, std::ostream& stream, EntryContext const& context, std::string const& msg) const { |
||||
|
t.stream(stream, context, msg); |
||||
|
} |
||||
|
|
||||
|
void formatElement(TimeStr const& t, std::ostream& stream, EntryContext const& context, std::string const& msg) const { |
||||
|
t.stream(stream, context, msg); |
||||
|
} |
||||
|
|
||||
|
template<typename T> |
||||
|
void formatElement(T const& t, std::ostream& stream, EntryContext const&, std::string const&) const { |
||||
|
stream << t; |
||||
|
} |
||||
|
public: |
||||
|
TemplateFormatter(Formatters ... formatters) : |
||||
|
formatters(std::forward<Formatters>(formatters)...) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
std::string format(EntryContext const& context, std::string const& msg) const override; |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Helper function to create a TemplateFormatter. Simply call with some |
||||
|
* formatable arguments, e.g., |
||||
|
* @code{.cpp} |
||||
|
* logging::makeTemplateFormatter( |
||||
|
* logging::FieldStr<logging::Field::LogLevel>(), " - ", |
||||
|
* logging::FieldStr<logging::Field::Message>(), "\n"); |
||||
|
* @endcode |
||||
|
*/ |
||||
|
template<typename ... Formatters> |
||||
|
FormatterPtr makeTemplateFormatter(Formatters&& ... formatters) { |
||||
|
return std::make_shared<TemplateFormatter<Formatters...>>(std::forward<Formatters>(formatters)...); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,106 @@ |
|||||
|
/** |
||||
|
* @file formatter.h |
||||
|
* @author Harold Bruintjes <h.bruintjes@cs.rwth-aachen.de> |
||||
|
* |
||||
|
* Implementation of Formatter classes |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include <ctime> |
||||
|
#include <iomanip> |
||||
|
#include <sstream> |
||||
|
#include <cstring> //strrchr |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
namespace detail { |
||||
|
/** |
||||
|
* Internal function to get wall-time |
||||
|
*/ |
||||
|
inline static std::chrono::system_clock::time_point GetStartTime() { |
||||
|
static std::chrono::system_clock::time_point startTime = std::chrono::system_clock::now(); |
||||
|
return startTime; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void Formatter::initialize() { |
||||
|
// Init wall-time |
||||
|
detail::GetStartTime(); |
||||
|
} |
||||
|
|
||||
|
inline std::string Formatter::format(EntryContext const& context, std::string const& msg) const { |
||||
|
std::stringstream stream; |
||||
|
stream << context.level << " - " << msg << '\n'; |
||||
|
return stream.str(); |
||||
|
} |
||||
|
|
||||
|
template<Field field, int Width, Justification j, char Fill> |
||||
|
inline void FieldStr<field, Width, j, Fill>::stream(std::ostream& os, EntryContext const& context, std::string const& msg) const { |
||||
|
os << std::setw(Width); |
||||
|
os << std::setfill(Fill); |
||||
|
switch(j) { |
||||
|
case Justification::LEFT: |
||||
|
os << std::left; |
||||
|
case Justification::RIGHT: |
||||
|
os << std::right; |
||||
|
} |
||||
|
|
||||
|
switch(field) { |
||||
|
case Field::FileName: |
||||
|
#ifdef _WIN32 |
||||
|
os << strrchr(context.filename, '\\')+1; |
||||
|
#else |
||||
|
os << strrchr(context.filename, '/')+1; |
||||
|
#endif |
||||
|
break; |
||||
|
case Field::FilePath: |
||||
|
os << context.filename; |
||||
|
break; |
||||
|
case Field::Line: |
||||
|
os << context.line; |
||||
|
break; |
||||
|
case Field::Function: |
||||
|
os << context.funcname; |
||||
|
break; |
||||
|
case Field::LoggerName: |
||||
|
os << context.logger->getName(); |
||||
|
break; |
||||
|
case Field::Message: |
||||
|
os << msg; |
||||
|
break; |
||||
|
case Field::LogLevel: |
||||
|
os << context.level; |
||||
|
break; |
||||
|
case Field::WallTime: |
||||
|
auto runtime = context.timestamp - detail::GetStartTime(); |
||||
|
os << std::chrono::duration_cast<std::chrono::milliseconds>(runtime).count(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void TimeStr::stream(std::ostream& os, EntryContext const& context, std::string const&) const { |
||||
|
auto time = std::chrono::system_clock::to_time_t(context.timestamp); |
||||
|
auto timeinfo = localtime (&time); |
||||
|
#if __GNUC__ >= 5 || __clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 7) || _MSC_VER >= 1700 |
||||
|
//TODO: Need better way to detect thing |
||||
|
os << std::put_time(timeinfo, formatStr.c_str()); |
||||
|
#else |
||||
|
char buffer[1024]; |
||||
|
if (strftime(buffer, 1024, formatStr.c_str(), timeinfo)) { |
||||
|
os << buffer; |
||||
|
} |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
template<typename ... Formatters> |
||||
|
inline std::string TemplateFormatter<Formatters...>::format(EntryContext const& context, std::string const& msg) const { |
||||
|
std::stringstream stream; |
||||
|
|
||||
|
formatTuple<0>(context, msg, stream); |
||||
|
|
||||
|
return stream.str(); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,123 @@ |
|||||
|
/** |
||||
|
* @file logger.h |
||||
|
* |
||||
|
* Defines the base Logger class |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <algorithm> |
||||
|
#include <map> |
||||
|
#include <sstream> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
namespace detail { |
||||
|
/** |
||||
|
* Internal function to get all configured loggers. Should not be used |
||||
|
* directly, see Logger::getLogger() |
||||
|
*/ |
||||
|
static inline std::map<std::string, LogPtr>& GetLoggers() { |
||||
|
static std::map<std::string, LogPtr> loggers; |
||||
|
return loggers; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline LogStream::~LogStream() { |
||||
|
if (level != LogLevel::OFF) { |
||||
|
logger.log(level, stream.str(), context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void Logger::logEntry(EntryContext const& context, std::string const& msg) { |
||||
|
for(auto& sink: sinks) { |
||||
|
sink->log(context, msg); |
||||
|
} |
||||
|
if (additive && parent) { |
||||
|
parent->logEntry(context, msg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void Logger::removeSink(SinkPtr sink) { |
||||
|
std::vector<SinkPtr>::iterator pos = std::find(sinks.begin(), sinks.end(), sink); |
||||
|
if (pos != sinks.end()) { |
||||
|
sinks.erase(pos); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void Logger::log(LogLevel level, std::string const& msg, EntryContext context) { |
||||
|
if (level < getLevel()) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
context.level = level; |
||||
|
context.logger = this; |
||||
|
logEntry(context, msg); |
||||
|
} |
||||
|
|
||||
|
inline LogStream Logger::log(LogLevel level, EntryContext context) { |
||||
|
if (level < getLevel()) { |
||||
|
// Effectively disables the stream |
||||
|
return LogStream(*this, LogLevel::OFF, context); |
||||
|
} else { |
||||
|
return LogStream(*this, level, context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
inline void Logger::initialize() { |
||||
|
// Setup root logger |
||||
|
getRootLogger(); |
||||
|
// Set wall time |
||||
|
Formatter::initialize(); |
||||
|
} |
||||
|
|
||||
|
inline void Logger::deinitialize() { |
||||
|
detail::GetLoggers().clear(); |
||||
|
getRootLogger()->sinks.clear(); |
||||
|
} |
||||
|
|
||||
|
inline LogPtr Logger::getRootLogger() { |
||||
|
static LogPtr rootLogger = LogPtr(new Logger()); |
||||
|
return rootLogger; |
||||
|
} |
||||
|
|
||||
|
inline LogPtr Logger::getLogger(std::string name) { |
||||
|
if (name.size() == 0) { |
||||
|
// Root logger |
||||
|
return getRootLogger(); |
||||
|
} |
||||
|
auto& loggers = detail::GetLoggers(); |
||||
|
auto it = loggers.find(name); |
||||
|
if (it != loggers.end()) { |
||||
|
return it->second; |
||||
|
} else { |
||||
|
auto n = name.rfind('.'); |
||||
|
LogPtr parent; |
||||
|
if (n == std::string::npos) { |
||||
|
parent = getRootLogger(); |
||||
|
} else{ |
||||
|
parent = getLogger(name.substr(0, n)); |
||||
|
} |
||||
|
LogPtr newLogger = LogPtr(new Logger(name, parent)); |
||||
|
loggers.emplace(name, newLogger); |
||||
|
return newLogger; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
template<typename T> |
||||
|
inline LogStream const& operator<<(LogStream const& stream, T const& val) { |
||||
|
if (stream.level != LogLevel::OFF) { |
||||
|
stream.stream << val; |
||||
|
} |
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
inline LogStream const& operator<<(LogStream const& stream, std::ostream& (*F)(std::ostream&)) { |
||||
|
if (stream.level != LogLevel::OFF) { |
||||
|
stream.stream << F; |
||||
|
} |
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* @file logging.h |
||||
|
* |
||||
|
* Implementation of general logging functionality |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <ostream> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
/** |
||||
|
* Streaming operator for LogLevel. |
||||
|
* @param os Output stream. |
||||
|
* @param level LogLevel. |
||||
|
* @return os. |
||||
|
*/ |
||||
|
inline std::ostream& operator<<(std::ostream& os, LogLevel level) { |
||||
|
switch (level) { |
||||
|
case LogLevel::TRACE: return os << "TRACE"; |
||||
|
case LogLevel::DEBUG: return os << "DEBUG"; |
||||
|
case LogLevel::INFO: return os << "INFO"; |
||||
|
case LogLevel::WARN: return os << "WARN"; |
||||
|
case LogLevel::ERR: return os << "ERROR"; |
||||
|
case LogLevel::FATAL: return os << "FATAL"; |
||||
|
case LogLevel::OFF: return os << "OFF"; |
||||
|
default: return os << "???"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
/** |
||||
|
* @file sink.h |
||||
|
* |
||||
|
* Implementation for Sinks |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
inline void Sink::log(EntryContext const& context, std::string const& message) const { |
||||
|
if (context.level >= this->level) { |
||||
|
logEntry((*formatter)(context, message)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,165 @@ |
|||||
|
/** |
||||
|
* @file l3pp.h |
||||
|
* @author Gereon Kremer <gereon.kremer@cs.rwth-aachen.de> |
||||
|
* @author Harold Bruintjes <h.bruintjes@cs.rwth-aachen.de> |
||||
|
* |
||||
|
* The lightweight logging library for C++. |
||||
|
* |
||||
|
* This logging facility is fairly generic and is used as a simple and |
||||
|
* header-only alternative to more advanced solutions like log4cplus or |
||||
|
* boost::log. |
||||
|
* |
||||
|
* The basic components are Sinks, Formatters and Loggers. |
||||
|
* |
||||
|
* A Sink represents a logging output like a terminal or a log file. |
||||
|
* This implementation provides a FileSink and a StreamSink, but the basic |
||||
|
* Sink class can be extended as necessary. |
||||
|
* |
||||
|
* A Formatter is associated with a Sink and produces the actual string that is |
||||
|
* sent to the Sink. |
||||
|
* Usually, it adds auxiliary information like the current time, LogLevel and |
||||
|
* source location to the string logged by the user. |
||||
|
* The Formatter implements a reasonable default behavior for simple logging, |
||||
|
* but it can be subclassed and modified as necessary. |
||||
|
* |
||||
|
* The Logger class finally plugs all these components together. |
||||
|
* Individual loggers can log to one or more sinks (inheritable) and are |
||||
|
* associated with an (inheritable) log level. |
||||
|
* |
||||
|
* Initial configuration may look like this: |
||||
|
* @code{.cpp} |
||||
|
* l3pp::Logger::initialize(); |
||||
|
* l3pp::SinkPtr sink = l3pp::StreamSink::create(std::clog); |
||||
|
* l3pp::Logger::getRootLogger()->addSink(sink); |
||||
|
* l3pp::Logger::getRootLogger()->setLevel(l3pp::LogLevel::INFO); |
||||
|
* @endcode |
||||
|
* |
||||
|
* Macro facilitate the usage: |
||||
|
* <ul> |
||||
|
* <li>`L3PP_LOG_<LVL>(logger, msg)` produces a normal log message where |
||||
|
* logger should be string identifying the logger (or a LogPtr) and msg is the |
||||
|
* message to be logged.</li> |
||||
|
* </ul> |
||||
|
* Any message (`msg`) can be an arbitrary expression that one would |
||||
|
* stream to an `std::ostream` like `stream << (msg);`. The default formatter |
||||
|
* adds newlines. |
||||
|
* Manipulators are generally supported. However, for performance avoid std::endl |
||||
|
* and use '\n' directly. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <chrono> |
||||
|
#include <memory> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
/** |
||||
|
* Indicated which log messages should be forwarded to some sink. |
||||
|
* |
||||
|
* All messages which have a level that is equal or greater than the specified |
||||
|
* value will be forwarded. |
||||
|
*/ |
||||
|
enum class LogLevel { |
||||
|
/// Log messages used for tracing the program flow in detail. |
||||
|
TRACE, |
||||
|
/// Log messages used for debugging. |
||||
|
DEBUG, |
||||
|
/// Log messages used for information. |
||||
|
INFO, |
||||
|
/// Log messages used to warn about an undesired state. |
||||
|
WARN, |
||||
|
/// Log messages used for errors that can be handled. |
||||
|
ERR, |
||||
|
/// Log messages used for errors that lead to program termination. |
||||
|
FATAL, |
||||
|
/// Log no messages. |
||||
|
OFF, |
||||
|
/// Parent level |
||||
|
INHERIT, |
||||
|
/// Default log level. |
||||
|
DEFAULT = WARN, |
||||
|
/// All log messages. |
||||
|
ALL = TRACE |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Streaming operator for LogLevel. |
||||
|
* @param os Output stream. |
||||
|
* @param level LogLevel. |
||||
|
* @return os. |
||||
|
*/ |
||||
|
inline std::ostream& operator<<(std::ostream& os, LogLevel level); |
||||
|
|
||||
|
class Logger; |
||||
|
|
||||
|
/** |
||||
|
* Contextual information for a new log entry, contains such this as location, |
||||
|
* log info (level, logger) and the time of the event. |
||||
|
* A context will be created automatically by using the macros |
||||
|
*/ |
||||
|
struct EntryContext { |
||||
|
// Program location |
||||
|
const char* filename; |
||||
|
size_t line; |
||||
|
const char* funcname; |
||||
|
|
||||
|
// Time of entry |
||||
|
std::chrono::system_clock::time_point timestamp; |
||||
|
|
||||
|
// Log event info |
||||
|
Logger const* logger; |
||||
|
LogLevel level; |
||||
|
|
||||
|
EntryContext(const char* filename, size_t line, const char* funcname) : |
||||
|
filename(filename), line(line), funcname(funcname), |
||||
|
timestamp(std::chrono::system_clock::now()), logger(nullptr), |
||||
|
level(LogLevel::OFF) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
EntryContext() : |
||||
|
filename(""), line(0), funcname(""), |
||||
|
timestamp(std::chrono::system_clock::now()), logger(nullptr), |
||||
|
level(LogLevel::OFF) |
||||
|
{ |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#include "formatter.h" |
||||
|
#include "sink.h" |
||||
|
#include "logger.h" |
||||
|
|
||||
|
#include "impl/logging.h" |
||||
|
#include "impl/logger.h" |
||||
|
#include "impl/formatter.h" |
||||
|
#include "impl/sink.h" |
||||
|
|
||||
|
#ifdef _MSC_VER |
||||
|
#define __func__ __FUNCTION__ |
||||
|
#endif |
||||
|
|
||||
|
/// Create a record info. |
||||
|
#define __L3PP_LOG_RECORD l3pp::EntryContext(__FILE__, __LINE__, __func__) |
||||
|
/// Basic logging macro. |
||||
|
#define __L3PP_LOG(level, channel, expr) do { \ |
||||
|
auto L3PP_channel = ::l3pp::Logger::getLogger(channel); \ |
||||
|
if (L3PP_channel->getLevel() <= level) { \ |
||||
|
L3PP_channel->log(level, __L3PP_LOG_RECORD) << expr; \ |
||||
|
} \ |
||||
|
} while(false) |
||||
|
|
||||
|
/// Log with level TRACE. |
||||
|
#define L3PP_LOG_TRACE(channel, expr) __L3PP_LOG(::l3pp::LogLevel::TRACE, channel, expr) |
||||
|
/// Log with level DEBUG. |
||||
|
#define L3PP_LOG_DEBUG(channel, expr) __L3PP_LOG(::l3pp::LogLevel::DEBUG, channel, expr) |
||||
|
/// Log with level INFO. |
||||
|
#define L3PP_LOG_INFO(channel, expr) __L3PP_LOG(::l3pp::LogLevel::INFO, channel, expr) |
||||
|
/// Log with level WARN. |
||||
|
#define L3PP_LOG_WARN(channel, expr) __L3PP_LOG(::l3pp::LogLevel::WARN, channel, expr) |
||||
|
/// Log with level ERROR. |
||||
|
#define L3PP_LOG_ERROR(channel, expr) __L3PP_LOG(::l3pp::LogLevel::ERR, channel, expr) |
||||
|
/// Log with level FATAL. |
||||
|
#define L3PP_LOG_FATAL(channel, expr) __L3PP_LOG(::l3pp::LogLevel::FATAL, channel, expr) |
@ -0,0 +1,179 @@ |
|||||
|
/** |
||||
|
* @file logger.h |
||||
|
* |
||||
|
* Defines the base Logger class |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <sstream> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
/** |
||||
|
* LogStream is a logger object that can be streamed into, writing an entry |
||||
|
* to the logger associated upon destruction. Instances of this classer are |
||||
|
* returned by Logger log() functions, so they can be used as such: |
||||
|
* logger->debug() << "Message"; |
||||
|
*/ |
||||
|
class LogStream { |
||||
|
friend class Logger; |
||||
|
|
||||
|
Logger& logger; |
||||
|
LogLevel level; |
||||
|
EntryContext context; |
||||
|
mutable std::ostringstream stream; |
||||
|
|
||||
|
LogStream(Logger& logger, LogLevel level, EntryContext context) : |
||||
|
logger(logger), level(level), context(context) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
LogStream(const LogStream&) = delete; |
||||
|
LogStream& operator=(const LogStream&) = delete; |
||||
|
public: |
||||
|
LogStream(LogStream&& other) : |
||||
|
logger(other.logger), level(other.level), context(std::move(other.context))/*, |
||||
|
stream(std::move(other.stream))*/ |
||||
|
{ |
||||
|
stream.str(other.stream.str()); |
||||
|
} |
||||
|
~LogStream(); |
||||
|
|
||||
|
template<typename T> |
||||
|
friend LogStream const& operator<<(LogStream const& stream, T const& val); |
||||
|
friend LogStream const& operator<<(LogStream const& stream, std::ostream& (*F)(std::ostream&)); |
||||
|
}; |
||||
|
|
||||
|
/** |
||||
|
* Main logger class. Keeps track of all Logger instances, and can be used to |
||||
|
* log various messages. Before the logging library is used, make sure to |
||||
|
* call Logger::initialize(). Loggers are hierarchically nested, by means of |
||||
|
* names separated by a period. All loggers are a (indirect) child of the root |
||||
|
* logger, see Logger::getRootLogger() and Logger::getLogger(). |
||||
|
* A logger is associated with a LogLevel. Any entry with a level below this |
||||
|
* level will be filtered out. A LogLevel of INHERIT means the parent log |
||||
|
* level will be compared against instead. |
||||
|
* A logger can be associated with 1 or more Sinks. A log entry is printed to |
||||
|
* each associated sink. If the Logger is set additive (see getAdditive(), |
||||
|
* setAdditive()) parent sinks are logged to as well (by default true). |
||||
|
* Logging can be performed either as a single string message, or by using a |
||||
|
* stream. The latter requires the end() method to be called before the entry |
||||
|
* is logged. For convenience, various logging macros are defined at the end |
||||
|
* of this header. |
||||
|
*/ |
||||
|
class Logger { |
||||
|
friend class Formatter; |
||||
|
|
||||
|
typedef std::shared_ptr<Logger> LogPtr; |
||||
|
|
||||
|
LogPtr parent; |
||||
|
std::string name; |
||||
|
LogLevel level; |
||||
|
std::vector<SinkPtr> sinks; |
||||
|
bool additive; |
||||
|
|
||||
|
// Logger constructors are private |
||||
|
Logger() : parent(nullptr), name(""), level(LogLevel::DEFAULT), |
||||
|
additive(true) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
Logger(std::string const& name, LogPtr parent) : parent(parent), name(name), |
||||
|
level(LogLevel::INHERIT), additive(true) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
void logEntry(EntryContext const& context, std::string const& msg); |
||||
|
|
||||
|
public: |
||||
|
void addSink(SinkPtr sink) { |
||||
|
sinks.push_back(sink); |
||||
|
} |
||||
|
|
||||
|
void removeSink(SinkPtr sink); |
||||
|
|
||||
|
void setLevel(LogLevel level) { |
||||
|
if (level == LogLevel::INHERIT && !parent) { |
||||
|
return; |
||||
|
} |
||||
|
this->level = level; |
||||
|
} |
||||
|
|
||||
|
LogLevel getLevel() const { |
||||
|
if (level == LogLevel::INHERIT) { |
||||
|
return parent->getLevel(); |
||||
|
} |
||||
|
return level; |
||||
|
} |
||||
|
|
||||
|
std::string const& getName() const { |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
bool getAdditive() const { |
||||
|
return additive; |
||||
|
} |
||||
|
|
||||
|
void setAdditive(bool additive) { |
||||
|
this->additive = additive; |
||||
|
} |
||||
|
|
||||
|
void log(LogLevel level, std::string const& msg, EntryContext context = EntryContext()); |
||||
|
|
||||
|
void trace(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::TRACE, msg, context); |
||||
|
} |
||||
|
void debug(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::DEBUG, msg, context); |
||||
|
} |
||||
|
void info(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::INFO, msg, context); |
||||
|
} |
||||
|
void warn(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::WARN, msg, context); |
||||
|
} |
||||
|
void error(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::ERR, msg, context); |
||||
|
} |
||||
|
void fatal(std::string const& msg, EntryContext context = EntryContext()) { |
||||
|
log(LogLevel::FATAL, msg, context); |
||||
|
} |
||||
|
|
||||
|
LogStream log(LogLevel level, EntryContext context = EntryContext()); |
||||
|
|
||||
|
LogStream trace(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::TRACE, context); |
||||
|
} |
||||
|
LogStream debug(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::DEBUG, context); |
||||
|
} |
||||
|
LogStream info(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::INFO, context); |
||||
|
} |
||||
|
LogStream warn(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::WARN, context); |
||||
|
} |
||||
|
LogStream error(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::ERR, context); |
||||
|
} |
||||
|
LogStream fatal(EntryContext context = EntryContext()) { |
||||
|
return log(LogLevel::FATAL, context); |
||||
|
} |
||||
|
|
||||
|
static void initialize(); |
||||
|
static void deinitialize(); |
||||
|
|
||||
|
static LogPtr getRootLogger(); |
||||
|
|
||||
|
static LogPtr getLogger(LogPtr logger) { |
||||
|
return logger; |
||||
|
} |
||||
|
|
||||
|
static LogPtr getLogger(std::string name); |
||||
|
}; |
||||
|
typedef std::shared_ptr<Logger> LogPtr; |
||||
|
|
||||
|
} |
@ -0,0 +1,108 @@ |
|||||
|
/** |
||||
|
* @file sink.h |
||||
|
* |
||||
|
* Defines classes for log sinks (i.e. outputs) |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <ostream> |
||||
|
#include <fstream> |
||||
|
|
||||
|
namespace l3pp { |
||||
|
|
||||
|
/** |
||||
|
* Base class for a logging sink. It can only log some log entry to which some |
||||
|
* formatting is applied (see Formatter). A Sink may be given a log level, |
||||
|
* which filters out all entries below that level. By default is logs all |
||||
|
* entries. |
||||
|
*/ |
||||
|
class Sink { |
||||
|
LogLevel level; |
||||
|
FormatterPtr formatter; |
||||
|
|
||||
|
virtual void logEntry(std::string const& entry) const = 0; |
||||
|
public: |
||||
|
Sink() : level(LogLevel::ALL), formatter(std::make_shared<Formatter>()) { |
||||
|
|
||||
|
} |
||||
|
Sink(FormatterPtr formatter) : level(LogLevel::ALL), formatter(formatter) { |
||||
|
|
||||
|
} |
||||
|
/** |
||||
|
* Default destructor. |
||||
|
*/ |
||||
|
virtual ~Sink() {} |
||||
|
|
||||
|
LogLevel getLevel() const { |
||||
|
return level; |
||||
|
} |
||||
|
|
||||
|
void setLevel(LogLevel level) { |
||||
|
this->level = level; |
||||
|
} |
||||
|
|
||||
|
FormatterPtr getFormatter() const { |
||||
|
return formatter; |
||||
|
} |
||||
|
|
||||
|
void setFormatter(FormatterPtr formatter) { |
||||
|
this->formatter = formatter; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Logs the given message with context info |
||||
|
*/ |
||||
|
void log(EntryContext const& context, std::string const& message) const; |
||||
|
}; |
||||
|
typedef std::shared_ptr<Sink> SinkPtr; |
||||
|
|
||||
|
/** |
||||
|
* Logging sink that wraps an arbitrary `std::ostream`. |
||||
|
* It is meant to be used for streams like `std::cout` or `std::cerr`. |
||||
|
*/ |
||||
|
class StreamSink: public Sink { |
||||
|
/// Output stream. |
||||
|
mutable std::ostream os; |
||||
|
|
||||
|
void logEntry(std::string const& entry) const override { |
||||
|
os << entry << std::flush; |
||||
|
} |
||||
|
|
||||
|
explicit StreamSink(std::ostream& _os) : |
||||
|
os(_os.rdbuf()) {} |
||||
|
public: |
||||
|
/** |
||||
|
* Create a StreamSink from some output stream. |
||||
|
* @param os Output stream. |
||||
|
*/ |
||||
|
static SinkPtr create(std::ostream& os) { |
||||
|
return SinkPtr(new StreamSink(os)); |
||||
|
} |
||||
|
}; |
||||
|
/** |
||||
|
* Logging sink for file output. |
||||
|
*/ |
||||
|
class FileSink: public Sink { |
||||
|
/// File output stream. |
||||
|
mutable std::ofstream os; |
||||
|
|
||||
|
void logEntry(std::string const& entry) const override { |
||||
|
os << entry << std::flush; |
||||
|
} |
||||
|
|
||||
|
explicit FileSink(const std::string& filename) : |
||||
|
os(filename, std::ios::out) {} |
||||
|
public: |
||||
|
/** |
||||
|
* Create a FileSink that logs to the specified file. |
||||
|
* The file is truncated upon construction. |
||||
|
* @param filename |
||||
|
*/ |
||||
|
static SinkPtr create(const std::string& filename) { |
||||
|
return SinkPtr(new FileSink(filename)); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,23 @@ |
|||||
|
#ifndef STORM_UTILITY_LOGGING_H_ |
||||
|
#define STORM_UTILITY_LOGGING_H_ |
||||
|
|
||||
|
#include <l3pp.h> |
||||
|
|
||||
|
#if !defined(STORM_LOG_DISABLE_DEBUG) && !defined(STORM_LOG_DISABLE_TRACE) |
||||
|
#define STORM_LOG_TRACE(message) L3PP_LOG_TRACE(l3pp::Logger::getRootLogger(), message) |
||||
|
#else |
||||
|
#define STORM_LOG_TRACE(message) (void)(0) |
||||
|
#endif |
||||
|
|
||||
|
#if !defined(STORM_LOG_DISABLE_DEBUG) |
||||
|
#define STORM_LOG_DEBUG(message) L3PP_LOG_DEBUG(l3pp::Logger::getRootLogger(), message) |
||||
|
#else |
||||
|
#define STORM_LOG_DEBUG(message) (void)(0) |
||||
|
#endif |
||||
|
|
||||
|
// Define STORM_LOG_WARN, STORM_LOG_ERROR and STORM_LOG_INFO to log the given message with the corresponding log levels. |
||||
|
#define STORM_LOG_INFO(message) L3PP_LOG_INFO(l3pp::Logger::getRootLogger(), message) |
||||
|
#define STORM_LOG_WARN(message) L3PP_LOG_WARN(l3pp::Logger::getRootLogger(), message) |
||||
|
#define STORM_LOG_ERROR(message) L3PP_LOG_ERROR(l3pp::Logger::getRootLogger(), message) |
||||
|
|
||||
|
#endif /* STORM_UTILITY_LOGGING_H_ */ |
Write
Preview
Loading…
Cancel
Save
Reference in new issue