namespace Unosquare.Swan { using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; /// /// A console terminal helper to create nicer output and receive input from the user /// This class is thread-safe :). /// public static partial class Terminal { #region Private Declarations private static ulong _loggingSequence; #endregion #region Events /// /// Occurs asynchronously, whenever a logging message is received by the terminal. /// Only called when Terminal writes data via Info, Error, Trace, Warn, Fatal, Debug methods, regardless of whether or not /// the console is present. Subscribe to this event to pass data on to your own logger. /// public static event EventHandler OnLogMessageReceived; /// /// Occurs synchronously (so handle quickly), whenever a logging message is about to be enqueued to the /// console output. Setting the CancelOutput to true in the event arguments prevents the /// logging message to be written out to the console. /// Message filtering only works with logging methods such as Trace, Debug, Info, Warn, Fatal, Error and Dump /// Standard Write methods do not get filtering capabilities. /// public static event EventHandler OnLogMessageDisplaying; #endregion #region Main Logging Method /// /// Logs a message. /// /// Type of the message. /// The text. /// Name of the source. /// The extended data. Could be an exception, or a dictionary of properties or anything the user specifies. /// Name of the caller member. /// The caller file path. /// The caller line number. private static void LogMessage( LogMessageType messageType, string message, string sourceName, object extendedData, string callerMemberName, string callerFilePath, int callerLineNumber) { lock (SyncLock) { if (!Settings.GlobalLoggingMessageType.HasFlag(messageType)) return; var prefix = GetConsoleColorAndPrefix(messageType, out var color); #region Create and Format the Output var sequence = _loggingSequence; var date = DateTime.UtcNow; _loggingSequence++; var loggerMessage = string.IsNullOrWhiteSpace(message) ? string.Empty : message.RemoveControlCharsExcept('\n'); var outputMessage = CreateOutputMessage(sourceName, loggerMessage, prefix, date); // Log the message asynchronously with the appropriate event args var eventArgs = new LogMessageReceivedEventArgs( sequence, messageType, date, sourceName, loggerMessage, extendedData, callerMemberName, callerFilePath, callerLineNumber); #endregion #region Fire Up External Logging Logic (Asynchronously) if (OnLogMessageReceived != null) { Task.Run(() => { try { OnLogMessageReceived?.Invoke(sourceName, eventArgs); } catch { // Ignore } }); } #endregion #region Display the Message by Writing to the Output Queue // Check if we are skipping these messages to be displayed based on settings if (!Settings.DisplayLoggingMessageType.HasFlag(messageType)) return; Write(messageType, sourceName, eventArgs, outputMessage, color); #endregion } } private static void Write( LogMessageType messageType, string sourceName, LogMessageReceivedEventArgs eventArgs, string outputMessage, ConsoleColor color) { // Select the writer based on the message type var writer = IsConsolePresent ? messageType.HasFlag(LogMessageType.Error) ? TerminalWriters.StandardError : TerminalWriters.StandardOutput : TerminalWriters.None; // Set the writer to Diagnostics if appropriate (Error and Debugging data go to the Diagnostics debugger // if it is attached at all if (IsDebuggerAttached && (IsConsolePresent == false || messageType.HasFlag(LogMessageType.Debug) || messageType.HasFlag(LogMessageType.Error))) writer = writer | TerminalWriters.Diagnostics; // Check if we really need to write this out if (writer == TerminalWriters.None) return; // Further format the output in the case there is an exception being logged if (writer.HasFlag(TerminalWriters.StandardError) && eventArgs.Exception != null) { try { outputMessage = $"{outputMessage}{Environment.NewLine}{eventArgs.Exception.Stringify().Indent()}"; } catch { // Ignore } } // Filter output messages via events var displayingEventArgs = new LogMessageDisplayingEventArgs(eventArgs); OnLogMessageDisplaying?.Invoke(sourceName, displayingEventArgs); if (displayingEventArgs.CancelOutput == false) outputMessage.WriteLine(color, writer); } private static string GetConsoleColorAndPrefix(LogMessageType messageType, out ConsoleColor color) { string prefix; // Select color and prefix based on message type // and settings switch (messageType) { case LogMessageType.Debug: color = Settings.DebugColor; prefix = Settings.DebugPrefix; break; case LogMessageType.Error: color = Settings.ErrorColor; prefix = Settings.ErrorPrefix; break; case LogMessageType.Info: color = Settings.InfoColor; prefix = Settings.InfoPrefix; break; case LogMessageType.Trace: color = Settings.TraceColor; prefix = Settings.TracePrefix; break; case LogMessageType.Warning: color = Settings.WarnColor; prefix = Settings.WarnPrefix; break; case LogMessageType.Fatal: color = Settings.FatalColor; prefix = Settings.FatalPrefix; break; default: color = Settings.DefaultColor; prefix = new string(' ', Settings.InfoPrefix.Length); break; } return prefix; } private static string CreateOutputMessage(string sourceName, string loggerMessage, string prefix, DateTime date) { var friendlySourceName = string.IsNullOrWhiteSpace(sourceName) ? string.Empty : sourceName.SliceLength(sourceName.LastIndexOf('.') + 1, sourceName.Length); var outputMessage = string.IsNullOrWhiteSpace(sourceName) ? loggerMessage : $"[{friendlySourceName}] {loggerMessage}"; return string.IsNullOrWhiteSpace(Settings.LoggingTimeFormat) ? $" {prefix} >> {outputMessage}" : $" {date.ToLocalTime().ToString(Settings.LoggingTimeFormat)} {prefix} >> {outputMessage}"; } #endregion #region Standard Public API #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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.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, LogMessageType 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, LogMessageType 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(LogMessageType.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(LogMessageType.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(LogMessageType.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(LogMessageType.Trace, message, source?.FullName, obj, callerMemberName, callerFilePath, callerLineNumber); } #endregion } }