skip unit tests for handling child errors in pipestream with ancient boost
[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  * @param keep_unsafe_chars don't replace characters that are considered unsafe to log, e.g. control characters.
200  *
201  * Write the message to every enabled log channel.
202  *
203  * If syslog is enabled the message is passed unmodified to syslog.
204  *
205  * If a stream log is enabled (stderr or file) then the message is prepended with date, time
206  * and process information (like syslog does). The message is splitted at line ends and
207  * consecutive lines are indented.
208  */
209 void log_msg( int level, const std::string& msg, bool keep_unsafe_chars)
210 {
211     boost::recursive_mutex::scoped_lock lock(LoggerLock);
212
213     if (not g_syslog_opened and not g_stderr_log and not g_log_stream_ptr)
214     {
215         // if nothing is opened for logging: we activate syslog!
216         enable_syslog(true);
217     }
218
219     if (g_syslog_opened)
220     {
221         std::string sane_msg;
222         if (keep_unsafe_chars)
223             sane_msg=msg;
224         else
225             sane_msg=sanitize_for_logging(msg);
226
227         ::syslog( get_syslog_level(level), "%s", sane_msg.c_str());
228     }
229     // file(/stream) logging:
230     if (g_stderr_log or g_log_stream_ptr) // add more log "enabled" expressions here...
231     {
232         // here we need must do something more with the msg...
233         std::string new_msg;
234         std::string prefix;
235         {
236             std::ostringstream ostr;
237             // add time stamp (syslog like: "Mon DD HH:MM:SS") :
238             {
239                 time_t t = time(NULL);
240                 char buffer[32];
241                 struct tm ta;
242                 if (localtime_r(&t, &ta) == NULL)
243                     memset(&ta, 0, sizeof(struct tm));
244
245                 std::strftime(buffer, sizeof(buffer),"%b %d %H:%M:%S ", &ta);
246                 ostr << buffer;
247             }
248             ostr << get_level_tag(level) << " ";
249             ostr << g_ident << "[" << getpid() << "]: ";
250             prefix= ostr.str();
251         }
252         {
253             {
254                 std::string indent_string(prefix.size(), ' ');
255                 std::list< std::string > parts;
256                 split_string( chomp(msg,"\n"), parts, "\n");
257                 std::ostringstream ostr;
258                 ostr << prefix;
259                 for(std::list< std::string >::const_iterator it= parts.begin();
260                     it != parts.end();
261                     ++it)
262                 {
263                     if (it != parts.begin())
264                     {
265                         ostr << indent_string;
266                     }
267
268                     if (keep_unsafe_chars)
269                         ostr << *it << std::endl;
270                     else
271                         ostr << sanitize_for_logging(*it) << std::endl;
272                 }
273                 new_msg= ostr.str();
274             }
275         }
276         if (g_stderr_log)
277         {
278             std::cerr << new_msg;
279         }
280         if (g_log_stream_ptr)
281         {
282             *g_log_stream_ptr << new_msg << std::flush;
283         }
284     }
285 } // eo log_msg
286
287
288 /**
289  * @brief "real" log function for part messages.
290  * @param level the log level.
291  * @param part the part(/module) name(/id)
292  * @param msg the log message.
293  * @param keep_unsafe_chars don't replace characters that are considered unsafe to log, e.g. control characters.
294  *
295  * basically calls @a log(), but prepends the part (if not empty) in square brackets to the message.
296  */
297 void log_part_msg(
298     int level,
299     const std::string& part,
300     const std::string& msg,
301     bool keep_unsafe_chars)
302 {
303     // Note: Locking is done in log_msg()
304
305     if (!part.empty())
306     {
307         std::ostringstream ostr;
308         ostr << "[" << part << "] " << msg;
309         log_msg(level, ostr.str(), keep_unsafe_chars);
310     }
311     else
312     {
313         log_msg(level, msg, keep_unsafe_chars);
314     }
315 } // eo log_part_msg(int,const std::string&,const std::string&)
316
317
318 /**
319  * @brief returns the name of the program (/binary)
320  * @return the program name if it could be determined; generated name else.
321  *
322  * Tries to determine the name of the binary.
323  *
324  * If no name could be determined, one is built.
325  */
326 std::string get_program_name()
327 {
328     // Note: Thread safe
329
330     std::string result;
331     // determine the program name:
332     {
333         // try to determine the name using the exe link:
334         std::string exe_path;
335         {
336             std::ostringstream ostr;
337             ostr << "/proc/" << ::getpid() << "/exe";
338             exe_path= ostr.str();
339         }
340         std::string binary_path= read_link(exe_path);
341         if (!binary_path.empty())
342         {
343             result= basename(binary_path);
344         }
345     }
346     if (result.empty())
347     {
348         // no program name found up to this point.
349         // make a name (as fallback solution):
350         std::ostringstream ostr;
351         ostr << "prg-" << ::getpid();
352         result= ostr.str();
353     }
354     return result;
355 } // eo get_program_name
356
357
358
359 void _cleanup()
360 {
361     // Note: Locking is done in close_syslog();
362     close_syslog();
363     //TODO other cleanups?
364 } // _cleanup
365
366
367 class __Initializer
368 {
369     public:
370         __Initializer()
371         {
372             std::atexit( _cleanup );
373         }
374 } __initialize;
375
376
377 } // eo namespace <anonymous>
378
379
380
381 /*
382 ** implementation of Facility
383 */
384
385
386 const int Facility::AuthPriv= LOG_AUTH;
387 const int Facility::Cron    = LOG_CRON;
388 const int Facility::Daemon  = LOG_DAEMON;
389 const int Facility::Kern    = LOG_KERN;
390 const int Facility::Mail    = LOG_MAIL;
391 const int Facility::News    = LOG_NEWS;
392 const int Facility::Syslog  = LOG_SYSLOG;
393 const int Facility::User    = LOG_USER;
394 const int Facility::UUCP    = LOG_UUCP;
395 const int Facility::Local0  = LOG_LOCAL0;
396 const int Facility::Local1  = LOG_LOCAL1;
397 const int Facility::Local2  = LOG_LOCAL2;
398 const int Facility::Local3  = LOG_LOCAL3;
399 const int Facility::Local4  = LOG_LOCAL4;
400 const int Facility::Local5  = LOG_LOCAL5;
401 const int Facility::Local6  = LOG_LOCAL6;
402 const int Facility::Local7  = LOG_LOCAL7;
403
404
405
406 /*
407 ** implementation of PartLogger::LogHelper
408 */
409
410 PartLogger::LogHelper::LogHelper(PartLogger& logger, int level, const SourceLocation& loc)
411 : Logger(logger)
412 , Level(level)
413 , Location(loc)
414 {
415     StreamPtr.reset(new std::ostringstream());
416 } // eo PartLogger::LogHelper::LogHelper(PartLogger&,int)
417
418 PartLogger::LogHelper::LogHelper(const LogHelper& helper)
419 : Logger(helper.Logger)
420 , Level(helper.Level)
421 , Location(helper.Location)
422 , StreamPtr(helper.StreamPtr)
423 {
424 } // eo PartLogger::LogHelper::LogHelper(const LogHelper&)
425
426
427 PartLogger::LogHelper::~LogHelper()
428 {
429     if (StreamPtr.get())
430     {
431         if (Location)
432         {
433             //*m_stream_ptr << "  at " << m_loc.Line << " in " << m_loc.FunctionName;
434             *StreamPtr << " @" << Location.get_location_tag();
435         }
436         std::string msg(StreamPtr->str());
437         if (!msg.empty())
438         {
439             Logger.log(Level,msg);
440         }
441     }
442 } // eo PartLogger::LogHelper::~LogHelper
443
444
445 /*
446 ** implementation of PartLogger
447 */
448
449 /**
450  * constructor for a part logger.
451  * @param part name of the part (module name) using the logger instance.
452  */
453 PartLogger::PartLogger(const std::string& part)
454 : Part(part), KeepUnsafeChars(false)
455 {
456 } // eo PartLogger::PartLogger(const std.:string&)
457
458
459 /**
460  * @brief constructor for a part logger at module level.
461  * @param loc the source location where the PartLogger is constructed.
462  *
463  * The part name is derived from the filename given with the source location by
464  * using the basename and cutting off the C++ file suffix (if it is a well known one;
465  * currently known extensions: cpp, cxx, c++, cc, C).
466  */
467 PartLogger::PartLogger( const SourceLocation& loc )
468 : KeepUnsafeChars(false)
469 {
470     if (loc.Line>0 && ! loc.File.empty())
471     {
472         std::string str= basename(loc.File);
473         Part= remove_suffix(str,".cpp");
474         if (Part == str) Part= remove_suffix(str,".cxx");
475         if (Part == str) Part= remove_suffix(str,".c++");
476         if (Part == str) Part= remove_suffix(str,".cc");
477         if (Part == str) Part= remove_suffix(str,".C");
478     }
479     else
480     {
481         Part="Unknown";
482     }
483 }// PartLogger::PartLogger(const SourceLocation&)
484
485
486 PartLogger::~PartLogger()
487 {
488 }
489
490
491 /**
492  * generic log function.
493  * @param level the log level.
494  * @param msg the log message.
495  */
496 void PartLogger::log(int level, const std::string &msg, bool keep_unsafe_chars)
497 {
498     boost::recursive_mutex::scoped_lock lock(LoggerLock);
499
500     if (level <= g_max_level)
501     {
502         log_part_msg(level, Part, msg, keep_unsafe_chars);
503     }
504 } // eo PartLogger::log(int,const std::string);
505
506
507 void PartLogger::fatal(const std::string& msg)
508 {
509     log(LOG_EMERG,msg,KeepUnsafeChars);
510 } // eo PartLogger::fatal(const std::string&)
511
512
513 void PartLogger::alert(const std::string& msg)
514 {
515     log(LOG_ALERT,msg,KeepUnsafeChars);
516 } // eo PartLogger::alert(const std::string&)
517
518
519 void PartLogger::critical(const std::string& msg)
520 {
521     log(LOG_CRIT,msg,KeepUnsafeChars);
522 } // eo PartLogger::critical(const std::string&)
523
524
525 void PartLogger::error(const std::string& msg)
526 {
527     log(LOG_ERR, msg,KeepUnsafeChars);
528 } // eo PartLogger::error(const std::string&)
529
530
531 void PartLogger::warning(const std::string& msg)
532 {
533     log(LOG_WARNING, msg,KeepUnsafeChars);
534 } // eo PartLogger::warning(const std::string&)
535
536
537 void PartLogger::notice(const std::string& msg)
538 {
539     log(LOG_NOTICE, msg,KeepUnsafeChars);
540 } // eo PartLogger::notice(const std::string&)
541
542
543 void PartLogger::info(const std::string& msg)
544 {
545     log(LOG_INFO, msg,KeepUnsafeChars);
546 } // eo PartLogger::info(const std::string&)
547
548
549 void PartLogger::debug(const std::string& msg)
550 {
551     log(LOG_DEBUG, msg,KeepUnsafeChars);
552 } // eo PartLogger::debug(const std::string&)
553
554 void PartLogger::set_keep_unsafe_chars(bool _keep_unsafe_chars)
555 {
556     KeepUnsafeChars=_keep_unsafe_chars;
557 }
558
559 bool PartLogger::get_keep_unsafe_chars()
560 {
561     return KeepUnsafeChars;
562 }
563
564 PartLogger::LogHelper PartLogger::fatal(const SourceLocation& loc)
565 {
566     return PartLogger::LogHelper(*this,LOG_EMERG,loc);
567 } // eo PartLogger::fatal(const SourceLocation&)
568
569
570 PartLogger::LogHelper PartLogger::alert(const SourceLocation& loc)
571 {
572     return PartLogger::LogHelper(*this,LOG_ALERT,loc);
573 } // eo PartLogger::alert(const SourceLocation&)
574
575
576 PartLogger::LogHelper PartLogger::critical(const SourceLocation& loc)
577 {
578     return PartLogger::LogHelper(*this,LOG_CRIT,loc);
579 } // eo PartLogger::critical(const SourceLocation&)
580
581
582 PartLogger::LogHelper PartLogger::error(const SourceLocation& loc)
583 {
584     return PartLogger::LogHelper(*this,LOG_ERR,loc);
585 } // eo PartLogger::error(const SourceLocation&)
586
587
588 PartLogger::LogHelper PartLogger::warning(const SourceLocation& loc)
589 {
590     return PartLogger::LogHelper(*this,LOG_WARNING,loc);
591 } // eo PartLogger::warning(const SourceLocation&)
592
593
594 PartLogger::LogHelper PartLogger::notice(const SourceLocation& loc)
595 {
596     return PartLogger::LogHelper(*this,LOG_NOTICE,loc);
597 } // eo PartLogger::notice(const SourceLocation&)
598
599
600 PartLogger::LogHelper PartLogger::info(const SourceLocation& loc)
601 {
602     return PartLogger::LogHelper(*this,LOG_INFO,loc);
603 } // eo PartLogger::info(const SourceLocation&)
604
605
606 PartLogger::LogHelper PartLogger::debug(const SourceLocation& loc)
607 {
608     return PartLogger::LogHelper(*this,LOG_DEBUG,loc);
609 } // eo PartLogger::debug(const SourceLocation&)
610
611 /*
612 **
613 */
614
615
616 /**
617  * enable logging to syslog with a name and a facility.
618  * @param name the name used as ident.
619  * @param facility the facility which should be used.
620  */
621 void enable_syslog( const std::string& name, Facility facility )
622 {
623     boost::recursive_mutex::scoped_lock lock(LoggerLock);
624
625     close_syslog();
626     g_ident= name;
627     g_facility= facility;
628     open_syslog();
629 } // eo enable_syslog(const std::string,Facility)
630
631
632 /**
633  * enable logging to syslog with a facility.
634  * The ident is used from a previous call or (if none was set) is
635  * determined by reading the program path from /proc/\<pid\>/exe.
636  * @param facility the facility which should be used.
637  */
638 void enable_syslog( Facility facility )
639 {
640     boost::recursive_mutex::scoped_lock lock(LoggerLock);
641
642     if (g_ident.empty())
643     {
644         g_ident= get_program_name();
645     }
646     close_syslog();
647     g_facility = facility;
648     open_syslog();
649 } // eo enable_syslog(Facility)
650
651
652 /**
653  * enable or disable logging to syslog.
654  * @param enable whether the logging to syslog should be enabled or not.
655  */
656 void enable_syslog( bool enable )
657 {
658     boost::recursive_mutex::scoped_lock lock(LoggerLock);
659
660     if (enable)
661     {
662         if (!g_syslog_opened)
663         {
664             enable_syslog( g_facility );
665         }
666     }
667     else // ! enable
668     {
669         close_syslog();
670     }
671 } // eo enable_syslog(bool)
672
673
674 /**
675  * enable/ disable loggin to stderr.
676  * @param enable whether to enable or disable logging to stderr.
677  */
678 void enable_stderr_log(bool enable)
679 {
680     boost::recursive_mutex::scoped_lock lock(LoggerLock);
681
682     g_stderr_log= enable;
683 } // eo enableStderr;
684
685
686
687 /**
688  * enable logging to a file.
689  * @param name path to the file.
690  *
691  * @note only one log file can be use at a time.
692  */
693 void enable_log_file( const std::string& name )
694 {
695     boost::recursive_mutex::scoped_lock lock(LoggerLock);
696
697     g_log_file_name= name;
698     g_log_stream_ptr.reset( new std::ofstream() );
699     g_log_stream_ptr->open( name.c_str(), std::ios::out|std::ios::app );
700     //std::cerr << "### opened \"" << name << "\"" << g_log_stream_ptr->good() << std::endl;
701 } // eo enable_log_file(const std::string&)
702
703
704 /**
705  * enable or disable loggin to a file.
706  * if a logfile was already set by a previous call to enable_log_file(const std::string&)
707  * that one is used; else it logs to <tt>/var/log/</tt><em>program name</em><tt>.log</tt>.
708  * @param enable whether to enable or disable logging to a file.
709  */
710 void enable_log_file( bool enable )
711 {
712     boost::recursive_mutex::scoped_lock lock(LoggerLock);
713
714     if (enable)
715     {
716         if (g_log_file_name.empty())
717         {
718             std::ostringstream ostr;
719             ostr << "/var/log/" << get_program_name() << ".log";
720             enable_log_file( ostr.str() );
721         }
722         else
723         {
724             enable_log_file( g_log_file_name );
725         }
726     }
727     else // ! enable
728     {
729         g_log_stream_ptr.reset();
730     }
731 } // eo enable_log_file(bool)
732
733
734 /**
735  * @brief returns if loging to file is enabled and active.
736  * @return @a true if logfile is enabled and opened.
737  */
738 bool is_logging_to_file()
739 {
740     boost::recursive_mutex::scoped_lock lock(LoggerLock);
741
742     return g_log_stream_ptr and g_log_stream_ptr->good();
743 } // eo is_logging_to_file()
744
745
746
747 /**
748  * @brief returns the current name of the log file.
749  * @return the name of the log file; empty if none was given.
750  *
751  * This function returns the last used log file name; even
752  * when logging to that file is currently disabled.
753  */
754 std::string get_log_file_name()
755 {
756     boost::recursive_mutex::scoped_lock lock(LoggerLock);
757
758     return g_log_file_name;
759 } // eo get_log_file_name()
760
761
762 /**
763  * @brief re-opens the logfiles (if applicable).
764  */
765 void reopen()
766 {
767     boost::recursive_mutex::scoped_lock lock(LoggerLock);
768
769     if (g_log_stream_ptr)
770     {
771         enable_log_file(false); // closes log, but holds the name.
772         enable_log_file(true);  // opens the log file again.
773     }
774 } // eo reopen()
775
776
777 /**
778  * set a new log level.
779  * @param level the new log level.
780  * @return the previous log level.
781  */
782 int set_log_level(int level)
783 {
784     boost::recursive_mutex::scoped_lock lock(LoggerLock);
785
786     int previous = g_max_level;
787
788     // Sanity check
789     if (level < LogLevel::Emergency)
790       level = LogLevel::Emergency;
791     else if (level > LogLevel::Debug)
792       level = LogLevel::Debug;
793
794     g_max_level = level;
795
796     return previous;
797 } // eo set_log_level(int)
798
799
800 /**
801  * returns the current log level.
802  * @return the current log level.
803  */
804 int get_log_level()
805 {
806     boost::recursive_mutex::scoped_lock lock(LoggerLock);
807
808     return g_max_level;
809 } // eo get_log_level()
810
811
812 /**
813  * returns if the current log level covers the given level.
814  * This is a convenience function for optimization of log output (especially debug output).
815  * @param level the level which should be tested for.
816  * @return @a true iff a message with the level would be written out.
817  */
818 bool has_log_level(int level)
819 {
820     boost::recursive_mutex::scoped_lock lock(LoggerLock);
821
822     return (g_max_level >= level);
823 } // eo has_log_level(int)
824
825
826
827 /**
828  * returns a string representation of the current log level.
829  * @return a string corresponding to get_log_level
830  */
831 std::string get_log_level_string()
832 {
833     int level = get_log_level();
834
835     if (level < 0)
836         return "Emergency";
837     else if (level >= LogLevel::_LogLevel_END)
838         return "Debug";
839     else
840     {
841         switch (level)
842         {
843             case LogLevel::Debug:     return "Debug";     break;
844             case LogLevel::Info:      return "Info";      break;
845             case LogLevel::Notice:    return "Notice";    break;
846             case LogLevel::Warning:   return "Warning";   break;
847             case LogLevel::Error:     return "Error";     break;
848             case LogLevel::Critical:  return "Critical";  break;
849             case LogLevel::Alert:     return "Alert";     break;
850             case LogLevel::Emergency: return "Emergency"; break;
851             default:               return "Non-standard"; break;
852         }
853     }
854 } // eo get_log_level_string
855
856
857 } // eo namespace Logger
858 } // eo namespace I2n