diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs index b858e9a0c..2f76a1087 100644 --- a/Dalamud.CorePlugin/PluginImpl.cs +++ b/Dalamud.CorePlugin/PluginImpl.cs @@ -6,6 +6,7 @@ using Dalamud.Game.Command; using Dalamud.Interface.Windowing; using Dalamud.Logging; using Dalamud.Plugin; +using Dalamud.Plugin.Services; using Dalamud.Utility; using Serilog; @@ -56,7 +57,7 @@ namespace Dalamud.CorePlugin /// /// Dalamud plugin interface. /// Logging service. - public PluginImpl(DalamudPluginInterface pluginInterface, PluginLog log) + public PluginImpl(DalamudPluginInterface pluginInterface, IPluginLog log) { try { diff --git a/Dalamud/Logging/PluginLog.cs b/Dalamud/Logging/PluginLog.cs index acbd663e7..b2f2a5065 100644 --- a/Dalamud/Logging/PluginLog.cs +++ b/Dalamud/Logging/PluginLog.cs @@ -1,9 +1,6 @@ using System; using System.Reflection; -using Dalamud.IoC; -using Dalamud.IoC.Internal; -using Dalamud.Plugin.Internal.Types; using Serilog; using Serilog.Events; @@ -12,29 +9,9 @@ namespace Dalamud.Logging; /// /// Class offering various static methods to allow for logging in plugins. /// -[PluginInterface] -[InterfaceVersion("1.0")] -[ServiceManager.ScopedService] -public class PluginLog : IServiceType, IDisposable +public static class PluginLog { - private readonly LocalPlugin plugin; - - /// - /// Initializes a new instance of the class. - /// Do not use this ctor, inject PluginLog instead. - /// - /// The plugin this service is scoped for. - internal PluginLog(LocalPlugin plugin) - { - this.plugin = plugin; - } - - /// - /// Gets or sets a prefix appended to log messages. - /// - public string? LogPrefix { get; set; } = null; - - #region Legacy static "Log" prefixed Serilog style methods + #region "Log" prefixed Serilog style methods /// /// Log a templated message to the in-game debug log. @@ -157,7 +134,7 @@ public class PluginLog : IServiceType, IDisposable #endregion - #region Legacy static Serilog style methods + #region Serilog style methods /// /// Log a templated verbose message to the in-game debug log. @@ -277,25 +254,6 @@ public class PluginLog : IServiceType, IDisposable public static void LogRaw(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values) => WriteLog(Assembly.GetCallingAssembly().GetName().Name, level, messageTemplate, exception, values); - /// - void IDisposable.Dispose() - { - // ignored - } - - #region New instanced methods - - /// - /// Log some information. - /// - /// The message. - internal void Information(string message) - { - Serilog.Log.Information($"[{this.plugin.InternalName}] {this.LogPrefix} {message}"); - } - - #endregion - private static ILogger GetPluginLogger(string? pluginName) { return Serilog.Log.ForContext("SourceContext", pluginName ?? string.Empty); @@ -314,24 +272,3 @@ public class PluginLog : IServiceType, IDisposable values); } } - -/// -/// Class offering logging services, for a specific type. -/// -/// The type to log for. -[PluginInterface] -[InterfaceVersion("1.0")] -[ServiceManager.ScopedService] -public class PluginLog : PluginLog -{ - /// - /// Initializes a new instance of the class. - /// Do not use this ctor, inject PluginLog instead. - /// - /// The plugin this service is scoped for. - internal PluginLog(LocalPlugin plugin) - : base(plugin) - { - this.LogPrefix = typeof(T).Name; - } -} diff --git a/Dalamud/Logging/ScopedPluginLogService.cs b/Dalamud/Logging/ScopedPluginLogService.cs new file mode 100644 index 000000000..8c502fcf0 --- /dev/null +++ b/Dalamud/Logging/ScopedPluginLogService.cs @@ -0,0 +1,130 @@ +using System; + +using Dalamud.IoC; +using Dalamud.IoC.Internal; +using Dalamud.Plugin.Internal.Types; +using Dalamud.Plugin.Services; +using Serilog; +using Serilog.Core; +using Serilog.Events; + +namespace Dalamud.Logging; + +/// +/// Implementation of . +/// +[PluginInterface] +[InterfaceVersion("1.0")] +[ServiceManager.ScopedService] +#pragma warning disable SA1015 +[ResolveVia] +#pragma warning restore SA1015 +public class ScopedPluginLogService : IServiceType, IPluginLog, IDisposable +{ + private readonly LocalPlugin localPlugin; + + private readonly LoggingLevelSwitch levelSwitch; + + /// + /// Initializes a new instance of the class. + /// + /// The plugin that owns this service. + internal ScopedPluginLogService(LocalPlugin localPlugin) + { + this.localPlugin = localPlugin; + + this.levelSwitch = new LoggingLevelSwitch(this.GetDefaultLevel()); + + var loggerConfiguration = new LoggerConfiguration() + .Enrich.WithProperty("Dalamud.PluginName", localPlugin.InternalName) + .MinimumLevel.ControlledBy(this.levelSwitch) + .WriteTo.Logger(Log.Logger); + + this.Logger = loggerConfiguration.CreateLogger(); + } + + /// + public LogEventLevel MinimumLogLevel + { + get => this.levelSwitch.MinimumLevel; + set => this.levelSwitch.MinimumLevel = value; + } + + /// + public ILogger Logger { get; } + + /// + public void Dispose() + { + GC.SuppressFinalize(this); + } + + /// + public void Fatal(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Fatal, null, messageTemplate, values); + + /// + public void Fatal(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Fatal, exception, messageTemplate, values); + + /// + public void Error(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Error, null, messageTemplate, values); + + /// + public void Error(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Error, exception, messageTemplate, values); + + /// + public void Warning(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Warning, null, messageTemplate, values); + + /// + public void Warning(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Warning, exception, messageTemplate, values); + + /// + public void Information(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Information, null, messageTemplate, values); + + /// + public void Information(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Information, exception, messageTemplate, values); + + /// + public void Debug(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Debug, null, messageTemplate, values); + + /// + public void Debug(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Debug, exception, messageTemplate, values); + + /// + public void Verbose(string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Verbose, null, messageTemplate, values); + + /// + public void Verbose(Exception? exception, string messageTemplate, params object[] values) => + this.Write(LogEventLevel.Verbose, exception, messageTemplate, values); + + /// + public void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values) + { + this.Logger.Write( + level, + exception: exception, + messageTemplate: $"[{this.localPlugin.InternalName}] {messageTemplate}", + values); + } + + /// + /// Gets the default log level for this plugin. + /// + /// A log level. + private LogEventLevel GetDefaultLevel() + { + // TODO: Add some way to save log levels to a config. Or let plugins handle it? + + return this.localPlugin.IsDev ? LogEventLevel.Verbose : LogEventLevel.Debug; + } +} diff --git a/Dalamud/Plugin/Services/IPluginLog.cs b/Dalamud/Plugin/Services/IPluginLog.cs new file mode 100644 index 000000000..87876f36f --- /dev/null +++ b/Dalamud/Plugin/Services/IPluginLog.cs @@ -0,0 +1,128 @@ +using System; + +using Serilog; +using Serilog.Events; + +namespace Dalamud.Plugin.Services; + +/// +/// An opinionated service to handle logging for plugins. +/// +public interface IPluginLog +{ + /// + /// Gets or sets the minimum log level that will be recorded from this plugin to Dalamud's logs. This may be set + /// by either the plugin or by Dalamud itself. + /// + /// + /// Defaults to for downloaded plugins, and + /// for dev plugins. + /// + LogEventLevel MinimumLogLevel { get; set; } + + /// + /// Gets an instance of the Serilog for advanced use cases. The provided logger will handle + /// tagging all log messages with the appropriate context variables and properties. + /// + /// + /// Not currently part of public API - will be added after some formatter work has been completed. + /// + internal ILogger Logger { get; } + + /// + /// Log a message to the Dalamud log for this plugin. This log level should be + /// used primarily for unrecoverable errors or critical faults in a plugin. + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Fatal(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Fatal(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Log a message to the Dalamud log for this plugin. This log level should be + /// used for recoverable errors or faults that impact plugin functionality. + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Error(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Error(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Log a message to the Dalamud log for this plugin. This log level should be + /// used for user error, potential problems, or high-importance messages that should be logged. + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Warning(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Warning(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Log an message to the Dalamud log for this plugin. This log level + /// should be used for general plugin operations and other relevant information to track a plugin's behavior. + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Information(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Information(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Log a message to the Dalamud log for this plugin. This log level should be + /// used for messages or information that aid with debugging or tracing a plugin's operations, but should not be + /// recorded unless requested. + /// + /// + /// By default, this log level is below the default log level of Dalamud. Messages logged at this level will not be + /// recorded unless the global log level is specifically set to Debug or lower. If information should be generally + /// or easily accessible for support purposes without the user taking additional action, consider using the + /// Information level instead. Developers should not use this log level where it can be triggered on a + /// per-frame basis. + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Debug(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Debug(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Log a message to the Dalamud log for this plugin. This log level is + /// intended almost primarily for development purposes and detailed tracing of a plugin's operations. Verbose logs + /// should not be used to expose information useful for support purposes. + /// + /// + /// By default, this log level is below the default log level of Dalamud. Messages logged at this level will not be + /// recorded unless the global log level is specifically set to Verbose. Release plugins must also set the + /// to to use this level, and should only do so + /// upon specific user request (e.g. a "Enable Troubleshooting Logs" button). + /// + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Verbose(string messageTemplate, params object[] values); + + /// + /// An (optional) exception that should be recorded alongside this event. + void Verbose(Exception? exception, string messageTemplate, params object[] values); + + /// + /// Write a raw log event to the plugin's log. Used for interoperability with other log systems, as well as + /// advanced use cases. + /// + /// The log level for this event. + /// An (optional) exception that should be recorded alongside this event. + /// Message template describing the event. + /// Objects positionally formatted into the message template. + void Write(LogEventLevel level, Exception? exception, string messageTemplate, params object[] values); +}