E-MailRelay

glogoutput.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "gomembuf.h"
27#include "glimits.h"
28#include <algorithm>
29#include <sstream>
30#include <stdexcept>
31#include <string>
32#include <cstring>
33#include <array>
34
35 namespace G
36{
37 namespace LogOutputImp /// An implementation namespace for G::LogOutput.
38 {
39 static std::string s_info ;
40 static std::string s_warning ;
41 static std::string s_error ;
42 static std::string s_fatal ;
43 constexpr int stderr_fileno = 2 ; // STDERR_FILENO
44 LogOutput * this_ = nullptr ;
45 constexpr std::size_t margin = 7U ;
46 constexpr std::size_t buffer_base_size = limits::log + 40U ;
47 std::array<char,buffer_base_size+margin> buffer_1 ;
48 std::array<char,8> buffer_2 ;
49 struct ostream : std::ostream /// An ostream using a G::omembuf streambuf.
50 {
51 explicit ostream( G::omembuf * p ) : std::ostream(p) {}
52 void reset() { clear() ; seekp(0) ; }
53 } ;
54 std::ostream & ostream1()
55 {
56 static G::omembuf buf( &buffer_1[0] , buffer_1.size() ) ; // bogus clang-tidy cert-err58-cpp
57 static ostream s( &buf ) ;
58 s.reset() ;
59 return s ;
60 }
61 std::ostream & ostream2()
62 {
63 // an ostream for junk that gets discarded
64 static G::omembuf buf( &buffer_2[0] , buffer_2.size() ) ;
65 static ostream s( &buf ) ;
66 s.reset() ;
67 return s ;
68 }
69 std::size_t tellp( std::ostream & s )
70 {
71 s.clear() ;
72 return static_cast<std::size_t>( std::max( std::streampos(0) , s.tellp() ) ) ;
73 }
74 }
75}
76
77 G::LogOutput::LogOutput( const std::string & exename , const Config & config ,
78 const std::string & path ) :
79 m_exename(exename) ,
80 m_config(config) ,
81 m_path(path)
82{
83 updateTime() ;
84 open( m_path , true ) ;
85 osinit() ;
86 m_depth = 0U ;
87 if( LogOutputImp::this_ == nullptr )
88 LogOutputImp::this_ = this ;
89}
90
91 G::LogOutput::LogOutput( bool output_enabled_and_summary_info ,
92 bool verbose_info_and_debug , const std::string & path ) :
93 m_path(path)
94{
95 m_config = Config()
96 .set_output_enabled(output_enabled_and_summary_info)
97 .set_summary_info(output_enabled_and_summary_info)
98 .set_verbose_info(verbose_info_and_debug)
99 .set_debug(verbose_info_and_debug) ;
100
101 updateTime() ;
102 open( m_path , true ) ;
103 osinit() ;
104 m_depth = 0U ;
105 if( LogOutputImp::this_ == nullptr )
106 LogOutputImp::this_ = this ;
107}
108
109 G::LogOutput::Config G::LogOutput::config() const
110{
111 return m_config ;
112}
113
114 void G::LogOutput::configure( const Config & config )
115{
116 m_config = config ;
117}
118
119 G::LogOutput::~LogOutput()
120{
121 if( LogOutputImp::this_ == this )
122 LogOutputImp::this_ = nullptr ;
123 if( !m_path.empty() && m_fd != LogOutputImp::stderr_fileno && m_fd >= 0 )
124 G::File::close( m_fd ) ;
125 oscleanup() ;
126}
127
128 G::LogOutput * G::LogOutput::instance() noexcept
129{
130 return LogOutputImp::this_ ;
131}
132
133 void G::LogOutput::context( std::string (*fn)(void *) , void * fn_arg ) noexcept
134{
135 LogOutput * p = instance() ;
136 if( p )
137 {
138 p->m_context_fn = fn ;
139 p->m_context_fn_arg = fn_arg ;
140 }
141}
142
143 void * G::LogOutput::contextarg() noexcept
144{
145 LogOutput * p = instance() ;
146 return p ? p->m_context_fn_arg : nullptr ;
147}
148
149 bool G::LogOutput::at( Log::Severity severity ) const noexcept
150{
151 bool do_output = m_config.m_output_enabled ;
152 if( severity == Log::Severity::s_Debug )
153 do_output = m_config.m_output_enabled && m_config.m_debug ;
154 else if( severity == Log::Severity::s_InfoSummary )
155 do_output = m_config.m_output_enabled && m_config.m_summary_info ;
156 else if( severity == Log::Severity::s_InfoVerbose )
157 do_output = m_config.m_output_enabled && m_config.m_verbose_info ;
158 return do_output ;
159}
160
161std::ostream & G::LogOutput::start( Log::Severity severity , const char * , int )
162{
163 if( instance() )
164 return instance()->start( severity ) ;
165 else
166 return LogOutputImp::ostream2() ;
167}
168
169 void G::LogOutput::output( std::ostream & ss )
170{
171 if( instance() )
172 instance()->output( ss , 0 ) ;
173}
174
175 void G::LogOutput::open( std::string path , bool do_throw )
176{
177 if( path.empty() )
178 {
179 m_fd = LogOutputImp::stderr_fileno ;
180 }
181 else
182 {
183 std::size_t pos = path.find( "%d" ) ;
184 if( pos != std::string::npos )
185 path.replace( pos , 2U , std::string(&m_time_buffer[0],8U) ) ;
186
187 int fd = File::open( path.c_str() , File::InOutAppend::Append ) ;
188 if( fd < 0 )
189 {
190 if( do_throw )
191 throw std::runtime_error( "cannot open log file: " + path ) ;
192 }
193 else
194 {
195 if( m_fd >= 0 && m_fd != LogOutputImp::stderr_fileno )
196 G::File::close( m_fd ) ;
197 m_fd = fd ;
198 }
199 }
200}
201
202std::ostream & G::LogOutput::start( Log::Severity severity )
203{
204 m_depth++ ;
205 if( m_depth > 1 )
206 return LogOutputImp::ostream2() ;
207
208 if( updateTime() )
209 open( m_path , false ) ;
210
211 std::ostream & ss = LogOutputImp::ostream1() ;
212 ss << std::dec ;
213 if( m_exename.length() )
214 ss << m_exename << ": " ;
215 if( m_config.m_with_timestamp )
216 appendTimeTo( ss ) ;
217 if( m_config.m_with_level )
218 ss << levelString( severity ) ;
219 if( m_config.m_with_context && m_context_fn )
220 ss << (*m_context_fn)( m_context_fn_arg ) ;
221
222 m_start_pos = LogOutputImp::tellp( ss ) ;
223 m_severity = severity ;
224 return ss ;
225}
226
227 void G::LogOutput::output( std::ostream & ss , int )
228{
229 // reject nested logging
230 if( m_depth ) m_depth-- ;
231 if( m_depth ) return ;
232
233 char * buffer = &LogOutputImp::buffer_1[0] ;
234 std::size_t n = LogOutputImp::tellp( ss ) ;
235
236 // elipsis on overflow
237 if( n >= LogOutputImp::buffer_base_size )
238 {
239 char * margin = buffer + LogOutputImp::buffer_base_size ;
240 margin[0] = ' ' ;
241 margin[1] = '.' ;
242 margin[2] = '.' ;
243 margin[3] = '.' ;
244 n = LogOutputImp::buffer_base_size + 4U ;
245 }
246
247 // strip the first word from the text - expected to be the method name
248 char * p = buffer ;
249 if( m_config.m_strip )
250 {
251 char * end = buffer + n ;
252 char * text = buffer + m_start_pos ;
253 char * space = std::find( text , end , ' ' ) ;
254 if( space != end && (space+1) != end )
255 {
256 // (move the preamble forwards)
257 p = std::copy_backward( buffer , text , space+1 ) ;
258 n -= (p-buffer) ;
259 }
260 }
261
262 // last-ditch removal of ansi escape sequences
263 p[n] = '\0' ;
264 for( char * pp = std::strchr(buffer,'\033') ; pp ; pp = std::strchr(p+1,'\033') )
265 *pp = '.' ;
266
267 // do the actual output in an o/s-specific manner -- the margin
268 // allows the implementation to extend the text with eg. a newline
269 osoutput( m_fd , m_severity , p , n ) ;
270}
271
272 void G::LogOutput::assertionFailure( const char * file , int line , const char * test_expression ) noexcept
273{
274 // ('noexcept' on this fn so we std::terminate() if any of this throws)
275 if( instance() )
276 {
277 std::ostream & ss = LogOutputImp::ostream1() ;
278 ss << "assertion error: " << basename(file) << "(" << line << "): " << test_expression ;
279 char * p = &LogOutputImp::buffer_1[0] ;
280 std::size_t n = LogOutputImp::tellp( ss ) ;
281 instance()->osoutput( instance()->m_fd , Log::Severity::s_Assertion , p , n ) ;
282 }
283 else
284 {
285 std::cerr << "assertion error: " << basename(file) << "(" << line << "): " << test_expression << std::endl ;
286 }
287}
288
289 void G::LogOutput::assertionAbort()
290{
291 std::abort() ;
292}
293
294 bool G::LogOutput::updateTime()
295{
296 SystemTime now = SystemTime::now() ;
297 m_time_us = now.us() ;
298 bool new_day = false ;
299 if( m_time_s == 0 || m_time_s != now.s() || m_time_buffer.empty() )
300 {
301 m_time_s = now.s() ;
302 m_time_buffer.resize( 17U ) ;
303 now.local().format( m_time_buffer , "%Y%m%d.%H%M%S." ) ;
304 m_time_buffer[16U] = '\0' ;
305 new_day = 0 != std::memcmp( &m_date_buffer[0] , &m_time_buffer[0] , m_date_buffer.size() ) ;
306 std::memcpy( &m_date_buffer[0] , &m_time_buffer[0] , m_date_buffer.size() ) ;
307 }
308 return new_day ;
309}
310
311 void G::LogOutput::appendTimeTo( std::ostream & ss )
312{
313 ss
314 << &m_time_buffer[0]
315 << static_cast<char>( '0' + ( ( m_time_us / 100000U ) % 10U ) )
316 << static_cast<char>( '0' + ( ( m_time_us / 10000U ) % 10U ) )
317 << static_cast<char>( '0' + ( ( m_time_us / 1000U ) % 10U ) )
318 << ": " ;
319}
320
321 const char * G::LogOutput::basename( const char * file ) noexcept
322{
323 if( file == nullptr ) return "" ;
324 const char * p1 = std::strrchr( file , '/' ) ;
325 const char * p2 = std::strrchr( file , '\\' ) ;
326 return p1 > p2 ? (p1+1) : (p2?(p2+1):file) ;
327}
328
329 const char * G::LogOutput::levelString( Log::Severity s ) noexcept
330{
331 namespace imp = LogOutputImp ;
332 if( s == Log::Severity::s_Debug ) return "debug: " ;
333 else if( s == Log::Severity::s_InfoSummary ) return imp::s_info.empty() ? "info: " : imp::s_info.c_str() ;
334 else if( s == Log::Severity::s_InfoVerbose ) return imp::s_info.empty() ? "info: " : imp::s_info.c_str() ;
335 else if( s == Log::Severity::s_Warning ) return imp::s_warning.empty() ? "warning: " : imp::s_warning.c_str() ;
336 else if( s == Log::Severity::s_Error ) return imp::s_error.empty() ? "error: " : imp::s_error.c_str() ;
337 else if( s == Log::Severity::s_Assertion ) return imp::s_fatal.empty() ? "fatal: " : imp::s_fatal.c_str() ;
338 return "" ;
339}
340
341 void G::LogOutput::translate( const std::string & info , const std::string & warning ,
342 const std::string & error , const std::string & fatal )
343{
344 namespace imp = LogOutputImp ;
345 imp::s_info = info ;
346 imp::s_warning = warning ;
347 imp::s_error = error ;
348 imp::s_fatal = fatal ;
349}
350
351// ==
352
353G::LogOutput::Config::Config()
354= default;
355
356 G::LogOutput::Config & G::LogOutput::Config::set_output_enabled( bool value )
357{
358 m_output_enabled = value ;
359 return *this ;
360}
361
362 G::LogOutput::Config & G::LogOutput::Config::set_summary_info( bool value )
363{
364 m_summary_info = value ;
365 return *this ;
366}
367
368 G::LogOutput::Config & G::LogOutput::Config::set_verbose_info( bool value )
369{
370 m_verbose_info = value ;
371 return *this ;
372}
373
374 G::LogOutput::Config & G::LogOutput::Config::set_debug( bool value )
375{
376 m_debug = value ;
377 return *this ;
378}
379
380 G::LogOutput::Config & G::LogOutput::Config::set_with_level( bool value )
381{
382 m_with_level = value ;
383 return *this ;
384}
385
386 G::LogOutput::Config & G::LogOutput::Config::set_with_timestamp( bool value )
387{
388 m_with_timestamp = value ;
389 return *this ;
390}
391
392 G::LogOutput::Config & G::LogOutput::Config::set_with_context( bool value )
393{
394 m_with_context = value ;
395 return *this ;
396}
397
398 G::LogOutput::Config & G::LogOutput::Config::set_strip( bool value )
399{
400 m_strip = value ;
401 return *this ;
402}
403
404 G::LogOutput::Config & G::LogOutput::Config::set_quiet_stderr( bool value )
405{
406 m_quiet_stderr = value ;
407 return *this ;
408}
409
410 G::LogOutput::Config & G::LogOutput::Config::set_use_syslog( bool value )
411{
412 m_use_syslog = value ;
413 return *this ;
414}
415
416 G::LogOutput::Config & G::LogOutput::Config::set_allow_bad_syslog( bool value )
417{
418 m_allow_bad_syslog = value ;
419 return *this ;
420}
421
422 G::LogOutput::Config & G::LogOutput::Config::set_facility( SyslogFacility facility )
423{
424 m_facility = facility ;
425 return *this ;
426}
427
G::BrokenDownTime::format
void format(std::vector< char > &out, const char *fmt) const
Puts the formatted date, including a terminating null character, into the given buffer.
Definition: gdatetime.cpp:173
G::File::open
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
Definition: gfile_unix.cpp:55
G::File::close
static void close(int fd) noexcept
Calls ::close() or equivalent.
Definition: gfile_unix.cpp:126
G::LogOutput
Controls and implements low-level logging output, as used by G::Log.
Definition: glogoutput.h:50
G::LogOutput::at
bool at(Log::Severity) const noexcept
Returns true if logging should occur for the given severity level.
Definition: glogoutput.cpp:149
G::LogOutput::translate
static void translate(const std::string &info, const std::string &warning, const std::string &error, const std::string &fatal)
Sets the prefix string for the various log levels (including trailing punctuation).
Definition: glogoutput.cpp:341
G::LogOutput::config
Config config() const
Returns the current configuration.
Definition: glogoutput.cpp:109
G::LogOutput::output
static void output(std::ostream &)
Emits the current log line (see start()).
Definition: glogoutput.cpp:169
G::LogOutput::contextarg
static void * contextarg() noexcept
Returns the functor argument as set by the last call to context().
Definition: glogoutput.cpp:143
G::LogOutput::LogOutput
LogOutput(const std::string &exename, const Config &config, const std::string &filename=std::string())
Constructor.
Definition: glogoutput.cpp:77
G::LogOutput::~LogOutput
~LogOutput()
Destructor.
Definition: glogoutput.cpp:119
G::LogOutput::configure
void configure(const Config &)
Updates the current configuration.
Definition: glogoutput.cpp:114
G::LogOutput::start
static std::ostream & start(Log::Severity, const char *file, int line)
Prepares the internal ostream for a new log line and returns a reference to it.
Definition: glogoutput.cpp:161
G::LogOutput::assertionFailure
static void assertionFailure(const char *file, int line, const char *test_expression) noexcept
Reports an assertion failure.
Definition: glogoutput.cpp:272
G::LogOutput::instance
static LogOutput * instance() noexcept
Returns a pointer to the controlling LogOutput object.
Definition: glogoutput.cpp:128
G::LogOutput::context
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:133
G::LogOutput::assertionAbort
static void assertionAbort() GDEF_NORETURN
Aborts the program when an assertion has failed.
Definition: glogoutput.cpp:289
G::SystemTime
Represents a unix-epoch time with microsecond resolution.
Definition: gdatetime.h:125
G::SystemTime::now
static SystemTime now()
Factory function for the current time.
Definition: gdatetime.cpp:260
G::SystemTime::s
std::time_t s() const noexcept
Returns the number of seconds since the start of the epoch.
Definition: gdatetime.cpp:308
G::SystemTime::us
unsigned int us() const
Returns the microsecond fraction.
Definition: gdatetime.cpp:302
G::SystemTime::local
BrokenDownTime local() const
Returns the locale-dependent local broken-down time.
Definition: gdatetime.cpp:286
G::omembuf
An output streambuf that writes to a fixed-size char buffer.
Definition: gomembuf.h:50
gdatetime.h
gfile.h
glimits.h
glogoutput.h
gomembuf.h
gscope.h
G
Low-level classes.
Definition: galign.h:28
G::LogOutputImp::ostream
An ostream using a G::omembuf streambuf.
Definition: glogoutput.cpp:50
G::LogOutput::Config
A configuration structure for G::LogOutput.
Definition: glogoutput.h:67