E-MailRelay
glogoutput.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file glogoutput.cpp
19///
20
21#include "gdef.h"
22#include "glogoutput.h"
23#include "gdatetime.h"
24#include "gscope.h"
25#include "gfile.h"
26#include "ggettext.h"
27#include "gomembuf.h"
28#include "gstringview.h"
29#include "glimits.h"
30#include "groot.h"
31#include "gtest.h"
32#include <algorithm>
33#include <sstream>
34#include <iostream>
35#include <stdexcept>
36#include <string>
37#include <cstring>
38#include <array>
39
40namespace G
41{
42 namespace LogOutputImp
43 {
44 constexpr int stdout_fileno = 1 ; // STDOUT_FILENO
45 constexpr int stderr_fileno = 2 ; // STDERR_FILENO
46 LogOutput * this_ = nullptr ;
47 constexpr std::size_t margin = 7U ;
48 constexpr std::size_t buffer_base_size = Limits<>::log + 40U ;
49 std::array<char,buffer_base_size+margin> buffer {} ;
50 struct ostream : std::ostream /// An ostream using G::omembuf.
51 {
52 explicit ostream( G::omembuf * p ) : std::ostream(p) {}
53 void reset() { clear() ; seekp(0) ; }
54 } ;
55 LogStream & ostream1()
56 {
57 static G::omembuf buf( &buffer[0] , buffer.size() ) ; // bogus clang-tidy cert-err58-cpp
58 static ostream s( &buf ) ;
59 static LogStream logstream( &s ) ;
60 s.reset() ;
61 return logstream ;
62 }
63 LogStream & ostream2() noexcept
64 {
65 // an ostream for junk that gets discarded
66 static LogStream logstream( nullptr ) ; // is noexcept
67 return logstream ;
68 }
69 std::size_t tellp( LogStream & logstream )
70 {
71 if( !logstream.m_ostream ) return 0U ;
72 logstream.m_ostream->clear() ;
73 return static_cast<std::size_t>( std::max( std::streampos(0) , logstream.m_ostream->tellp() ) ) ;
74 }
75 G::string_view info()
76 {
77 static std::string s( txt("info: ") ) ;
78 return {s.data(),s.size()} ;
79 }
80 G::string_view warning()
81 {
82 static std::string s( txt("warning: ") ) ;
83 return {s.data(),s.size()} ;
84 }
85 G::string_view error()
86 {
87 static std::string s( txt("error: ") ) ;
88 return {s.data(),s.size()} ;
89 }
90 G::string_view fatal()
91 {
92 static std::string s( txt("fatal: ") ) ;
93 return {s.data(),s.size()} ;
94 }
95 }
96}
97
98G::LogOutput::LogOutput( const std::string & exename , const Config & config ,
99 const std::string & path ) :
100 m_exename(exename) ,
101 m_config(config) ,
102 m_path(path)
103{
104 updateTime() ;
105 updatePath( m_path , m_real_path ) ;
106 open( m_real_path , true ) ;
107 osinit() ;
108 if( LogOutputImp::this_ == nullptr )
109 LogOutputImp::this_ = this ;
110}
111
112#ifndef G_LIB_SMALL
113G::LogOutput::LogOutput( bool output_enabled_and_summary_info ,
114 bool verbose_info_and_debug , const std::string & path ) :
115 m_path(path)
116{
117 m_config = Config()
118 .set_output_enabled(output_enabled_and_summary_info)
119 .set_summary_info(output_enabled_and_summary_info)
120 .set_verbose_info(verbose_info_and_debug)
121 .set_more_verbose_info(verbose_info_and_debug)
122 .set_debug(verbose_info_and_debug) ;
123
124 updateTime() ;
125 updatePath( m_path , m_real_path ) ;
126 open( m_real_path , true ) ;
127 osinit() ;
128 if( LogOutputImp::this_ == nullptr )
129 LogOutputImp::this_ = this ;
130}
131#endif
132
134{
135 return m_config ;
136}
137
138void G::LogOutput::configure( const Config & config )
139{
140 m_config = config ;
141}
142
144{
145 if( LogOutputImp::this_ == this )
146 {
147 LogOutputImp::this_ = nullptr ;
148 }
149 if( !m_path.empty() && m_fd >= 0 &&
150 m_fd != LogOutputImp::stderr_fileno &&
151 m_fd != LogOutputImp::stdout_fileno )
152 {
153 G::File::close( m_fd ) ;
154 }
155 oscleanup() ;
156}
157
159{
160 return LogOutputImp::this_ ;
161}
162
163void G::LogOutput::context( std::string (*fn)(void *) , void * fn_arg ) noexcept
164{
165 LogOutput * p = instance() ;
166 if( p )
167 {
168 p->m_context_fn = fn ;
169 p->m_context_fn_arg = fn_arg ;
170 }
171}
172
174{
175 LogOutput * p = instance() ;
176 return p ? p->m_context_fn_arg : nullptr ;
177}
178
179bool G::LogOutput::at( Log::Severity severity ) const noexcept
180{
181 bool do_output = m_config.m_output_enabled ;
182 if( severity == Log::Severity::Debug )
183 do_output = m_config.m_output_enabled && m_config.m_debug ;
184 else if( severity == Log::Severity::InfoSummary )
185 do_output = m_config.m_output_enabled && m_config.m_summary_info ;
186 else if( severity == Log::Severity::InfoVerbose )
187 do_output = m_config.m_output_enabled && m_config.m_verbose_info ;
188 else if( severity == Log::Severity::InfoMoreVerbose )
189 do_output = m_config.m_output_enabled && m_config.m_more_verbose_info ;
190 return do_output ;
191}
192
193G::LogStream & G::LogOutput::start( Log::Severity severity , const char * , int ) noexcept
194{
195 try
196 {
197 if( instance() )
198 return instance()->start( severity ) ; // not noexcept
199 else
200 return LogOutputImp::ostream2() ;
201 }
202 catch(...)
203 {
204 return LogOutputImp::ostream2() ; // is noexcept
205 }
206}
207
208void G::LogOutput::output( LogStream & s ) noexcept
209{
210 try
211 {
212 if( instance() )
213 instance()->output( s , 0 ) ; // not noexcept
214 }
215 catch(...)
216 {
217 }
218}
219
220bool G::LogOutput::updatePath( const std::string & path_in , std::string & path_out ) const
221{
222 bool changed = false ;
223 if( !path_in.empty() )
224 {
225 std::string new_path_out = makePath( path_in ) ;
226 changed = new_path_out != path_out ;
227 path_out.swap( new_path_out ) ;
228 }
229 return changed ;
230}
231
232std::string G::LogOutput::makePath( const std::string & path_in ) const
233{
234 // this is called at most hourly, so not optimised
235 std::string path_out = path_in ;
236 std::size_t pos = 0U ;
237 if( (pos=path_out.find("%d")) != std::string::npos )
238 {
239 std::string yyyymmdd( &m_time_buffer[0] , 8U ) ;
240 path_out.replace( pos , 2U , yyyymmdd ) ;
241 }
242 if( (pos=path_out.find("%h")) != std::string::npos )
243 {
244 path_out[pos] = m_time_buffer[9] ;
245 path_out[pos+1U] = m_time_buffer[10] ;
246 }
247 return path_out ;
248}
249
250void G::LogOutput::open( const std::string & path , bool do_throw )
251{
252 if( path.empty() )
253 {
254 m_fd = m_config.m_stdout ? LogOutputImp::stdout_fileno : LogOutputImp::stderr_fileno ;
255 }
256 else
257 {
258 int fd = -1 ;
259 {
260 Process::Umask set_umask( m_config.m_umask ) ;
261 Root claim_root ;
262 fd = File::open( path.c_str() , File::InOutAppend::Append ) ;
263 if( fd < 0 && do_throw )
264 throw LogFileError( path ) ;
265 }
266 if( fd >= 0 )
267 {
268 if( m_fd >= 0 && m_fd != LogOutputImp::stderr_fileno && m_fd != LogOutputImp::stdout_fileno )
269 G::File::close( m_fd ) ;
270 m_fd = fd ;
271 }
272 }
273}
274
275G::LogStream & G::LogOutput::start( Log::Severity severity )
276{
277 m_depth++ ;
278 if( m_depth > 1 )
279 return LogOutputImp::ostream2() ;
280
281 if( updateTime() && updatePath(m_path,m_real_path) )
282 open( m_real_path , false ) ;
283
284 LogStream & logstream = LogOutputImp::ostream1() ;
285 logstream << std::dec ;
286 if( m_exename.length() )
287 logstream << m_exename << ": " ;
288 if( m_config.m_with_timestamp )
289 appendTimeTo( logstream ) ;
290 if( m_config.m_with_level )
291 logstream << levelString( severity ) ;
292 if( m_config.m_with_context && m_context_fn )
293 logstream << (*m_context_fn)( m_context_fn_arg ) ;
294
295 m_start_pos = LogOutputImp::tellp( logstream ) ;
296 m_severity = severity ;
297 return logstream ;
298}
299
300void G::LogOutput::output( LogStream & logstream , int )
301{
302 // reject nested logging
303 if( m_depth ) m_depth-- ;
304 if( m_depth ) return ;
305
306 char * buffer = &LogOutputImp::buffer[0] ;
307 std::size_t n = LogOutputImp::tellp( logstream ) ;
308
309 // elipsis on overflow
310 if( n >= LogOutputImp::buffer_base_size )
311 {
312 char * margin = buffer + LogOutputImp::buffer_base_size ;
313 margin[0] = ' ' ;
314 margin[1] = '.' ;
315 margin[2] = '.' ;
316 margin[3] = '.' ;
317 n = LogOutputImp::buffer_base_size + 4U ;
318 }
319
320 // strip the first word from the text - expected to be the method name
321 char * p = buffer ;
322 if( m_config.m_strip )
323 {
324 char * end = buffer + n ;
325 char * text = buffer + m_start_pos ;
326 char * space = std::find( text , end , ' ' ) ;
327 if( space != end && (space+1) != end )
328 {
329 // (move the preamble forwards)
330 p = std::copy_backward( buffer , text , space+1 ) ;
331 n -= (p-buffer) ;
332 }
333 }
334
335 // last-ditch removal of ansi escape sequences
336 p[n] = '\0' ;
337 for( char * pp = std::strchr(buffer,'\033') ; pp ; pp = std::strchr(p+1,'\033') )
338 *pp = '.' ;
339
340 if( m_fd == LogOutputImp::stdout_fileno )
341 std::cout.flush() ;
342
343 // do the actual output in an o/s-specific manner -- the margin
344 // allows the implementation to extend the text with eg. a newline
345 osoutput( m_fd , m_severity , p , n ) ;
346}
347
348void G::LogOutput::assertionFailure( const char * file , int line , const char * test_expression ) noexcept
349{
350 // ('noexcept' on this fn so we std::terminate() if any of this throws)
351 if( instance() )
352 {
353 LogStream & logstream = LogOutputImp::ostream1() ;
354 logstream << txt("assertion error: ") << basename(file) << "(" << line << "): " << test_expression ;
355 char * p = &LogOutputImp::buffer[0] ;
356 std::size_t n = LogOutputImp::tellp( logstream ) ;
357 instance()->osoutput( instance()->m_fd , Log::Severity::Assertion , p , n ) ;
358 }
359 else
360 {
361 std::cerr << txt("assertion error: ") << basename(file) << "(" << line << "): " << test_expression << std::endl ;
362 }
363}
364
366{
367 std::abort() ;
368}
369
370bool G::LogOutput::updateTime()
371{
373 m_time_us = now.us() ;
374 bool new_hour = false ;
375 if( m_time_s == 0 || m_time_s != now.s() || m_time_buffer[0] == '\0' )
376 {
377 m_time_s = now.s() ;
378 m_time_buffer[0] = '\0' ;
379 now.local().format( &m_time_buffer[0] , m_time_buffer.size() , "%Y%m%d.%H%M%S." ) ;
380 m_time_buffer[16U] = '\0' ;
381
382 new_hour = 0 != std::memcmp( &m_time_change_buffer[0] , &m_time_buffer[0] , 11U ) ;
383
384 static_assert( sizeof(m_time_change_buffer) == sizeof(m_time_buffer) , "" ) ;
385 std::memcpy( &m_time_change_buffer[0] , &m_time_buffer[0] , m_time_buffer.size() ) ;
386 }
387 return new_hour ;
388}
389
390void G::LogOutput::appendTimeTo( LogStream & logstream )
391{
392 logstream
393 << &m_time_buffer[0]
394 << static_cast<char>( '0' + ( ( m_time_us / 100000U ) % 10U ) )
395 << static_cast<char>( '0' + ( ( m_time_us / 10000U ) % 10U ) )
396 << static_cast<char>( '0' + ( ( m_time_us / 1000U ) % 10U ) )
397 << ": " ;
398}
399
400const char * G::LogOutput::basename( const char * file ) noexcept
401{
402 if( file == nullptr ) return "" ;
403 const char * p1 = std::strrchr( file , '/' ) ;
404 const char * p2 = std::strrchr( file , '\\' ) ;
405 return p1 > p2 ? (p1+1) : (p2?(p2+1):file) ;
406}
407
408G::string_view G::LogOutput::levelString( Log::Severity s ) noexcept
409{
410 namespace imp = LogOutputImp ;
411 if( s == Log::Severity::Debug ) return "debug: " ;
412 else if( s == Log::Severity::InfoSummary ) return imp::info() ;
413 else if( s == Log::Severity::InfoVerbose ) return imp::info() ;
414 else if( s == Log::Severity::InfoMoreVerbose ) return imp::info() ;
415 else if( s == Log::Severity::Warning ) return imp::warning() ;
416 else if( s == Log::Severity::Error ) return imp::error() ;
417 else if( s == Log::Severity::Assertion ) return imp::fatal() ;
418 return "" ;
419}
420
421// ==
422
423G::LogOutput::Config::Config()
424= default;
425
426G::LogOutput::Config & G::LogOutput::Config::set_output_enabled( bool value )
427{
428 m_output_enabled = value ;
429 return *this ;
430}
431
432G::LogOutput::Config & G::LogOutput::Config::set_summary_info( bool value )
433{
434 m_summary_info = value ;
435 return *this ;
436}
437
438G::LogOutput::Config & G::LogOutput::Config::set_verbose_info( bool value )
439{
440 m_verbose_info = value ;
441 return *this ;
442}
443
444G::LogOutput::Config & G::LogOutput::Config::set_more_verbose_info( bool value )
445{
446 m_more_verbose_info = value ;
447 return *this ;
448}
449
450G::LogOutput::Config & G::LogOutput::Config::set_debug( bool value )
451{
452 m_debug = value ;
453 return *this ;
454}
455
456G::LogOutput::Config & G::LogOutput::Config::set_with_level( bool value )
457{
458 m_with_level = value ;
459 return *this ;
460}
461
462G::LogOutput::Config & G::LogOutput::Config::set_with_timestamp( bool value )
463{
464 m_with_timestamp = value ;
465 return *this ;
466}
467
468G::LogOutput::Config & G::LogOutput::Config::set_with_context( bool value )
469{
470 m_with_context = value ;
471 return *this ;
472}
473
474G::LogOutput::Config & G::LogOutput::Config::set_strip( bool value )
475{
476 m_strip = value ;
477 return *this ;
478}
479
480G::LogOutput::Config & G::LogOutput::Config::set_quiet_stderr( bool value )
481{
482 m_quiet_stderr = value ;
483 return *this ;
484}
485
486G::LogOutput::Config & G::LogOutput::Config::set_use_syslog( bool value )
487{
488 m_use_syslog = value ;
489 return *this ;
490}
491
492G::LogOutput::Config & G::LogOutput::Config::set_allow_bad_syslog( bool value )
493{
494 m_allow_bad_syslog = value ;
495 return *this ;
496}
497
498G::LogOutput::Config & G::LogOutput::Config::set_facility( SyslogFacility facility )
499{
500 m_facility = facility ;
501 return *this ;
502}
503
504G::LogOutput::Config & G::LogOutput::Config::set_umask( Process::Umask::Mode umask )
505{
506 m_umask = umask ;
507 return *this ;
508}
509
510#ifndef G_LIB_SMALL
511G::LogOutput::Config & G::LogOutput::Config::set_stdout( bool value )
512{
513 m_stdout = value ;
514 return *this ;
515}
516#endif
517
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.
Definition: gdatetime.cpp:232
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
static void close(int fd) noexcept
Calls ::close() or equivalent.
Definition: gfile_unix.cpp:149
Controls and implements low-level logging output, as used by G::Log.
Definition: glogoutput.h:55
static GDEF_NORETURN_LHS void assertionAbort() GDEF_NORETURN_RHS
Aborts the program when an assertion has failed.
Definition: glogoutput.cpp:365
bool at(Log::Severity) const noexcept
Returns true if logging should occur for the given severity level.
Definition: glogoutput.cpp:179
static LogStream & start(Log::Severity, const char *file, int line) noexcept
Prepares the internal ostream for a new log line and returns a reference to it.
Definition: glogoutput.cpp:193
Config config() const
Returns the current configuration.
Definition: glogoutput.cpp:133
static void * contextarg() noexcept
Returns the functor argument as set by the last call to context().
Definition: glogoutput.cpp:173
static void output(LogStream &) noexcept
Emits the current log line (see start()).
Definition: glogoutput.cpp:208
LogOutput(const std::string &exename, const Config &config, const std::string &filename={})
Constructor.
Definition: glogoutput.cpp:98
~LogOutput()
Destructor.
Definition: glogoutput.cpp:143
void configure(const Config &)
Updates the current configuration.
Definition: glogoutput.cpp:138
static void assertionFailure(const char *file, int line, const char *test_expression) noexcept
Reports an assertion failure.
Definition: glogoutput.cpp:348
static LogOutput * instance() noexcept
Returns a pointer to the controlling LogOutput object.
Definition: glogoutput.cpp:158
static void context(std::string(*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.
Definition: glogoutput.cpp:163
Represents a unix-epoch time with microsecond resolution.
Definition: gdatetime.h:134
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:328
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
Definition: gdatetime.cpp:380
unsigned int us() const
Returns the microsecond fraction.
Definition: gdatetime.cpp:374
BrokenDownTime local() const
Returns the locale-dependent local broken-down time.
Definition: gdatetime.cpp:356
An output streambuf that writes to a fixed-size char buffer.
Definition: gomembuf.h:50
A class like c++17's std::string_view.
Definition: gstringview.h:51
Low-level classes.
Definition: garg.h:30
const char * txt(const char *p)
A briefer alternative to G::gettext().
Definition: ggettext.h:74
STL namespace.
A configuration structure for G::LogOutput.
Definition: glogoutput.h:77
A non-throwing wrapper for std::ostream, used by G::Log.
Definition: glogstream.h:37