using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Swan.Logging { /// /// Entry-point for logging. Use this static class to register/unregister /// loggers instances. By default, the ConsoleLogger is registered. /// public static class Logger { private static readonly object SyncLock = new object(); private static readonly List Loggers = new List(); private static ulong _loggingSequence; static Logger() { if (Terminal.IsConsolePresent) Loggers.Add(ConsoleLogger.Instance); if (DebugLogger.IsDebuggerAttached) Loggers.Add(DebugLogger.Instance); } #region Standard Public API /// /// Registers the logger. /// /// The type of logger to register. /// There is already a logger with that class registered. public static void RegisterLogger() where T : ILogger { lock (SyncLock) { var loggerInstance = Loggers.FirstOrDefault(x => x.GetType() == typeof(T)); if (loggerInstance != null) throw new InvalidOperationException("There is already a logger with that class registered."); Loggers.Add(Activator.CreateInstance()); } } /// /// Registers the logger. /// /// The logger. public static void RegisterLogger(ILogger logger) { lock (SyncLock) Loggers.Add(logger); } /// /// Unregisters the logger. /// /// The logger. /// logger. public static void UnregisterLogger(ILogger logger) => RemoveLogger(x => x == logger); /// /// Unregisters the logger. /// /// The type of logger to unregister. public static void UnregisterLogger() => RemoveLogger(x => x.GetType() == typeof(T)); /// /// Remove all the loggers. /// public static void NoLogging() { lock (SyncLock) Loggers.Clear(); } #region Debug /// /// Logs a debug message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Debug( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a debug message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Debug( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Debug, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a debug message to the console. /// /// The exception. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Debug( this Exception extendedData, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Debug, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } #endregion #region Trace /// /// Logs a trace message to the console. /// /// The text. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Trace( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a trace message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Trace( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Trace, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a trace message to the console. /// /// The extended data. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Trace( this Exception extendedData, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Trace, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } #endregion #region Warn /// /// Logs a warning message to the console. /// /// The text. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Warn( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a warning message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Warn( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Warning, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a warning message to the console. /// /// The extended data. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Warn( this Exception extendedData, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Warning, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } #endregion #region Fatal /// /// Logs a warning message to the console. /// /// The text. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Fatal( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a warning message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Fatal( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Fatal, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a warning message to the console. /// /// The extended data. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Fatal( this Exception extendedData, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Fatal, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } #endregion #region Info /// /// Logs an info message to the console. /// /// The text. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Info( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an info message to the console. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Info( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Info, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an info message to the console. /// /// The extended data. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Info( this Exception extendedData, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Info, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } #endregion #region Error /// /// Logs an error message to the console's standard error. /// /// The text. /// The source. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Error( this string message, string source = null, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Error, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an error message to the console's standard error. /// /// The message. /// The source. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Error( this string message, Type source, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Error, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an error message to the console's standard error. /// /// The exception. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Error( this Exception ex, string source, string message, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Error, message, source, ex, callerMemberName, callerFilePath, callerLineNumber); } #endregion #endregion #region Extended Public API /// /// Logs the specified message. /// /// The message. /// The source. /// Type of the message. /// The extended data. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Log( this string message, string source, LogLevel messageType, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(messageType, message, source, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs the specified message. /// /// The message. /// The source. /// Type of the message. /// The extended data. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Log( this string message, Type source, LogLevel messageType, object extendedData = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(messageType, message, source?.FullName, extendedData, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an error message to the console's standard error. /// /// The ex. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Log( this Exception ex, string source = null, string message = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Error, message ?? ex.Message, source ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs an error message to the console's standard error. /// /// The ex. /// The source. /// The message. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Log( this Exception ex, Type source = null, string message = null, [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { LogMessage(LogLevel.Error, message ?? ex.Message, source?.FullName ?? ex.Source, ex, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a trace message showing all possible non-null properties of the given object /// This method is expensive as it uses Stringify internally. /// /// The object. /// The source. /// The title. /// Name of the caller member. This is automatically populated. /// The caller file path. This is automatically populated. /// The caller line number. This is automatically populated. public static void Dump( this object obj, string source, string text = "Object Dump", [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { if (obj == null) return; var message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}"; LogMessage(LogLevel.Trace, message, source, obj, callerMemberName, callerFilePath, callerLineNumber); } /// /// Logs a trace message showing all possible non-null properties of the given object /// This method is expensive as it uses Stringify internally. /// /// The object. /// The source. /// The text. /// Name of the caller member. /// The caller file path. /// The caller line number. public static void Dump( this object obj, Type source, string text = "Object Dump", [CallerMemberName] string callerMemberName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0) { if (obj == null) return; var message = $"{text} ({obj.GetType()}): {Environment.NewLine}{obj.Stringify().Indent(5)}"; LogMessage(LogLevel.Trace, message, source?.FullName, obj, callerMemberName, callerFilePath, callerLineNumber); } #endregion private static void RemoveLogger(Func criteria) { lock (SyncLock) { var loggerInstance = Loggers.FirstOrDefault(criteria); if (loggerInstance == null) throw new InvalidOperationException("The logger is not registered."); loggerInstance.Dispose(); Loggers.Remove(loggerInstance); } } private static void LogMessage( LogLevel logLevel, string message, string sourceName, object extendedData, string callerMemberName, string callerFilePath, int callerLineNumber) { var sequence = _loggingSequence; var date = DateTime.UtcNow; _loggingSequence++; var loggerMessage = string.IsNullOrWhiteSpace(message) ? string.Empty : message.RemoveControlCharsExcept('\n'); var eventArgs = new LogMessageReceivedEventArgs( sequence, logLevel, date, sourceName, loggerMessage, extendedData, callerMemberName, callerFilePath, callerLineNumber); foreach (var logger in Loggers) { Task.Run(() => { if (logger.LogLevel <= logLevel) logger.Log(eventArgs); }); } } } }