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