using System.Collections.ObjectModel; using System.IO; using System.Text; using System.Windows; using System.Windows.Threading; using EmbyToolbox.ViewModels; namespace EmbyToolbox.Services; public sealed class LoggingService { private const int UiLogLimit = 1000; private readonly string _logsDirectory; public LoggingService() { _logsDirectory = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EmbyToolbox", "Logs"); Directory.CreateDirectory(_logsDirectory); } public ObservableCollection UiEntries { get; } = new(); public LogLevel MinimumFileLogLevel { get; set; } = LogLevel.Info; public string LogsDirectory => _logsDirectory; public void Debug(string message, string module = "app", Exception? exception = null, string? command = null, string? stdout = null, string? stderr = null) { Write(LogLevel.Debug, message, module, exception, command, stdout, stderr); } public void Info(string message, string module = "app", Exception? exception = null, string? command = null, string? stdout = null, string? stderr = null) { Write(LogLevel.Info, message, module, exception, command, stdout, stderr); } public void Warning(string message, string module = "app", Exception? exception = null, string? command = null, string? stdout = null, string? stderr = null) { Write(LogLevel.Warning, message, module, exception, command, stdout, stderr); } public void Error(string message, string module = "app", Exception? exception = null, string? command = null, string? stdout = null, string? stderr = null) { Write(LogLevel.Error, message, module, exception, command, stdout, stderr); } public void ClearUi() { UiEntries.Clear(); } private void Write(LogLevel level, string message, string module, Exception? exception, string? command, string? stdout, string? stderr) { var now = DateTime.Now; var entry = new LogEntryViewModel { Timestamp = now, Level = level, LevelText = level.ToString(), Module = module, Message = message }; AddUiEntryThreadSafe(entry); if (level < MinimumFileLogLevel) { return; } WriteToFile(now, level, module, message, exception, command, stdout, stderr); } private void AddUiEntryThreadSafe(LogEntryViewModel entry) { var dispatcher = Application.Current?.Dispatcher; if (dispatcher is null || dispatcher.CheckAccess()) { UiEntries.Add(entry); while (UiEntries.Count > UiLogLimit) { UiEntries.RemoveAt(0); } return; } dispatcher.BeginInvoke( DispatcherPriority.DataBind, new Action( () => { UiEntries.Add(entry); while (UiEntries.Count > UiLogLimit) { UiEntries.RemoveAt(0); } })); } private void WriteToFile(DateTime timestamp, LogLevel level, string module, string message, Exception? exception, string? command, string? stdout, string? stderr) { var path = Path.Combine(_logsDirectory, $"app-{timestamp:yyyy-MM-dd}.log"); var sb = new StringBuilder(); sb.Append('[').Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss")).Append("] "); sb.Append(level.ToString().ToUpperInvariant()).Append(" "); sb.Append("module=").Append(module).Append(" "); sb.Append("message=").Append(message); sb.AppendLine(); if (exception is not null) { sb.AppendLine("exception:"); sb.AppendLine(exception.ToString()); } if (!string.IsNullOrWhiteSpace(command)) { sb.Append("command: ").AppendLine(command); } if (!string.IsNullOrWhiteSpace(stdout)) { sb.AppendLine("stdout:"); sb.AppendLine(stdout); } if (!string.IsNullOrWhiteSpace(stderr)) { sb.AppendLine("stderr:"); sb.AppendLine(stderr); } sb.AppendLine(new string('-', 80)); File.AppendAllText(path, sb.ToString(), Encoding.UTF8); } }