Logging datetime with ms precision using NLog event context layout renderer - c#

I am trying to log some audit information to a SQL Server 2008 table using NLog 2. In order to be able to pass parameters to the SQL insert query, I'm using LogEventInfo and the Event Context Layout Renderer.
The logging itself works but the datetime is stored with only second precision. I want to be able to store with millisecond precision but have not found anything that shows me how to do this.
This is the C# code where I log the event:
private void LogMessage(DateTime requestDateTime)
{
LogEventInfo theEvent = new LogEventInfo(LogLevel.Debug, "", "Pass my custom value");
theEvent.Properties["RequestDate"] = requestDateTime;
}
This is the target I have in my NLog.config configuration:
<target xsi:type="Database"
name="SqlLog"
dbProvider="sqlserver"
connectionString="server=localhost;database=Test;integrated security=sspi">
<commandText>
INSERT [dbo].[ApiLog]
(
[ServerName], [RequestDate]
)
VALUES (#ServerName, #RequestDate);
</commandText>
<parameter name="#ServerName" layout="${machinename}"/>
<parameter name="#RequestDate" layout="${event-context:item=RequestDate}"/>
</target>
There is a workaround I have found using theEvent.Properties["RequestDate"] = requestDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff") but I would prefer not to have to do this because then you may run into problems with date time formatting and culture.
Does anyone know of a way that I can change the precision with config in NLog.config?

It looks like the EventContextLayoutRenderer uses Convert.ToString which will lose the precision you are looking for:
public class EventContextLayoutRenderer : LayoutRenderer
{
//....
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
object value;
if (logEvent.Properties.TryGetValue(this.Item, out value))
{
builder.Append(Convert.ToString(value, CultureInfo.InvariantCulture));
}
}
}
As you probably know (and I'm not sure if it's helpful in your situation), but there is a Date layout where you can specify the format and/or a LongDate one which will provide something like 2014-01-01 12:12:12.1234
EDIT
For what it's worth, you can add a customer layout renderer very easily
[LayoutRenderer("event-context-dateTime")]
public class DateTimeContextLayoutRenderer : EventContextLayoutRenderer
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
object value;
if (logEvent.Properties.TryGetValue(this.Item, out value))
{
//Your code here
}
}
}
And in your configuration
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
<add assembly="YourAssembly"/>
</extensions>
<targets>
<target xsi:type="File" name="file" fileName="c:\temp\NlogLayout.txt"
layout="${longdate} ${event-context-dateTime:item=RequestDate}" />
...
</nlog>

Related

C# - NLog.config setting 'archiveEvery' value programatically on start

I have a NLog.config file with the following target:
<?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"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
throwExceptions="true"
internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log" >
<variable name="ProgramDataPath"
<target name="MyLog" xsi:type="File" fileName="${ProgramDataPath}/MyLog.txt"
archiveFileName="${ProgramDataPath}/Archive/Mine/{#}_MyLog.txt" archiveNumbering="Date" archiveEvery="Day" archiveDateFormat="dd-MM-yyyy" maxArchiveFiles="7" />
I'd like to be able to modify the 'archiveEvery' property by setting it to either 'Day' or 'Month' value each time I start my app, and that should be done by reading another config file where I have the property:
<IsMylogDailyArchiveEnabled>false</IsMylogDailyArchiveEnabled>
False means it should log monthly, True - daily.
So far I am able to read the later, however nothing inside NLog.config seems to be changed after I start my app...
public string IsMylogDailyArchiveEnabled
{
get { return _isMylogDailyArchiveEnabled ; }
set
{
_isMylogDailyArchiveEnabled = value;
//Here I can see that 'false' is being returned as it should
EnableMyLogDailyArchiving(_isMylogDailyArchiveEnabled );
}
}
private void EnableMyLogDailyArchiving(string value)
{
var config = new XmlLoggingConfiguration("NLog.config");
var target = config.FindTargetByName("MyLog") as FileTarget;
if (value == "false")
{
//Does not work
target.ArchiveEvery = FileArchivePeriod.Month;
}
else
{
target.ArchiveEvery = FileArchivePeriod.Day;
}
//LogManager.ReconfigExistingLoggers();
LogManager.Configuration = config;
}
Tried both options 'LogManager.ReconfigExistingLoggers()' and reassigning configuration (see above), however as mentioned before, I can't see any changes inside the NLog.config file.
Any thoughts or ideas? Thanks
It should work,
When doing LogManager.Configuration = config;, the configuration is reloaded and all targets are initialized. So the new ArchiveEvery setting will be used.
But be aware that auto reload is enabled (autoReload="true"), so every change in the XML config will revert the change made in code. If you don't want that, then change the config by subscribing to
LogManager.ConfigurationReloaded
PS: it's recommend to disabled throwExceptions in production (e.g. by removing throwExceptions="true")

