Prepare data before NLog send it to target? - c#

I need to serialize some objects before NLog sends it to its targets. I could write custom a target, but then I will have to write a custom target for each possible log target.
What I need to know is if NLog will be logging the message(based on the level) to any target and if so, serialize the data. If the logdata states a level that is not to be logged according to the NLog configuration, then I want to avoid serializing the objects because this takes time.
Is there any way to prepare data before NLog sends it to targets or will I have to solve this in custom target classes?

You can get a Logger and check the IsXXXEnabled property.
For example:
class MyExpensiveClass
{
public void string Serialize()
{
return SomethingExpensive;
}
}
class ThisNeedsLogging
{
private static Logger logger = LogManager.GetCurrentClassLogger();
private MyExpensiveClass expensive = new MyExpensiveClass();
public void TraceSomething()
{
if (logger.IsDebugEnabled)
logger.Debug(expensive.Serialize());
}
}

Related

Any way to read Serilog's LogContext later in a downstream class?

I'm pushing values to the Serilog LogContext in a middleware class of a .NET Core web app:-
using LogContext.PushProperty("MyAct", "some clever joke") ...
Is it possible to read the value for MyAct property in a downstream class? I don't see anything promising on the LogContext class - seems like a write only entity, but maybe I am missing something?
Serilog does not provide for you to Just Read the values from the LogContext.
Normally the PushProperty stashes it, and Enrich.FromLogContext is charged with grabbing the contextual information that will accompany the destructured message and tokens as the logging call is capturing them into the LogEvent that'll then be passed to the chain of sinks.
Really this begs the question: why do you feel you need to do this?
Actually answering the question: It is possible the underlying APIs are exposed in a usable fashion though; I'd go to https://github.com/serilog/serilog - the LogContext impl and tests are all in that core repo.
I have a similar problem. I want to write a Unit test for my Middleware that pushes context and want to test if I am pushing the right context information.
I solved it by creating a Logger that writes to an observer.
(Adding the Serilog.Sinks.Observable to my test dependencies)
private class LogEventStoreObserver : IObserver<LogEvent>
{
public LogEvent LastEvent { get; set; }
public void OnCompleted() { }
public void OnError(Exception error) { }
public void OnNext(LogEvent value)
{
LastEvent = value;
}
}
Log.Logger = new LoggerConfiguration().WriteTo.Observers(events =>
events.Subscribe(logObserver))
.Enrich.FromLogContext()
.CreateLogger();
Now I can read the LogEvent properties which contains the LogContext like this:
// Setup
LogEvent testLogEvent = null;
nextMock.Setup(_ => _(It.IsAny<HttpContext>())).Callback<HttpContext>((_) =>
{
Log.Information("Test");
testLogEvent = logObserver.LastEvent;
});
// Act
await middleware.InvokeAsync(httpContext);
// Verify
testLogEvent.Properties.Should().ContainKey("mySpecialContext");

Including logging as part of my domain model

