Check NLog minlevel before logging? - c#

I want to enable developers to log objects as JSON with NLog. To do this I need to implement some logic before sending to nLog OR before sending to target.
I can build my own Target(TargetWithLayout) but I canĀ“t find a way to check the log level from the config for this specific target/logger? Another drawback is that I need to make a new TargetWithLayout class for each target that we will use (EventLog, File, WebService and so on).
Another solution would be to do it in my LogHandler that uses NLog. The only way to know if I should translate the object is probably to read all the loggers from the config file, if any of them is set to log objects then I serialize. I am however not sure if I can check this information from the LogHandler (without doing it manually)?

You can use the NLog-Logger object to query active logging-rules:
if (myLogger.IsTraceEnabled)
myLogger.Trace("Hello World");

You can use the NLog json layout to write json in you log files, no need to check and do the serialization yourself:
<target name="jsonFile" xsi:type="File" fileName="${logFileNamePrefix}.json">
<layout xsi:type="JsonLayout">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:upperCase=true}"/>
<attribute name="message" layout="${message}" />
</layout>
</target>
The log messages formatting is handled by NLog instead of doing it yourself.
release notes nlog

To add some theory ;)
Another drawback is that I need to make a new TargetWithLayout class for each target that we will use (EventLog, File, WebService and so on).
That's the reasons there are Layouts in NLog. Those are the layouts that could be used in the target, but those are independent of the target.
(don't get confused with Layout Renderers, those ${..} things.)
There are multiple layouts (plain text, CSV, JSON) (see list) , and you could easily add your own layout, analogous to adding a custom Target / Layout renderer, see the wiki

Related

Why is NLog not logging scope data to Application Insights custom Dimensions

I am currently logging to Application Insights using NLog configured in an nlog.config file. I don't have IncludeScopes set anywhere (it is true by default).
I am trying to log custom properties using scope. It works when logging to a file or the console but not when logging to the Application Insights customDimensions.
This is how I am logging my scope:
using (_logger.BeginScope(new Dictionary<string, object> { ["ActivityId"] = Guid.NewGuid()})
{
_logger.LogInformation("Logging from with scope");
}
and this is the nlog.config file:
<target name="applicationInsights" xsi:type="ApplicationInsightsTarget" >
<instrumentationKey>8d9f67d5-fe36-45cf-935f-2f87bb240b12</instrumentationKey>
<!-- Only required if not using ApplicationInsights.config -->
<contextproperty name="threadId" layout="${threadid}" />
<contextproperty name="processName" layout="${processname}" />
<!-- Can be repeated with more context -->
</target>
Unfortunately I don't see the ActivityId when I look in the customDimensions in Application Insights.
I am running my Console app in Azure so have registered a worker service (which processes messages) like this:
services.AddHostedService<PositionMessageProcessor>()
What do I need to do get the logging scope to log my ActivityId in Application Insights?
Update
I have managed to it logging the ActivityId by adding it as a specific contextProperty. I don't really want to have to update the config file everytime I call BeginScope(...) with different properties.
Is there a generic way to get it to work for all scope properties?
Is there a generic way to get it to work for all scope properties?
I assume you mean that it sends all the scoped properties to application insights without specifying which keys.
Currently this isn't supported by the target, see source.
In NLog 4, the scope properties are pushed to the NestedDiagnosticsLogicalContext.
You could do that by creating your own target:
Copy the target from source
Loop over NestedDiagnosticsLogicalContext.GetAllObjects() in BuildPropertyBag inside ApplicationInsightsTarget.
Register your target, see NLog-Register your custom component
You can have scope-context included as blob-data by using a <contextproperty> with a JsonLayout:
<target type="ApplicationInsightsTarget" name="aiTarget">
<contextProperty name="scopeproperties">
<layout type="JsonLayout" includeMdlc="true" />
</contextProperty/>
</target>

NLog console highlight properties

I've watched the following video explaining about Serilog. I would like to use structured logging in my application as well (at least parts of it), but I prefer to use NLog (1) because it's already part of our stack and we're used to it and prefer to have the same logger on across our projects, and (2) I have read the comparison here and seems like NLog is more performant.
So I read that NLog also support structured logging and I have implemented it in a test application without a problem. It works very well.
What I saw in the video and liked about Serilog, is when writing to console it highlights the parameters passed to the logging function like this:
I would like to have the same on the console application I'm currently building. I have tried both Console target and ColoredConsole but non has that effect. Is it possible in NLog?
This is my targets configurations:
<target name="file"
xsi:type="File"
archiveEvery="Day"
archiveFileName="Logs\log.{#}.txt"
fileName="Logs\log.txt"
archiveNumbering="DateAndSequence"
archiveDateFormat="yyyy-MM-dd"
archiveAboveSize="104857600"
maxArchiveFiles="30"
layout="${longdate} | ${uppercase:${level}} | ${logger} | ${threadid} | ${message} ${exception}"
/>
<target xsi:type="ColoredConsole"
name="ColorConsole"
layout="${uppercase:${level}}: ${message} ${exception:innerFormat=Message,StackTrace}"
header="Memoriez API"
useDefaultRowHighlightingRules="false"
>
<highlight-word foregroundColor="Green" ignoreCase="true" text="info" wholeWords="true" />
<highlight-word foregroundColor="Red" ignoreCase="true" text="warn" wholeWords="true" />
<highlight-word backgroundColor="Red" foregroundColor="White" ignoreCase="true" text="error" wholeWords="true" />
<highlight-row backgroundColor="DarkRed" foregroundColor="Yellow" condition="level == LogLevel.Fatal" />
</target>
I do not think you can colorized the parameters in ColoredConsoleTarget. It might be possible to use WordHighlighting, but you would quickly run into issues where matching a number would highlight all numbers and not just the parameter.
My guess is that you would need to write a custom ColoredConsoleTarget in order to highlight parameters. I just looked at the src\NLog\Targets\ColoredConsoleTarget.cs file and it only offers RowHighlightingRules and WordHighlightingRules. It looks like when the color codes are applied the LogEventInfo has been rendered into a plain string.
I think you would need to write a custom RenderLogEvent function that would render the color escape sequences for parameters. It would be a bit tricky because the GenerateColorEscapeSequences for WordHighlighting would escape any color sequences generated before it is called).
Here are my thoughts:
Create a new class ColoredParamConsoleTarget with code copied from ColoredConsoleTarget. It doesn't look like there are virtual methods to just override the existing class.
Create a ColoredRenderLogEvent method and parse for parameters and add color sequences before and after parameters.
Replace calls to RenderLogEvent with ColoredRenderLogEvent

Using 'Environment' values in log4net configuration

My application can be started both as a Windows Service and in console environment. For each case, I need some log4net appenders to be active (or not, respectively) as well as some which are active in both cases. (Service: RollingFileAppender, DebugAppender (if enabled), EventLogAppender | Console: ColoredConsoleAppender, DebugAppender (if enabled))
The only way to achieve something similar is using the PropertyFilter together with ThreadContext.Properties like so:
<filter type="log4net.Filter.PropertyFilter">
<key value="ApplicationMode" />
<stringToMatch value="Service" />
</filter>
if(!Environment.UserInteractive)
ThreadContext.Properties["ApplicationMode"] = "Service";
However, since the property is declared on the thread context, it only works on the current thread. If the thread changes, the configuration is being reset and I have to declare it again.
Does log4net support a way to declare a PropertyFilter in configuration to setup the desired environment automatically? Like this:
<filter type="log4net.Filter.PropertyFilter">
<key value="{Environment.UserInteractive}" />
<stringToMatch value="false" />
</filter>
Or... is there a better approach? Since I didn't found a solution yet.. is this an uncommon practice?
See the last part of my answer to this question:
Capture username with log4net
To summarize, you can implement an object that contains the logic that you have above and put that object in GlobalContext.Properties. When log4net retrieves the value (your object) from the Properties, it will call ToString to get the actual value. Put your logic inside ToString.
Maybe something like this:
public class ApplicationModeProvider
{
public override string ToString()
{
return Environment.UserInteractive ? "Console" : "Service";
}
}
Put it in your dictionary at startup:
GlobalContext.Properties["ApplicationMode"] = new ApplicationModeProvider();
In effect, this is sort of like adding
if(!Environment.UserInteractive)
ThreadContext.Properties["ApplicationMode"] = "Service";
before ever logging statement.
I'm not sure, but I think that then you can configure your filter as you describe in your post.

Custom headers and footers with NLog

I am trying to add some targets to NLog that log certain details out to a separate XML file for analysis. I have been trying to get NLog to generate fully valid XML that is in a schema different than the Log4JXmlEventLayout provider.
Towards this end, I've been using a file target that has a header (for the XML declaration and root element opening) and footer (for the root element closing), like so:
<target name="someFileTarget" xsi:type="File" fileName="afile.xml"
header="<?xml version="1.0" encoding="utf-8"?>
<my-events>"
footer="</my-events>">
<layout xsi:type="SimpleLayout">
<text><![CDATA[<event><timestamp>${longdate}</timestamp></event>]]> </text>
</layout>
</target>
However, the footer (closing element) never gets written to the log file, even after the log file gets rolled over. Is there something about this configuration that is incorrect?
I also attempted the LayoutWithHeaderAndFooter provider (as documented here: https://github.com/nlog/NLog/wiki/LayoutWithHeaderAndFooter), but this did not appear to work at all and no events were written to the target.

dynamic log4net appender name?

Let's say i have 3 smtp appenders in same log4net file whose names are:
<appender name = "emailDevelopment".. />
<appender name = "emailBeta".. />
<appender name = "emailProduction".. />
Let's say i have 3 different servers(Dev, Beta, Production). Depending upon the server, i want to fire the log. In case of Development server, it would fire log from "emailDevelopment". I have a system variable in each server named "ApplicationEnvironment" whose value is Development, Beta, Production based on the server names. Now is there anyway i can setup root in log4net so that it fires email depending upon the server name.
<root>
<priority value="ALL" />
<appender-ref ref="email<environment name from whose appender should be used>" />
</root>
This doesn't directly answer your question, but another approach is to simply have multiple log4net configuration files and call XmlConfigurator.Configure() on the right one. For example, you might have Logging.Development.Config, Logging.Beta.Config and so on.
Somewhere in code, you determine the "environment" and configure using the file you want.
I've even gone so far as to have multiple config files and pull different parts of them out into a single XML representing the "true" config, and then calling the Configure() method on that. For example, Logging.Appenders.Config which has all the appenders, and takes all of them and combines it with one of your environment-specific config files above; the environment-specific ones simply reference what they need, and the rest are effectively inactive/unreferenced for that environment.
Even after having written the only XSD file for log4net configuration I'm still not aware of an easy way to achieve this.
You might be able to do something like:
log4net.GlobalContext.Properties["host"] = new ClassThatToStringsHost();
class ClassThatToStringsHost
{ public override string ToString() { return "whatever"; } }
Now you can reference this value from the Log format with: "%property{host}"
To perform the filtering you will need to use a filter configuration in the adapter(s):
<appender name="file" type="log4net.Appender.RollingFileAppender">
<filter type="log4net.Filter.PropertyFilter">
<Key value="host" />
<StringToMatch value="whatever" />
</filter>
<!-- Anything not accepted by the above should be excluded -->
<filter type="log4net.Filter.DenyAllFilter" />
</appender>
There may even be a built-in property you could leverage and this should work. See also this post: http://geekswithblogs.net/rgupta/archive/2009/03/03/dynamic-log-filenames-with-log4net.aspx
For me, myself, and I... I would approach it another way all together. I would derive my own SMTP appender from the default and in the ActivateOptions() method I'd configure the values according to the environment. This would allow you to use one SMTP appender with consistent rules and yet provide three public properties for each of the email addresses you want to send from. It's not hard, give it a try!

Categories

Resources