aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Common/Logging
diff options
context:
space:
mode:
authorjduncanator <1518948+jduncanator@users.noreply.github.com>2019-02-11 23:00:32 +1100
committergdkchan <gab.dark.100@gmail.com>2019-02-11 09:00:32 -0300
commitd306115750df9df170cfef4d49c6b0b7af498962 (patch)
tree13e51d71ad264c8815106dc6a3190af02d193777 /Ryujinx.Common/Logging
parenta694420d11ef74e4f0bf473be2b6f64635bc89c7 (diff)
Logger and Configuration Refactoring (#573)
* Logging: Refactor log targets into Ryujinx.Common * Logger: Implement JSON Log Target * Logger: Optimize Console/File logging targets Implement a simple ObjectPool to pool up StringBuilders to avoid causing excessive GCing of gen1/2 items when large amounts of log entries are being generated. We can also pre-determine the async overflow action at initialization time, allowing for an easy optimization in the message enqueue function, avoiding a number of comparisons. * Logger: Implement LogFormatters * Config: Refactor configuration file and loading * Config: Rename to .jsonc to avoid highlighting issues in VSC and GitHub * Resolve style nits * Config: Resolve incorrect default key binding * Config: Also update key binding default in schema * Tidy up namespace imports * Config: Update CONFIG.md to reflect new Config file
Diffstat (limited to 'Ryujinx.Common/Logging')
-rw-r--r--Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs53
-rw-r--r--Ryujinx.Common/Logging/Formatters/ILogFormatter.cs7
-rw-r--r--Ryujinx.Common/Logging/LogClass.cs1
-rw-r--r--Ryujinx.Common/Logging/Logger.cs28
-rw-r--r--Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs76
-rw-r--r--Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs48
-rw-r--r--Ryujinx.Common/Logging/Targets/FileLogTarget.cs36
-rw-r--r--Ryujinx.Common/Logging/Targets/ILogTarget.cs9
-rw-r--r--Ryujinx.Common/Logging/Targets/JsonLogTarget.cs35
9 files changed, 289 insertions, 4 deletions
diff --git a/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
new file mode 100644
index 00000000..0c4396e7
--- /dev/null
+++ b/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
@@ -0,0 +1,53 @@
+using System.Reflection;
+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.AppendFormat(@"{0:hh\:mm\:ss\.fff}", args.Time);
+ sb.Append(" | ");
+ sb.AppendFormat("{0:d4}", args.ThreadId);
+ sb.Append(' ');
+ sb.Append(args.Message);
+
+ if (args.Data != null)
+ {
+ PropertyInfo[] props = args.Data.GetType().GetProperties();
+
+ sb.Append(' ');
+
+ foreach (var prop in props)
+ {
+ sb.Append(prop.Name);
+ sb.Append(": ");
+ sb.Append(prop.GetValue(args.Data));
+ sb.Append(" - ");
+ }
+
+ // We remove the final '-' from the string
+ if (props.Length > 0)
+ {
+ sb.Remove(sb.Length - 3, 3);
+ }
+ }
+
+ return sb.ToString();
+ }
+ finally
+ {
+ _stringBuilderPool.Release(sb);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
new file mode 100644
index 00000000..9a55bc6b
--- /dev/null
+++ b/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Common.Logging
+{
+ interface ILogFormatter
+ {
+ string Format(LogEventArgs args);
+ }
+}
diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs
index f20347b6..66a83b37 100644
--- a/Ryujinx.Common/Logging/LogClass.cs
+++ b/Ryujinx.Common/Logging/LogClass.cs
@@ -2,6 +2,7 @@ namespace Ryujinx.Common.Logging
{
public enum LogClass
{
+ Application,
Audio,
Cpu,
Font,
diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs
index 35ca416b..88ebe473 100644
--- a/Ryujinx.Common/Logging/Logger.cs
+++ b/Ryujinx.Common/Logging/Logger.cs
@@ -1,8 +1,7 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
-using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Text;
using System.Threading;
namespace Ryujinx.Common.Logging
@@ -14,9 +13,9 @@ namespace Ryujinx.Common.Logging
private static readonly bool[] m_EnabledLevels;
private static readonly bool[] m_EnabledClasses;
- public static event EventHandler<LogEventArgs> Updated;
+ private static readonly List<ILogTarget> m_LogTargets;
- public static bool EnableFileLog { get; set; }
+ public static event EventHandler<LogEventArgs> Updated;
static Logger()
{
@@ -33,9 +32,30 @@ namespace Ryujinx.Common.Logging
m_EnabledClasses[index] = true;
}
+ m_LogTargets = new List<ILogTarget>();
+
m_Time = Stopwatch.StartNew();
}
+ public static void AddTarget(ILogTarget target)
+ {
+ m_LogTargets.Add(target);
+
+ Updated += target.Log;
+ }
+
+ public static void Shutdown()
+ {
+ Updated = null;
+
+ foreach(var target in m_LogTargets)
+ {
+ target.Dispose();
+ }
+
+ m_LogTargets.Clear();
+ }
+
public static void SetEnable(LogLevel logLevel, bool enabled)
{
m_EnabledLevels[(int)logLevel] = enabled;
diff --git a/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
new file mode 100644
index 00000000..a805a83b
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
@@ -0,0 +1,76 @@
+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;
+
+ 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.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();
+ }
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
new file mode 100644
index 00000000..871076a4
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Common.Logging
+{
+ public class ConsoleLogTarget : ILogTarget
+ {
+ private static readonly ConcurrentDictionary<LogLevel, ConsoleColor> _logColors;
+
+ private readonly ILogFormatter _formatter;
+
+ static ConsoleLogTarget()
+ {
+ _logColors = new ConcurrentDictionary<LogLevel, ConsoleColor> {
+ [ LogLevel.Stub ] = ConsoleColor.DarkGray,
+ [ LogLevel.Info ] = ConsoleColor.White,
+ [ LogLevel.Warning ] = ConsoleColor.Yellow,
+ [ LogLevel.Error ] = ConsoleColor.Red
+ };
+ }
+
+ public ConsoleLogTarget()
+ {
+ _formatter = new DefaultLogFormatter();
+ }
+
+ public void Log(object sender, LogEventArgs args)
+ {
+ if (_logColors.TryGetValue(args.Level, out ConsoleColor color))
+ {
+ Console.ForegroundColor = color;
+
+ Console.WriteLine(_formatter.Format(args));
+
+ Console.ResetColor();
+ }
+ else
+ {
+ Console.WriteLine(_formatter.Format(args));
+ }
+ }
+
+ public void Dispose()
+ {
+ Console.ResetColor();
+ }
+ }
+}
diff --git a/Ryujinx.Common/Logging/Targets/FileLogTarget.cs b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
new file mode 100644
index 00000000..85dc8249
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using System.Text;
+
+namespace Ryujinx.Common.Logging
+{
+ public class FileLogTarget : ILogTarget
+ {
+ private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
+
+ private readonly StreamWriter _logWriter;
+ private readonly ILogFormatter _formatter;
+
+ public FileLogTarget(string path)
+ : this(path, FileShare.Read, FileMode.Append)
+ { }
+
+ public FileLogTarget(string path, FileShare fileShare, FileMode fileMode)
+ {
+ _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();
+ }
+ }
+}
diff --git a/Ryujinx.Common/Logging/Targets/ILogTarget.cs b/Ryujinx.Common/Logging/Targets/ILogTarget.cs
new file mode 100644
index 00000000..261c5e64
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/ILogTarget.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.Common.Logging
+{
+ public interface ILogTarget : IDisposable
+ {
+ void Log(object sender, LogEventArgs args);
+ }
+}
diff --git a/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
new file mode 100644
index 00000000..410394aa
--- /dev/null
+++ b/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
@@ -0,0 +1,35 @@
+using System.IO;
+using Utf8Json;
+
+namespace Ryujinx.Common.Logging
+{
+ public class JsonLogTarget : ILogTarget
+ {
+ private Stream _stream;
+ private bool _leaveOpen;
+
+ public JsonLogTarget(Stream stream)
+ {
+ _stream = stream;
+ }
+
+ public JsonLogTarget(Stream stream, bool leaveOpen)
+ {
+ _stream = stream;
+ _leaveOpen = leaveOpen;
+ }
+
+ public void Log(object sender, LogEventArgs e)
+ {
+ JsonSerializer.Serialize(_stream, e);
+ }
+
+ public void Dispose()
+ {
+ if (!_leaveOpen)
+ {
+ _stream.Dispose();
+ }
+ }
+ }
+}