initial commit

This commit is contained in:
ParantezTech
2026-03-11 15:48:28 +03:00
commit 4d73f469bc
92 changed files with 166746 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.IO;
namespace SharpEmu.Logging;
public sealed class ConsoleLogSink : ISharpEmuLogSink
{
private readonly object _sync = new();
public ConsoleLogSink(bool useColors = true, bool includeTimestamp = false)
{
UseColors = useColors;
IncludeTimestamp = includeTimestamp;
}
public bool UseColors { get; set; }
public bool IncludeTimestamp { get; set; }
public void Write(in LogEntry entry)
{
lock (_sync)
{
var writer = entry.Level >= LogLevel.Error ? Console.Error : Console.Out;
if (IncludeTimestamp)
{
writer.Write('[');
writer.Write(entry.Timestamp.ToString("HH:mm:ss.fff"));
writer.Write(']');
}
var levelLabel = ToLevelLabel(entry.Level);
WriteLevelSegment(writer, levelLabel, entry.Level);
writer.Write('[');
writer.Write(entry.Category);
writer.Write(']');
writer.Write(' ');
writer.Write(entry.SourceFileName);
if (entry.SourceLine > 0)
{
writer.Write(':');
writer.Write(entry.SourceLine);
}
writer.Write(' ');
WriteMessageSegment(writer, entry.Message);
writer.WriteLine();
if (entry.Exception is not null)
{
writer.WriteLine(entry.Exception);
}
}
}
private static string ToLevelLabel(LogLevel level)
{
return level switch
{
LogLevel.Trace => "TRACE",
LogLevel.Debug => "DEBUG",
LogLevel.Info => "INFO",
LogLevel.Warning => "WARNING",
LogLevel.Error => "ERROR",
LogLevel.Critical => "CRITICAL",
_ => "LOG",
};
}
private void WriteLevelSegment(TextWriter writer, string label, LogLevel level)
{
if (!UseColors)
{
writer.Write('[');
writer.Write(label);
writer.Write(']');
return;
}
var originalColor = Console.ForegroundColor;
Console.ForegroundColor = GetLevelColor(level);
writer.Write('[');
writer.Write(label);
writer.Write(']');
Console.ForegroundColor = originalColor;
}
private static ConsoleColor GetLevelColor(LogLevel level)
{
return level switch
{
LogLevel.Trace => ConsoleColor.DarkGray,
LogLevel.Debug => ConsoleColor.Gray,
LogLevel.Info => ConsoleColor.Blue,
LogLevel.Warning => ConsoleColor.Yellow,
LogLevel.Error => ConsoleColor.Red,
LogLevel.Critical => ConsoleColor.DarkRed,
_ => ConsoleColor.White,
};
}
private void WriteMessageSegment(TextWriter writer, string message)
{
if (!UseColors || !TryGetMessageColor(message, out var messageColor))
{
writer.Write(message);
return;
}
var originalColor = Console.ForegroundColor;
Console.ForegroundColor = messageColor;
writer.Write(message);
Console.ForegroundColor = originalColor;
}
private static bool TryGetMessageColor(string message, out ConsoleColor color)
{
if (string.IsNullOrWhiteSpace(message))
{
color = default;
return false;
}
if (ContainsIgnoreCase(message, "unresolved import") ||
ContainsIgnoreCase(message, "unresolved symbol") ||
ContainsIgnoreCase(message, "hot_unresolved_imports=") ||
ContainsIgnoreCase(message, "unresolved_imports_hit="))
{
color = ConsoleColor.Red;
return true;
}
if (ContainsIgnoreCase(message, "syscall") ||
ContainsIgnoreCase(message, "UnhandledSyscall"))
{
color = ConsoleColor.DarkYellow;
return true;
}
if (ContainsIgnoreCase(message, "Import trace") ||
ContainsIgnoreCase(message, "import_stub_return") ||
ContainsIgnoreCase(message, "last_import=") ||
ContainsIgnoreCase(message, "hot_imports=") ||
ContainsIgnoreCase(message, "resolved import"))
{
color = ConsoleColor.Blue;
return true;
}
color = default;
return false;
}
private static bool ContainsIgnoreCase(string text, string value)
{
return text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
+9
View File
@@ -0,0 +1,9 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Logging;
public interface ISharpEmuLogSink
{
void Write(in LogEntry entry);
}
+14
View File
@@ -0,0 +1,14 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Logging;
public readonly record struct LogEntry(
DateTimeOffset Timestamp,
LogLevel Level,
string Category,
string Message,
string SourceFileName,
int SourceLine,
string SourceMemberName,
Exception? Exception = null);
+21
View File
@@ -0,0 +1,21 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Logging;
public enum LogLevel
{
Trace = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4,
Critical = 5,
None = 6,
}
@@ -0,0 +1,7 @@
<!--
Copyright (C) 2026 SharpEmu Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
<Project Sdk="Microsoft.NET.Sdk">
</Project>
+168
View File
@@ -0,0 +1,168 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.IO;
namespace SharpEmu.Logging;
public static class SharpEmuLog
{
private static readonly ConcurrentDictionary<string, SharpEmuLogger> LoggerByCategory =
new(StringComparer.Ordinal);
private static readonly object ConfigurationSync = new();
private static volatile LogLevel _minimumLevel = ResolveMinimumLevelFromEnvironment();
private static ISharpEmuLogSink _sink = new ConsoleLogSink(
useColors: ResolveColorEnabledFromEnvironment(),
includeTimestamp: false);
public static LogLevel MinimumLevel
{
get => _minimumLevel;
set => _minimumLevel = value;
}
public static ISharpEmuLogSink Sink
{
get
{
lock (ConfigurationSync)
{
return _sink;
}
}
set
{
ArgumentNullException.ThrowIfNull(value);
lock (ConfigurationSync)
{
_sink = value;
}
}
}
public static void Configure(LogLevel? minimumLevel = null, ISharpEmuLogSink? sink = null)
{
if (minimumLevel.HasValue)
{
_minimumLevel = minimumLevel.Value;
}
if (sink is not null)
{
Sink = sink;
}
}
public static SharpEmuLogger For(string category)
{
ArgumentException.ThrowIfNullOrWhiteSpace(category);
return LoggerByCategory.GetOrAdd(category, static key => new SharpEmuLogger(key));
}
public static bool TryParseLevel(string? text, out LogLevel level)
{
if (string.IsNullOrWhiteSpace(text))
{
level = default;
return false;
}
var normalized = text.Trim();
if (Enum.TryParse<LogLevel>(normalized, ignoreCase: true, out level))
{
return true;
}
if (string.Equals(normalized, "warn", StringComparison.OrdinalIgnoreCase))
{
level = LogLevel.Warning;
return true;
}
if (string.Equals(normalized, "fatal", StringComparison.OrdinalIgnoreCase))
{
level = LogLevel.Critical;
return true;
}
return false;
}
internal static bool IsEnabled(LogLevel level)
{
var minimum = _minimumLevel;
return minimum != LogLevel.None && level >= minimum;
}
internal static void Write(
LogLevel level,
string category,
string message,
Exception? exception,
string sourceFilePath,
int sourceLine,
string sourceMemberName)
{
if (!IsEnabled(level))
{
return;
}
var entry = new LogEntry(
DateTimeOffset.Now,
level,
category,
message,
Path.GetFileName(sourceFilePath),
sourceLine,
sourceMemberName,
exception);
ISharpEmuLogSink sink;
lock (ConfigurationSync)
{
sink = _sink;
}
sink.Write(in entry);
}
private static LogLevel ResolveMinimumLevelFromEnvironment()
{
var raw = Environment.GetEnvironmentVariable("SHARPEMU_LOG_LEVEL");
return TryParseLevel(raw, out var level) ? level : LogLevel.Info;
}
private static bool ResolveColorEnabledFromEnvironment()
{
if (Console.IsOutputRedirected)
{
return false;
}
var raw = Environment.GetEnvironmentVariable("SHARPEMU_LOG_NO_COLOR");
return !IsTrueLike(raw);
}
private static bool IsTrueLike(string? text)
{
if (string.IsNullOrWhiteSpace(text))
{
return false;
}
return text.Trim() switch
{
"1" => true,
"true" => true,
"TRUE" => true,
"yes" => true,
"YES" => true,
"on" => true,
"ON" => true,
_ => false,
};
}
}
+87
View File
@@ -0,0 +1,87 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Runtime.CompilerServices;
namespace SharpEmu.Logging;
public sealed class SharpEmuLogger
{
public SharpEmuLogger(string category)
{
Category = category ?? throw new ArgumentNullException(nameof(category));
}
public string Category { get; }
public bool IsEnabled(LogLevel level) => SharpEmuLog.IsEnabled(level);
public void Trace(
string message,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Trace, message, exception: null, sourceFilePath, sourceLine, sourceMemberName);
public void Debug(
string message,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Debug, message, exception: null, sourceFilePath, sourceLine, sourceMemberName);
public void Info(
string message,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Info, message, exception: null, sourceFilePath, sourceLine, sourceMemberName);
public void Warn(
string message,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Warning(message, sourceFilePath, sourceLine, sourceMemberName);
public void Warning(
string message,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Warning, message, exception: null, sourceFilePath, sourceLine, sourceMemberName);
public void Error(
string message,
Exception? exception = null,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Error, message, exception, sourceFilePath, sourceLine, sourceMemberName);
public void Critical(
string message,
Exception? exception = null,
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLine = 0,
[CallerMemberName] string sourceMemberName = "")
=> Write(LogLevel.Critical, message, exception, sourceFilePath, sourceLine, sourceMemberName);
private void Write(
LogLevel level,
string message,
Exception? exception,
string sourceFilePath,
int sourceLine,
string sourceMemberName)
{
SharpEmuLog.Write(
level,
Category,
message,
exception,
sourceFilePath,
sourceLine,
sourceMemberName);
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"version": 2,
"dependencies": {
"net10.0": {}
}
}