Add logging system

This commit is contained in:
Stone_Red
2026-06-10 00:28:28 +02:00
parent 1cd322a0aa
commit 91e8cbb00a
9 changed files with 214 additions and 17 deletions
+3 -15
View File
@@ -1,7 +1,7 @@
global using Sys = Cosmos.Kernel.System; global using Sys = Cosmos.Kernel.System;
using Cosmos.Kernel.Core; using Cosmos.Kernel.Core;
using RemSox.Logging;
using RemSox.Processes; using RemSox.Processes;
using RemSox.Processing; using RemSox.Processing;
using RemSox.Processing.IPC; using RemSox.Processing.IPC;
@@ -30,7 +30,8 @@ public class Kernel : Sys.Kernel
new ListProcessesCommand(), new ListProcessesCommand(),
new StopProcessCommand(), new StopProcessCommand(),
new StartGuiCommand(), new StartGuiCommand(),
new StopGuiCommand() new StopGuiCommand(),
new ViewProcessLogs()
]); ]);
Sys.Mouse.MouseManager.Initialize(); Sys.Mouse.MouseManager.Initialize();
@@ -62,19 +63,6 @@ public class Kernel : Sys.Kernel
} }
} }
[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute : Attribute
{
public string Name { get; set; } = "default";
}
[Test(Name = "HelloCosmos")]
public class TestClass
{
}
public class TestProcess() : Process("Test Process") public class TestProcess() : Process("Test Process")
{ {
internal override void Run(string[] args) internal override void Run(string[] args)
+20
View File
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RemSox.Logging
{
public interface ILogger
{
void Log(string message, LogSeverity severity);
void LogInfo(string message);
void LogError(string message);
void LogWarning(string message);
IEnumerable<LogEntry> GetLogs(int? count = null);
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace RemSox.Logging
{
public class InMemoryLogger : ILogger
{
readonly ConcurrentBag<LogEntry> logs = [];
public void Log(string message, LogSeverity severity)
{
logs.Add(new LogEntry(message, severity, DateTimeOffset.UtcNow));
}
public void LogError(string message)
{
Log(message, LogSeverity.Error);
}
public void LogInfo(string message)
{
Log(message, LogSeverity.Info);
}
public void LogWarning(string message)
{
Log(message, LogSeverity.Warning);
}
public IEnumerable<LogEntry> GetLogs(int? count = null)
{
IEnumerable<LogEntry> orderedLogs = logs.OrderBy(l => l.Timestamp);
return count.HasValue ? orderedLogs.TakeLast(count.Value) : orderedLogs;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RemSox.Logging
{
public record LogEntry(string Message, LogSeverity Severity, DateTimeOffset Timestamp);
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RemSox.Logging
{
public enum LogSeverity
{
Info,
Warning,
Error
}
}
+38
View File
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace RemSox.Logging
{
public class ProxyLogger(IEnumerable<ILogger> loggers) : ILogger
{
public void Log(string message, LogSeverity severity)
{
foreach (var logger in loggers)
{
logger.Log(message, severity);
}
}
public void LogError(string message)
{
Log(message, LogSeverity.Error);
}
public void LogInfo(string message)
{
Log(message, LogSeverity.Info);
}
public void LogWarning(string message)
{
Log(message, LogSeverity.Warning);
}
public IEnumerable<LogEntry> GetLogs(int? count = null)
{
return loggers.SelectMany(logger => logger.GetLogs(count / loggers.Count())).OrderBy(entry => entry.Timestamp);
}
}
}
+4 -1
View File
@@ -1,10 +1,13 @@
using RemSox.Logging;
using RemSox.Processing.IPC; using RemSox.Processing.IPC;
namespace RemSox.Processing; namespace RemSox.Processing;
public abstract class Process(string name) public abstract class Process(string name)
{ {
public int Id { get; init; } public int Id { get; init; } // Will be set by ProcessManager when the process is spawned
public ILogger Logger { protected get; init; } = null!; // Will be set by ProcessManager when the process is spawned
public string Name { get; set; } = name; public string Name { get; set; } = name;
+43 -1
View File
@@ -1,3 +1,4 @@
using RemSox.Logging;
using RemSox.UI.GUI.Windows; using RemSox.UI.GUI.Windows;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -10,22 +11,34 @@ public static class ProcessManager
private static readonly ConcurrentDictionary<Type, ConcurrentHashSet<int>> processesByType = new(); private static readonly ConcurrentDictionary<Type, ConcurrentHashSet<int>> processesByType = new();
private static readonly InMemoryLogger logger = new();
private static readonly ConcurrentDictionary<int, InMemoryLogger> processLoggers = new();
private static int nextProcessId = 0; private static int nextProcessId = 0;
private static int nextSystemProcessId = -1; private static int nextSystemProcessId = -1;
public static int SpawnProcess<T>(string[]? args = null) where T : Process, new() public static int SpawnProcess<T>(string[]? args = null) where T : Process, new()
{ {
logger.Log($"Attempting to spawn process of type {typeof(T).Name}...", LogSeverity.Info);
if (ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.Singleton) && IsProcessRunning<T>()) if (ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.Singleton) && IsProcessRunning<T>())
{ {
logger.Log($"Cannot spawn process of type {typeof(T).Name} because it is marked as a singleton and an instance is already running.", LogSeverity.Warning);
throw new InvalidOperationException($"An instance of process type {typeof(T).Name} is already running."); throw new InvalidOperationException($"An instance of process type {typeof(T).Name} is already running.");
} }
int id = ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.System) ? GetNextSystemProcessId() : GetNextProcessId(); int id = ProcessManifest.HasFlag<T>(ProcessManifest.ProcessManifestFlags.System) ? GetNextSystemProcessId() : GetNextProcessId();
InMemoryLogger processLogger = new();
ProxyLogger proxyLogger = new([logger, processLogger]);
processLoggers.TryAdd(id, processLogger);
T process = new() T process = new()
{ {
Id = id Id = id,
Logger = proxyLogger
}; };
processesByType.AddOrUpdate(typeof(T), _ => [id], (_, set) => processesByType.AddOrUpdate(typeof(T), _ => [id], (_, set) =>
@@ -40,6 +53,10 @@ public static class ProcessManager
{ {
process.Run(args ?? []); process.Run(args ?? []);
} }
catch (Exception ex)
{
logger.Log($"Process {process.Name} (ID: {process.Id}) terminated with an exception: {ex}", LogSeverity.Error);
}
finally finally
{ {
processes.TryRemove(id, out _); processes.TryRemove(id, out _);
@@ -52,12 +69,18 @@ public static class ProcessManager
} }
WindowManager.CloseWindowsForProcess(id); WindowManager.CloseWindowsForProcess(id);
logger.Log($"Process {process.Name} (ID: {process.Id}) has stopped.", LogSeverity.Info);
processLoggers.TryRemove(id, out _);
} }
}); });
processes.TryAdd(id, (process, thread)); processes.TryAdd(id, (process, thread));
thread.Start(); thread.Start();
logger.Log($"Spawned process {process.Name} of type {typeof(T).Name} with ID {id}.", LogSeverity.Info);
return id; return id;
} }
@@ -68,6 +91,7 @@ public static class ProcessManager
return; return;
} }
logger.Log($"Requesting stop of process {entry.Process.Name} (ID: {entry.Process.Id}).", LogSeverity.Info);
entry.Process.RequestStop(); entry.Process.RequestStop();
} }
@@ -78,7 +102,10 @@ public static class ProcessManager
return; return;
} }
logger.Log($"Requesting stop of process {entry.Process.Name} (ID: {entry.Process.Id}).", LogSeverity.Info);
entry.Process.RequestStop(); entry.Process.RequestStop();
logger.Log($"Waiting for process {entry.Process.Name} (ID: {entry.Process.Id}) to stop.", LogSeverity.Info);
await Task.Run(() => entry.Thread.Join()); await Task.Run(() => entry.Thread.Join());
} }
@@ -141,6 +168,21 @@ public static class ProcessManager
return false; return false;
} }
public static IEnumerable<LogEntry> GetLogs(int? count = null)
{
return logger.GetLogs(count);
}
public static IEnumerable<LogEntry> GetProcessLogs(int processId, int? count = null)
{
if (processLoggers.TryGetValue(processId, out InMemoryLogger? processLogger))
{
return processLogger.GetLogs(count);
}
return [];
}
private static int GetNextProcessId() private static int GetNextProcessId()
{ {
return nextProcessId++; return nextProcessId++;
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using RemSox.Logging;
using RemSox.Processing;
namespace RemSox.UI.CLI.Commands
{
public class ViewProcessLogs : ICommand
{
public string Name => "logs";
public string Description => "View logs for a process";
public void Execute(string? arguments, Action<string> printLine)
{
if (int.TryParse(arguments, out int processId))
{
IEnumerable<LogEntry> logs = ProcessManager.GetProcessLogs(processId);
PrintLogs(logs, printLine);
}
else
{
IEnumerable<LogEntry> logs = ProcessManager.GetLogs();
PrintLogs(logs, printLine);
}
}
private static void PrintLogs(IEnumerable<LogEntry> logs, Action<string> printLine)
{
if (!logs.Any())
{
printLine("No logs found!");
return;
}
foreach (LogEntry log in logs)
{
printLine($"[{log.Timestamp:HH:mm:ss}] [{log.Severity}] {log.Message}");
}
}
}
}