Logger.cs
1 using Ryujinx.Common.Logging.Targets; 2 using Ryujinx.Common.SystemInterop; 3 using System; 4 using System.Collections.Generic; 5 using System.Diagnostics; 6 using System.IO; 7 using System.Runtime.CompilerServices; 8 using System.Threading; 9 10 namespace Ryujinx.Common.Logging 11 { 12 public static class Logger 13 { 14 private static readonly Stopwatch _time; 15 16 private static readonly bool[] _enabledClasses; 17 18 private static readonly List<ILogTarget> _logTargets; 19 20 private static readonly StdErrAdapter _stdErrAdapter; 21 22 public static event EventHandler<LogEventArgs> Updated; 23 24 public readonly struct Log 25 { 26 private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 27 private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]"); 28 29 internal readonly LogLevel Level; 30 31 internal Log(LogLevel level) 32 { 33 Level = level; 34 } 35 36 [MethodImpl(MethodImplOptions.AggressiveInlining)] 37 public void PrintMsg(LogClass logClass, string message) 38 { 39 if (_enabledClasses[(int)logClass]) 40 { 41 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message))); 42 } 43 } 44 45 [MethodImpl(MethodImplOptions.AggressiveInlining)] 46 public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "") 47 { 48 if (_enabledClasses[(int)logClass]) 49 { 50 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message))); 51 } 52 } 53 54 [MethodImpl(MethodImplOptions.AggressiveInlining)] 55 public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") 56 { 57 if (_enabledClasses[(int)logClass]) 58 { 59 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data)); 60 } 61 } 62 63 [StackTraceHidden] 64 [MethodImpl(MethodImplOptions.AggressiveInlining)] 65 public void PrintStack(LogClass logClass, string message, [CallerMemberName] string caller = "") 66 { 67 if (_enabledClasses[(int)logClass]) 68 { 69 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), new StackTrace(true))); 70 } 71 } 72 73 [MethodImpl(MethodImplOptions.AggressiveInlining)] 74 public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "") 75 { 76 if (_enabledClasses[(int)logClass]) 77 { 78 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message))); 79 } 80 } 81 82 [MethodImpl(MethodImplOptions.AggressiveInlining)] 83 public void PrintStub(LogClass logClass, object data, [CallerMemberName] string caller = "") 84 { 85 if (_enabledClasses[(int)logClass]) 86 { 87 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed."), data)); 88 } 89 } 90 91 [MethodImpl(MethodImplOptions.AggressiveInlining)] 92 public void PrintStub(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") 93 { 94 if (_enabledClasses[(int)logClass]) 95 { 96 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data)); 97 } 98 } 99 100 [MethodImpl(MethodImplOptions.AggressiveInlining)] 101 public void PrintRawMsg(string message) 102 { 103 Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, message)); 104 } 105 106 [MethodImpl(MethodImplOptions.AggressiveInlining)] 107 private static string FormatMessage(LogClass logClass, string caller, string message) 108 { 109 message = message.Replace(_homeDir, _homeDirRedacted); 110 111 return $"{logClass} {caller}: {message}"; 112 } 113 } 114 115 public static Log? Debug { get; private set; } 116 public static Log? Info { get; private set; } 117 public static Log? Warning { get; private set; } 118 public static Log? Error { get; private set; } 119 public static Log? Guest { get; private set; } 120 public static Log? AccessLog { get; private set; } 121 public static Log? Stub { get; private set; } 122 public static Log? Trace { get; private set; } 123 public static Log Notice { get; } // Always enabled 124 125 static Logger() 126 { 127 _enabledClasses = new bool[Enum.GetNames<LogClass>().Length]; 128 129 for (int index = 0; index < _enabledClasses.Length; index++) 130 { 131 _enabledClasses[index] = true; 132 } 133 134 _logTargets = new List<ILogTarget>(); 135 136 _time = Stopwatch.StartNew(); 137 138 // Logger should log to console by default 139 AddTarget(new AsyncLogTargetWrapper( 140 new ConsoleLogTarget("console"), 141 1000, 142 AsyncLogTargetOverflowAction.Discard)); 143 144 Notice = new Log(LogLevel.Notice); 145 146 // Enable important log levels before configuration is loaded 147 Error = new Log(LogLevel.Error); 148 Warning = new Log(LogLevel.Warning); 149 Info = new Log(LogLevel.Info); 150 Trace = new Log(LogLevel.Trace); 151 152 _stdErrAdapter = new StdErrAdapter(); 153 } 154 155 public static void RestartTime() 156 { 157 _time.Restart(); 158 } 159 160 private static ILogTarget GetTarget(string targetName) 161 { 162 foreach (var target in _logTargets) 163 { 164 if (target.Name.Equals(targetName)) 165 { 166 return target; 167 } 168 } 169 170 return null; 171 } 172 173 public static void AddTarget(ILogTarget target) 174 { 175 _logTargets.Add(target); 176 177 Updated += target.Log; 178 } 179 180 public static void RemoveTarget(string target) 181 { 182 ILogTarget logTarget = GetTarget(target); 183 184 if (logTarget != null) 185 { 186 Updated -= logTarget.Log; 187 188 _logTargets.Remove(logTarget); 189 190 logTarget.Dispose(); 191 } 192 } 193 194 public static void Shutdown() 195 { 196 Updated = null; 197 198 _stdErrAdapter.Dispose(); 199 200 foreach (var target in _logTargets) 201 { 202 target.Dispose(); 203 } 204 205 _logTargets.Clear(); 206 } 207 208 public static IReadOnlyCollection<LogLevel> GetEnabledLevels() 209 { 210 var logs = new[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace }; 211 List<LogLevel> levels = new(logs.Length); 212 foreach (var log in logs) 213 { 214 if (log.HasValue) 215 { 216 levels.Add(log.Value.Level); 217 } 218 } 219 220 return levels; 221 } 222 223 public static void SetEnable(LogLevel logLevel, bool enabled) 224 { 225 switch (logLevel) 226 { 227 #pragma warning disable IDE0055 // Disable formatting 228 case LogLevel.Debug : Debug = enabled ? new Log(LogLevel.Debug) : new Log?(); break; 229 case LogLevel.Info : Info = enabled ? new Log(LogLevel.Info) : new Log?(); break; 230 case LogLevel.Warning : Warning = enabled ? new Log(LogLevel.Warning) : new Log?(); break; 231 case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : new Log?(); break; 232 case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break; 233 case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break; 234 case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break; 235 case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break; 236 default: throw new ArgumentException("Unknown Log Level"); 237 #pragma warning restore IDE0055 238 } 239 } 240 241 public static void SetEnable(LogClass logClass, bool enabled) 242 { 243 _enabledClasses[(int)logClass] = enabled; 244 } 245 } 246 }