In a previous blog post – Custom Layout Renderer for NLog using C#, I have mentioned about how to create a class inheriting LayoutRenderer and writing custom Layout Renderer.
This blog post talks about inheriting from WrapperLayoutRendererBase class.
The WrapperLayoutRendererBase class has a method with the signature:
protected override string Transform(string text)
This method needs to be overridden. The nice thing about using this class is other layout renderers can be used in combination.
Here is a GitHub repo implementing hash functions that can be used as LayoutRenderers.
In the above scenarion, we can use ${name} when defining a layout, of course instead of returning static string we can do something useful based on the use-case.
Class:
Define a new class inherited from NLog.LayoutRenderers.LayoutRenderer. Use class attribute LayoutRenderer(“name”). LayoutRenderer is an abstract class, implement the class by overriding Append(StringBuilder builder, LogEventInfo logEvent) method i.e Append the value that needs to be added to the log by appending to the StringBuilder parameter.
[LayoutRenderer("Example")]
public class ExampleLayoutRenderer : NLog.LayoutRenderers.LayoutRenderer
{
public string Value { get; set; }
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
builder.Append("Value");
}
}
Extra parameters can be passed and assigned as properties of the custom class.
[LayoutRenderer("example")]
public class ExampleLayoutRenderer : NLog.LayoutRenderers.LayoutRenderer
{
public string Value { get; set; }
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
// Your custom code
builder.Append(Value);
}
}
In the config file the custom layout renderer with params can be used like follows:
{example}
{example:value=abc}
Sometimes, in logs we write some data that might be needed for identifying but not necessarily the real value and for various reasons we might just need a hash. For example: SessionId / Some Cookie Value / IP address etc… Having a custom renderer for hashing such a value could be used.
Very few lines of code, i.e hashing and appending to a stringbuilder would solve the need i.e 2 – 3 lines of code. But, I think the 2 – 3 lines of code could be helpful for other people. I am considering creating an opensource version. I might or might not release nuget package. If I release nuget package, I would definitely make an anouncement. Anyway, not a big open-source project, but just few lines of code.
And I have discussed about a possibility of capturing more information in logs only when needed such as in the case of errors or exceptions in the following blog post:
I am planning to use Gelf logging for easier compatibility reasons. Gelf logs can be ingested into pretty much every major centralized logging platforms such as: Kibana, GrayLog, Seq, Grafana. Some would require some intermediary software to accept Gelf formatted logs and some can directly ingest Gelf formatted logs. However, for various reasons, sometimes the logging server might not be available, specifically when the log ingestors are not in a cluster. Log files can be easily ingested into the above mentioned centralized logging agents using different sofware.
Based on the above use-case I wanted to use Gelf for directly logging into the centralized logging server and as a failover, I want to write the logs to a file that would get ingested at a later point by some other software.
Now, by combing the previous post example, we can achieve AspNetBuffering and ingest different levels of logs only when errors occur. The code samples should be very easy to understand.
In the above code we have wrapped Gelf logger, File logger inside a FallBackGroup logger. The FallBackGroup logger is wrapped inside a PostFilteringWrapper. The PostFilteringWrapper is wrapped inside a AspNetBufferingWrapper.
In the above code in the <rules> section we are sending all Debug and above logs to the AspNetBufferingWrapper.
Now AspNetBufferingWrapper buffers the log messages for an entire request, response cycle and sends the log messages to the PostFilteringWrapper.
The PostFilteringWrapper sees if there are any Warnings or above loglevel, if yes sends all the messages that have Debug and above loglevels. Else sends Info and above messages. The target of PostFilteringWrapper is the FallbackGroup logger which receives these messages.
The FallBackGroup logger attempts to use the Gelf logger, if the Gelf logger is unable to process the messages, the logs are sent to the File logger.
And I have discussed about a possibility of capturing more information in logs only when needed such as in the case of errors or exceptions in the following blog post:
The above configuration by default logs Info and above logs, but if there is a Warn or higher, logs debug or higher. For this to work properly obviously this logger has to receive Debug messages otherwise there is no point in using this logger.
Now combing these two loggers, here is an example:
*Instead of hardcoing IP address or “localhost”, I would say use some name such as “elasticsearch” or “kibana” and then use the HOSTS file for mapping to the actual server. Then even if you have several applications on the same server and if the elasticsearch server gets changed, you don’t have to edit all the config files, you can edit just the hosts file. hosts file is located at /etc/hosts on Linux and C:\Windows\System32\drivers\etc\hosts on Windows.
Now we will discuss about 4 different interesting wrappers:
Buffering Wrapper
Async Wrapper
AspNetBuffering Wrapper
FallbackGroup Wrapper
These 4 loggers are wrappers i.e these loggers don’t write logs directly. Instead they are used to wrap other loggers by providing some interesting functionality that can be used to take advantage based upon necessity and use-case.
Buffering Wrapper
Buffers log events and sends in batches.
As mentioned above in the ElasticSearch example, the wrapper would buffer messages and sends in batches.
There is a very interesting use-case by using AutoFlushWrapper with BufferingWrapper and the actual target that writes the logs, such as writing the logs only when error happen.
2. Async Wrapper
When you don’t need buffering but at the same time if you don’t want your application to wait until logging is done, this could be useful.
This wrapper can be used for wrapping around multiple targets. For example ElasticSearch followed by Cloudwatch followed by File. i.e if the logger is unable to write to ElasticSearch, it would write to Cloudwatch, if that too failed it would write the logs into file.
While I have been brainstorming about something, some small idea came to my mind. People who would read this blog post would either call me stooooopid or might say nice idea.
Anyway the point is, we use logging for various purposes – mostly for troubleshooting. Very verbose logs are a nightmare in terms of performance, storage, retrieval and digging through for the right information. Sometimes, issues troubleshooting becomes a pain because of inadequate information in logs.
What if we log Info and above under normal circumstances, trace and / or debug in certain conditions such as unexpected expectations or errors?
Here is a brief overview of how this might be implemented – in this case, there is a slight memory pressure.
Collect trace and / or debug into Memory log i.e for example if using NLog, use Memory target.
Have some static method that writes the logs from Memory target into a different log target such as File / Database etc…
In the specific conditions such as exception call the static method and in ASP.Net even implement a exception filter to perform the same.
This might be a win-win scenario i.e collecting detailed information in case of unexpected exceptions and error, for any other normal scenario normal logging. Because memory target is being used, very small performance hit, slightly higher memory usage are the drawbacks.
I would love to know how other developers are implementing or handling such use cases.
Some people for various reasons might prefer programatically configuring NLog. Some use cases are for example, may be you don’t want to store sensitive information in nlog.config file. Some of the targets that require sensitive information in the config file are (Not an exhaustive list):
You might want to store the sensitive information somewhere else in an encrypted format. Then you might decrypt the password and programmatically configure the logger. Here is some code sample on how to configure such loggers.
var logConfig = new LoggingConfiguration();
//File
var fileTarget = new FileTarget
{
FileName=typeof(Program).FullName + ".log"
};
fileTarget.Layout = @"${date:format=HH\:mm\:ss} ${logger}:${message};${exception}";
var fileRule = new LoggingRule("*", LogLevel.Debug, fileTarget);
logConfig.LoggingRules.Add(fileRule);
logConfig.AddTarget("logfile", fileTarget);
//DB
var dbTarget = new DatabaseTarget();
dbTarget.ConnectionString = YourSecureMethodForDecryptingAndObtainingConnectionString();
dbTarget.CommandText = @"INSERT INTO [Log] (Date, Thread, Level, Logger, Message, Exception) VALUES (GETDATE(), @thread, @level, @logger, @message, @exception)";
dbTarget.Parameters.Add(new DatabaseParameterInfo("@thread", new NLog.Layouts.SimpleLayout("${threadid}")));
.
.
.
logConfig.AddTarget("database", dbTarget);
var dbRule = new LoggingRule("*", LogLevel.Debug, dbTarget);
logConfig.LoggingRules.Add(dbRule);
LogManager.Configuration = logConfig;
In the above sample code, we have looked into how to add multiple types of targets – File and DB. How to set the layout for the FileTarget. How to configure logging rules and finally how to assign the programmatic config.
An interesting target is the Memory target, allows writing log messages to an ArrayList in memory for programmatic retrieval. Great for unit testing.
There are some code samples in the above mentioned link for Memory target.
We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.
This website uses cookies to improve your experience while you navigate through the website. Out of these, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may affect your browsing experience.
Necessary cookies are absolutely essential for the website to function properly. These cookies ensure basic functionalities and security features of the website, anonymously.
Cookie
Duration
Description
cookielawinfo-checkbox-advertisement
1 year
Set by the GDPR Cookie Consent plugin, this cookie is used to record the user consent for the cookies in the "Advertisement" category .
cookielawinfo-checkbox-analytics
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics".
cookielawinfo-checkbox-functional
11 months
The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional".
cookielawinfo-checkbox-necessary
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary".
cookielawinfo-checkbox-others
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other.
cookielawinfo-checkbox-performance
11 months
This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance".
viewed_cookie_policy
11 months
The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data.
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.