How to include NLog config files in code behind

I have a nlog.config file with the following entry:
<include file="${basedir}/ActiveConfig/NLog/*.config"/>
But I want to solve this via code behind and have not found a method to do so.
LogManager.LoadConfiguration()
is overwriting my existing configuration.
Did I miss something?
You could set it like this:
var config = XmlLoggingConfiguration
.CreateFromXmlString("<include file='${basedir}/ActiveConfig/NLog/*.config' />");
LogManager.Configuration = config; // apply
Think the easy solution is just to load all config-files into an in-memorystream, and then load that into a single XmlLoggingConfiguration:
var xmlReader = System.Xml.XmlReader.Create(memorystream);
NLog.LogManager.Configuration = new XmlLoggingConfiguration(xmlReader, null);
Something like this, where you put the contents of all config-files within the same <nlog>-root:
<nlog>
<!-- XML File 1 -->
<targets>
</targets>
<rules>
</rules>
<! -- XML File 2 -->
<targets>
</targets>
<rules>
</rules>
</nlog>
If you want the AutoReload-feature working also, then this might work:
class MySpecialLoggingConfiguration : XmlLoggingConfiguration
{
private string[] _fileNames;
public MySpecialLoggingConfiguration(string[] fileNames)
{
_fileNames = fileNames;
// Your special concat-logic in memory
}
public override LoggingConfiguration Reload()
{
return new MySpecialLoggingConfiguration(_fileNames);
}
public override IEnumerable<string> FileNamesToWatch => _fileNames;
}

Unable to get NLog to send fields as numbers/ints/longs from code

