NLOG: how to use variable to specify target - c#

Is it possible to use a variable to specify a specific target?
I want to switch between database and filelogging depending on the environment i'm running my application on.
This however does not seem to work:
<logger name="MyLogger" minlevel="Warn" writeTo="${var:myTargetName}" />
In my application startup I have (error occurs on first line):
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
var config = LoadConfiguration();
NLog.LogManager.Configuration.Variables["myTargetName"] = config.GetSection("LogTargets:TargetName").Value;
NLog.LogManager.KeepVariablesOnReload = true;
NLog.LogManager.Configuration.Reload();
When startin the application the following exception is thrown:
"Target ${var:myTargetName} not found."
I guess the variables are not available when parsing the config file.
How can I set the variables so NLOG uses them while parsing the config-file?
Is it possible to use a variable to set an attribute value?
Or is this not supported by NLOG?
Note: I do have another variable in the NLOG config which does work
<target xsi:type="Database" name="databaseTarget">
<connectionString>${var:connectionString}</connectionString>
...........
</target>
I guess the loggers are checked once on startup and the actual target is evaluated when a logevent occurs.
Update: got it working without variable in config file.
I had to remove the logger from the config and create it through code
var myCustomTarget = NLog.LogManager.Configuration.FindTargetByName(config.GetSection("LogTargets:TargetName").Value);
NLog.LogManager.Configuration.AddRuleForAllLevels(myCustomTarget , "MyLogger", true);

One way to do it is to have a different Web.config file based on the environment and there you will change the connection string. That is the method I am using.
Also NLOG is initialized once the application starts thus you cannot change what's written in the NLOG.config

Setting variables via NLog.LogManager.Configuration.Variables does not work for all attributes in nlog.config file. I don't know why, but that's a known problem and that is unfortunate how it works. But there is a simple workaround, here is an example how I solved this problem for the attribute connectionString of a target.
<target xsi:type="Database" name="tDatabase"
dbProvider="Microsoft.Data.Sqlite.SqliteConnection, Microsoft.Data.Sqlite"
connectionString="Data Source =.\${environment:DATABASE_FILE_NAME};"
commandText="INSERT INTO ...">
<parameter name="#MachineName" layout="${machinename}" />
...
</target>
You can than simple set the environment variable in your code like this
Environment.SetEnvironmentVariable("DATABASE_FILE_NAME", "foo.db");
So you just use ${environment:DATABASE_FILE_NAME} and
Environment.SetEnvironmentVariable("DATABASE_FILE_NAME", "foo.db");
instead of ${var:DATABASE_FILE_NAME} and
NLog.LogManager.Configuration.Variables["DATABASE_FILE_NAME"] = "foo.db";

NLog 4.6.7 makes it easier to update the logging-rules at runtime like this:
<nlog>
<variable name="fileMinLevel" value="Off" />
<variable name="databaseMinLevel" value="Off" />
<rules>
<logger minLevel="${var:fileMinLevel}" writeTo="fileTarget" />
<logger minLevel="${var:databaseMinLevel}" writeTo="databaseTarget" />
</rules>
</nlog>
Then you can do this:
if (IsDevelopment())
LogManager.Configuration.Variables["fileMinLevel"] = "Debug";
else
LogManager.Configuration.Variables["databaseMinLevel"] = "Debug";
LogManager.ReconfigExistingLoggers();
See also: https://github.com/NLog/NLog/wiki/Filtering-log-messages#semi-dynamic-routing-rules
See also: https://github.com/NLog/NLog/wiki/Environment-specific-NLog-Logging-Configuration

Related

NLog: Logs in wrong file on reload

