sharplib/logging/Log.cs
Marc Hernandez f873c42cbf fix(logging): Account for nullable value types
This commit updates logging to handle nullable value types and parameters.

The following changes were made:
- Updated the Value record to use '!' instead of '?' for nullability.
- Added '!' to the default Value.
- Added '!= null' checks to conditional assignment.
- Removed unnecessary nullable-disable compiler directives.
2024-08-15 22:26:47 -07:00

936 lines
23 KiB
C#

using System;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Reflection;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using lib.Net;
using System.Text;
//using System.Threading.Tasks;
#nullable enable
/*
T O D O :
x) Hook the C# prints from glue.
x) Fix
x) Refactor various logs in order to do automagic structured logging
ref: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/interpolated-string-handler
D O N E:
N O T D O I N G :
*/
public record struct Value<T>( T _val, string _exp = "" )
{
public static T Default = default!;
public static implicit operator T( Value<T> v )
{
return v._val;
}
static public Value<U> Get<U>( U v,
[CallerArgumentExpression("v")]
string dbgExp = ""
)
{
return new( v, dbgExp );
}
}
public struct SourceLoc
{
readonly string _reason = "";
readonly string _dbgName = "";
readonly string _dbgPath = "";
readonly int _dbgLine = -1;
public SourceLoc( string reason, string dbgName, string dbgPath, int dbgLine )
{
_reason = reason;
_dbgName = dbgName;
_dbgPath = dbgPath;
_dbgLine = dbgLine;
}
static public SourceLoc Record(
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0
)
{
return new SourceLoc( reason, dbgName, dbgPath, dbgLine );
}
}
static public class log
{
//static
#region CLR Logging
static log()
{
log.high( $"Starting tracers" );
/*
{
var start = new ThreadStart( StartGCWatcher );
var thread = new Thread( start );
thread.Priority = ThreadPriority.BelowNormal;
thread.Name = $"Logging";
thread.Start();
}
// */
//log.info( $"Tracing.Program.CreateTracingSession" );
//var task = log.call( Tracing.Program.CreateTracingSession( false, true, 64 ) );
/*
log.info( $"Tracing.MemoryPipe" );
var memoryPipe = log.call( () => new Tracing.MemoryPipe() );
//var pipeTask = log.call( () => memoryPipe.StartAsync( true ) );
//*/
/*
{
var start = new ThreadStart( StartTracing );
var thread = new Thread( start );
thread.Priority = ThreadPriority.BelowNormal;
thread.Name = $"Logging";
thread.Start();
}
// */
}
static void StartGCWatcher()
{
while( !s_running )
{
Thread.Sleep( 10 );
}
var processId = Process.GetCurrentProcess().Id;
LogGC.PrintRuntimeGCEvents( processId );
}
static void StartTracing()
{
while( !s_running )
{
Thread.Sleep( 10 );
}
Tracing.TraceLogMonitor.Run();
}
#endregion // CLR Logging
static public Value<T> Value<T>( T val,
[CallerArgumentExpression("val")]
string dbgExp = ""
)
{
return new( val, dbgExp );
}
[Flags]
public enum LogType
{
Invalid = 0,
Trace = 1,
Debug = 2,
Info = 3,
High = 4,
Warn = 5,
Error = 6,
Fatal = 7,
Time = 64,
Raw = 65,
}
[Flags]
public enum Endpoints
{
None = 0,
File = 1 << 0,
Console = 1 << 1,
All = File | Console,
}
public struct LogEvent
{
public DateTime Time;
public LogType LogType;
public string Msg;
public string Path;
public int Line;
public string Member;
public string Exp;
public string Cat;
public object? Obj;
static ImmutableDictionary<int, string> s_shortname = ImmutableDictionary<int, string>.Empty;
public LogEvent( LogType logType, string msg, string path, int line, string member, string cat, string exp, object? obj )
{
//Cache the automatic category names
// R A R E and S L O W and S A F E
if( string.IsNullOrEmpty( cat ) )
{
var pathHash = path.GetHashCode();
if( s_shortname.TryGetValue( pathHash, out var autoCat ) )
{
cat = autoCat;
}
else
{
var pathPieces = path.Split( '\\' );
if( pathPieces.Length < 2 )
{
pathPieces = path.Split( '/' );
}
var lastDir = pathPieces[pathPieces.Length - 2];
ImmutableInterlocked.AddOrUpdate( ref s_shortname, pathHash, lastDir, ( key, value ) => { return lastDir; } );
cat = lastDir;
}
}
Time = DateTime.Now;
LogType = logType;
Msg = msg;
Path = path;
Line = line;
Member = member;
Cat = cat;
Exp = exp;
Obj = obj;
}
}
static LogEvent CreateLogEvent( LogType logType, string msg, string cat, object? obj, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", string exp = "" )
{
var logEvent = new LogEvent( logType, msg, path, line, member, cat, exp, obj );
return logEvent;
}
public delegate void Log_delegate( LogEvent evt );
static ImmutableDictionary<string, Endpoints> s_logEPforCat = ImmutableDictionary<string, Endpoints>.Empty;
static public void endpointForCat( string cat, Endpoints ep )
{
ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, (k, v) => ep );
}
static public void create( string filename, Endpoints endpoints )
{
startup( filename, endpoints );
}
static public void shutdown()
{
string msg = "==============================================================================\nLogfile shutdown at " + DateTime.Now.ToString();
var evt = CreateLogEvent( LogType.Info, msg, "System", null );
s_events.Enqueue( evt );
stop();
}
static internal ConcurrentQueue<LogEvent> s_events = new ConcurrentQueue<LogEvent>();
static private Thread s_thread = Thread.CurrentThread;
static string s_cwd = Directory.GetCurrentDirectory();
static int s_cwdLength = s_cwd.Length;
static ImmutableDictionary<int, string> s_files = ImmutableDictionary<int, string>.Empty;
#region Util
static public (string, string, int) record( [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1 )
=> (dbgName, dbgPath, dbgLine);
static public string whatFile( string path )
{
var file = "";
var pathHash = path.GetHashCode();
if( s_files.TryGetValue( pathHash, out var autoCat ) )
{
file = autoCat;
}
else
{
var pathPieces = path.Split( '\\' );
if( pathPieces.Length < 2 )
{
pathPieces = path.Split( '/' );
}
var lastDir = pathPieces[pathPieces.Length - 1];
ImmutableInterlocked.AddOrUpdate( ref s_files, pathHash, lastDir, ( key, value ) => { return lastDir; } );
file = lastDir;
}
return file;
}
static public string relativePath( string fullPath )
{
if( fullPath.Length < s_cwdLength )
return fullPath;
var rel = fullPath.Substring( s_cwdLength + 1 );
return rel;
}
static public string thisFilePath( [CallerFilePath] string path = "" )
{
return relativePath( path );
}
#endregion // Util
#region Forwards
static public T call<T>( Func<T> func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("func")] string dbgExp = "" )
{
log.info( $"Calling {dbgExp}" );
var val = func();
log.info( $"| Got {val}" );
return val;
}
static public T var<T>( T val, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("val")] string dbgExp = "" )
{
log.info( $"Called {dbgExp} Got: {val}" );
return val;
}
static public void call( Action func, [CallerMemberName] string dbgName = "", [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerArgumentExpression("func")] string dbgExp = "" )
{
log.info( $"Calling {dbgExp}" );
func();
log.info( $"| Done" );
}
static public void fatal( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Fatal, path, line, member, cat, dbgExp, obj );
}
[StackTraceHidden]
static public void error( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Error, path, line, member, cat, dbgExp, obj );
}
[StackTraceHidden]
static public void warn( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Warn, path, line, member, cat, dbgExp, obj );
}
static public void high( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.High, path, line, member, cat, dbgExp, obj );
}
static public void info( string msg, string cat = "", object? obj = null,
[CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Info, path, line, member, cat, dbgExp, obj );
}
static public void debug( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Debug, path, line, member, cat, dbgExp, obj );
}
static public void trace( string msg, string cat = "", object? obj = null, [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("msg")] string dbgExp = "" )
{
logBase( msg, LogType.Trace, path, line, member, cat, dbgExp, obj );
}
static public void inf_valueo<A>( string msg, Value<A> v1, string cat = "" )
{
}
static public void info<A>( string msg, A a, string cat = "", [CallerArgumentExpression("a")] string dbgExpA = "" )
{
}
static public void info( string msg, object a, object b, string cat = "", [CallerArgumentExpression("a")] string dbgExpA = "", [CallerArgumentExpression("a")] string dbgExpB = "" )
{
}
#endregion
#region Helpers
static public void logProps( object obj, string header, LogType type = LogType.Debug, string cat = "", string prefix = "", [CallerFilePath] string path = "", [CallerLineNumber] int line = -1, [CallerMemberName] string member = "", [CallerArgumentExpression("obj")] string dbgExpObj = "" )
{
var list = refl.GetAllProperties( obj.GetType() );
lock( s_lock )
{
var evt = new LogEvent( type, header, path, line, member, cat, dbgExpObj, obj );
{
s_events.Enqueue( evt );
foreach( var pi in list )
{
try
{
var v = pi.GetValue( obj );
logBase( $"{prefix}{pi.Name} = {v}", type, path, line, member, dbgExpObj, cat );
}
catch( Exception ex )
{
logBase( $"Exception processing {pi.Name} {ex.Message}", LogType.Error, path, line, member, cat, dbgExpObj, obj);
}
}
}
}
}
//This might seem a little odd, but the intent is that usually you wont need to set notExpectedValue.
static public void expected<T>( T value, string falseString, string trueString = "", T? notExpectedValue = default( T ) )
{
if( !object.Equals( value, notExpectedValue ) )
{
log.debug( $"Properly got {value}{trueString}" );
}
else
{
log.warn( $"Got {notExpectedValue} instead of {value}{falseString}" );
}
}
#endregion
static object s_lock = new object();
static object s_lockTypeCallback = new object();
static ImmutableDictionary<LogType, Action<LogEvent>> s_callbacks = ImmutableDictionary<LogType, Action<LogEvent>>.Empty;
[StackTraceHidden]
static public void addDirectCallback( LogType logType, Action<LogEvent> callback )
{
//ImmutableInterlocked.Add( ref s_callbacks, logType, callback );
var added = II.TryAdd( ref s_callbacks, logType, callback );
if( !added )
{
log.warn( $"Failed to add callback for {logType}" );
}
}
static public LogEvent logCreateEvent( string msg, LogType type = LogType.Debug, string path = "", int line = -1, string member = "", string cat = "unk", string exp = "", object? obj = null )
{
LogEvent evt = new LogEvent( type, msg, path, line, member, cat, exp, obj );
return evt;
}
[StackTraceHidden]
static public void logBase( string msg, LogType type = LogType.Debug, string path = "", int line = -1, string member = "", string cat = "unk", string exp = "", object? obj = null )
{
var evt = logCreateEvent( msg, type, path, line, member, cat, exp );
s_callbacks.TryGetValue( type, out var callback );
if( callback != null )
{
lock( s_lockTypeCallback )
{
callback( evt );
}
}
else
{
s_events.Enqueue( evt );
}
}
static Endpoints s_endpoints = Endpoints.Console;
static int s_catWidth = 14;
static public void startup( string filename, Endpoints endpoints )
{
if( string.IsNullOrWhiteSpace( filename ) )
{
return;
}
lock( s_lock )
{
//We're already running, so just tell folks, and jump back
if( s_running )
{
log.info( $"Already running, so this is a NOP" );
}
s_running = true;
s_startTime = DateTime.Now;
s_cwd = Directory.GetCurrentDirectory();
s_cwdLength = s_cwd.Length;
s_endpoints = endpoints;
var dir = Path.GetDirectoryName( filename );
if( dir?.Length > 0 )
{
Directory.CreateDirectory( dir );
}
s_stream = new FileStream( filename, FileMode.Append, FileAccess.Write );
s_writer = new StreamWriter( s_stream, Encoding.UTF8, 128, true );
//s_errorStream = new FileStream( filename + ".error", FileMode.Append, FileAccess.Write );
//s_errorWriter = new StreamWriter( s_errorStream );
{
var time = DateTime.Now;
// Header for this run
var blankLine = new LogEvent( LogType.Raw, $"", "", 0, "", "lib.time", "", null );
var beginLine = new LogEvent( LogType.Raw, $"Begin B E G I N ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
var timeLine = new LogEvent( LogType.Raw, $"D A T E {time.Year}/{time.Month.ToString("00")}/{time.Day.ToString("00")} T I M E {time.Hour.ToString("00")}:{time.Minute.ToString("00")}:{time.Second.ToString("00")}.{time.Millisecond.ToString("000")}{time.Microsecond.ToString("000")}", "", 0, "", "lib.time", "", null );
//writeToAll( endLine );
writeToAll( blankLine );
writeToAll( blankLine );
writeToAll( beginLine );
writeToAll( blankLine );
writeToAll( timeLine );
writeToAll( blankLine );
}
LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup BEGIN", "", 0, "", "log.startup", "", null );
writeToAll( msgStartupBegin );
LogEvent msgFilename = new LogEvent( LogType.Info, $"Logging in {filename}", "", 0, "", "log.startup", "", null );
writeToAll( msgFilename );
var start = new ThreadStart( run );
s_thread = new Thread( start );
s_thread.Priority = ThreadPriority.BelowNormal;
s_thread.Name = $"Logging";
s_thread.Start();
//info( $"Logging in {filename}" );
LogGC.RegisterObjectId( s_lock );
//Debug.Listeners.Add( this );
//var evt = CreateLogEvent( LogType.Info, $"startup", "System", null );
//s_events.Enqueue( evt );
/*
if( (endpoints & Endpoints.Console) == Endpoints.Console )
{
addDelegate(WriteToConsole);
}
*/
//LogEvent msgStartupBegin = new LogEvent( LogType.Info, $"startup END", "", 0, "", "lib.time", "", null );
//writeToAll( msgStartupBegin );
info( $"startup END", cat: "log.startup" );
}
}
static bool s_running = false;
static void run()
{
while( !s_running )
{
// TODO PERF Replace this with a semaphore/mutex
Thread.Sleep( 1 );
}
while( s_running )
{
while( s_events.TryDequeue( out var evt ) )
{
writeToAll( evt );
}
// TODO PERF Replace this with a semaphore/mutex
Thread.Sleep( 1 );
}
var endLine = new LogEvent( LogType.Raw, $"End E N D ******************************************************************************************************************", "", 0, "", "lib.time", "", null );
writeToAll( endLine );
}
public static void stop()
{
if( !s_running ) return;
s_running = false;
while( s_thread.IsAlive )
{
Thread.Sleep( 0 );
}
s_writer?.Close();
s_stream?.Close();
//s_errorWriter?.Close();
//s_errorStream?.Close();
}
static public void addDelegate( Log_delegate cb )
{
s_delegates.Add( cb );
}
public static char getSymbol( LogType type )
{
switch( type )
{
case LogType.Trace: return ' ';
case LogType.Debug: return ' ';
case LogType.Info: return ' ';
case LogType.High: return '+';
case LogType.Warn: return '+';
case LogType.Error: return '*';
case LogType.Fatal: return '*';
default: return '?';
}
}
private static void setConsoleColor( log.LogEvent evt )
{
switch( evt.LogType )
{
case log.LogType.Trace:
Console.ForegroundColor = ConsoleColor.DarkGray;
break;
case log.LogType.Debug:
Console.ForegroundColor = ConsoleColor.Gray;
break;
case log.LogType.Info:
Console.ForegroundColor = ConsoleColor.DarkGreen;
break;
case log.LogType.High:
Console.ForegroundColor = ConsoleColor.Cyan;
break;
case log.LogType.Warn:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
case log.LogType.Error:
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.BackgroundColor = ConsoleColor.DarkGray;
break;
case log.LogType.Fatal:
Console.ForegroundColor = ConsoleColor.Red;
Console.BackgroundColor = ConsoleColor.DarkGray;
break;
case log.LogType.Invalid:
case log.LogType.Time:
case log.LogType.Raw:
Console.ForegroundColor = ConsoleColor.Red;
break;
}
}
static private DateTime s_startTime = DateTime.MinValue;
static private int s_lastDisplaySeconds = -1;
static private int s_lastSecond = -1;
static private string s_timeHeader = " ";
static public string headerPrint( LogEvent evt )
{
if( evt.LogType != LogType.Raw )
{
var span = evt.Time - s_startTime;
char sym = getSymbol( evt.LogType );
var truncatedCat = evt.Cat.Substring( 0, Math.Min( s_catWidth, evt.Cat.Length ) );
var timeHdr = $"{s_timeHeader}{((int)span.TotalMinutes).ToString("000")}:{span.Seconds.ToString("D2")}.{span.Milliseconds.ToString("000")}";
var msgHdr = string.Format( $"{timeHdr} | {{0,-{s_catWidth}}}{{1}}| ", truncatedCat, sym );
return msgHdr;
}
else
{
return "";
}
}
static public string headerFile( LogEvent evt )
{
if( evt.LogType != LogType.Raw )
{
try
{
var span = evt.Time - s_startTime;
char sym = getSymbol( evt.LogType );
var truncatedCat = evt.Cat.Substring( 0, Math.Min( s_catWidth, evt.Cat.Length ) );
if( string.IsNullOrWhiteSpace( truncatedCat) ) truncatedCat = $"B R O K E N truncatedCat";
//Dont really need the year-month-day frankly.
//var timeHdr = $"{evt.Time.Year}-{evt.Time.Month.ToString("00")}-{evt.Time.Day.ToString("00")} {evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")}{evt.Time.Microsecond.ToString("000")}";
var timeHdr = $"{evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")}{evt.Time.Microsecond.ToString("000")}";
if( string.IsNullOrWhiteSpace( timeHdr) ) timeHdr = $"B R O K E N timeHdr";
var msgHdr = string.Format( $"{timeHdr} | {{0,-{s_catWidth}}}{{1}}| ", truncatedCat, sym );
if( string.IsNullOrWhiteSpace( msgHdr) ) msgHdr = $"B R O K E N msgHdr";
return msgHdr;
}
catch( Exception ex )
{
return $"Ex {ex.Message} processing msg";
}
}
else
{
// Empty for RAW
return "";
}
}
static public string msgPrint( LogEvent evt )
{
var msgHdr = headerPrint( evt );
string finalLine = $"{msgHdr}{evt.Msg}";
return finalLine;
}
static public string msgFile( LogEvent evt )
{
var msgHdr = headerFile( evt );
var msg = evt.Msg;
if( (string.IsNullOrWhiteSpace(msg) && evt.LogType != LogType.Raw) || msg.Contains( (char)0 ) )
{
msg = "B R O K E N msg";
}
string finalLine = $"{msgHdr}{msg}";
return finalLine;
}
static private void writeSpecialEvent( LogEvent evt )
{
if( ( s_endpoints & Endpoints.Console ) == Endpoints.Console )
{
//setConsoleColor( evt );
//Console.WriteLine( $"{evt.Time.ToShortTimeString()}" );
//Console.ResetColor();
}
foreach( Log_delegate cb in s_delegates )
{
cb( evt );
}
}
static private void writeToAll( LogEvent evt )
{
try
{
#region Time
var span = evt.Time - s_startTime;
var curSeconds = (int)span.TotalSeconds;
if( curSeconds - s_lastDisplaySeconds > 10 )
{
s_lastDisplaySeconds = curSeconds;
var minuteEvt = new LogEvent( LogType.Raw, $"T I M E ==> {evt.Time.Hour.ToString("00")}:{evt.Time.Minute.ToString("00")}:{evt.Time.Second.ToString("00")}.{evt.Time.Millisecond.ToString("000")} : {evt.Time.ToShortDateString()}", "", 0, "", "lib.time", "", null );
minuteEvt.Time = evt.Time;
writeSpecialEvent( minuteEvt );
}
if( evt.LogType != LogType.Raw )
{
var curSecond = (int)span.TotalSeconds;
if( s_lastSecond == curSecond )
{
s_timeHeader = " ";
}
else
{
s_timeHeader = "*";
s_lastSecond = curSecond;
}
}
#endregion
#region Write
var finalEndpoints = s_logEPforCat.TryGetValue( evt.Cat, out var ep ) ? ep & s_endpoints : s_endpoints;
if( ( finalEndpoints & Endpoints.File ) == Endpoints.File )
{
var line = msgFile( evt );
if( !string.IsNullOrWhiteSpace( line ) )
{
var trimmedLine = line.Trim( (char)0 );
s_writer?.WriteLine( trimmedLine );
}
}
if( ( finalEndpoints & Endpoints.Console ) == Endpoints.Console )
{
var line = msgPrint( evt );
if( !string.IsNullOrWhiteSpace( line ) )
{
setConsoleColor( evt );
Console.WriteLine( line );
Console.ResetColor();
}
}
//Debug.WriteLine( finalLine );
s_writer?.Flush();
foreach( Log_delegate cb in s_delegates )
{
cb( evt );
}
#endregion
}
catch( Exception ex )
{
#region Catch
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( "EXCEPTION DURING LOGGING" );
Console.WriteLine( $"Exception {ex}" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( "EXCEPTION DURING LOGGING" );
Debug.WriteLine( $"Exception {ex}" );
#endregion
}
}
public static void WriteToConsole( LogEvent evt )
{
char sym = getSymbol( evt.LogType );
var truncatedCat = evt.Cat.Substring( 0, Math.Min( 8, evt.Cat.Length ) );
string finalLine = string.Format( "{0,-8}{1}| {2}", truncatedCat, sym, evt.Msg );
Console.WriteLine( finalLine );
}
private static Stream? s_stream;
private static StreamWriter? s_writer;
//private static Stream? s_errorStream;
//private static StreamWriter? s_errorWriter;
private static ArrayList s_delegates = new ArrayList();
}