38G_LOG_THREAD_LOCAL
G::LogOutput * G::LogOutput::m_instance = nullptr ;
42 namespace LogOutputImp
44 constexpr int stdout_fileno = 1 ;
45 constexpr int stderr_fileno = 2 ;
46 std::size_t tellp( LogStream & log_stream )
48 if( log_stream.m_ostream ==
nullptr )
return 0U ;
49 log_stream.m_ostream->clear() ;
50 return static_cast<std::size_t
>( std::max( std::streampos(0) , log_stream.m_ostream->tellp() ) ) ;
52 std::string_view info()
54 static const std::string s(
txt(
"info: ") ) ;
55 return {s.data(),s.size()} ;
57 std::string_view warning()
59 static const std::string s(
txt(
"warning: ") ) ;
60 return {s.data(),s.size()} ;
62 std::string_view error()
64 static const std::string s(
txt(
"error: ") ) ;
65 return {s.data(),s.size()} ;
67 std::string_view assertion()
69 static const std::string s(
txt(
"assertion error: ") ) ;
70 return {s.data(),s.size()} ;
78 m_buffer(m_buffer_size) ,
79 m_streambuf(m_buffer.data(),m_buffer.size()) ,
80 m_stream(&m_streambuf) ,
81 m_fd(m_config.m_stdout?LogOutputImp::stdout_fileno:LogOutputImp::stderr_fileno)
112void G::LogOutput::init()
114 updatePath( m_path , m_real_path ) ;
115 open( m_real_path ,
true ) ;
117 if( m_instance ==
nullptr )
140 static_assert(
noexcept(m_path.empty()) ,
"" ) ;
142 static_assert(
noexcept(oscleanup()) ,
"" ) ;
144 if( m_instance ==
this )
146 m_instance = nullptr ;
148 if( !m_path.empty() && m_fd >= 0 &&
149 m_fd != LogOutputImp::stderr_fileno &&
150 m_fd != LogOutputImp::stdout_fileno )
167 p->m_context_fn = fn ;
168 p->m_context_fn_arg = fn_arg ;
176 return p ? p->m_context_fn_arg : nullptr ;
182 bool do_output = m_config.m_output_enabled ;
183 if( severity == Severity::Debug )
184 do_output = m_config.m_output_enabled && m_config.m_debug ;
185 else if( severity == Severity::InfoSummary )
186 do_output = m_config.m_output_enabled && m_config.m_summary_info ;
187 else if( severity == Severity::InfoVerbose )
188 do_output = m_config.m_output_enabled && m_config.m_verbose_info ;
189 else if( severity == Severity::InfoMoreVerbose )
190 do_output = m_config.m_output_enabled && m_config.m_more_verbose_info ;
199 return instance()->start( severity ) ;
214 instance()->output( s , 0 ) ;
221bool G::LogOutput::updatePath(
const Path & path_in ,
Path & path_out )
const
223 bool changed = false ;
224 if( !path_in.
empty() )
226 Path new_path_out = makePath( path_in ) ;
227 changed = new_path_out != path_out ;
228 path_out.
swap( new_path_out ) ;
233G::Path G::LogOutput::makePath(
const Path & path_in )
const
236 std::string_view yyyymmdd( m_time_buffer.data() , 8U ) ;
237 std::string_view hh( m_time_buffer.data()+9 , 2U ) ;
238 Path path_out = path_in ;
239 path_out.
replace(
"%d" , yyyymmdd ,
true ) ;
240 path_out.replace(
"%h" , hh ,
true ) ;
244void G::LogOutput::open(
const Path & path ,
bool do_throw )
250 Process::Umask set_umask( m_config.m_umask ) ;
252 fd =
File::open( path , File::InOutAppend::Append ) ;
253 if( fd < 0 && do_throw )
254 throw LogFileError( path.str() ) ;
258 if( m_fd >= 0 && m_fd != LogOutputImp::stderr_fileno && m_fd != LogOutputImp::stdout_fileno )
269 return LogStream(
nullptr ) ;
271 if( updateTime() && updatePath(m_path,m_real_path) )
272 open( m_real_path ,
false ) ;
275 LogStream log_stream( &m_stream ) ;
277 log_stream << std::dec ;
278 if( !m_exename.empty() )
279 log_stream << m_exename <<
": " ;
280 if( m_config.m_with_timestamp )
281 appendTimeTo( log_stream ) ;
282 if( m_config.m_with_level )
283 log_stream << levelString( severity ) ;
284 if( m_config.m_with_context && m_context_fn )
285 log_stream << (*m_context_fn)( m_context_fn_arg ) ;
287 m_start_pos = LogOutputImp::tellp( log_stream ) ;
288 m_severity = severity ;
295 if( m_depth ) m_depth-- ;
296 if( m_depth ) return ;
298 char * buffer = m_buffer.data() ;
299 std::size_t n = LogOutputImp::tellp( log_stream ) ;
302 if( n >= m_buffer_base_size )
304 static_assert( m_rhs_margin > 4U ,
"" ) ;
305 char * margin = buffer + m_buffer_base_size ;
310 n = m_buffer_base_size + 4U ;
315 if( m_config.m_strip )
317 char * end = buffer + n ;
318 char * text = buffer + m_start_pos ;
319 char * space = std::find( text , end ,
' ' ) ;
320 if( space != end && (space+1) != end )
323 p = std::copy_backward( buffer , text , space+1 ) ;
330 for(
char * pp = std::strchr(buffer,
'\033') ; pp ; pp = std::strchr(p+1,
'\033') )
333 if( m_fd == LogOutputImp::stdout_fileno )
338 osoutput( m_fd , m_severity , p , n ) ;
347 static std::array<char,m_buffer_size> buffer ;
348 omembuf streambuf( buffer.data() , buffer.size() ) ;
349 Stream stream( &streambuf ) ;
352 log_stream << LogOutputImp::assertion() << basename(file) <<
"(" << line <<
"): " << test_expression ;
353 char * p = buffer.data() ;
354 std::size_t n = std::min( std::size_t(m_buffer_base_size) , LogOutputImp::tellp(log_stream) ) ;
355 instance->osoutput( instance->m_fd , Severity::Assertion , p , n ) ;
359 std::cerr << LogOutputImp::assertion() << basename(file) <<
"(" << line <<
"): " << test_expression << std::endl ;
368bool G::LogOutput::updateTime()
371 m_time_us = now.
us() ;
372 bool new_hour = false ;
373 if( m_time_s == 0 || m_time_s != now.
s() || m_time_buffer[0] ==
'\0' )
376 m_time_buffer[0] =
'\0' ;
377 now.
local().
format( m_time_buffer.data() , m_time_buffer.size() ,
"%Y%m%d.%H%M%S." ) ;
378 m_time_buffer[16U] =
'\0' ;
380 new_hour = 0 != std::memcmp( m_time_change_buffer.data() , m_time_buffer.data() , 11U ) ;
382 static_assert(
sizeof(m_time_change_buffer) ==
sizeof(m_time_buffer) ,
"" ) ;
383 std::memcpy( m_time_change_buffer.data() , m_time_buffer.data() , m_time_buffer.size() ) ;
388void G::LogOutput::appendTimeTo( LogStream & log_stream )
391 << m_time_buffer.data()
392 <<
static_cast<char>(
'0' + ( ( m_time_us / 100000U ) % 10U ) )
393 <<
static_cast<char>(
'0' + ( ( m_time_us / 10000U ) % 10U ) )
394 <<
static_cast<char>(
'0' + ( ( m_time_us / 1000U ) % 10U ) )
398const char * G::LogOutput::basename(
const char * file )
noexcept
400 if( file ==
nullptr )
return "" ;
401 const char * p1 = std::strrchr( file ,
'/' ) ;
402 const char * p2 = std::strrchr( file ,
'\\' ) ;
403 return p1 > p2 ? (p1+1) : (p2?(p2+1):file) ;
406std::string_view G::LogOutput::levelString( Severity s )
noexcept
408 namespace imp = LogOutputImp ;
409 if( s == Severity::Debug )
return "debug: " ;
410 else if( s == Severity::InfoSummary )
return imp::info() ;
411 else if( s == Severity::InfoVerbose )
return imp::info() ;
412 else if( s == Severity::InfoMoreVerbose )
return imp::info() ;
413 else if( s == Severity::Warning )
return imp::warning() ;
414 else if( s == Severity::Error )
return imp::error() ;
415 else if( s == Severity::Assertion )
return imp::assertion() ;
421G::LogOutput::Config::Config() noexcept
424G::LogOutput::Config::Config(
bool enabled ,
bool verbose ) noexcept :
427 m_verbose_info(verbose),
428 m_more_verbose_info(verbose),
bool format(char *out, std::size_t out_size, const char *fmt) const
Puts the formatted date, including a terminating null character, into the given output buffer.
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
static void close(int fd) noexcept
Calls ::close() or equivalent.
Controls and implements low-level logging output, as used by G::Log.
LogStream start(Severity, const char *file, int line) noexcept
Returns an ostream for a new log line.
bool at(Severity) const noexcept
Returns true if logging should occur for the given severity level.
static GDEF_NORETURN_LHS void assertionAbort() GDEF_NORETURN_RHS
Aborts the program when an assertion has failed.
LogOutput(const std::string &exename, const Config &config, const Path &logfile={})
Constructor.
static void assertionFailure(LogOutput *, const char *file, int line, const char *test_string) noexcept
Reports an assertion failure.
void context(std::string_view(*fn)(void *)=nullptr, void *fn_arg=nullptr) noexcept
Sets a functor that is used to provide a context string for every log line, if configured.
Config config() const noexcept
Returns the current configuration.
void * contextarg() noexcept
Returns the functor argument as set by the last call to context().
void output(LogStream &) noexcept
Emits the current log line (see start()).
void configure(const Config &)
Updates the current configuration.
static LogOutput * instance() noexcept
Returns a pointer to the controlling LogOutput object.
int fd() const noexcept
Returns the output file descriptor.
A Path object represents a file system path.
void swap(Path &other) noexcept
Swaps this with other.
bool replace(const std::string_view &from, const std::string_view &to, bool ex_root=false)
Replaces the first occurrence of 'from' with 'to', optionally excluding the root part.
bool empty() const noexcept
Returns true if the path is empty.
Represents a unix-epoch time with microsecond resolution.
static SystemTime now()
Factory function for the current time.
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
unsigned int us() const
Returns the microsecond fraction.
BrokenDownTime local() const
Returns the locale-dependent local broken-down time.
bool enabled() noexcept
Returns true if pop code is built in.
const char * txt(const char *p) noexcept
A briefer alternative to G::gettext().
A configuration structure for G::LogOutput.
A non-throwing copyable wrapper for std::ostream, used by G::LogOutput and associated logging macros.