We have a config file that is edited by our GUI to dynamically change the log level of our file target.
Our filename looks like this: fileName="${logDir}${var:subLogDir}/${var:regionName}${logName}.dlog"
All of the variables are in the base .config file of the program and the regionName is set dynamically when the program starts by setting
variable directly in the configuration via NLog.LogManager.Configuration.Variables["regionName"] = string.Concat(RegionName, "/");.
Our problem is when we modify the log level while the program is generating logs, there will be a moment where the logs will fall into the ${var:subLogDir} directly as if the ${var:regionName} is not set even if we know it is.
It seems like there is a moment in time where the dynamic variables are not resolved yet. Is there something we can do to prevent this? Buffering the logs while reloading?
NLog version: 4.7.4
.config file
<nlog keepVariablesOnReload="true">
<variable name="subLogDir" value="/Regions" />
<include file="..\Common\Logs\BaseVariables.xml" />
<include file="..\Common\Logs\BaseTargets.xml" />
<include file="..\Common\Logs\BaseRules.xml" />
</nlog>
BaseVariables.xml
<nlog>
<!-- Default variables values -->
<variable name="logLevel" value="LogLevel.Info"/>
<variable name="callstackLogLevel" value="LogLevel.Debug"/>
<variable name="keepFileOpen" value="true"/>
<variable name="autoFlush" value="true"/>
<variable name="concurrentWrites" value="false"/>
<variable name="hasAudit" value="false"/>
<variable name="archiveAboveSize" value="52428800"/>
<variable name="maxArchiveDays" value="365"/>
<variable name="archiveNumbering" value="DateAndSequence"/>
</nlog>
BaseTargets.xml
<nlog>
<targets async="true">
<target Type="File" name="LogFile" createDirs="true" keepFileOpen="${keepFileOpen}" autoFlush="${autoFlush}"
fileName="${logDir}${var:subLogDir}/${var:regionName}${logName}.dlog"
concurrentWrites="${concurrentWrites}"
cleanupFileName="false"
archiveFileName="${archiveLogDir}${var:subLogDir}/${var:regionName}${archiveLogName}.dlog"
archiveEvery="${archiveEvery}" archiveAboveSize="${archiveAboveSize}"
archiveNumbering="${archiveNumbering}" archiveDateFormat="${archiveDateFormat}" maxArchiveDays="${maxArchiveDays}">
<layout Type="CSVLayout" delimiter="Tab" >
<column name="date" layout="${longdate}" />
<column name="level" layout="${uppercase:${level}}" />
<column name="source,keywords" layout="${logger}" />
<column name="message" layout="${message}" />
</layout>
</target>
</targets>
</nlog>
Thanks
You want the NLog Config Variables to behave in two ways:
When editing the NLog.config file, then it should discard the existing NLog Config Variables and take those when reloading the updated NLog.config.
This requires that you have configured KeepVariablesOnReload=false (default)
It would be easier if you had included NLog.config in your question. Instead of having to guess.
After the reload of the NLog.config then there is one NLog Config Variable (${var:regionName}) that you would like to keep.
Because you have configured KeepVariablesOnReload=false (default) then I guess you have hooked into LogManager.ConfigurationReloaded-event and re-assigning it manually.
It would be easier if you had included the logic for dynamic assigment of NLog config variable in your question. Instead of having to guess.
The LogManager.ConfigurationReloaded-event is called after the reloaded NLog Config has been assigned and initialized. So any logging happening until having dynamically re-assigned the ${var:regionName} will get empty value. See also https://github.com/NLog/NLog/pull/3954 and https://github.com/NLog/NLog/pull/3952
The current work-around is stop using LogManager.ConfigurationReloaded-event and instead just use NLog Global Diagnostic Context (GDC) for the single NLog Config Variable that needs to be restored on every reload. So it becomes ${gdc:regionName} and you assign it like this (once at startup):
NLog.GlobalDiagnosticsContext.Set("regionName", "Europe");
See also: https://github.com/NLog/NLog/wiki/Gdc-layout-renderer

Using NLog to output to one of two log files

I have a single-threaded .NET Winforms application. I am using a static NLog logger to log errors using this syntax:
MyLogger.Log.Error("Error occurred...");
In accordance with my one NLog.config-defined target and one NLog.config-defined rule, that log-error output always is written to a file called "errors.txt". Sometimes I want an error to be written to "errors2.txt" instead. How would I do that without creating another static logger instance?
You could use filters in the config or pass the filename to the logger call.
Note: These examples are using .WithProperty (on the NLog Logger), introduced in NLog 4.6.3
Filters
Use filters in the logger rules, we use the LogFinal so we need only the rules once (not needed for the 2nd logger rule)
See Filtering log messages on the NLog wiki
<rules>
<logger name="*" writeTo="file-error1">
<filters defaultAction='LogFinal'>
<when condition="${event-properties:target} == 'error2'" action="Ignore" />
</filters>
</logger>
<logger name="*" writeTo="file-error2">
</logger>
</rules>
Logger call:
MyLogger.Log.Error("Error occurred..."); // writes to file-error1 target
MyLogger.Log.WithProperty("target", "error2").Error("Error occurred..."); // writes to file-error2 target
Pass file name
Pass the file name in as property. Maybe you could set a default property in the static Log method.
See also Event properties documentation
<targets>
<target name="file" xsi:type="File"
....
fileName="${basedir}/logs/{event-properties:filename}"
... />
</targets>
Logger call:
MyLogger.Log.WithProperty("filename", "error1.txt").Error("Error occurred...");
MyLogger.Log.WithProperty("filename", "error2.txt").Error("Error occurred...");

Is there a fail over mechanism with NLog?

