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);
});
}
}
}
}