diff --git a/.gitignore b/.gitignore index 7700920..83f4d69 100644 --- a/.gitignore +++ b/.gitignore @@ -258,3 +258,6 @@ paket-files/ #VS Code .vscode + +#Sonar Lint +.sonarlint diff --git a/README.md b/README.md index 502b9b3..f33c1f2 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,68 @@ _logger.LogTrace("This won't be displayed, only critical will be"); _logger.LogCritical("Critical message will be displayed"); ``` +### Create your own formatting + +You can use a custom formatter which will give you the name of the logger, the log level, the event ID, the message itself and a potential exception. The function definition should follow the following pattern: + +```csharp +public interface IMessageFormatter +{ + string MessageFormatter(string className, LogLevel logLevel, EventId eventId, string state, Exception exception); +} +``` + +**Important**: this function will be called directly, without instantiating the class it is part of. So make sure either this function is a static, either it's part of the class using the logger. The static option always works. The interface is given for convenience and to give the format. + +To setup the formatting, just use the following line. The type of the class containing the function and the exact name of the function are required. + +```csharp +LoggerExtensions.MessageFormatter = typeof(MyFormatter).GetType().GetMethod("MessageFormatterStatic"); + +public class MyFormatter +{ + public string MessageFormatterStatic(string className, LogLevel logLevel, EventId eventId, string state, Exception exception) + { + string logstr = string.Empty; + switch (logLevel) + { + case LogLevel.Trace: + logstr = "TRACE: "; + break; + case LogLevel.Debug: + logstr = "I love debug: "; + break; + case LogLevel.Warning: + logstr = "WARNING: "; + break; + case LogLevel.Error: + logstr = "ERROR: "; + break; + case LogLevel.Critical: + logstr = "CRITICAL:"; + break; + case LogLevel.None: + case LogLevel.Information: + default: + break; + } + + string eventstr = eventId.Id != 0 ? $" Event ID: {eventId}, " : string.Empty; + string msg = $"[{className}] {eventstr}{logstr} {state}"; + if (exception != null) + { + msg += $" {exception}"; + } + + return msg; + } +} +``` + +You are free to use anything you'd like and format as you like the message. + +Note: It is **not** necessary to add a \r\n at the end, this is done by each logger. + ## Code of Conduct This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. diff --git a/Tests/UnitTestDebugLogging/FormattingTest.cs b/Tests/UnitTestDebugLogging/FormattingTest.cs new file mode 100644 index 0000000..f845f82 --- /dev/null +++ b/Tests/UnitTestDebugLogging/FormattingTest.cs @@ -0,0 +1,114 @@ +using nanoFramework.TestFramework; +using System; +using System.Diagnostics; +using nanoFramework.Logging; +using Microsoft.Extensions.Logging; +using nanoFramework.Logging.Debug; + +namespace UnitTestDebugLogging +{ + [TestClass] + class FormattingTest + { + static DebugLogger _logger; + + [Setup] + public void SteupFormattingTest() + { + _logger = new DebugLogger(nameof(FormattingTest)); + LoggerExtensions.MessageFormatter = typeof(MyFormatter).GetType().GetMethod("MessageFormatterStatic"); + Debug.WriteLine($"{LoggerExtensions.MessageFormatter.Name}"); + _logger.MinLogLevel = LogLevel.Trace; + } + + [TestMethod] + public void TestInvoke() + { + string msg = (string)LoggerExtensions.MessageFormatter.Invoke(null, new object[] { "test", LogLevel.Trace, new EventId(0), "some text", null }); + Debug.WriteLine(msg); + } + + [TestMethod] + public void TestFormatting() + { + LogAll(); + } + + [TestMethod] + public void TestFormattingSimple() + { + LoggerExtensions.MessageFormatter = typeof(MyFormatter).GetType().GetMethod("MessageFormatterSimple"); + LogAll(); + } + + private void LogAll() + { + Debug.WriteLine($"Expexcted level: {_logger.MinLogLevel}"); + _logger.LogTrace("{0} {1}", new object[] { "param 1", 42 }); + _logger.LogDebug("{0} {1}", new object[] { "param 1", 42 }); + _logger.LogInformation("Just some information and nothing else"); + _logger.LogWarning("{0} {1}", new object[] { "param 1", 42 }); + _logger.LogError(new Exception("Big problem"), "{0} {1}", new object[] { "param 1", 42 }); + _logger.LogCritical(42, new Exception("Insane problem"), "{0} {1}", new object[] { "param 1", 42 }); + } + + [Cleanup] + public void CleanupFormattingTest() + { + LoggerExtensions.MessageFormatter = null; + } + } + + public class MyFormatter : IMessageFormatter + { + public string MessageFormatter(string className, LogLevel logLevel, EventId eventId, string state, Exception exception) + => MessageFormatterStatic(className, logLevel, eventId, state, exception); + + public string MessageFormatterStatic(string className, LogLevel logLevel, EventId eventId, string state, Exception exception) + { + string logstr = string.Empty; + switch (logLevel) + { + case LogLevel.Trace: + logstr = "TRACE: "; + break; + case LogLevel.Debug: + logstr = "I love debug: "; + break; + case LogLevel.Warning: + logstr = "WARNING: "; + break; + case LogLevel.Error: + logstr = "ERROR: "; + break; + case LogLevel.Critical: + logstr = "CRITICAL:"; + break; + case LogLevel.None: + case LogLevel.Information: + default: + break; + } + + string eventstr = eventId.Id != 0 ? $" Event ID: {eventId}, " : string.Empty; + string msg = $"[{className}] {eventstr}{logstr} {state}"; + if (exception != null) + { + msg += $" {exception}"; + } + + return msg; + } + + public string MessageFormatterSimple(string className, LogLevel logLevel, EventId eventId, string state, Exception exception) + { + string msg = $"[{className}] {logLevel}-{state}"; + if (exception != null) + { + msg += $" {exception}"; + } + + return msg; + } + } +} diff --git a/Tests/UnitTestDebugLogging/UnitTestDebugLogging.nfproj b/Tests/UnitTestDebugLogging/UnitTestDebugLogging.nfproj index 32d797e..4bfa020 100644 --- a/Tests/UnitTestDebugLogging/UnitTestDebugLogging.nfproj +++ b/Tests/UnitTestDebugLogging/UnitTestDebugLogging.nfproj @@ -29,6 +29,7 @@ + diff --git a/nanoFramework.Logging.Serial/SerialLogger.cs b/nanoFramework.Logging.Serial/SerialLogger.cs index 19351f9..01d83a1 100644 --- a/nanoFramework.Logging.Serial/SerialLogger.cs +++ b/nanoFramework.Logging.Serial/SerialLogger.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; +using System.Reflection; using Windows.Devices.SerialCommunication; using Windows.Storage.Streams; @@ -21,13 +22,20 @@ public class SerialLogger : ILogger /// Creates a new instance of the /// /// The serial port to use - public SerialLogger(ref SerialDevice serialDevice) + /// The logger name + public SerialLogger(ref SerialDevice serialDevice, string loggerName) { SerialDevice = serialDevice; + LoggerName = loggerName; _outputDataWriter = new DataWriter(serialDevice.OutputStream); MinLogLevel = LogLevel.Debug; } + /// + /// Name of the logger + /// + public string LoggerName { get; } + /// /// Name of the serial device /// @@ -42,12 +50,21 @@ public SerialLogger(ref SerialDevice serialDevice) public bool IsEnabled(LogLevel logLevel) => logLevel >= MinLogLevel; /// - public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception) + public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception, MethodInfo format) { if (logLevel >= MinLogLevel) { - string msg = exception == null ? $"{state}\r\n" : $"{state} {exception}\r\n"; - _outputDataWriter.WriteString(msg); + string msgSerial; + if (format == null) + { + msgSerial = exception == null ? $"{state}\r\n" : $"{state} {exception}\r\n"; + } + else + { + msgSerial = $"{(string)format.Invoke(null, new object[] { LoggerName, logLevel, eventId, state, exception })}\r\n"; + } + + _outputDataWriter.WriteString(msgSerial); _outputDataWriter.Store(); } } diff --git a/nanoFramework.Logging.Serial/SerialLoggerFactory.cs b/nanoFramework.Logging.Serial/SerialLoggerFactory.cs index cb07404..bd8b3fd 100644 --- a/nanoFramework.Logging.Serial/SerialLoggerFactory.cs +++ b/nanoFramework.Logging.Serial/SerialLoggerFactory.cs @@ -55,7 +55,7 @@ public ILogger CreateLogger(string categoryName) _serial.StopBits = _stopBits; _serial.Handshake = _handshake; _serial.DataBits = _dataBits; - return new SerialLogger(ref _serial); + return new SerialLogger(ref _serial, categoryName); } /// diff --git a/nanoFramework.Logging.Stream/StreamLogger.cs b/nanoFramework.Logging.Stream/StreamLogger.cs index 3012071..c75c76f 100644 --- a/nanoFramework.Logging.Stream/StreamLogger.cs +++ b/nanoFramework.Logging.Stream/StreamLogger.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; using System.IO; +using System.Reflection; using System.Text; namespace nanoFramework.Logging.Stream @@ -12,20 +13,27 @@ namespace nanoFramework.Logging.Stream /// /// A logger that outputs to a . /// - public class StreamLogger : ILogger, IDisposable + public class StreamLogger : ILogger { - private System.IO.Stream _stream = null; + private readonly System.IO.Stream _stream = null; /// /// Creates a new instance of the /// /// Stream to output the log to. - public StreamLogger(System.IO.Stream stream) + /// The logger name + public StreamLogger(System.IO.Stream stream, string loggerName) { _stream = stream; + LoggerName = loggerName; MinLogLevel = LogLevel.Debug; } + /// + /// Name of the logger + /// + public string LoggerName { get; } + /// /// Name of the logger /// @@ -40,25 +48,24 @@ public StreamLogger(System.IO.Stream stream) public bool IsEnabled(LogLevel logLevel) => logLevel >= MinLogLevel; /// - public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception) + public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception, MethodInfo format) { if (logLevel >= MinLogLevel) { - string msg = exception == null ? $"{state}\r\n" : $"{state} {exception}\r\n"; - byte[] sampleBuffer = Encoding.UTF8.GetBytes(msg); + string msgStream; + if (format == null) + { + msgStream = exception == null ? $"{state}\r\n" : $"{state} {exception}\r\n"; + } + else + { + msgStream = $"{(string)format.Invoke(null, new object[] { LoggerName, logLevel, eventId, state, exception })}\r\n"; + } + + byte[] sampleBuffer = Encoding.UTF8.GetBytes(msgStream); _stream.Seek(0, SeekOrigin.End); _stream.Write(sampleBuffer, 0, sampleBuffer.Length); } } - - /// - public void Dispose() - { - if(_stream != null) - { - _stream.Dispose(); - _stream = null; - } - } } } diff --git a/nanoFramework.Logging.Stream/StreamLoggerFactory.cs b/nanoFramework.Logging.Stream/StreamLoggerFactory.cs index d7052b9..560edf0 100644 --- a/nanoFramework.Logging.Stream/StreamLoggerFactory.cs +++ b/nanoFramework.Logging.Stream/StreamLoggerFactory.cs @@ -40,7 +40,7 @@ public ILogger CreateLogger(string categoryName) throw new IOException(); } - return new StreamLogger(_stream); + return new StreamLogger(_stream, categoryName); } /// diff --git a/nanoFramework.Logging/DebugLogger.cs b/nanoFramework.Logging/DebugLogger.cs index 404f35c..d42de45 100644 --- a/nanoFramework.Logging/DebugLogger.cs +++ b/nanoFramework.Logging/DebugLogger.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; +using System.Reflection; namespace nanoFramework.Logging.Debug { @@ -16,6 +17,7 @@ public class DebugLogger : ILogger /// /// Creates a new instance of the /// + /// The logger name public DebugLogger(string loggerName) { LoggerName = loggerName; @@ -36,11 +38,20 @@ public DebugLogger(string loggerName) public bool IsEnabled(LogLevel logLevel) => logLevel >= MinLogLevel; /// - public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception) + public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception, MethodInfo format) { if (logLevel >= MinLogLevel) { - string msg = exception == null ? state : $"{state} {exception}"; + string msg; + if (format == null) + { + msg = exception == null ? state : $"{state} {exception}"; + } + else + { + msg = (string)format.Invoke(null, new object[] { LoggerName, logLevel, eventId, state, exception }); + } + System.Diagnostics.Debug.WriteLine(msg); } } diff --git a/nanoFramework.Logging/ILogger.cs b/nanoFramework.Logging/ILogger.cs index 1707fdc..0c1799e 100644 --- a/nanoFramework.Logging/ILogger.cs +++ b/nanoFramework.Logging/ILogger.cs @@ -4,6 +4,7 @@ // using System; +using System.Reflection; namespace Microsoft.Extensions.Logging { @@ -20,7 +21,8 @@ public interface ILogger /// Id of the event. /// The entry to be written. /// The exception related to this entry. - void Log(LogLevel logLevel, EventId eventId, string state, Exception exception); + /// + void Log(LogLevel logLevel, EventId eventId, string state, Exception exception, MethodInfo format); /// /// Checks if the given is enabled. diff --git a/nanoFramework.Logging/IMessageFormatter.cs b/nanoFramework.Logging/IMessageFormatter.cs new file mode 100644 index 0000000..f421610 --- /dev/null +++ b/nanoFramework.Logging/IMessageFormatter.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Logging; +using System; + +namespace nanoFramework.Logging +{ + /// + /// Provide the skeleton for a Message Formatter logger + /// + public interface IMessageFormatter + { + /// + /// The function to format the message + /// + /// The name of the logger + /// The log level + /// The event id + /// The message itself + /// The exception + /// A formatted string + string MessageFormatter(string className, LogLevel logLevel, EventId eventId, string state, Exception exception); + } +} diff --git a/nanoFramework.Logging/LogDispatcher.cs b/nanoFramework.Logging/LogDispatcher.cs index 83dfe05..590b781 100644 --- a/nanoFramework.Logging/LogDispatcher.cs +++ b/nanoFramework.Logging/LogDispatcher.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using System; +using System.Reflection; namespace nanoFramework.Logging { @@ -54,7 +55,7 @@ public static ILogger GetCurrentClassLogger(this object currentClass) private class NullLogger : ILogger { - public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception) + public void Log(LogLevel logLevel, EventId eventId, string state, Exception exception, MethodInfo format) { // nothing to do here } diff --git a/nanoFramework.Logging/LoggerExtensions.cs b/nanoFramework.Logging/LoggerExtensions.cs index 8846410..86137a3 100644 --- a/nanoFramework.Logging/LoggerExtensions.cs +++ b/nanoFramework.Logging/LoggerExtensions.cs @@ -4,6 +4,7 @@ // using System; +using System.Reflection; namespace Microsoft.Extensions.Logging { @@ -12,17 +13,22 @@ namespace Microsoft.Extensions.Logging /// public static class LoggerExtensions { - #region Debug - /// - /// Formats and writes a debug log message. + /// Provide a way to have the message formated /// - /// The to write to. - /// The event id associated with the log. - /// The exception to log. - /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" - /// An object array that contains zero or more objects to format. - /// logger.LogDebug(0, exception, "Error while processing request from {Address}", address) + public static MethodInfo MessageFormatter { get; set; } + + #region Debug + + /// + /// Formats and writes a debug log message. + /// + /// The to write to. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(0, exception, "Error while processing request from {Address}", address) public static void LogDebug(this ILogger logger, EventId eventId, Exception exception, string message, params object[] args) { logger.Log(LogLevel.Debug, eventId, exception, message, args); @@ -346,6 +352,10 @@ public static void LogCritical(this ILogger logger, string message, params objec logger.Log(LogLevel.Critical, message, args); } + #endregion + + #region Primitives + /// /// Formats and writes a log message at the specified log level. /// @@ -400,7 +410,7 @@ public static void Log(this ILogger logger, LogLevel logLevel, EventId eventId, throw new ArgumentNullException(nameof(logger)); } - logger.Log(logLevel, eventId, string.Format(message, args), exception); + logger.Log(logLevel, eventId, string.Format(message, args), exception, MessageFormatter); } #endregion diff --git a/nanoFramework.Logging/nanoFramework.Logging.nfproj b/nanoFramework.Logging/nanoFramework.Logging.nfproj index 8e5fc85..582221e 100644 --- a/nanoFramework.Logging/nanoFramework.Logging.nfproj +++ b/nanoFramework.Logging/nanoFramework.Logging.nfproj @@ -1,7 +1,7 @@ - $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ + $(MSBuildToolsPath)..\..\..\nanoFramework\v1.0\ @@ -23,6 +23,7 @@ +