19#include "marley/Error.hh"
20#include "marley/JSON.hh"
21#include "marley/Logger.hh"
22#include "marley/marley_utils.hh"
24const char* marley::Logger::loglevel_to_str(LogLevel lev)
48 const std::string& str )
50 LogLevel result = LogLevel::INFO;
53 std::string low = marley_utils::to_lowercase( str );
54 static const std::unordered_map< std::string, LogLevel > conversion_map = {
55 {
"trace", LogLevel::TRACE },
56 {
"debug", LogLevel::DEBUG },
57 {
"info", LogLevel::INFO },
58 {
"notice", LogLevel::NOTICE },
59 {
"warn", LogLevel::WARN },
60 {
"error", LogLevel::ERROR },
61 {
"fatal", LogLevel::FATAL }
63 auto iter = conversion_map.find( low );
64 if ( iter != conversion_map.end() ) result = iter->second;
65 else throw marley::Error(
"Unrecognized marley::Logger logging level \""
70marley::Logger::OutStream::OutStream( std::shared_ptr< std::ostream > os,
71 LogLevel min, LogLevel max ) : stream_( os )
73 if ( min > max )
throw marley::Error(
"Minimum severity more than maximum"
74 " severity in constructor of marley::Logger::OutStream" );
82marley::Logger::OutStream::OutStream( std::ostream& os, LogLevel min,
83 LogLevel max ) : OutStream( std::shared_ptr< std::ostream >( &os,
84 [](std::ostream*) -> void {} ), min, max )
90 if ( config.has_key(
"out") ) {
91 auto out_array = config.at(
"out" );
92 if ( !out_array.is_array() ) {
94 " marley::Logger JSON configuration. Read \""
95 + out_array.dump_string() +
'\"' );
97 auto streams = out_array.array_range();
98 for (
const auto& s : streams ) {
104 if ( s.has_key(
"min") ) {
105 auto jmin = s.at(
"min" );
106 if ( !jmin.is_string() )
throw marley::Error(
"Invalid marley::Logger"
107 " minimum logging level specification " + jmin.dump_string() );
108 min = this->string_to_loglevel( jmin.to_string() );
111 if ( s.has_key(
"max") ) {
112 auto jmax = s.at(
"max" );
113 if ( !jmax.is_string() )
throw marley::Error(
"Invalid marley::Logger"
114 " maximum logging level specification " + jmax.dump_string() );
115 max = this->string_to_loglevel( jmax.to_string() );
118 bool is_file = s.has_key(
"file" );
119 bool is_stream = s.has_key(
"stream" );
122 throw marley::Error(
"marley::Logger output stream definition uses"
123 "both the \"stream\" and \"file\" keys" );
125 auto fname = s.at(
"file" );
126 if ( !fname.is_string() )
throw marley::Error(
"Invalid marley::Logger"
127 " output file specification " + fname.dump_string() );
132 auto file_mode = std::ios::out;
133 if ( s.has_key(
"overwrite") ) {
135 auto ow = s.at(
"overwrite" );
138 bool overwrite = ow.to_bool( ok );
140 " setting \"" + ow.dump_string() +
'\"' );
142 if ( overwrite ) file_mode |= std::ios::trunc;
143 else file_mode |= std::ios::app;
145 else file_mode |= std::ios::app;
147 auto outfile = std::make_shared< std::ofstream >( fname.to_string(),
149 if ( !outfile || (!outfile->good()) )
throw marley::Error(
"Unable"
150 " to open the log file \"" + fname.to_string() +
"\"" );
153 this->add_stream( outfile, min, max );
156 auto st = s.at(
"stream" );
157 if ( !st.is_string() )
throw marley::Error(
"Invalid marley::Logger"
158 " output stream specification " + st.dump_string() );
159 auto stream_name = st.to_string();
160 if ( stream_name ==
"stdout" ) {
161 this->add_stream( std::cout, min, max );
163 else if ( stream_name ==
"stderr" ) {
164 this->add_stream( std::cerr, min, max );
166 else throw marley::Error(
"Unrecognized stream name \"" + stream_name
167 +
"\" encountered in marley::Logger::configure()" );
173 bool set_default_categ =
false;
174 if ( !config.has_key(
"categories") ) {
177 default_level_ = LogLevel::INFO;
181 auto categ_spec = config.at(
"categories" );
182 if ( !categ_spec.is_object() ) {
183 throw marley::Error(
"marley::Logger categories must be specified as"
186 auto categs = categ_spec.object_range();
187 for (
const auto& [ cat, lev ] : categs ) {
188 if ( !lev.is_string() ) {
189 throw marley::Error(
"Invalid marley::Logger level specification \""
190 + lev.dump_string() +
'\"' );
192 LogLevel ll = this->string_to_loglevel( lev.to_string() );
195 if ( cat ==
"default" ) {
196 set_default_categ =
true;
199 else if ( cat.empty() ) {
200 throw marley::Error(
"Empty name encountered in the category"
201 " configuration for marley::Logger" );
203 else category_map_[ cat ] = ll;
206 if ( !set_default_categ ) {
207 throw marley::Error(
"Missing \"default\" logging level in the category"
208 " configuration for marley::Logger" );
217 char* mar = std::getenv(
"MARLEY" );
218 if ( !mar )
throw marley::Error(
"The MARLEY environment variable is not"
219 " set. Please set it (e.g., by sourcing the setup_marley.sh script) and"
223 std::string config_file_name = std::string( mar ) +
"/data/config/logger.js";
224 auto json_config = marley::JSON::load_file( config_file_name );
234 auto stream = get_stream( &os );
235 if ( stream )
return true;
240const marley::Logger::OutStream* marley::Logger::get_stream(
241 const std::ostream* os )
const
243 auto end = streams_.end();
244 auto iter = std::find_if( streams_.begin(), end,
245 [ os ](
const OutStream& s ) ->
bool { return s.stream_.get() == os; }
247 if ( iter == end )
return nullptr;
248 else return &( *iter );
251marley::Logger::OutStream* marley::Logger::get_stream(
252 const std::ostream* os )
254 auto end = streams_.end();
255 auto iter = std::find_if( streams_.begin(), end,
256 [ os ](
const OutStream& s ) ->
bool { return s.stream_.get() == os; }
258 if ( iter == end )
return nullptr;
259 else return &( *iter );
262void marley::Logger::add_stream( std::shared_ptr< std::ostream > stream,
263 LogLevel min, LogLevel max )
266 marley::Logger::OutStream* os = get_stream( stream.get() );
269 if ( !os ) streams_.emplace_back( stream, min, max );
273 os->min_level_ = min;
274 os->max_level_ = max;
278void marley::Logger::add_stream( std::ostream& stream,
279 LogLevel min, LogLevel max )
282 marley::Logger::OutStream* os = get_stream( &stream );
285 if ( !os ) streams_.emplace_back( stream, min, max );
289 os->min_level_ = min;
290 os->max_level_ = max;
295 const std::string& category )
297 std::vector< std::ostream* > active_streams;
301 for(
auto& s : streams_ ) {
304 if ( lev <= s.max_level_ && lev >= s.min_level_ ) {
306 active_streams.push_back( s.stream_.get() );
315 msg << loglevel_to_str( lev );
320 (*manip)(std::ostream&) )
327 (*manip)(std::ios_base&) )
334 const std::string& category )
339 auto iter = resolved_level_cache_.find( category );
340 if ( iter != resolved_level_cache_.end() )
return iter->second;
343 size_t delim_pos = std::string::npos;
347 std::string categ( category );
353 if ( delim_pos != std::string::npos ) categ.erase( delim_pos );
357 const auto cit = category_map_.find( categ );
358 if ( cit != category_map_.cend() ) {
360 resolved_level_cache_[ categ ] = resolved;
365 delim_pos = categ.rfind( CATEG_DELIM_ );
369 }
while( delim_pos != std::string::npos );
373 return default_level_;
Base class for all exceptions thrown by MARLEY functions.
Temporary object used for forming logger messages.
std::ostringstream buffer_
Simple singleton logging class.
static Logger & Instance()
Get the singleton instance of the Logger class.
bool should_emit(const std::string &category, LogLevel lev)
Returns whether the logger should emit a message for the given category and level.
Logger()
Create the singleton Logger.
LogLevel category_level(const std::string &category)
Looks up the severity setting for the input category, including inheritance from the hierarchy.
void configure(const marley::JSON &json)
Initialize the Logger using settings expressed as a JSON object.
Message log(LogLevel lev, const std::string &category="")
Prepare the Logger to receive a log message via the << stream operator.
LogLevel
Defines the logging levels recognized by the marley::Logger.
bool has_stream(const std::ostream &stream) const
Returns true if stream is already registered with the Logger, or false otherwise.