/ src / Ryujinx.Common / Logging / Logger.cs
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  }