using NLog; using System.Threading.Channels; namespace Sandbox.Diagnostics; internal static partial class Logging { static bool _initialized; internal static void InitializeConfig() { if ( _initialized ) return; _initialized = true; var config = new NLog.Config.LoggingConfiguration(); var game_target = new GameLog(); NLog.LogManager.Setup().SetupExtensions( s => { s.RegisterLayoutRenderer( "nicestack", ( logEvent ) => { var frames = logEvent.StackTrace.GetFrames().Skip( 1 ).Take( 10 ).Where( x => x.GetMethod().DeclaringType.Name != "Logger" ); var stack = string.Join( "\n", frames.Select( x => $"\t\t{x.GetMethod()?.DeclaringType?.Name}.{x.GetMethod()?.Name} - {x.GetFileName()}:{x.GetFileLineNumber()}" ) ); if ( stack.StartsWith( "\t\tEngineLoop.Print - " ) ) return ""; return $"\n{stack}\n"; } ); } ); var appName = Process.GetCurrentProcess().ProcessName.Split( '.' )[0]; var gamePath = System.Environment.GetEnvironmentVariable( "FACEPUNCH_ENGINE", EnvironmentVariableTarget.User ); gamePath ??= AppContext.BaseDirectory; var file_target = new NLog.Targets.FileTarget { FileName = System.IO.Path.Combine( gamePath, $"logs/{appName}.log" ), ArchiveOldFileOnStartup = true, OpenFileCacheSize = 10, MaxArchiveFiles = 10, KeepFileOpen = true, //DeleteOldFileOnStartup = true, //Layout = "${date:format=yyyy/MM/dd HH\\:mm\\:ss.ffff}\t[${logger}] ${message}\t${exception:format=ToString}${nicestack}" Layout = "${date:format=yyyy/MM/dd HH\\:mm\\:ss.ffff}\t[${logger}] ${message}\t${exception:format=ToString}" }; // // Targets // config.AddTarget( "file", file_target ); config.AddTarget( "console", game_target ); //config.AddTarget( "null", new NLog.Targets.NullTarget() ); config.LoggingRules.Clear(); // // Create a logging rule that captures everything // { var rule = new NLog.Config.LoggingRule( "global" ); rule.LoggerNamePattern = "*"; rule.EnableLoggingForLevels( NLog.LogLevel.Trace, NLog.LogLevel.Fatal ); rule.Targets.Add( file_target ); rule.Targets.Add( game_target ); //rule.Filters.Add( new WhenMethodFilter( TestLogFilter ) ); config.LoggingRules.Add( rule ); } NLog.LogManager.Configuration = config; // // When we quit, shut nlog down // AppDomain.CurrentDomain.ProcessExit += ( x, y ) => { NLog.LogManager.Shutdown(); }; SetRule( "*", LogLevel.Info ); } // // Garry: I imagine at some point we'll expose rules in a way where we can choose which systems // are which levels. Right now that seems like overkill - so I'm just exposing the ability to change // verbosity globally. This is complicated by the fact that I want to pull in all the engine logging // system too - hence the OnRulesChanged callback, so we can call shit in the sandbox.engine.dll on change. // public static LogLevel GetDefaultLevel() { if ( Rules.TryGetValue( "*", out var r ) ) return r; return LogLevel.Info; } public static void SetRule( string wildcard, LogLevel minimumLevel ) { Rules[wildcard] = minimumLevel; RuleCache.Clear(); } internal static Dictionary Rules = new(); static Dictionary RuleCache = new(); /// /// Return true if we should print this log entry. Use a cache to avoid craziness. /// public static bool ShouldLog( string loggerName, LogLevel level ) { lock ( RuleCache ) { var hash = HashCode.Combine( loggerName, level ); if ( RuleCache.TryGetValue( hash, out var should ) ) return should; should = WorkOutShouldLog( loggerName, level ); RuleCache[hash] = should; return should; } } static bool WorkOutShouldLog( string loggerName, LogLevel level ) { foreach ( var v in Rules.OrderByDescending( x => x.Key.Length ) ) { if ( loggerName.WildcardMatch( v.Key ) && level >= v.Value ) return true; } return false; } // Todo - turn off logging? public static bool Enabled { get; set; } = true; internal static bool PrintToConsole { get; set; } = true; internal static event Action OnMessage; internal static Action OnException; static Channel QueuedMessages = Channel.CreateUnbounded(); private static int callDepth = 0; internal static void Write( in LogEvent e ) { if ( ThreadSafe.IsMainThread && callDepth < 3 ) { try { callDepth++; OnMessage?.Invoke( e ); } finally { callDepth--; } } else { QueuedMessages.Writer.TryWrite( e ); } } internal static void PushQueuedMessages() { ThreadSafe.AssertIsMainThread(); while ( QueuedMessages.Reader.TryRead( out var msg ) ) { try { OnMessage?.Invoke( msg ); } catch ( System.Exception e ) { Log.Error( e ); } } } public static Logger GetLogger( string name = null ) { if ( name == null ) { var frame = new System.Diagnostics.StackFrame( 1, false ); var method = frame.GetMethod(); name = method.DeclaringType.Name; } return new Logger( name ); } /// /// Keep a list of loggers /// public static HashSet Loggers = new( StringComparer.OrdinalIgnoreCase ); internal static void RegisterEngineLogger( int id, string v ) { v = $"engine/{v}"; Loggers.Add( v ); } internal static void RegisterLogger( string name ) { Loggers.Add( name ); } }