Move solution and projects to src
This commit is contained in:
42
src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
Normal file
42
src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DefaultLogFormatter : ILogFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public string Format(LogEventArgs args)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
sb.Clear();
|
||||
|
||||
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
|
||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||
|
||||
if (args.ThreadName != null)
|
||||
{
|
||||
sb.Append(args.ThreadName);
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
sb.Append(args.Message);
|
||||
|
||||
if (args.Data is not null)
|
||||
{
|
||||
sb.Append(' ');
|
||||
DynamicObjectFormatter.Format(sb, args.Data);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DynamicObjectFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public static string? Format(object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
Format(sb, dynamicObject);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Format(StringBuilder sb, object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
|
||||
|
||||
sb.Append('{');
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Array? array = (Array?) prop.GetValue(dynamicObject);
|
||||
|
||||
if (array is not null)
|
||||
{
|
||||
foreach (var item in array)
|
||||
{
|
||||
sb.Append(item);
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(prop.GetValue(dynamicObject));
|
||||
}
|
||||
|
||||
sb.Append(" ; ");
|
||||
}
|
||||
|
||||
// We remove the final ';' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
}
|
||||
}
|
7
src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
Normal file
7
src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
interface ILogFormatter
|
||||
{
|
||||
string Format(LogEventArgs args);
|
||||
}
|
||||
}
|
76
src/Ryujinx.Common/Logging/LogClass.cs
Normal file
76
src/Ryujinx.Common/Logging/LogClass.cs
Normal file
@ -0,0 +1,76 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||
public enum LogClass
|
||||
{
|
||||
Application,
|
||||
Audio,
|
||||
AudioRenderer,
|
||||
Configuration,
|
||||
Cpu,
|
||||
Emulation,
|
||||
FFmpeg,
|
||||
Font,
|
||||
Gpu,
|
||||
Hid,
|
||||
Host1x,
|
||||
Kernel,
|
||||
KernelIpc,
|
||||
KernelScheduler,
|
||||
KernelSvc,
|
||||
Loader,
|
||||
ModLoader,
|
||||
Nvdec,
|
||||
Ptc,
|
||||
Service,
|
||||
ServiceAcc,
|
||||
ServiceAm,
|
||||
ServiceApm,
|
||||
ServiceAudio,
|
||||
ServiceBcat,
|
||||
ServiceBsd,
|
||||
ServiceBtm,
|
||||
ServiceCaps,
|
||||
ServiceFatal,
|
||||
ServiceFriend,
|
||||
ServiceFs,
|
||||
ServiceHid,
|
||||
ServiceIrs,
|
||||
ServiceLdn,
|
||||
ServiceLdr,
|
||||
ServiceLm,
|
||||
ServiceMii,
|
||||
ServiceMm,
|
||||
ServiceMnpp,
|
||||
ServiceNfc,
|
||||
ServiceNfp,
|
||||
ServiceNgct,
|
||||
ServiceNifm,
|
||||
ServiceNim,
|
||||
ServiceNs,
|
||||
ServiceNsd,
|
||||
ServiceNtc,
|
||||
ServiceNv,
|
||||
ServiceOlsc,
|
||||
ServicePctl,
|
||||
ServicePcv,
|
||||
ServicePl,
|
||||
ServicePrepo,
|
||||
ServicePsm,
|
||||
ServicePtm,
|
||||
ServiceSet,
|
||||
ServiceSfdnsres,
|
||||
ServiceSm,
|
||||
ServiceSsl,
|
||||
ServiceSss,
|
||||
ServiceTime,
|
||||
ServiceVi,
|
||||
SurfaceFlinger,
|
||||
TamperMachine,
|
||||
Ui,
|
||||
Vic
|
||||
}
|
||||
}
|
23
src/Ryujinx.Common/Logging/LogEventArgs.cs
Normal file
23
src/Ryujinx.Common/Logging/LogEventArgs.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class LogEventArgs : EventArgs
|
||||
{
|
||||
public readonly LogLevel Level;
|
||||
public readonly TimeSpan Time;
|
||||
public readonly string ThreadName;
|
||||
|
||||
public readonly string Message;
|
||||
public readonly object Data;
|
||||
|
||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
30
src/Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class LogEventArgsJson
|
||||
{
|
||||
public LogLevel Level { get; }
|
||||
public TimeSpan Time { get; }
|
||||
public string ThreadName { get; }
|
||||
|
||||
public string Message { get; }
|
||||
public string Data { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
|
||||
{
|
||||
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonSerializable(typeof(LogEventArgsJson))]
|
||||
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
19
src/Ryujinx.Common/Logging/LogLevel.cs
Normal file
19
src/Ryujinx.Common/Logging/LogLevel.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Stub,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Guest,
|
||||
AccessLog,
|
||||
Notice,
|
||||
Trace
|
||||
}
|
||||
}
|
224
src/Ryujinx.Common/Logging/Logger.cs
Normal file
224
src/Ryujinx.Common/Logging/Logger.cs
Normal file
@ -0,0 +1,224 @@
|
||||
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<ILogTarget> m_LogTargets;
|
||||
|
||||
private static readonly StdErrAdapter _stdErrAdapter;
|
||||
|
||||
public static event EventHandler<LogEventArgs> 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));
|
||||
}
|
||||
}
|
||||
|
||||
[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<LogClass>().Length];
|
||||
|
||||
for (int index = 0; index < m_EnabledClasses.Length; index++)
|
||||
{
|
||||
m_EnabledClasses[index] = true;
|
||||
}
|
||||
|
||||
m_LogTargets = new List<ILogTarget>();
|
||||
|
||||
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<LogLevel> GetEnabledLevels()
|
||||
{
|
||||
var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
|
||||
List<LogLevel> levels = new List<LogLevel>(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;
|
||||
}
|
||||
}
|
||||
}
|
79
src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
Normal file
79
src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public enum AsyncLogTargetOverflowAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Block until there's more room in the queue
|
||||
/// </summary>
|
||||
Block = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Discard the overflowing item
|
||||
/// </summary>
|
||||
Discard = 1
|
||||
}
|
||||
|
||||
public class AsyncLogTargetWrapper : ILogTarget
|
||||
{
|
||||
private ILogTarget _target;
|
||||
|
||||
private Thread _messageThread;
|
||||
|
||||
private BlockingCollection<LogEventArgs> _messageQueue;
|
||||
|
||||
private readonly int _overflowTimeout;
|
||||
|
||||
string ILogTarget.Name { get => _target.Name; }
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target)
|
||||
: this(target, -1, AsyncLogTargetOverflowAction.Block)
|
||||
{ }
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
|
||||
{
|
||||
_target = target;
|
||||
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
||||
_overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0;
|
||||
|
||||
_messageThread = new Thread(() => {
|
||||
while (!_messageQueue.IsCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
_target.Log(this, _messageQueue.Take());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// IOE means that Take() was called on a completed collection.
|
||||
// Some other thread can call CompleteAdding after we pass the
|
||||
// IsCompleted check but before we call Take.
|
||||
// We can simply catch the exception since the loop will break
|
||||
// on the next iteration.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_messageThread.Name = "Logger.MessageThread";
|
||||
_messageThread.IsBackground = true;
|
||||
_messageThread.Start();
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
if (!_messageQueue.IsAddingCompleted)
|
||||
{
|
||||
_messageQueue.TryAdd(e, _overflowTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue.CompleteAdding();
|
||||
_messageThread.Join();
|
||||
}
|
||||
}
|
||||
}
|
41
src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
Normal file
41
src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class ConsoleLogTarget : ILogTarget
|
||||
{
|
||||
private readonly ILogFormatter _formatter;
|
||||
|
||||
private readonly string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
private static ConsoleColor GetLogColor(LogLevel level) => level switch {
|
||||
LogLevel.Info => ConsoleColor.White,
|
||||
LogLevel.Warning => ConsoleColor.Yellow,
|
||||
LogLevel.Error => ConsoleColor.Red,
|
||||
LogLevel.Stub => ConsoleColor.DarkGray,
|
||||
LogLevel.Notice => ConsoleColor.Cyan,
|
||||
LogLevel.Trace => ConsoleColor.DarkCyan,
|
||||
_ => ConsoleColor.Gray,
|
||||
};
|
||||
|
||||
public ConsoleLogTarget(string name)
|
||||
{
|
||||
_formatter = new DefaultLogFormatter();
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
{
|
||||
Console.ForegroundColor = GetLogColor(args.Level);
|
||||
Console.WriteLine(_formatter.Format(args));
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
55
src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
Normal file
55
src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class FileLogTarget : ILogTarget
|
||||
{
|
||||
private readonly StreamWriter _logWriter;
|
||||
private readonly ILogFormatter _formatter;
|
||||
private readonly string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public FileLogTarget(string path, string name)
|
||||
: this(path, name, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
|
||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
||||
{
|
||||
// Ensure directory is present
|
||||
DirectoryInfo logDir = new DirectoryInfo(Path.Combine(path, "Logs"));
|
||||
logDir.Create();
|
||||
|
||||
// Clean up old logs, should only keep 3
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||
for (int i = 0; i < files.Length - 2; i++)
|
||||
{
|
||||
files[i].Delete();
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.GetVersion();
|
||||
|
||||
// Get path for the current time
|
||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log");
|
||||
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_formatter = new DefaultLogFormatter();
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
{
|
||||
_logWriter.WriteLine(_formatter.Format(args));
|
||||
_logWriter.Flush();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_logWriter.WriteLine("---- End of Log ----");
|
||||
_logWriter.Flush();
|
||||
_logWriter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Common/Logging/Targets/ILogTarget.cs
Normal file
11
src/Ryujinx.Common/Logging/Targets/ILogTarget.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public interface ILogTarget : IDisposable
|
||||
{
|
||||
void Log(object sender, LogEventArgs args);
|
||||
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
40
src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
Normal file
40
src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class JsonLogTarget : ILogTarget
|
||||
{
|
||||
private Stream _stream;
|
||||
private bool _leaveOpen;
|
||||
private string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public JsonLogTarget(Stream stream, string name)
|
||||
{
|
||||
_stream = stream;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public JsonLogTarget(Stream stream, bool leaveOpen)
|
||||
{
|
||||
_stream = stream;
|
||||
_leaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
|
||||
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_leaveOpen)
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user