Replace localtime() with thread safe localtime_r()
[libi2ncommon] / src / logfunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /** @file
21  * @brief implementaton of logging functions.
22  *
23  * @copyright © Copyright 2007-2008 by Intra2net AG
24  * 
25  * @note Don't call loggers from global constructed objects
26  * as this depends on the global object construction sequence.
27  */
28
29 #include "logfunc.hpp"
30
31 #include <syslog.h>
32 #include <cstdlib>
33 #include <ctime>
34 #include <algorithm>
35 #include <iostream>
36 #include <fstream>
37 #include <unistd.h>
38 #include <string.h>
39 #include <boost/shared_ptr.hpp>
40 #include <boost/thread/recursive_mutex.hpp>
41 #include <boost/thread/thread.hpp>
42
43 #include <stringfunc.hxx>
44 #include <filefunc.hxx>
45
46
47 namespace I2n
48 {
49 namespace Logger
50 {
51
52 /**
53  * @brief the global logger instance.
54  *
55  * This may be used in all cases wheer a specialized logger is not available
56  * or shouldn't be used for some reason.
57  */
58 PartLogger GlobalLogger("");
59
60
61 namespace
62 {
63
64 /// Global lock for the logger. Used during all syslog operations
65 /// and modification of our shared local variables.
66 static boost::recursive_mutex LoggerLock;
67
68 /*
69 ** local globals:
70 */
71
72 static std::string g_ident;
73 static Facility    g_facility;
74 static bool        g_syslog_opened = false;
75 static int         g_max_level= LogLevel::Warning;
76
77 static bool g_stderr_log = false;
78
79 static std::string g_log_file_name;
80 static boost::shared_ptr< std::ofstream > g_log_stream_ptr;
81
82 /**
83  * @brief lookup array for translating our log levels to syslog level.
84  */
85 static int loglevel_2_syslog_level[ LogLevel::_LogLevel_END ] = {
86     LOG_EMERG,
87     LOG_ALERT,
88     LOG_CRIT,
89     LOG_ERR,
90     LOG_WARNING,
91     LOG_NOTICE,
92     LOG_INFO,
93     LOG_DEBUG
94 };
95
96
97 /**
98  * @brief lookup array for translating our log levels to a short (4 character) tag.
99  *
100  * These tags are used when logs are written to stderr or into a log file.
101  */
102 static std::string loglevel_2_short_tag[ LogLevel::_LogLevel_END ] = {
103     "EMRG",
104     "ALRT",
105     "CRIT",
106     "ERR ",
107     "WARN",
108     "NOTE",
109     "INFO",
110     "DBUG"
111 };
112
113
114 /**
115  * @brief a copy of the current identifier used for syslog.
116  *
117  * Keeping a copy of this ident is necessary since openlog doen't copy it's first
118  * argument but copies only the pointer! (what a **censored**!)
119  */
120 static char* syslog_ident= NULL;
121
122
123 /*
124 ** functions:
125 */
126
127 /**
128  * @brief close syslog.
129  */
130 void close_syslog()
131 {
132     boost::recursive_mutex::scoped_lock lock(LoggerLock);
133
134     if (g_syslog_opened)
135     {
136         closelog();
137         if (syslog_ident)
138         {
139             free(syslog_ident);
140             syslog_ident= NULL;
141         }
142         g_syslog_opened= false;
143     }
144 } // eo close_syslog()
145
146
147 /**
148  * @brief open syslog.
149  */
150 void open_syslog()
151 {
152     boost::recursive_mutex::scoped_lock lock(LoggerLock);
153
154     close_syslog();
155     syslog_ident= strdup(g_ident.c_str());
156     openlog( syslog_ident, LOG_CONS|LOG_PID, g_facility);
157     g_syslog_opened= true;
158 } // eo open_syslog()
159
160
161 /**
162  * @brief get syslog level from internal log level.
163  * @param level log level.
164  * @return syslog level
165  */
166 int get_syslog_level( int level )
167 {
168     // Note: Thread safe
169
170     if (level >=0 &&  level < LogLevel::_LogLevel_END)
171     {
172         return loglevel_2_syslog_level[level];
173     }
174     return (level<0) ? (LOG_EMERG) : (LOG_DEBUG);
175 } // eo get_syslog_level(int)
176
177
178 /**
179  * @brief get a short tag for the log level (/ message type)
180  * @param level the log level
181  * @return a short tag for the level.
182  */
183 std::string get_level_tag( int level )
184 {
185     // Note: Thread safe
186
187     if (level >=0 &&  level < LogLevel::_LogLevel_END)
188     {
189         return loglevel_2_short_tag[level];
190     }
191     return (level<0) ? loglevel_2_short_tag[0] : loglevel_2_short_tag[ LogLevel::_LogLevel_END -1 ];
192 } // eo get_level_tag(int)
193
194
195 /**
196  * @brief the "real" log function which logs a message at a given level.
197  * @param level the log level to log the message at.
198  * @param msg the message.
199  *
200  * Write the message to every enabled log channel.
201  *
202  * If syslog is enabled the message is passed unmodified to syslog.
203  *
204  * If a stream log is enabled (stderr or file) then the message is prepended with date, time
205  * and process information (like syslog does). The message is splitted at line ends and
206  * consecutive lines are indented.
207  */
208 void log_msg( int level, const std::string& msg)
209 {
210     boost::recursive_mutex::scoped_lock lock(LoggerLock);
211
212     if (not g_syslog_opened and not g_stderr_log and not g_log_stream_ptr)
213     {
214         // if nothing is opened for logging: we activate syslog!
215         enable_syslog(true);
216     }
217
218     if (g_syslog_opened)
219     {
220         ::syslog( get_syslog_level(level), "%s", msg.c_str());
221     }
222     // file(/stream) logging:
223     if (g_stderr_log or g_log_stream_ptr) // add more log "enabled" expressions here...
224     {
225         // here we need must do something more with the msg...
226         std::string new_msg;
227         std::string prefix;
228         {
229             std::ostringstream ostr;
230             // add time stamp (syslog like: "Mon DD HH:MM:SS") :
231             {
232                 time_t t = time(NULL);
233                 char buffer[32];
234                 struct tm ta;
235                 if (localtime_r(&t, &ta) == NULL)
236                     memset(&ta, 0, sizeof(struct tm));
237
238                 std::strftime(buffer, sizeof(buffer),"%b %d %H:%M:%S ", &ta);
239                 ostr << buffer;
240             }
241             ostr << get_level_tag(level) << " ";
242             ostr << g_ident << "[" << getpid() << "]: ";
243             prefix= ostr.str();
244         }
245         {
246             {
247                 std::string indent_string(prefix.size(), ' ');
248                 std::list< std::string > parts;
249                 split_string( chomp(msg,"\n"), parts, "\n");
250                 std::ostringstream ostr;
251                 ostr << prefix;
252                 for(std::list< std::string >::const_iterator it= parts.begin();
253                     it != parts.end();
254                     ++it)
255                 {
256                     if (it != parts.begin())
257                     {
258                         ostr << indent_string;
259                     }
260                     ostr << *it << std::endl;
261                 }
262                 new_msg= ostr.str();
263             }
264         }
265         if (g_stderr_log)
266         {
267             std::cerr << new_msg;
268         }
269         if (g_log_stream_ptr)
270         {
271             *g_log_stream_ptr << new_msg << std::flush;
272         }
273     }
274 } // eo log_msg
275
276
277 /**
278  * @brief "real" log function for part messages.
279  * @param level the log level.
280  * @param part the part(/module) name(/id)
281  * @param msg the log message.
282  *
283  * basically calls @a log(), but prepends the part (if not empty) in square brackets to the message.
284  */
285 void log_part_msg(
286     int level,
287     const std::string& part,
288     const std::string& msg)
289 {
290     // Note: Locking is done in log_msg()
291
292     if (!part.empty())
293     {
294         std::ostringstream ostr;
295         ostr << "[" << part << "] " << msg;
296         log_msg(level, ostr.str());
297     }
298     else
299     {
300         log_msg(level, msg);
301     }
302 } // eo log_part_msg(int,const std::string&,const std::string&)
303
304
305 /**
306  * @brief returns the name of the program (/binary)
307  * @return the program name if it could be determined; generated name else.
308  *
309  * Tries to determine the name of the binary.
310  *
311  * If no name could be determined, one is built.
312  */
313 std::string get_program_name()
314 {
315     // Note: Thread safe
316
317     std::string result;
318     // determine the program name:
319     {
320         // try to determine the name using the exe link:
321         std::string exe_path;
322         {
323             std::ostringstream ostr;
324             ostr << "/proc/" << ::getpid() << "/exe";
325             exe_path= ostr.str();
326         }
327         std::string binary_path= read_link(exe_path);
328         if (!binary_path.empty())
329         {
330             result= basename(binary_path);
331         }
332     }
333     if (result.empty())
334     {
335         // no program name found up to this point.
336         // make a name (as fallback solution):
337         std::ostringstream ostr;
338         ostr << "prg-" << ::getpid();
339         result= ostr.str();
340     }
341     return result;
342 } // eo get_program_name
343
344
345
346 void _cleanup()
347 {
348     // Note: Locking is done in close_syslog();
349     close_syslog();
350     //TODO other cleanups?
351 } // _cleanup
352
353
354 class __Initializer
355 {
356     public:
357         __Initializer()
358         {
359             std::atexit( _cleanup );
360         }
361 } __initialize;
362
363
364 } // eo namespace <anonymous>
365
366
367
368 /*
369 ** implementation of Facility
370 */
371
372
373 const int Facility::AuthPriv= LOG_AUTH;
374 const int Facility::Cron    = LOG_CRON;
375 const int Facility::Daemon  = LOG_DAEMON;
376 const int Facility::Kern    = LOG_KERN;
377 const int Facility::Mail    = LOG_MAIL;
378 const int Facility::News    = LOG_NEWS;
379 const int Facility::Syslog  = LOG_SYSLOG;
380 const int Facility::User    = LOG_USER;
381 const int Facility::UUCP    = LOG_UUCP;
382 const int Facility::Local0  = LOG_LOCAL0;
383 const int Facility::Local1  = LOG_LOCAL1;
384 const int Facility::Local2  = LOG_LOCAL2;
385 const int Facility::Local3  = LOG_LOCAL3;
386 const int Facility::Local4  = LOG_LOCAL4;
387 const int Facility::Local5  = LOG_LOCAL5;
388 const int Facility::Local6  = LOG_LOCAL6;
389 const int Facility::Local7  = LOG_LOCAL7;
390
391
392
393 /*
394 ** implementation of PartLogger::LogHelper
395 */
396
397 PartLogger::LogHelper::LogHelper(PartLogger& logger, int level, const SourceLocation& loc)
398 : Logger(logger)
399 , Level(level)
400 , Location(loc)
401 {
402     StreamPtr.reset(new std::ostringstream());
403 } // eo PartLogger::LogHelper::LogHelper(PartLogger&,int)
404
405 PartLogger::LogHelper::LogHelper(const LogHelper& helper)
406 : Logger(helper.Logger)
407 , Level(helper.Level)
408 , Location(helper.Location)
409 , StreamPtr(helper.StreamPtr)
410 {
411 } // eo PartLogger::LogHelper::LogHelper(const LogHelper&)
412
413
414 PartLogger::LogHelper::~LogHelper()
415 {
416     if (StreamPtr.get())
417     {
418         if (Location)
419         {
420             //*m_stream_ptr << "  at " << m_loc.Line << " in " << m_loc.FunctionName;
421             *StreamPtr << " @" << Location.get_location_tag();
422         }
423         std::string msg(StreamPtr->str());
424         if (!msg.empty())
425         {
426             Logger.log(Level,msg);
427         }
428     }
429 } // eo PartLogger::LogHelper::~LogHelper
430
431
432 /*
433 ** implementation of PartLogger
434 */
435
436 /**
437  * constructor for a part logger.
438  * @param part name of the part (module name) using the logger instance.
439  */
440 PartLogger::PartLogger(const std::string& part)
441 : Part(part)
442 {
443 } // eo PartLogger::PartLogger(const std.:string&)
444
445
446 /**
447  * @brief constructor for a part logger at module level.
448  * @param loc the source location where the PartLogger is constructed.
449  *
450  * The part name is derived from the filename given with the source location by
451  * using the basename and cutting off the C++ file suffix (if it is a well known one;
452  * currently known extensions: cpp, cxx, c++, cc, C).
453  */
454 PartLogger::PartLogger( const SourceLocation& loc )
455 {
456     if (loc.Line>0 && ! loc.File.empty())
457     {
458         std::string str= basename(loc.File);
459         Part= remove_suffix(str,".cpp");
460         if (Part == str) Part= remove_suffix(str,".cxx");
461         if (Part == str) Part= remove_suffix(str,".c++");
462         if (Part == str) Part= remove_suffix(str,".cc");
463         if (Part == str) Part= remove_suffix(str,".C");
464     }
465     else
466     {
467         Part="Unknown";
468     }
469 }// PartLogger::PartLogger(const SourceLocation&)
470
471
472 PartLogger::~PartLogger()
473 {
474 }
475
476
477 /**
478  * generic log function.
479  * @param level the log level.
480  * @param msg the log message.
481  */
482 void PartLogger::log(int level, const std::string &msg)
483 {
484     boost::recursive_mutex::scoped_lock lock(LoggerLock);
485
486     if (level <= g_max_level)
487     {
488         log_part_msg(level, Part, msg);
489     }
490 } // eo PartLogger::log(int,const std::string);
491
492
493 void PartLogger::fatal(const std::string& msg)
494 {
495     log(LOG_EMERG,msg);
496 } // eo PartLogger::fatal(const std::string&)
497
498
499 void PartLogger::alert(const std::string& msg)
500 {
501     log(LOG_ALERT,msg);
502 } // eo PartLogger::alert(const std::string&)
503
504
505 void PartLogger::critical(const std::string& msg)
506 {
507     log(LOG_CRIT,msg);
508 } // eo PartLogger::critical(const std::string&)
509
510
511 void PartLogger::error(const std::string& msg)
512 {
513     log(LOG_ERR, msg);
514 } // eo PartLogger::error(const std::string&)
515
516
517 void PartLogger::warning(const std::string& msg)
518 {
519     log(LOG_WARNING, msg);
520 } // eo PartLogger::warning(const std::string&)
521
522
523 void PartLogger::notice(const std::string& msg)
524 {
525     log(LOG_NOTICE, msg);
526 } // eo PartLogger::notice(const std::string&)
527
528
529 void PartLogger::info(const std::string& msg)
530 {
531     log(LOG_INFO, msg);
532 } // eo PartLogger::info(const std::string&)
533
534
535 void PartLogger::debug(const std::string& msg)
536 {
537     log(LOG_DEBUG, msg);
538 } // eo PartLogger::debug(const std::string&)
539
540
541
542 PartLogger::LogHelper PartLogger::fatal(const SourceLocation& loc)
543 {
544     return PartLogger::LogHelper(*this,LOG_EMERG,loc);
545 } // eo PartLogger::fatal(const SourceLocation&)
546
547
548 PartLogger::LogHelper PartLogger::alert(const SourceLocation& loc)
549 {
550     return PartLogger::LogHelper(*this,LOG_ALERT,loc);
551 } // eo PartLogger::alert(const SourceLocation&)
552
553
554 PartLogger::LogHelper PartLogger::critical(const SourceLocation& loc)
555 {
556     return PartLogger::LogHelper(*this,LOG_CRIT,loc);
557 } // eo PartLogger::critical(const SourceLocation&)
558
559
560 PartLogger::LogHelper PartLogger::error(const SourceLocation& loc)
561 {
562     return PartLogger::LogHelper(*this,LOG_ERR,loc);
563 } // eo PartLogger::error(const SourceLocation&)
564
565
566 PartLogger::LogHelper PartLogger::warning(const SourceLocation& loc)
567 {
568     return PartLogger::LogHelper(*this,LOG_WARNING,loc);
569 } // eo PartLogger::warning(const SourceLocation&)
570
571
572 PartLogger::LogHelper PartLogger::notice(const SourceLocation& loc)
573 {
574     return PartLogger::LogHelper(*this,LOG_NOTICE,loc);
575 } // eo PartLogger::notice(const SourceLocation&)
576
577
578 PartLogger::LogHelper PartLogger::info(const SourceLocation& loc)
579 {
580     return PartLogger::LogHelper(*this,LOG_INFO,loc);
581 } // eo PartLogger::info(const SourceLocation&)
582
583
584 PartLogger::LogHelper PartLogger::debug(const SourceLocation& loc)
585 {
586     return PartLogger::LogHelper(*this,LOG_DEBUG,loc);
587 } // eo PartLogger::debug(const SourceLocation&)
588
589 /*
590 **
591 */
592
593
594 /**
595  * enable logging to syslog with a name and a facility.
596  * @param name the name used as ident.
597  * @param facility the facility which should be used.
598  */
599 void enable_syslog( const std::string& name, Facility facility )
600 {
601     boost::recursive_mutex::scoped_lock lock(LoggerLock);
602
603     close_syslog();
604     g_ident= name;
605     g_facility= facility;
606     open_syslog();
607 } // eo enable_syslog(const std::string,Facility)
608
609
610 /**
611  * enable logging to syslog with a facility.
612  * The ident is used from a previous call or (if none was set) is
613  * determined by reading the program path from /proc/\<pid\>/exe.
614  * @param facility the facility which should be used.
615  */
616 void enable_syslog( Facility facility )
617 {
618     boost::recursive_mutex::scoped_lock lock(LoggerLock);
619
620     if (g_ident.empty())
621     {
622         g_ident= get_program_name();
623     }
624     close_syslog();
625     g_facility = facility;
626     open_syslog();
627 } // eo enable_syslog(Facility)
628
629
630 /**
631  * enable or disable logging to syslog.
632  * @param enable whether the logging to syslog should be enabled or not.
633  */
634 void enable_syslog( bool enable )
635 {
636     boost::recursive_mutex::scoped_lock lock(LoggerLock);
637
638     if (enable)
639     {
640         if (!g_syslog_opened)
641         {
642             enable_syslog( g_facility );
643         }
644     }
645     else // ! enable
646     {
647         close_syslog();
648     }
649 } // eo enable_syslog(bool)
650
651
652 /**
653  * enable/ disable loggin to stderr.
654  * @param enable whether to enable or disable logging to stderr.
655  */
656 void enable_stderr_log(bool enable)
657 {
658     boost::recursive_mutex::scoped_lock lock(LoggerLock);
659
660     g_stderr_log= enable;
661 } // eo enableStderr;
662
663
664
665 /**
666  * enable logging to a file.
667  * @param name path to the file.
668  *
669  * @note only one log file can be use at a time.
670  */
671 void enable_log_file( const std::string& name )
672 {
673     boost::recursive_mutex::scoped_lock lock(LoggerLock);
674
675     g_log_file_name= name;
676     g_log_stream_ptr.reset( new std::ofstream() );
677     g_log_stream_ptr->open( name.c_str(), std::ios::out|std::ios::app );
678     //std::cerr << "### opened \"" << name << "\"" << g_log_stream_ptr->good() << std::endl;
679 } // eo enable_log_file(const std::string&)
680
681
682 /**
683  * enable or disable loggin to a file.
684  * if a logfile was already set by a previous call to enable_log_file(const std::string&)
685  * that one is used; else it logs to <tt>/var/log/</tt><em>program name</em><tt>.log</tt>.
686  * @param enable whether to enable or disable logging to a file.
687  */
688 void enable_log_file( bool enable )
689 {
690     boost::recursive_mutex::scoped_lock lock(LoggerLock);
691
692     if (enable)
693     {
694         if (g_log_file_name.empty())
695         {
696             std::ostringstream ostr;
697             ostr << "/var/log/" << get_program_name() << ".log";
698             enable_log_file( ostr.str() );
699         }
700         else
701         {
702             enable_log_file( g_log_file_name );
703         }
704     }
705     else // ! enable
706     {
707         g_log_stream_ptr.reset();
708     }
709 } // eo enable_log_file(bool)
710
711
712 /**
713  * @brief returns if loging to file is enabled and active.
714  * @return @a true if logfile is enabled and opened.
715  */
716 bool is_logging_to_file()
717 {
718     boost::recursive_mutex::scoped_lock lock(LoggerLock);
719
720     return g_log_stream_ptr and g_log_stream_ptr->good();
721 } // eo is_logging_to_file()
722
723
724
725 /**
726  * @brief returns the current name of the log file.
727  * @return the name of the log file; empty if none was given.
728  *
729  * This function returns the last used log file name; even
730  * when logging to that file is currently disabled.
731  */
732 std::string get_log_file_name()
733 {
734     boost::recursive_mutex::scoped_lock lock(LoggerLock);
735
736     return g_log_file_name;
737 } // eo get_log_file_name()
738
739
740 /**
741  * @brief re-opens the logfiles (if applicable).
742  */
743 void reopen()
744 {
745     boost::recursive_mutex::scoped_lock lock(LoggerLock);
746
747     if (g_log_stream_ptr)
748     {
749         enable_log_file(false); // closes log, but holds the name.
750         enable_log_file(true);  // opens the log file again.
751     }
752 } // eo reopen()
753
754
755 /**
756  * set a new log level.
757  * @param level the new log level.
758  * @return the previous log level.
759  */
760 int set_log_level(int level)
761 {
762     boost::recursive_mutex::scoped_lock lock(LoggerLock);
763
764     int previous = g_max_level;
765
766     // Sanity check
767     if (level < LogLevel::Emergency)
768       level = LogLevel::Emergency;
769     else if (level > LogLevel::Debug)
770       level = LogLevel::Debug;
771
772     g_max_level = level;
773
774     return previous;
775 } // eo set_log_level(int)
776
777
778 /**
779  * returns the current log level.
780  * @return the current log level.
781  */
782 int get_log_level()
783 {
784     boost::recursive_mutex::scoped_lock lock(LoggerLock);
785
786     return g_max_level;
787 } // eo get_log_level()
788
789
790 /**
791  * returns if the current log level covers the given level.
792  * This is a convenience function for optimization of log output (especially debug output).
793  * @param level the level which should be tested for.
794  * @return @a true iff a message with the level would be written out.
795  */
796 bool has_log_level(int level)
797 {
798     boost::recursive_mutex::scoped_lock lock(LoggerLock);
799
800     return (g_max_level >= level);
801 } // eo has_log_level(int)
802
803
804
805 } // eo namespace Logger
806 } // eo namespace I2n