Our tack is c# .net 4.7 logging through NLog library to a "Graylog" GELF input which of course is storing them in an elasticsearch cluster.
The application utilizes logging heavily, but EVERYTHING is sent through as strings - no matter if it was something other than a string in code.
I want to be able send my custom logger properties as their true data types so that I can aggregate on my statistical number fields.
I DO NOT want to constrain my developers and do this with scripted fields, or pre-mapping an index in elasticsearch - in fact, graylog manages my index for me. Nor do I want to utilize graylog pipeline processor - my poor graylog does enough work. I want them to be able to send stat data as their real data types and have them mapped as "long" or "number".
A code snippet from my stuff:
public void InfoExtended2(String Message, Dictionary<string, int> extrafields, [CallerMemberName] string callerMethodName = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
var fileName = callerFilePath.Substring(callerFilePath.LastIndexOf(#"\") + 1).Replace(".cs", "");
var caller = $"{fileName}.{callerMethodName}()Line:{sourceLineNumber}";
var logger = LogManager.GetLogger(caller);
if (!(extrafields.Count == 0))
{
foreach (var field in extrafields)
{
string mykey = field.Key.ToString();
extrafields.TryGetValue(field.Key, out int myvalue);
//be careful whats in object!
if (!logger.Properties.ContainsKey(mykey)) { logger.Properties.Add(mykey, "{#myvalue}"); } else { logger.Properties[mykey] = "{#myvalue}"; }
}
}
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
// Create the LogEventInfo object
//LogEventInfo logEvent = new LogEventInfo();
// Now add the event characteristics
//logEvent.Properties["EventCode"] = 2222;
//logEvent.Level = NLog.LogLevel.Info;
//logEvent.Properties["EventCode]."]
//logEvent.Message = "My Message";
////logEvent.Exception = ex;
//logger.Log(logEvent);
//logger.Info("{#Message}", new Dictionary<string, int> { { "key1", 1 }, { "key2", 2 } }); // dict. Result: Test "key1"=1, "key2"=2
}
I've tried a few different ways there with no luck.
If I make a POST GELF call myself with JSON fields representing ints instead of strings, it works. My field is brand new to the index and when I go check the mapping it comes back as a long. I want my code to do that.
Test GELF Call:
{
"version": "1.1",
"host": "example.org",
"short_message": "A short message that helps you identify what is going on",
"full_message": "Backtrace here\n\nmore stuff",
"timestamp": 1385053862.3072,
"level": 1,
"_user_id": 9001,
"_some_info": "foo",
"_some_env_var": "bar"
}
Resulting mapping for user id:
"user_id" : {
"type" : "long"
What our nlog target currently looks like:
<targets>
<target name="gelftcp"
type="gelftcp"
facility="Custom Facility"
remoteaddress="192.168.95.15"
remoteport="12201"
layout="${longdate}${newline}type: ${level:uppercase=true}${newline}class: ${logger}${newline}stacktrace: ${stacktrace}${newline}error message: ${message}${newline}exception: ${exception:format=tostring,data:maxinnerexceptionlevel=10}">
<parameter name="logged_at" layout="${longdate}" />
<parameter name="type" layout="${level:uppercase=true}" />
   <parameter name="class" layout="${logger}" />
<parameter name="CustomInt" layout="${event-properties:item=CustomInt}" as="number" />
   <parameter name="stacktrace" layout="${stacktrace}" />
   <parameter name="error_message" layout="${message}" />
<parameter name="ComeOnNow3" layout="${event-properties:item=ComeOnNow3} " />
   <parameter name="exception" layout="${exception:format=tostring,data:maxinnerexceptionlevel=10}" />
<attribute name="eventProperties" encode="false" >
<layout type='JsonLayout' includeAllProperties="true" maxRecursionLimit="2"/>
</attribute>
<variable name="ComeOnNow2" value ="${event-context:ComeOnNow2}" />
</target>
Take it easy on me, I'm not the original author of this code. Just someone tasked with picking up slack & deploying in its current state.
UPDATE:
Tried the first suggestion. Even added the field I'm testing:
<field name="ComeOnNow3" layout="${threadid}" type="System.Int32" />
Attempted this log entry:
logger.Info()
.Message(Message)
.Property("ComeOnNow3", 87)
.Write();
My mapping still comes back as "keyword".
SOLVED:
The trick was to switch to Http instead of Tcp.
Instead of using "gelftcp". Then you can try out GelfLayout instead:
https://www.nuget.org/packages/NLog.GelfLayout
You can combine it with the TCP network-target:
<nlog>
<extensions>
<add assembly="NLog.Layouts.GelfLayout" />
</extensions>
<targets async="true">
<target type="Network" name="gelftcp" address="tcp://192.168.95.15:12201" newLine="true" lineEnding="Null">
<layout type="GelfLayout" facility="MyFacility">
<field name="threadid" layout="${threadid}" type="System.Int32" />
</layout>
</target>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="gelftcp" />
</rules>
</nlog>
It also has some automatic conversion logic for simple NLog LogEventInfo.Properties (Decimal + Double + Integer + Boolean)
More examples can be found here: https://github.com/farzadpanahi/NLog.GelfLayout

How to inject values into my NLog.config?

I use a NLog.config, but some values I want to change from outside. E.g. target:adress could change so I want to set it each time the software starts.
I imagine some thing like
var logger = new LoggerFactory().AddNLog().CreateLogger<Program>();
logger.target.adress = "myNewAdress";
How can I set values to my NLog.config?
You could edit the config in C# like this:
var configuration = LogManager.Configuration;
var fileTarget = configuration.FindTargetByName<FileTarget>("myTargetName");
fileTarget.FileName = "${basedir}/file.log";
LogManager.Configuration = configuration; //apply
Please note that combining the config file (nlog.config) and changing it in code, the reload of nlog.config could undo your changes. If you combine both, then reapply the changes on the reload event. E.g.
public void UpdateConfig()
{
var configuration = LogManager.Configuration;
var fileTarget = configuration.FindTargetByName<FileTarget>("myTargetName");
fileTarget.FileName = "${basedir}/file.log";
LogManager.Configuration = configuration; //apply
}
// On start of your program
UpdateConfig();
LogManager.ConfigurationReloaded += (sender, e) =>
{
//Re apply if config reloaded
UpdateConfig();
};
See also: https://github.com/NLog/NLog/wiki/Configure-from-code
I recommend that one makes use of the NLog context layoutrenderers instead of modifying target properties at runtime. It ofcourse requires that the target-property supports NLog Layout.
Example of NLog.config:
<nlog>
<targets>
<target type="file" name="file" fileName="${gdc:item=LogDir}\LogFile.txt}" />
</targets>
<rules>
<logger minLevel="Trace" writeTo="file" />
</rules>
</nlog>
Then at runtime you can change the GDC-variables:
NLog.GlobalDiagnosticsContext.Set("LogDir", "C:\\Temp");
${gdc} Can also be combined with WhenEmpty, so one can provide a fallback default value, when nothing has been asigned from code.
See also: https://github.com/NLog/NLog/wiki/Context

How can I automatically verify that all web.config transforms have the same elements?

I'm working on an ASP.NET project that has several web.config transformations that are generated at build time (all at once, using MsBuild); one .config file for each deployment environment.
EX:
<Target Name="BeforeBuild">
<TransformXml
Source="Web.Base.config"
Transform="Web.DevServer1.config"
Destination="ConfigBuild\Web.DevServer1.config" />
<TransformXml
Source="Web.Base.config"
Transform="Web.QAServer1.config"
Destination="ConfigBuild\Web.QAServer1.config" />
<!-- ... -->
<!-- ... -->
</Target>
Each transformation has several elements whose values are substituted into the base web.config file. Management and I are concerned that a necessary element could potentially be overlooked by mistake in one of the transformation files.
Is there a way to automatically verify that none of our transformation files are missing any elements, using either MsBuild or some other VisualStudio tool?
Preferably, this check would be carried out at build time.
Is there a way to automatically verify that none of our transformation files are missing any elements, using either MsBuild or some other VisualStudio tool?
You can create a custom task, which compare the config file by using XmlDocument class, if they are different, use Log.LogMessage to output the node message. Like this:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Xml;
namespace Common
{
public class SimpleTask3 : Task
{
private string myProperty;
// The [Required] attribute indicates a required property.
// If a project file invokes this task without passing a value
// to this property, the build will fail immediately.
[Required]
public string MyProperty
{
get
{
return myProperty;
}
set
{
myProperty = value;
}
}
public override bool Execute()
{
// Log a high-importance comment
Log.LogMessage(MessageImportance.High, "The task was passed \"" + myProperty + "\"");
XmlDocument xDoc = new XmlDocument();
xDoc.Load(myProperty + "/web.base.config");
XmlDocument sDoc = new XmlDocument();
sDoc.Load(myProperty + "/ConfigBuild/Web.DevServer1.config");
//compare with them and check the different.
//if different
Log.LogMessage(MessageImportance.High, "different message");
return true;
}
}
}
Web.config:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeBuild">
<!--<Message Text="BuildDependsOn: $(BuildDependsOn)" />-->
<Message Text="Inside of BeforeBuild, time: $([System.DateTime]::Now)" />
<TransformXml Source="D:\Project\Msbuild\App1\App2\Web.Base.config" Transform="D:\Project\Msbuild\App1\App2\Web.DevServer1.config" Destination="D:\Project\Msbuild\App1\App2\ConfigBuild\Web.DevServer1.config" />
<TransformXml Source="D:\Project\Msbuild\App1\App2\Web.Base.config" Transform="D:\Project\Msbuild\App1\App2\Web.QAServer1.config" Destination="D:\Project\Msbuild\App1\App2\ConfigBuild\Web.QAServer1.config" />
</Target>
<UsingTask TaskName="Common.SimpleTask3"
AssemblyFile="D:\Project\Msbuild\App1\Common\bin\Debug\Common.dll"/>
<Target Name="AfterBuild">
<SimpleTask3 MyProperty="D:\Project\Msbuild\App1\App2"/>
</Target>

Categories

Resources