diff --git a/Commands/InteractionServiceHandler.cs b/Commands/InteractionServiceHandler.cs index 27e4632..d311063 100644 --- a/Commands/InteractionServiceHandler.cs +++ b/Commands/InteractionServiceHandler.cs @@ -1,6 +1,7 @@ -using Discord; +using Discord; using Discord.Interactions; using Discord.WebSocket; +using Serilog; namespace QuickEdit.Commands; public class InteractionServiceHandler @@ -23,9 +24,9 @@ public static async Task InitAsync() // So the event has to be used to handle the result _interactionService.SlashCommandExecuted += OnSlashCommandExecutedAsync; } - catch + catch (Exception e) { - await Program.LogAsync("InteractionServiceHandler", "Error initializing InteractionService", LogSeverity.Critical); + Log.Fatal($"Error initializing InteractionService: {e.Message}"); throw; } } @@ -38,7 +39,7 @@ public static async Task RegisterModulesAsync() // The service might not have been initialized yet if (_interactionService == null) { - await Program.LogAsync("InteractionServiceManager.RegisterModulesAsync()", "InteractionService not initialized yet", LogSeverity.Error); + Log.Error("Failed to register modules: InteractionService not initialized."); throw new InvalidOperationException("InteractionService not initialized while trying to register commands"); } @@ -52,11 +53,11 @@ public static async Task RegisterModulesAsync() await _interactionService.RegisterCommandsGloballyAsync(); _client!.InteractionCreated += OnInteractionCreatedAsync; - await Program.LogAsync("InteractionServiceManager", "Modules registered successfully", LogSeverity.Info); + Log.Information("Modules registered successfully"); } catch (Exception e) { - await Program.LogAsync("InteractionServiceManager", $"Error registering modules. ({e})", LogSeverity.Critical); + Log.Fatal($"Error registering modules: {(Program.config != null && Program.config.debug ? e : e.Message)}"); throw; } } @@ -66,7 +67,7 @@ public static async Task OnInteractionCreatedAsync(SocketInteraction interaction // The service might not have been initialized yet if (_interactionService == null) { - await Program.LogAsync("InteractionServiceManager.OnInteractionCreatedAsync()", "InteractionService not initialized yet", LogSeverity.Error); + Log.Error("Error handling interaction: InteractionService not initialized."); return; } @@ -79,7 +80,7 @@ public static async Task OnInteractionCreatedAsync(SocketInteraction interaction } catch (Exception e) { - await Program.LogAsync("InteractionServiceManager", $"Error handling interaction. {e.Message}", LogSeverity.Error); + Log.Error($"Error handling interaction: {e.Message}"); if (interaction.Type is InteractionType.ApplicationCommand) { @@ -90,19 +91,20 @@ public static async Task OnInteractionCreatedAsync(SocketInteraction interaction } } - public static async Task OnSlashCommandExecutedAsync(SlashCommandInfo commandInfo, IInteractionContext interactionContext, IResult result) { + public static async Task OnSlashCommandExecutedAsync(SlashCommandInfo commandInfo, IInteractionContext interactionContext, IResult result) + { // Only trying to handle errors lol if (result.IsSuccess) return; try { - await Program.LogAsync("InteractionServiceManager", $"Error handling interaction: {result.Error}", LogSeverity.Error); + Log.Error($"Error handling interaction: {result.Error}"); await interactionContext.Interaction.FollowupAsync("An error occurred while executing the command.", ephemeral: true); } catch (Exception e) { - await Program.LogAsync("InteractionServiceManager", $"Error handling interaction exception bruh: {e.ToString()}", LogSeverity.Error); + Log.Error($"Error handling interaction exception bruh: {e.ToString()}"); throw; } } diff --git a/Commands/Modules/VideoUtils.cs b/Commands/Modules/VideoUtils.cs index b96fd35..79366d7 100644 --- a/Commands/Modules/VideoUtils.cs +++ b/Commands/Modules/VideoUtils.cs @@ -1,6 +1,7 @@ using Discord; using Discord.Interactions; using FFMpegCore; +using Serilog; using System.Text.RegularExpressions; namespace QuickEdit.Commands.Modules; @@ -40,20 +41,25 @@ public async Task TrimVideoAsync( // Get TimeSpans TimeSpan trimStart = TimeSpan.Zero; TimeSpan trimEnd = TimeSpan.Zero; - try { + try + { // Avoid invalid format exceptions if (!string.IsNullOrEmpty(trimStartString)) trimStart = TimeSpanFromHMS(trimStartString); if (!string.IsNullOrEmpty(trimEndString)) trimEnd = TimeSpanFromHMS(trimEndString); - } catch (Exception e) { + } + catch (Exception e) + { if (e is ArgumentException) { await FollowupAsync("Invalid time format. Please provide a valid time format (XXh XXm XXs XXms).", ephemeral: true); - } else { + } + else + { throw; } return; } - + // Make sure the times are not negative | https://stackoverflow.com/a/1018659/17003609 (comment) trimStart = trimStart.Duration(); trimEnd = trimEnd.Duration(); @@ -69,7 +75,7 @@ public async Task TrimVideoAsync( if (!Directory.Exists("./tmp")) { Directory.CreateDirectory("./tmp"); - await Program.LogAsync("VideoUtils", "TMP directory not found. Created it automatically.", LogSeverity.Info); + Log.Information("TMP directory not found. Created it automatically"); } await DownloadVideoAsync(video.Url, videoInputPath); diff --git a/ConfigManager.cs b/ConfigManager.cs index 3c41807..6dfc584 100644 --- a/ConfigManager.cs +++ b/ConfigManager.cs @@ -1,5 +1,6 @@ -using Discord; +using Discord; using Newtonsoft.Json; +using Serilog; namespace QuickEdit; public class Config @@ -14,7 +15,7 @@ public class Config string path = "./config.json"; if (!File.Exists(path)) { - Program.LogAsync("Config", $"Config file not found at: {path}", LogSeverity.Critical); + Log.Fatal($"Config file not found at: {Path.GetFullPath(path)}"); return null; } @@ -22,9 +23,9 @@ public class Config { return JsonConvert.DeserializeObject(File.ReadAllText(path))!; } - catch + catch (Exception e) { - Program.LogAsync("Config", "Failed to parse config file.", LogSeverity.Critical); + Log.Fatal($"Failed to parse config file: {e.Message}"); return null; } } diff --git a/Discord QuickEdit.csproj b/Discord QuickEdit.csproj index 09fb9fe..a48676f 100644 --- a/Discord QuickEdit.csproj +++ b/Discord QuickEdit.csproj @@ -1,18 +1,21 @@  - Exe - net8.0 - QuickEdit - enable - enable - quickedit - 0.0.0.0 + Exe + net8.0 + QuickEdit + enable + enable + quickedit + 0.0.0.0 - - + + + + + diff --git a/Logger/AutoLog.cs b/Logger/AutoLog.cs new file mode 100644 index 0000000..17f02a6 --- /dev/null +++ b/Logger/AutoLog.cs @@ -0,0 +1,27 @@ +using Discord; +using Serilog; +using Serilog.Events; + +namespace QuickEdit.Logger; + +public class AutoLog +{ + // LogMessage does not run asynchronously, so we can ignore the DeepSource error + // skipcq: CS-R1073 + public static Task LogMessage(LogMessage message) + { + var logLevel = message.Severity switch + { + LogSeverity.Critical => LogEventLevel.Fatal, + LogSeverity.Error => LogEventLevel.Error, + LogSeverity.Warning => LogEventLevel.Warning, + LogSeverity.Info => LogEventLevel.Information, + LogSeverity.Verbose => LogEventLevel.Verbose, + LogSeverity.Debug => LogEventLevel.Debug, + _ => LogEventLevel.Information + }; + + Log.Write(logLevel, message.Exception, "{Source}: {Message}", message.Source, message.Message); + return Task.CompletedTask; + } +} diff --git a/Logger/SerilogConfiguration.cs b/Logger/SerilogConfiguration.cs new file mode 100644 index 0000000..8b8ee92 --- /dev/null +++ b/Logger/SerilogConfiguration.cs @@ -0,0 +1,27 @@ +using Serilog; + +namespace QuickEdit.Logger; + +public class SerilogConfiguration +{ + + public static void ConfigureLogger() + { + var logDirectory = "logs"; + + Directory.CreateDirectory(logDirectory); + + var logPath = Path.Combine(logDirectory, "quickedit-.log"); + + var loggerConfig = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.File(logPath, rollingInterval: RollingInterval.Day); + + if (Program.config != null && Program.config.debug) + loggerConfig.MinimumLevel.Debug(); + else + loggerConfig.MinimumLevel.Information(); + + Log.Logger = loggerConfig.CreateLogger(); + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index 78424fb..bf0bb32 100644 --- a/Program.cs +++ b/Program.cs @@ -1,12 +1,14 @@ -using Discord; +using Discord; using Discord.WebSocket; using FFMpegCore; using FFMpegCore.Helpers; using QuickEdit.Commands; -using System.Reflection; +using QuickEdit.Logger; +using Serilog; namespace QuickEdit; -class Program + +internal class Program { public static DiscordSocketClient? client; public static Config? config = Config.GetConfig(); @@ -16,6 +18,10 @@ class Program public async Task MainAsync() { + SerilogConfiguration.ConfigureLogger(); + + AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush(); + ShowStartMessage(); // If the config is null, we can't continue as the bot won't have a token to login with @@ -24,7 +30,7 @@ public async Task MainAsync() client = new DiscordSocketClient(socketConfig); - client.Log += LogAsync; + client.Log += AutoLog.LogMessage; client.Ready += OnReadyAsync; await client.LoginAsync(TokenType.Bot, config.token); @@ -60,59 +66,9 @@ private async Task OnReadyAsync() } catch { - await LogAsync("Program", "Exiting", LogSeverity.Info); + Log.Fatal("Program is exiting due to an error in InteractionServiceHandler."); // The program cannot continue without the InteractionService, so terminate it. Nothing important should be running at this point. Environment.Exit(1); // skipcq: CS-W1005 } } - - public Task LogAsync(LogMessage message) - { - string msg = $"[{DateTime.UtcNow.ToString("HH.mm.ss")}] {message.Source}: {message.Message}"; - Console.WriteLine(msg + " " + message.Exception); - return Task.CompletedTask; - } - - public static Task LogAsync(string source, string message, LogSeverity severity = LogSeverity.Info) - { - string msg = $"[{DateTime.UtcNow.ToString("HH.mm.ss")}] {source}: {message}"; - - // Change color / display based on severity - // TODO: Maybe use ANSI escape codes instead? - switch (severity) - { - case LogSeverity.Warning: - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine(msg); - Console.ResetColor(); - break; - case LogSeverity.Critical: - Console.ForegroundColor = ConsoleColor.DarkRed; - Console.WriteLine(msg); - Console.ResetColor(); - break; - - case LogSeverity.Error: - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(msg); - Console.ResetColor(); - break; - - case LogSeverity.Verbose: - // Verbose logs are only displayed if the debug flag is set to true in the config - if (config == null || !config.debug) break; - - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.WriteLine(msg); - Console.ResetColor(); - break; - - default: - // All other severities should have the default color of the console - Console.ResetColor(); - Console.WriteLine(msg); - break; - } - return Task.CompletedTask; - } }