/* ANY-LICENSE V1.0 ---------------- You can use these files under any license approved by the Open Source Initiative, preferrably one of the popular licenses, as long as the license you choose is compatible to the dependencies of these files. See http://www.opensource.org/licenses/ for a list of approved licenses. Author: Martin Furter Project: borgnet, borg.ch C# classes and examples. Modified: 2019 A simple logging system. */ using BorgCh; using System; using System.Collections.Generic; using System.IO; using BorgCh.Hacks; namespace BorgCh { public enum LogLevel { ALL = 0, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, CRITICAL, NONE } public abstract class LogSink { protected abstract void log( String message ); protected void log( LogSink parent, String message ) { parent.log( message ); } } public abstract class LogSinkOutput : LogSink { } public class LogSinkNull : LogSinkOutput { public LogSinkNull() { } protected override void log( String message ) { } } public class LogSinkConsole : LogSinkOutput { public LogSinkConsole() { } protected override void log( String message ) { Console.WriteLine( message ); } } public class LogSinkFile : LogSinkOutput { String filename; StreamWriter? writer; private int lineCntMax; private int lineCnt; public LogSinkFile( String _filename, int maxLines=10000 ) { filename = _filename; lineCntMax = maxLines; reopen(); } private void reopen() { bool replace = false; try { if( writer != null ) { StreamWriter w = writer; writer = null; replace = true; w.Close(); } } catch( Exception ) { } try { if( replace ) { Replace.replace( filename, filename+".old" ); } } catch( Exception ) { } try { writer = new StreamWriter( filename ); } catch( Exception ) { writer = null; } } protected override void log( String message ) { lineCnt++; if( writer != null ) { writer.WriteLine( message ); writer.Flush(); } if( lineCnt >= lineCntMax ) { lineCnt = 0; reopen(); } } } public class Logger : LogSink { private static Logger? top; private static SortedList loggers = new SortedList(); private static LogLevel default_log_level = LogLevel.WARNING; private String name; private LogSink parent; private LogLevel log_level; public static Logger get_logger( String class_name ) { return get_logger( class_name, default_log_level ); } public static Logger get_logger( String class_name, LogLevel level ) { if( !loggers.ContainsKey( class_name ) ) { create_logger( class_name, level ); } return loggers[class_name]; } private static void create_logger( String class_name, LogLevel level ) { Logger logger; if( class_name.Length == 0 ) { // logger = new Logger( "", new LogSinkConsole(), level ); logger = new Logger( "", new LogSinkNull(), level ); top = logger; } else { int dot = class_name.LastIndexOf( '.' ); String parent_name; if( dot < 0 ) { parent_name = ""; } else if( dot == 0 ) { throw new Exception( "Parent class name must not be empty." ); } else if( dot >= (class_name.Length-1) ) { throw new Exception( "Class name must not be empty." ); } else { parent_name = class_name.Substring( 0, dot ); } Logger pl = get_logger( parent_name, default_log_level ); logger = new Logger( class_name, pl, level ); } loggers.Add( class_name, logger ); } public static void set_default_level( LogLevel level ) { default_log_level = level; } public static void set_sink( LogSink sink ) { if( null != (LogSinkOutput)sink ) { Logger l = get_logger( "" ); l.parent = sink; } } public static void log_to_console() { set_sink( new LogSinkConsole() ); } public static void log_to_file( String filename, int maxlines=10000 ) { set_sink( new LogSinkFile( filename, maxlines ) ); } public void set_level( String class_name, LogLevel level, bool recursive=false ) { int i = loggers.IndexOfKey( class_name ); if( i < 0 ) { return; } loggers.Values[i].log_level = level; if( !recursive ) { return; } if( class_name.Length > 0 ) { class_name += "."; } for( i++; i < loggers.Count; i++ ) { Logger l = loggers.Values[i]; if( !l.name.StartsWith( class_name ) ) { break; } l.log_level = level; } } private Logger( String class_name, LogSink _parent, LogLevel level ) { name = class_name; parent = _parent; log_level = level; } public Logger get_sub_logger( String class_name ) { return get_sub_logger( name, default_log_level ); } public Logger get_sub_logger( String class_name, LogLevel level ) { if( name.Length > 0 ) { class_name = name + "." + class_name; } return get_logger( class_name, level ); } public bool is_on( LogLevel level ) { return level >= log_level; } public static String level2string( LogLevel level ) { switch( level ) { case LogLevel.ALL: return "ALL"; case LogLevel.CRITICAL: return "CRITICAL"; case LogLevel.DEBUG1: return "DEBUG1"; case LogLevel.DEBUG2: return "DEBUG2"; case LogLevel.DEBUG3: return "DEBUG3"; case LogLevel.ERROR: return "ERROR"; case LogLevel.INFO: return "INFO"; case LogLevel.NONE: return "NONE"; case LogLevel.NOTICE: return "NOTICE"; case LogLevel.WARNING: return "WARNING"; default: return "unknown"; } } public void log( LogLevel level, string msg, params object[] args ) { if( is_on( level ) ) { DateTime dt = DateTime.UtcNow; // https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings // String fmt = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff': '"; String fmt = "HH':'mm':'ss'.'fff"; String time = dt.ToString( fmt ); String levstr = level2string( level ); //Console.WriteLine( time + String.Format( msg, args ) ); //log( time + String.Format( msg, args ) ); log( String.Format( "{0} {1} {2}: {3}", time, levstr, name, String.Format( msg, args ) ) ); } } public void log( LogLevel level, Exception exc ) { log( level, exc.ToString() ); } public void log_array( LogLevel level, String message, byte[] data, int offset=0, int length=-1 ) { string dstr = ""; /* foreach( byte d in data ) { dstr += String.Format( " {0:X02}", d ); } */ if( offset >= data.Length ) { length = 0; } else if( length == -1 || (offset + length) > data.Length ) { length = data.Length - offset; } while( length > 0 ) { dstr += String.Format( " {0:X02}", data[offset] ); offset++; length--; } log( level, String.Format( message, dstr ) ); } public void log_array( LogLevel level, String message, ushort[] data, int offset=0, int length=-1 ) { string dstr = ""; if( offset >= data.Length ) { length = 0; } else if( length == -1 || (offset + length) > data.Length ) { length = data.Length - offset; } while( length > 0 ) { dstr += String.Format( " {0:X04}", data[offset] ); offset++; length--; } log( level, String.Format( message, dstr ) ); } protected override void log( String message ) { log( parent, message ); } public void set_level( LogLevel level ) { log_level = level; } } } // namespace BorgCh