--- /dev/null
+#include "log/logger.hpp"
+
+#include <algorithm>
+#include <chrono>
+#include <cstring>
+#include <iostream>
+#include <iterator>
+
+namespace logger {
+
+ namespace {
+
+ std::ostream*& ostream_pointer() {
+ static std::ostream* stream = &std::cout;
+ return stream;
+ }
+
+ std::ostream& get_stream() {
+ return *ostream_pointer();
+ }
+
+ std::string make_prefix( level l ) {
+ auto prefix = std::string {};
+
+ switch( l ) {
+ case level::debug:
+ prefix = "[debug][";
+ break;
+
+ case level::note:
+ prefix = "[note ][";
+ break;
+
+ case level::warn:
+ prefix = "[warn ][";
+ break;
+
+ case level::error:
+ prefix = "[error][";
+ break;
+
+ case level::fatal:
+ prefix = "[fatal][";
+ break;
+ }
+
+ using clock = std::chrono::system_clock;
+ const auto now = clock::to_time_t( clock::now() );
+ // ctime appends a newline, we don't want that here:
+ auto time_str = std::ctime( &now );
+ prefix.append( time_str, time_str + std::strlen( time_str ) - 1 );
+ prefix += "]: ";
+ return prefix;
+ }
+
+ } // anonymous namespace
+
+ namespace impl {
+
+ std::string replace_newlines( const std::string& str, std::size_t length ) {
+ auto returnstring = std::string {};
+ auto it = str.begin();
+ const auto end = str.end();
+ auto nl_it = it;
+
+ while( ( nl_it = std::find( it, end, '\n' ) ) != end ) {
+ ++nl_it;
+ returnstring.append( it, nl_it );
+ returnstring.append( length, ' ' );
+ it = nl_it;
+ }
+
+ returnstring.append( it, end );
+ return returnstring;
+ }
+
+ void log( level l, const std::vector<std::string>& args ) {
+ const auto prefix = make_prefix( l );
+ const auto length = prefix.length();
+ get_stream() << prefix;
+ std::transform( args.begin(), args.end(), std::ostream_iterator<std::string> {get_stream()},
+ [length]( const std::string & str ) {
+ return replace_newlines( str, length );
+ } );
+ get_stream() << '\n' << std::flush;
+ }
+
+ void logf( level l, const std::string& format, std::vector<std::string> args ) {
+ const auto prefix = make_prefix( l );
+ const auto length = prefix.length();
+ const auto fmt = replace_newlines( format, length );
+ std::transform( args.begin(), args.end(), args.begin(),
+ [length]( const std::string & str ) {
+ return replace_newlines( str, length );
+ } );
+
+ auto mesg = prefix;
+ auto arg_index = std::size_t {0};
+ auto it = fmt.begin();
+ const auto end = fmt.end();
+
+ while( it != end ) {
+ auto pos = std::find( it, end, '%' );
+ mesg.append( it, pos );
+
+ if( pos == end ) {
+ break;
+ }
+
+ ++pos;
+
+ if( pos == end ) {
+ throw std::invalid_argument {"Invalid formatstring (ends on single '%')"};
+ }
+
+ switch( *pos ) {
+ case '%':
+ mesg.push_back( '%' );
+ break;
+
+ case 's':
+ if( arg_index >= args.size() ) {
+ throw std::invalid_argument {"Invalid formatstring (not enough arguments)"};
+ }
+
+ mesg.append( args[arg_index++] );
+ break;
+
+ default:
+ throw std::invalid_argument {"Invalid formatstring (unknown format-character)"};
+ }
+
+ it = std::next( pos );
+ }
+
+ mesg.push_back( '\n' );
+ get_stream() << mesg << std::flush;
+ }
+
+ } // namespace impl
+
+ void set_stream( std::ostream& stream ) {
+ ostream_pointer() = &stream;
+ }
+
+} // namespace logger
--- /dev/null
+#pragma once
+
+#include <ostream>
+#include <string>
+#include <sstream>
+#include <vector>
+
+namespace logger {
+
+ enum class level {
+ debug, note, warn, error, fatal
+ };
+
+ /**
+ * conv::to_string will be used to convert whatever argument is send
+ * to the logger to a string. If another type shall be supported,
+ * just add the appropriate overload and you can start using it right
+ * away. The default conversion will use a stringstream.
+ */
+ namespace conv {
+ template <typename T>
+ inline std::string to_string( const T& arg ) {
+ std::ostringstream stream;
+ stream << arg;
+ return stream.str();
+ }
+ inline std::string to_string( std::string&& arg ) {
+ return arg;
+ }
+ inline std::string to_string( const std::string& arg ) {
+ return arg;
+ }
+ inline std::string to_string( const char* arg ) {
+ return arg;
+ }
+ }
+
+ namespace impl {
+ void log( level, const std::vector<std::string>& args );
+ void logf( level, const std::string&, std::vector<std::string> args );
+ }
+
+ void set_stream( std::ostream& );
+
+ template <typename... Args>
+ void log( level l, Args&& ... data ) {
+ impl::log( l, {conv::to_string( std::forward<Args>( data ) )...} );
+ }
+
+ template <typename... Args>
+ void logf( level l, const std::string& format, Args&& ... data ) {
+ impl::logf( l, format, {conv::to_string( std::forward<Args>( data ) )...} );
+ }
+
+ template <typename... Args>
+ void debug( Args&& ... args ) {
+ log( level::debug, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void note( Args&& ... args ) {
+ log( level::note, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void warn( Args&& ... args ) {
+ log( level::warn, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void error( Args&& ... args ) {
+ log( level::error, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void fatal( Args&& ... args ) {
+ log( level::fatal, std::forward<Args>( args )... );
+ }
+
+ template <typename... Args>
+ void debugf( const std::string& fmt, Args&& ... args ) {
+ logf( level::debug, fmt, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void notef( const std::string& fmt, Args&& ... args ) {
+ logf( level::note, fmt, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void warnf( const std::string& fmt, Args&& ... args ) {
+ logf( level::warn, fmt, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void errorf( const std::string& fmt, Args&& ... args ) {
+ logf( level::error, fmt, std::forward<Args>( args )... );
+ }
+ template <typename... Args>
+ void fatalf( const std::string& fmt, Args&& ... args ) {
+ logf( level::fatal, fmt, std::forward<Args>( args )... );
+ }
+
+}