diff --git a/CMakeLists.txt b/CMakeLists.txt index 6775e3fff..846980de3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,16 @@ message(STATUS "StoRM - Using Compiler Configuration: ${STORM_COMPILED_BY}") # in the the system does not have a library add_subdirectory(resources/3rdparty) +############################################################# +## +## l3pp +## +############################################################# + +# l3pp is set up as external project +include_directories(${l3pp_INCLUDE}) +add_dependencies(resources l3pp) + ############################################################# ## ## gmm diff --git a/resources/3rdparty/CMakeLists.txt b/resources/3rdparty/CMakeLists.txt index 9b30445f3..88263eaad 100644 --- a/resources/3rdparty/CMakeLists.txt +++ b/resources/3rdparty/CMakeLists.txt @@ -81,3 +81,16 @@ set(GTEST_INCLUDE_DIR ${source_dir}/include PARENT_SCOPE) # Specify MainTest's link libraries ExternalProject_Get_Property(googletest binary_dir) set(GTEST_LIBRARIES ${binary_dir}/libgtest.a ${binary_dir}/libgtest_main.a PARENT_SCOPE) + +ExternalProject_Add( + l3pp + GIT_REPOSITORY https://github.com/hbruintjes/l3pp.git + GIT_TAG master + SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/l3pp + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_INSTALL ON +) +ExternalProject_Get_Property(l3pp source_dir) +set(l3pp_INCLUDE "${source_dir}/" PARENT_SCOPE) diff --git a/resources/3rdparty/l3pp/LICENSE b/resources/3rdparty/l3pp/LICENSE new file mode 100644 index 000000000..16ace88d6 --- /dev/null +++ b/resources/3rdparty/l3pp/LICENSE @@ -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. diff --git a/resources/3rdparty/l3pp/Readme.md b/resources/3rdparty/l3pp/Readme.md new file mode 100644 index 000000000..b0c4cadd7 --- /dev/null +++ b/resources/3rdparty/l3pp/Readme.md @@ -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. diff --git a/resources/3rdparty/l3pp/formatter.h b/resources/3rdparty/l3pp/formatter.h new file mode 100644 index 000000000..846a8d5e2 --- /dev/null +++ b/resources/3rdparty/l3pp/formatter.h @@ -0,0 +1,158 @@ +/** + * @file formatter.h + * @author Harold Bruintjes + * + * Define the Formatter class, used to format output of a sink + */ + +#pragma once + +#include +#include + +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 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 +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 +class TemplateFormatter : public Formatter { + std::tuple formatters; + + template + typename std::enable_if::type + formatTuple(EntryContext const& context, std::string const& msg, std::ostream& os) const { + formatElement(std::get(formatters), os, context, msg); + formatTuple(context, msg, os); + } + + template + typename std::enable_if<(N >= sizeof...(Formatters))>::type + formatTuple(EntryContext const&, std::string const&, std::ostream&) const { + } + + template + void formatElement(FieldStr 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 + void formatElement(T const& t, std::ostream& stream, EntryContext const&, std::string const&) const { + stream << t; + } +public: + TemplateFormatter(Formatters ... formatters) : + formatters(std::forward(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::FieldStr(), "\n"); + * @endcode + */ +template +FormatterPtr makeTemplateFormatter(Formatters&& ... formatters) { + return std::make_shared>(std::forward(formatters)...); +} + +} diff --git a/resources/3rdparty/l3pp/impl/formatter.h b/resources/3rdparty/l3pp/impl/formatter.h new file mode 100644 index 000000000..c4e29d18a --- /dev/null +++ b/resources/3rdparty/l3pp/impl/formatter.h @@ -0,0 +1,106 @@ +/** + * @file formatter.h + * @author Harold Bruintjes + * + * Implementation of Formatter classes + */ + +#pragma once + +#include +#include +#include +#include +#include //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 +inline void FieldStr::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(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 +inline std::string TemplateFormatter::format(EntryContext const& context, std::string const& msg) const { + std::stringstream stream; + + formatTuple<0>(context, msg, stream); + + return stream.str(); +} + +} diff --git a/resources/3rdparty/l3pp/impl/logger.h b/resources/3rdparty/l3pp/impl/logger.h new file mode 100644 index 000000000..1b14e8bdb --- /dev/null +++ b/resources/3rdparty/l3pp/impl/logger.h @@ -0,0 +1,123 @@ +/** + * @file logger.h + * + * Defines the base Logger class + */ + +#pragma once + +#include +#include +#include +#include + +namespace l3pp { + +namespace detail { + /** + * Internal function to get all configured loggers. Should not be used + * directly, see Logger::getLogger() + */ + static inline std::map& GetLoggers() { + static std::map 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::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 +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; +} + +} diff --git a/resources/3rdparty/l3pp/impl/logging.h b/resources/3rdparty/l3pp/impl/logging.h new file mode 100644 index 000000000..290317478 --- /dev/null +++ b/resources/3rdparty/l3pp/impl/logging.h @@ -0,0 +1,32 @@ +/** + * @file logging.h + * + * Implementation of general logging functionality + */ + +#pragma once + +#include + +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 << "???"; + } +} + +} diff --git a/resources/3rdparty/l3pp/impl/sink.h b/resources/3rdparty/l3pp/impl/sink.h new file mode 100644 index 000000000..19a22bca9 --- /dev/null +++ b/resources/3rdparty/l3pp/impl/sink.h @@ -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)); + } +} + +} + diff --git a/resources/3rdparty/l3pp/l3pp.h b/resources/3rdparty/l3pp/l3pp.h new file mode 100644 index 000000000..ea44a45ec --- /dev/null +++ b/resources/3rdparty/l3pp/l3pp.h @@ -0,0 +1,165 @@ +/** + * @file l3pp.h + * @author Gereon Kremer + * @author Harold Bruintjes + * + * 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: + *
    + *
  • `L3PP_LOG_(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.
  • + *
+ * 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 +#include + +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) diff --git a/resources/3rdparty/l3pp/logger.h b/resources/3rdparty/l3pp/logger.h new file mode 100644 index 000000000..6dab1cbcb --- /dev/null +++ b/resources/3rdparty/l3pp/logger.h @@ -0,0 +1,179 @@ +/** + * @file logger.h + * + * Defines the base Logger class + */ + +#pragma once + +#include +#include + +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 + 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 LogPtr; + + LogPtr parent; + std::string name; + LogLevel level; + std::vector 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 LogPtr; + +} diff --git a/resources/3rdparty/l3pp/sink.h b/resources/3rdparty/l3pp/sink.h new file mode 100644 index 000000000..9b381ffb7 --- /dev/null +++ b/resources/3rdparty/l3pp/sink.h @@ -0,0 +1,108 @@ +/** + * @file sink.h + * + * Defines classes for log sinks (i.e. outputs) + */ + +#pragma once + +#include +#include + +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()) { + + } + 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 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)); + } +}; + +} + diff --git a/src/builder/ExplicitDFTModelBuilder.cpp b/src/builder/ExplicitDFTModelBuilder.cpp index 4fb6131df..3f0682e54 100644 --- a/src/builder/ExplicitDFTModelBuilder.cpp +++ b/src/builder/ExplicitDFTModelBuilder.cpp @@ -86,7 +86,7 @@ namespace storm { } else { STORM_LOG_TRACE("Transition matrix: too big to print"); } - STORM_LOG_TRACE("Exit rates: " << modelComponents.exitRates); + STORM_LOG_TRACE("Exit rates: " << storm::utility::vector::toString(modelComponents.exitRates)); STORM_LOG_TRACE("Markovian states: " << modelComponents.markovianStates); // Build state labeling diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index c9e88410d..0c2e80f53 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -186,14 +186,13 @@ namespace storm { } if (storm::settings::getModule().isVerboseSet()) { - STORM_GLOBAL_LOGLEVEL_INFO(); + storm::utility::setLogLevel(l3pp::LogLevel::INFO); } if (storm::settings::getModule().isDebugSet()) { - STORM_GLOBAL_LOGLEVEL_DEBUG(); - + storm::utility::setLogLevel(l3pp::LogLevel::DEBUG); } if (storm::settings::getModule().isTraceSet()) { - STORM_GLOBAL_LOGLEVEL_TRACE(); + storm::utility::setLogLevel(l3pp::LogLevel::TRACE); } if (storm::settings::getModule().isLogfileSet()) { storm::utility::initializeFileLogging(); diff --git a/src/settings/modules/DebugSettings.cpp b/src/settings/modules/DebugSettings.cpp index 0f37e7ce7..92e8de9a9 100644 --- a/src/settings/modules/DebugSettings.cpp +++ b/src/settings/modules/DebugSettings.cpp @@ -38,7 +38,7 @@ namespace storm { } std::string DebugSettings::getLogfilename() const { - return this->getOption(traceOptionName).getArgumentByName("filename").getValueAsString(); + return this->getOption(logfileOptionName).getArgumentByName("filename").getValueAsString(); } bool DebugSettings::isTestSet() const { @@ -47,4 +47,4 @@ namespace storm { } // namespace modules } // namespace settings -} // namespace storm \ No newline at end of file +} // namespace storm diff --git a/src/utility/initialize.cpp b/src/utility/initialize.cpp index ac139d1c9..273d0dee0 100644 --- a/src/utility/initialize.cpp +++ b/src/utility/initialize.cpp @@ -1,30 +1,55 @@ #include "initialize.h" -#include "src/utility/macros.h" #include "src/settings/SettingsManager.h" #include "src/settings/modules/DebugSettings.h" -int storm_runtime_loglevel = STORM_LOGLEVEL_WARN; +#include +#include namespace storm { namespace utility { - + void initializeLogger() { - // Intentionally left empty. + l3pp::Logger::initialize(); + // By default output to std::cout + l3pp::SinkPtr sink = l3pp::StreamSink::create(std::cout); + l3pp::Logger::getRootLogger()->addSink(sink); + // Default to warn, set by user to something else + l3pp::Logger::getRootLogger()->setLevel(l3pp::LogLevel::WARN); + + l3pp::FormatterPtr fptr = l3pp::makeTemplateFormatter( + l3pp::FieldStr(), + " (", l3pp::FieldStr(), ':', l3pp::FieldStr(), "): ", + l3pp::FieldStr(), '\n' + ); + sink->setFormatter(fptr); } - + void setUp() { initializeLogger(); std::cout.precision(10); } - + void cleanUp() { // Intentionally left empty. } - + + void setLogLevel(l3pp::LogLevel level) { + l3pp::Logger::getRootLogger()->setLevel(level); + if (level <= l3pp::LogLevel::DEBUG) { +#if STORM_LOG_DISABLE_DEBUG + std::cout << "***** warning ***** requested loglevel is not compiled\n"; +#endif + } + } + void initializeFileLogging() { - // FIXME. + if (storm::settings::getModule().isLogfileSet()) { + std::string logFileName = storm::settings::getModule().getLogfilename(); + l3pp::SinkPtr sink = l3pp::FileSink::create(logFileName); + l3pp::Logger::getRootLogger()->addSink(sink); + } } - + } } diff --git a/src/utility/initialize.h b/src/utility/initialize.h index 6241add70..48e4855c9 100644 --- a/src/utility/initialize.h +++ b/src/utility/initialize.h @@ -1,6 +1,8 @@ #ifndef STORM_UTILITY_INITIALIZE_H #define STORM_UTILITY_INITIALIZE_H +#include "src/utility/logging.h" + namespace storm { namespace utility { /*! @@ -17,6 +19,11 @@ namespace storm { */ void cleanUp(); + /*! + * Set the global log level + */ + void setLogLevel(l3pp::LogLevel level); + /*! * Sets up the logging to file. */ diff --git a/src/utility/logging.h b/src/utility/logging.h new file mode 100644 index 000000000..2fc58a008 --- /dev/null +++ b/src/utility/logging.h @@ -0,0 +1,23 @@ +#ifndef STORM_UTILITY_LOGGING_H_ +#define STORM_UTILITY_LOGGING_H_ + +#include + +#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_ */ diff --git a/src/utility/macros.h b/src/utility/macros.h index b72cf5f26..2d49be58b 100644 --- a/src/utility/macros.h +++ b/src/utility/macros.h @@ -1,128 +1,47 @@ #ifndef STORM_UTILITY_MACROS_H_ #define STORM_UTILITY_MACROS_H_ -#include -#include - -#include "storm-config.h" +#include "src/utility/logging.h" #include -#include - -extern int storm_runtime_loglevel; - -#define STORM_LOGLEVEL_ERROR 0 -#define STORM_LOGLEVEL_WARN 1 -#define STORM_LOGLEVEL_INFO 2 -#define STORM_LOGLEVEL_DEBUG 3 -#define STORM_LOGLEVEL_TRACE 4 - -#ifdef STORM_LOG_DISABLE_DEBUG -#define STORM_LOG_DISABLE_TRACE -#endif - - -#define __SHORT_FORM_OF_FILE__ \ -(strrchr(__FILE__,'/') \ -? strrchr(__FILE__,'/')+1 \ -: __FILE__ \ -) - -#ifndef STORM_LOG_DISABLE_DEBUG -#define STORM_LOG_DEBUG(message) \ -do { \ - if(storm_runtime_loglevel >= STORM_LOGLEVEL_DEBUG) { \ - std::stringstream __ss; \ - __ss << message; \ - std::cout << "DEBUG (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << __ss.str() << std::endl; \ - } \ -} while (false) -#else -#define STORM_LOG_DEBUG(message) \ -do { \ -} while (false) -#endif - -#ifndef STORM_LOG_DISABLE_TRACE -#define STORM_LOG_TRACE(message) \ -do { \ - if(storm_runtime_loglevel >= STORM_LOGLEVEL_TRACE) { \ - std::stringstream __ss; \ - __ss << message; \ - std::cout << "TRACE (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << __ss.str() << std::endl; \ - } \ -} while(false) -#else -#define STORM_LOG_TRACE(message) \ -do { \ -} while (false) -#endif - +#include // Define STORM_LOG_ASSERT which is only checked when NDEBUG is not set. #ifndef NDEBUG #define STORM_LOG_ASSERT(cond, message) \ do { \ -if (!(cond)) { \ -std::cout << "ASSERT FAILED (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << message << std::endl; \ -assert(cond); \ -} \ -} while (false) + if (!(cond)) { \ + STORM_LOG_ERROR(message); \ + assert(cond); \ + } \ +} while (false) #else -#define STORM_LOG_ASSERT(cond, message) +#define STORM_LOG_ASSERT(cond, message) #endif + // Define STORM_LOG_THROW to always throw the exception with the given message if the condition fails to hold. #define STORM_LOG_THROW(cond, exception, message) \ do { \ if (!(cond)) { \ - std::cout << "ERROR (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << message << std::endl; \ + STORM_LOG_ERROR(message); \ throw exception() << message; \ } \ -} while (false) - - -// Define STORM_LOG_WARN, STORM_LOG_ERROR and STORM_LOG_INFO to log the given message with the corresponding log levels. -#define STORM_LOG_WARN(message) \ -do { \ - if(storm_runtime_loglevel >= STORM_LOGLEVEL_WARN) { \ - std::stringstream __ss; \ - __ss << message; \ - std::cout << "WARN (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << __ss.str() << std::endl; \ - } \ -} while (false) +} while (false) #define STORM_LOG_WARN_COND(cond, message) \ do { \ if (!(cond)) { \ - STORM_LOG_WARN(message); \ - } \ -} while (false) - -#define STORM_LOG_INFO(message) \ -do { \ - if(storm_runtime_loglevel >= STORM_LOGLEVEL_INFO) { \ - std::stringstream __ss; \ - __ss << message; \ - std::cout << "INFO (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << __ss.str() << std::endl; \ + STORM_LOG_WARN(message); \ } \ -} while (false) +} while (false) #define STORM_LOG_INFO_COND(cond, message) \ do { \ if (!(cond)) { \ STORM_LOG_INFO(message); \ } \ -} while (false) - -#define STORM_LOG_ERROR(message) \ -do { \ - if(storm_runtime_loglevel >= STORM_LOGLEVEL_ERROR) { \ - std::stringstream __ss; \ - __ss << message; \ - std::cout << "ERROR (" << __SHORT_FORM_OF_FILE__ << ":" << __LINE__ << "): " << __ss.str() << std::endl; \ - } \ -} while (false) \ +} while (false) #define STORM_LOG_ERROR_COND(cond, message) \ do { \ @@ -131,37 +50,6 @@ do { \ } \ } while (false) \ -#define STORM_GLOBAL_LOGLEVEL_ERROR() \ -do { \ -storm_runtime_loglevel = STORM_LOGLEVEL_ERROR; \ -} while(false) - -#define STORM_GLOBAL_LOGLEVEL_INFO() \ -do { \ -storm_runtime_loglevel = STORM_LOGLEVEL_INFO; \ -} while (false) - -#ifndef STORM_LOG_DISABLE_DEBUG -#define STORM_GLOBAL_LOGLEVEL_DEBUG() \ -do { \ -storm_runtime_loglevel = STORM_LOGLEVEL_DEBUG; \ -} while(false) -#else -#define STORM_GLOBAL_LOGLEVEL_DEBUG() \ -std::cout << "***** warning ***** loglevel debug is not compiled\n" -#endif - -#ifndef STORM_LOG_DISABLE_TRACE -#define STORM_GLOBAL_LOGLEVEL_TRACE() \ -do { \ -storm_runtime_loglevel = STORM_LOGLEVEL_TRACE; \ -} while(false) -#else -#define STORM_GLOBAL_LOGLEVEL_TRACE() \ -std::cout << "***** warning ***** loglevel trace is not compiled\n" -#endif - - /*! * Define the macros that print information and optionally also log it. */