Configure NLog programmatically, web.config - c#

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.

Related

NLOG: how to use variable to specify target

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

NLog - Archive Start / Stop Time

Just started working with NLog and have it running with the following configuration:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwConfigExceptions="true">
<targets async="true">
<target name="logfile"
xsi:type="File"
layout="${longdate} [${level:uppercase=true}] (${threadid}) ${logger}: ${message} ${onexception:${newline}Exception\: ${exception:format=type,message,method,stacktrace:maxInnerExceptionLevel=5:innerFormat=shortType,message,method}}"
fileName="logs/current.log"
archiveFileName="logs/Archive/${ticks}.log"
archiveEvery="Minute"
archiveOldFileOnStartup="true"
keepFileOpen="false"
/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>
Everything is working as expected. However, I need to have the rotated file be in the format of ${archive_start_ticks}_${arhive_end_ticks}.log rather than the current format which is ${archive_end_ticks}.log.
I was initially hoping I could name the active log file as ${ticks} and then, on archive, use the active log file's name as a parameter into the archive file to compose some like:
fileName="logs/${ticks}"
archiveFileName="logs/Archive/${fileName}_${ticks}.log"
Of course, there's two issues here:
Using ${ticks} for the active file creates a new file for each log line.
I can't seem to reference the original fileName as an input variable into archiveFileName.
That said, what is the best way to achieve this goal? Is this something NLog can handle natively or with minor extensions?
Updating in case anyone ever cares:
I bailed on using the FileTarget with configurations and wrote my own Target wrapped in a BufferedWrapper. On each flush, I use the first and last LogEvents to determine the timespan which gives me what I need to for the required file format with little custom code to support.

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 send e-mails using NLog?

I'm using NLog, and I'm pretty new at logging. I created the log files but somehow I have a problem whit sending e-mails. I followed all instructions but couldn't make it.
Mail settings in configuration tags in web.config:
<system.net>
<mailSettings>
<smtp deliveryMethod="SpecifiedPickupDirectory" from="some#some.org">
<network host="localhost"/>
<specifiedPickupDirectory pickupDirectoryLocation="d:\tmp\email"/>
</smtp>
</mailSettings>
</system.net>
And this is the target inside the nlog.config:
<target name="Mail" xsi:type="Mail" html="true" subject="Error Received"
body="${message}"
to="some1#some.com"
from="some#gmail.com"
encoding="UTF-8"
smtpUserName="some#some.com"
enableSsl="false"
smtpPassword="password"
smtpAuthentication="Basic"
smtpServer="smtp.some.com"
smtpPort="25" />
The rule I used:
<logger name="*" minlevel="Error" writeTo="Mail" />
And I called the logger like this:
Logger logger = LogManager.GetCurrentClassLogger();
try{ //something }
catch(Exception ex){ logger.Error(ex); }
And also I'm pretty confused about the places of the settings and configurations. Thank you.
The body is a layout format, not sure if it will process the ${message} tag. Change ${message] to a layout name that is in nlog.config or leave it off for the ${message}${newline} default.
You can turn on the Internal Debugging
Change the top XML parent to
<nlog internalLogFile="c:\log.txt" internalLogLevel="Trace">
This might give you an idea on the issue. Could be an authentication issue with the server or another issue.
Since you are specifying all the server information in the target, you don't need the web.config settings.
Not sure if it is fixed yet, but you should add a timeout="10000" to the target so it closes the connection.
There is a pretty good example in the NLog Wiki for GMail that works

NLog Header (Ignore layout)

Is is possible to write a line to the log file that's not a log?
I'd like to write a "Note" that will ignore the layout in my NLog.config and just write the text exactly as I pass it for one or two lines.
This could be used for writing a header on the log file.
I'm not sure if this will matter to the solution but I'm using a separate ClassLogger (LogManager.GetCurrentClassLogger()) for every class and it's not an option for me to change that.
I was able to do this with a few creative rules.
First in my code I created a logger specifically for the header like this:
var loggerNoLayout = LogManager.GetLogger("NoLayout");
loggerNoLayout.Info("<Header Line 1>");
loggerNoLayout.Info("<Header Line 2>");
loggerNoLayout.Info("<Header Line 3>");
Then I modified my NLog.config like this:
<?xml version="1.0" encoding="utf-8"?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="NoLayout"
xsi:type="File"
fileName="${specialfolder:folder=Desktop}\LogFile.log"
layout="${message}" />
<target name="FileTrace"
xsi:type="File"
fileName="${specialfolder:folder=Desktop}\LogFile.log"
layout="${level:padding=-5:fixedLength=true} | ${logger:padding=-62:fixedLength=true:alignmentOnTruncation=right}| ${date:format=HH\:mm\:ss} | ${message:padding=-100}"
deleteOldFileOnStartup="true" />
</targets>
<rules>
<logger writeTo="NoLayout"
levels="Info"
name="NoLayout"
final="true" />
<logger writeTo="FileTrace"
minlevel="Trace"
name="*" />
</rules>
</nlog>
The key here is that the NoLayout target's layout only writes the message and the rule for writing to NoLayout only works if the logger is named NoLayout. The final step is to add the final="true" so that my normal (FileTrace) logger does not pick up the header logs.
Sure. Write a string to the log that starts with a newline. Then write your content.
var longMultilineMessage = "pretend this variable contains\n a long multiline message";
logger.Info($"\n{longMultilineMessage}");
will show up in logs as :
2017-01-01 INFO
pretend this variable contains
a long multiline message

Categories

Resources