using Ryujinx.Common.Logging.Targets; using Ryujinx.Common.SystemInterop; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Threading; namespace Ryujinx.Common.Logging { public static class Logger { private static readonly Stopwatch m_Time; private static readonly bool[] m_EnabledClasses; private static readonly List m_LogTargets; private static readonly StdErrAdapter _stdErrAdapter; public static event EventHandler Updated; public readonly struct Log { internal readonly LogLevel Level; internal Log(LogLevel level) { Level = level; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintMsg(LogClass logClass, string message) { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message))); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message))); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data)); } } [StackTraceHidden] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintStack(LogClass logClass, string message, [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), new StackTrace(true))); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message))); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintStub(LogClass logClass, object data, [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed."), data)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintStub(LogClass logClass, string message, object data, [CallerMemberName] string caller = "") { if (m_EnabledClasses[(int)logClass]) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintRawMsg(string message) { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}"; } public static Log? Debug { get; private set; } public static Log? Info { get; private set; } public static Log? Warning { get; private set; } public static Log? Error { get; private set; } public static Log? Guest { get; private set; } public static Log? AccessLog { get; private set; } public static Log? Stub { get; private set; } public static Log? Trace { get; private set; } public static Log Notice { get; } // Always enabled static Logger() { m_EnabledClasses = new bool[Enum.GetNames().Length]; for (int index = 0; index < m_EnabledClasses.Length; index++) { m_EnabledClasses[index] = true; } m_LogTargets = new List(); m_Time = Stopwatch.StartNew(); // Logger should log to console by default AddTarget(new AsyncLogTargetWrapper( new ConsoleLogTarget("console"), 1000, AsyncLogTargetOverflowAction.Discard)); Notice = new Log(LogLevel.Notice); // Enable important log levels before configuration is loaded Error = new Log(LogLevel.Error); Warning = new Log(LogLevel.Warning); Info = new Log(LogLevel.Info); Trace = new Log(LogLevel.Trace); _stdErrAdapter = new StdErrAdapter(); } public static void RestartTime() { m_Time.Restart(); } private static ILogTarget GetTarget(string targetName) { foreach (var target in m_LogTargets) { if (target.Name.Equals(targetName)) { return target; } } return null; } public static void AddTarget(ILogTarget target) { m_LogTargets.Add(target); Updated += target.Log; } public static void RemoveTarget(string target) { ILogTarget logTarget = GetTarget(target); if (logTarget != null) { Updated -= logTarget.Log; m_LogTargets.Remove(logTarget); logTarget.Dispose(); } } public static void Shutdown() { Updated = null; _stdErrAdapter.Dispose(); foreach (var target in m_LogTargets) { target.Dispose(); } m_LogTargets.Clear(); } public static IReadOnlyCollection GetEnabledLevels() { var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace }; List levels = new List(logs.Length); foreach (var log in logs) { if (log.HasValue) { levels.Add(log.Value.Level); } } return levels; } public static void SetEnable(LogLevel logLevel, bool enabled) { switch (logLevel) { case LogLevel.Debug : Debug = enabled ? new Log(LogLevel.Debug) : new Log?(); break; case LogLevel.Info : Info = enabled ? new Log(LogLevel.Info) : new Log?(); break; case LogLevel.Warning : Warning = enabled ? new Log(LogLevel.Warning) : new Log?(); break; case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : new Log?(); break; case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break; case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): new Log?(); break; case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break; case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break; default: throw new ArgumentException("Unknown Log Level"); } } public static void SetEnable(LogClass logClass, bool enabled) { m_EnabledClasses[(int)logClass] = enabled; } } }