I'm writing an application where logging is part of my actual domain model. It's an automation and batch processing tool where end users will be able to view the logs of a batch processing job in the actual application and not just text log files.
So my domain model includes a LogMessage class:
public sealed class LogMessage
{
public string Message { get; }
public DateTime TimestampUtc { get; }
public LogLevel Level { get; }
}
public enum LogLevel
{
Fatal = 5,
Error = 4,
Warn = 3,
Info = 2,
Debug = 1,
Trace = 0
}
I also have a Result class which has a collection property of LogMessages. Results can be saved to and opened from files with my application by end users.
public class Result
{
public bool Succeeded {get; set;}
public string StatusMessage {get; set;}
public IList<LogMessage> LogMessages {get; set;}
}
My application also supports third party developers extending the application with plug-ins that can also write log messages. So I've defined a generic ILogger interface for the plug-in developers.
public interface ILogger
{
void Debug(string message);
void Error(string message);
void Fatal(string message);
void Info(string message);
void Log(LogLevel level, string message);
void Trace(string message);
void Warn(string message);
}
I provide an instance of an ILogger to the plug-ins which writes to Result.LogMessages.
public interface IPlugIn
{
Output DoSomeThing(Input in, ILogger logger);
}
I obviously also want to be able to log from my own internal code and ultimately want Result.LogMessages to contain a mixture of my internal log messages and log messages from plug-ins. So an end user having trouble could send me a Result file that would contain debug logs both from my internal code, and any plug-ins used.
Currently, I have a solution working using a custom NLog target.
public class LogResultTarget : NLog.Targets.Target
{
public static Result CurrentTargetResult { get; set; }
protected override void Write(NLog.LogEventInfo logEvent)
{
if (CurrentTargetResult != null)
{
//Convert NLog logEvent to LogMessage
LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name);
LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message);
CurrentTargetResult.LogMessages.Add(lm);
}
}
protected override void Write(NLog.Common.AsyncLogEventInfo logEvent)
{
Write(logEvent.LogEvent);
}
}
This class forwards message to the Result assigned to the static LogResultTarget.CurrentTargetResult property. My internal code logs to NLog loggers, and and I have a implementation of ILogger that logs to an NLog.Logger as well.
This is working, but feels really fragile. If CurrentTargetResult is not set correctly or not set back to null I can end up with log messages being stored to results that they do not apply to. Also because there is only one static CurrentTargetResult there's no way I could support processing multiple results simultaneously.
Is there a different/better way I could approach this? Or is what I'm trying to do fundamentally wrong?
I think your approach is the right one, but you could save effort by using a library which already does this abstraction for you. The Common Logging library is what you're after.
Your domain code will depend only on the ILogger interface from Common Logging. Only when your domain is used by a runtime e.g. Web API, do you then configure what logging provider you're going to use.
There are a number of pre-built providers available as separate nuget packages:
Common.Logging provides adapters that support all of the following popular logging targets/frameworks in .NET:
Log4Net (v1.2.9 - v1.2.15)
NLog (v1.0 - v4.4.1)
SeriLog (v1.5.14)
Microsoft Enterprise Library Logging Application Block (v3.1 - v6.0)
Microsoft AppInsights (2.4.0)
Microsoft Event Tracing for Windows (ETW)
Log to STDOUT
Log to DEBUG OUT
I've used this for a number of years and it's been great to have your domain/library code be reused in another context, but not have to have a fixed dependency on a logging framework (I've moved from Enterprise Libraries to log4net, to finally NLog ... it was a breeze).
In think the static CurrentTargetResult is indeed a bit fragile, but the overal approach is fine.
I propose the following changes:
unstatic the CurrentTargetResult, and always make it initialized,
Something like this:
public class LogResultTarget : NLog.Targets.Target
{
public Result CurrentTargetResult { get; } = new Result();
protected override void Write(NLog.LogEventInfo logEvent)
{
//Convert NLog logEvent to LogMessage
LogLevel level = (LogLevel)Enum.Parse(typeof(LogLevel), logEvent.Level.Name);
LogMessage lm = new LogMessage(logEvent.TimeStamp.ToUniversalTime(), level, logEvent.Message);
CurrentTargetResult.LogMessages.Add(lm);
}
protected override void Write(NLog.Common.AsyncLogEventInfo logEvent)
{
Write(logEvent.LogEvent);
}
}
Always initialize the LogMessages:
public class Result
{
public bool Succeeded {get; set;}
public string StatusMessage {get; set;}
public IList<LogMessage> LogMessages {get; set;} = new List<LogMessage>();
}
Retrieve the messages - when needed - with the following calls:
// find target by name
var logResultTarget1 = LogManager.Configuration.FindTargetByName<LogResultTarget>("target1");
var results = logResultTarget1.CurrentTargetResult;
// or multiple targets
var logResultTargets = LogManager.Configuration.AllTargets.OfType<LogResultTarget>();
var allResults = logResultTargets.Select(t => t.CurrentTargetResult);
PS: You could also overwrite InitializeTarget in LogResultTarget for initialing the target

can I pass a custom property to NLOG and output to file?

EDIT 4: "From" seems to be a reserved word in NLog. Changing it "FromID" worked. this is an awesome way to pass variables to NLog and still keep your code clean !!!! THANK MIKE!!!
EDIT 3. I really like this idea.:
Implemented a helper class as Mike suggested below:
public class NLogHelper
{
//
// Class Properties
//
private Logger m_logger;
private Dictionary<string, object> m_properties;
//
// Constructor
//
public NLogHelper(Logger logger)
{
m_logger = logger;
m_properties = new Dictionary<string, object>();
}
//
// Setting Logger properties per instancce
//
public void Set(string key, object value)
{
m_properties.Add(key, value);
}
//
// Loggers
//
public void Debug(string format, params object[] args)
{
m_logger.Debug()
.Message(format, args)
.Properties(m_properties)
.Write();
}
and in my main code, I have:
private NLogHelper m_logger;
public void Start()
{
m_logger = new NLogHelper(LogManager.GetCurrentClassLogger());
m_logger.Set("From", "QRT123"); // Class setting.
m_logger.Debug("Hello ");
}
And the target set in the config file as follows:
<target xsi:type="File"
name ="LogFile" fileName="C:\QRT\Logs\QRTLog-${shortdate}.log"
layout ="${date}|${level}|${event-properties:item=From}|${message} "/>
But the output has a BLANK in the place of the 'from' property ???
So I'm ALMOST THERE... but it does not seem to work??
EDIT 2:
I am now trying to create my own version of the NLog call:
private void Log_Debug (string Message)
{
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "What is this?", Message);
theEvent.Properties["EmployeeID"] = m_employeeID;
m_logger.Log(theEvent);
}
The issue is that I have to format the string for the calls (but a huge performance deal)... but this seems like a hack??
Ideally, I would declare properties in the custom layout renderer and instead of setting those properties in the configuration file, each instance of my class would have the property set... something like [ID = m_ID] for the whole class. This way whenever a NLog is called from that class, the ID property is set and NLog's custom layout renderer can use this property to output it. Am I making sense??
I'm new to NLog and have been looking at custom renderers.
Basically, my goal is to have my log statements be:
_logger.Debug ("My Name is {0}", "Ed", ID=87);
and I'd like my rendered to be something like:
layout = ${ID} ${date} ${Level} ${Message}
That's it. ${ID} can have a default value of 0. fine. But ideally, I'd like every call to have the ability to specify an ID without needing to have 3 lines everytime I want to log.
I've seen custom renderers allowing me to customize what I output but i'm not sure how I can customize the properties I pass to it without
https://github.com/NLog/NLog/wiki/Extending%20NLog shows how I can add properties but I don't know how to call them.
Also, https://github.com/NLog/NLog/wiki/Event-Context-Layout-Renderer shows how I can set custom properties but that involved the creation of a LogEventInfo object every time I want to log something.
Nlog Custom layoutrenderer shows how to customize the output.. again... not how to customize the inputs.
This is for a Console app in C# targeting .NET 4.0 using VS2013
Thanks
-Ed
Event properties (used to be called event-context) would be the built-in way to do what you want. If you are using NLog 3.2+ you can use the fluent api, which may be a bit more appealing than creating LogEventInfo objects. You can access this api by by using the namespace NLog.Fluent.
Your layout would then be defined like this:
${event-properties:item=ID} ${date} ${Level} ${Message}
Then using the fluent api, log like this:
_logger.Debug()
.Message("My name is {0}", "Ed")
.Property("ID", 87)
.Write();
Other than setting properties per event as above, the only other option would be to set properties per thread using MDC or MDLS.
NLog dosen't have a way (that I have found) of setting per-logger properties. Internally, NLog caches Logger instances by logger name, but does not guarantee that the same instance of Logger will always be returned for a given logger name. So for example if you call LogManager.GetCurrentClassLogger() in the constructor of your class, most of the time you will get back the same instance of Logger for all instances of your class. In which case, you would not be able to have separate values on your logger, per instance of your class.
Perhaps you could create a logging helper class that you can instantiate in your class. The helper class can be initialized with per-instance property values to be logged with every message. The helper class would also provide convenience methods to log messages as above, but with one line of code. Something like this:
// Example of a class that needs to use logging
public class MyClass
{
private LoggerHelper _logger;
public MyClass(int id)
{
_logger = new LoggerHelper(LogManager.GetCurrentClassLogger());
// Per-instance values
_logger.Set("ID", id);
}
public void DoStuff()
{
_logger.Debug("My name is {0}", "Ed");
}
}
// Example of a "stateful" logger
public class LoggerHelper
{
private Logger _logger;
private Dictionary<string, object> _properties;
public LoggerHelper(Logger logger)
{
_logger = logger;
_properties = new Dictionary<string, object>();
}
public void Set(string key, object value)
{
_properties.Add(key, value);
}
public void Debug(string format, params object[] args)
{
_logger.Debug()
.Message(format, args)
.Properties(_properties)
.Write();
}
}
This would work with the same layout as above.
NLog 4.5 supports structured logging using message templates:
logger.Info("Logon by {user} from {ip_address}", "Kenny", "127.0.0.1");
See also https://github.com/NLog/NLog/wiki/How-to-use-structured-logging
See also https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-properties-with-Microsoft-Extension-Logging
Use MDLC Layout Renderer
MappedDiagnosticsLogicalContext.Set("PropertyName", "PropertyValue");
MappedDiagnosticsLogicalContext.Set("PropertyName2",
"AnotherPropertyValue");
In your nlog config:
${mdlc:item=PropertyName} ${mdlc:item=PropertyName2}
https://github.com/NLog/NLog/wiki/MDLC-Layout-Renderer
I had 6 variables I wanted to send to structured logging in multiple places (so when I get a user report I can search the log database on our key id fields). I created a logging scope class that leverages the MDLC. So it should be thread safe, work with async/await code and be 3 lines of code for 6 variables everywhere used.
public class MyClassLoggingScope : IDisposable
{
private readonly List<IDisposable> context;
public MyClassLoggingScope(MyClass varSource)
{
this.context = new List<IDisposable>
{
MappedDiagnosticsLogicalContext.SetScoped("Var1", varSource.Var1)
// list all scoped logging context variables
}
}
public void Dispose()
{
foreach (IDisposable disposable in this.context)
{
disposable.Dispose();
}
}
}
Usage:
using (new MyClassLoggingScope(this))
{
// do the things that may cause logging somewhere in the stack
}
Then as flux earlier suggested, in the logging config you can use ${mdlc:item=Var1}
This is propably not the best way to do this and not even thread safe but a quick and dirty solution: You could use Environment variables:
Environment.SetEnvironmentVariable("NLogOptionID", "87");
logger.Debug("My Name id Ed");
Environment.SetEnvironmentVariable("NLogOptionID", null);
In your layout, you can use environment variables.
${environment:variable=NLogOptionID}

Passing a unique value to all classes using Dependency Injection?

The following code shows the flow I’m currently trying to implement within a WCF service. The service on startup calls the Bootstrapper class which uses Unity to register and resolve the required types. The Gateway class contains the public method which then kicks off the main flow of processing a message (there are many more levels to the code than is shown below).
public static class Bootstrapper
{
public static IGateway InitializeGateway()
{
IUnityContainer resolver = new UnityContainer();
resolver.RegisterType<IGateway, Gateway>();
resolver.RegisterType<ITranslator, Translator>();
resolver.RegisterType<IFormatter, IFormatter>();
return resolver.Resolve<IGateway>();
}
}
public class Gateway : IGateway
{
private readonly ITranslator translator;
private readonly IFormatter formatter;
public Gateway(ITranslator translator, IFormatter formatter)
{
this.translator = translator;
this.formatter = formatter;
}
public string ProcessMessage(string requestMessage)
{
// Create a new GUID for use in main flow for logging
Guid messageGuid = Guid.NewGuid();
requestMessage = this.translator.TranslateMessage(requestMessage);
requestMessage = this.formatter.FormatMessage(requestMessage);
return requestMessage;
}
}
Now what I’m trying to achieve is take the GUID (created for each message) and pass this down within the flow of the service such that each class has a reference to it for logging purposes.
I have tried to find a way of using DI and constructor injection but don’t know if this can be done as the GUID is created on receipt of a message by the gateway (after the bootstrapper call). What I’m trying to get away from is passing the GUID into each method as a parameter.
Any suggestions?
Instead of rolling your own solution to this problem with DI, I would recommend you use the thread-static property Trace.CorrelationManager.ActivityId for this purpose.
Take a look at this article on WCF End-To-End Tracing.

Is it possible to get the currently executing classname using reflection?

I'm writing a logging class and I would like to be able to get the name of the class that has the call to Helper.Log(string message).
Is this possible using reflection and c#?
Yes, it is quite easy.
Helper.Log("[" + this.GetType().Name + "]: " + message);
Note that if your logger class is really a wrapper around a logging framework (like log4net or NLog), the logging framework can be configured to get the calling class/method for you. For this to work correctly, you have to wrap the logging framework correctly. For NLog and log4net, correctly wrapping (to preserve call site information) generally involves using the "Log" method (rather than the Error, Warn, Info, etc variants) and passing the "logger type" as the first parameter. The "logger type" is the type of your logger that wraps the logging framework's logger.
Here is one way to wrap NLog (taken from here):
class MyLogger
{
private Logger _logger;
public MyLogger(string name)
{
_logger = LogManager.GetLogger(name);
}
public void WriteMessage(string message)
{
///
/// create log event from the passed message
///
LogEventInfo logEvent = new LogEventInfo(LogLevel.Info, _logger.Name, message);
// Call the Log() method. It is important to pass typeof(MyLogger) as the
// first parameter. If you don't, ${callsite} and other callstack-related
// layout renderers will not work properly.
//
_logger.Log(typeof(MyLogger), logEvent);
}
}
And here is how you could do it with log4net:
class MyLogger
{
private ILog _logger;
public MyLogger(string name)
{
_logger = LogManager.GetLogger(name);
}
public void WriteMessage(string message)
{
// Call the Log() method. It is important to pass typeof(MyLogger) as the
// first parameter. If you don't, ${callsite} and other callstack-related
// formatters will not work properly.
//
_logger.Log(typeof(MyLogger), LogLevel.Info, message);
}
}

Categories