I am using NLog for DB logging. If the DB is down then I only need it to log to a file. Is there a way to do this?
I don't want to record log entries log in both a file and the DB. If the DB target fails to log (if DB is down) only then do I have to log to the file. Is there any fail over or fallback feature with Nlog?
Yes, there is a way. It's called a FallbackGroup.
Documentation can be found here: https://github.com/nlog/NLog/wiki/FallbackGroup-target
Here is an example taken blatantly and ashamedly from the documentation:
<target xsi:type="FallbackGroup"
name="mail"
returnToFirstOnSuccess="true">
<target xsi:type="Mail"
name="mailserver1"
subject="Layout"
to="Layout"
from="Layout"
smtpServer="mx1.example.com"
smtpPort="Integer"
layout="Layout" />
<target xsi:type="Mail"
name="mailserver2"
subject="Layout"
to="Layout"
from="Layout"
smtpServer="mx2.example.com"
smtpPort="Integer"
layout="Layout" />
</target>
<rules>
<logger name="*" minlevel="Trace" writeTo="mail" />
</rules>
It'll try another mail server if the first fails. It'll also return to the first target if it's successful.
I never used it in production, maybe the other options in the documentation can help you fine tune it to your problem.

How to specify template, or tokens in filename of Enterprise Library Logging file listener

From what I read it appears, that enterprise library does not allow tokens in filename, which is very strange, because all other logging solutions do.
It's very confusing, I am definetly missing something. Cause I am not even able to specify Process name in flat or lolling file listener.
NLog
<target name="txtFile"
xsi:type="File"
fileName="${cached:cached=true:inner=${date:format=yyyy-MM-dd HH-mm-ss}}.txt"
layout="${longdate} ${level} ${message}"/>
Log4Net
<file type="log4net.Util.PatternString" value="~/App_Data/%property{LogName}" />
The only solution I know (excluding programmatic setup) now is using Environment Variables on Process Level
<add name="loggingErrorsLog"
fileName="%LOGS_DIR%\%PROCESSNAME%\loggingErrors.log" ...
And to set those variables in code:
var logsDir = Environment.GetEnvironmentVariable(EnvVarLogsRoot, EnvironmentVariableTarget.Machine) ?? "Logs";
Environment.SetEnvironmentVariable(EnvVarLogsRoot, logsDir);

Configure NLog programmatically, web.config

I have following Nlog config section in my web.config (modified to show only relevant info)
<nlog>
<targets async="true">
<target name="mail" type="Mail"
body="${date:format=yyyy-MM-dd HH\:mm\:ss} - ${level} [${logger}] - ${message} ${newline}${newline}${event-context:item=OtherInfo}${newline}${newline}${exception:maxInnerExceptionLevel=2:format=ToString}${newline}${newline}"
subject="[${machinename}] ${logger}"
to="mail#domain.com" encoding="UTF-8" from="anotheremail#domain.com" smtpServer="" enableSsl="true" smtpAuthentication="Basic"
/>
</targets>
<targets>
<target name="mailsync" type="Mail" body="${date:format=yyyy-MM-dd HH\:mm\:ss} - ${level} [${logger}] - ${message} ${newline}${newline}${event-context:item=OtherInfo}${newline}${newline}${exception:maxInnerExceptionLevel=2:format=ToString}${newline}${newline}" subject="[${machinename}] ${logger}"
to="mail#domain.com" encoding="UTF-8" from="anotheremail#domain.com" smtpServer="" enableSsl="true" smtpAuthentication="Basic"
/>
</targets>
<rules>
<logger name="*" level="Error" writeTo="mail" />
</rules>
</nlog>
I am updating the configuration via code in Application_Start,
var config = LogManager.Configuration;
const string targetName = "mail";
var wrapper = (AsyncTargetWrapper) config.FindTargetByName(targetName);
wrapper.WrappedTarget.SmtpServer = "hostname";
wrapper.WrappedTarget.SmtpUserName = "username";
wrapper.WrappedTarget.SmtpPassword = "password";
config.RemoveTarget(targetName);
config.AddTarget(targetName, wrapper);
LogManager.Configuration = config;
However, when I log any errors, the emails are not being sent. I have an additional file target (not shown in the code snippet), that contains the error message. That tells me the errors are getting logged, but somehow not sent via email.
If instead of updating the config via code, if I hard code the values in the web.config, then the emails get sent. I have verified that the smtp values that I am using via code are valid.
I did search through many similar questions on SO, but I haven't yet found one that mentions a solution that works for me.
EDIT:
Based on Xharze's answer, I enabled exceptions, internal logging and I also outputted the values of the target after I had made my changes. The internal log showed an exception about a MailAdrress being the incorrect format. So I checked all target values that accept an email address and I found the issue. The from property of the target accepts an email address whereas I was providing it a display name!
Could you try enabling throw exceptions and the interalnal log, and post it? It may provide more information. See github.com/NLog/NLog/wiki/Logging-troubleshooting.

Categories

Resources