Files
sbox-public/engine/Sandbox.System/Logging/Logging.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

216 lines
5.3 KiB
C#

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<string, LogLevel> Rules = new();
static Dictionary<int, bool> RuleCache = new();
/// <summary>
/// Return true if we should print this log entry. Use a cache to avoid craziness.
/// </summary>
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<LogEvent> OnMessage;
internal static Action<Exception> OnException;
static Channel<LogEvent> QueuedMessages = Channel.CreateUnbounded<LogEvent>();
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 );
}
/// <summary>
/// Keep a list of loggers
/// </summary>
public static HashSet<string> 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 );
}
}