NLog console highlight properties - c#

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

Related

Msbuild step to generate c# file and then compile it

I have a .csproj named P with a custom task in it.
<UsingTask TaskName="[FullTaskName]" AssemblyFile="$(TargetPath)" />
<Target Name="[CustomTaskName]" BeforeTargets="Build">
<[CustomTask] />
</Target>
This custom task replace the content of an included file F in P. This replacement can lead to errors in F.
However when I build P (and then run the custom task), the build pass whatever the content of F.
How to include the new content of F when building P?
Changing BeforeTargets="Build" to BeforeTargets="BeforeBuild" seems to be working.

Check NLog minlevel before logging?

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

Adding method name in NLog

I am using NLog and following the recommended pattern of having a log declare on each class, for the purpose of being able to track which class/method has written to the log. I do find this very useful to have a bit of a top level 'stack trace' with each log write.
My code used to look like this:
class SomeClass {
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
void DoStuff() {
logger.Debug("stuff"); }
}
I recently had the requirement my single project to write to 3 separate log files, and to do this, I added multiple loggers and targets as specified here: https://stackoverflow.com/a/21711838/191206
However, now in my log files, I have lost the class level name. It now just writes the log name that I specified in the NLog.config. I've considered simply adding the method name myself with a call to
System.Reflection.MethodBase.GetCurrentMethod(); // use Name property
or using something else in Reflection like this
However, I'm wondering if NLog has something built into this that I'm missing? The Debug() method I only see the ability to write a string, with parameters & optionally formatted..
Is this built into NLog?
There is a built in layout renderer called ${callsite} that you can use to include the call site information (class name, method name and source information) in your log entries:
<targets>
<target
name="task1File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog1.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
<target
name="task2File"
xsi:type="File"
layout="${callsite} - ${message}"
fileName="${basedir}../Data/debugLog2.txt"
archiveAboveSize ="5000000"
maxArchiveFiles="2"/>
</targets>

Using ILMerge with log4net is causing "inaccessible due to protection level" error

I created a wrapper class for the initialization of my log4net logging objects in order to make it easier to establish custom properties in the ThreadContext. This occurs within a class library that I have established along with many other useful functions. To join all of the various libraries I have also added an AfterBuild target to ILMerge using the '/internalize' switch.
All references to this initializer method within the library targeted by ILMerge seem to work just fine. However, when I reference this merged library in other places. My implementation throws protection level errors. I have tried adding various things to the optional exclude (/internalize:excludes.txt) file but this doesn't seem to work.
Example excludes.txt:
log4net.Config
log4net.ThreadContext
log4net.LogManager
Has anyone else had this issue?
[EDIT]:
Here is the code:
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
namespace Logging
{
public static class Log4NetThreadContext
{
public static ILog Initialize(Type declaringType)
{
// Read from configuration
XmlConfigurator.Configure();
// Set Properties
ThreadContext.Properties["ID"] = ...
...
...
...
if(System.Diagnostics.Debugger.IsAttached)
{
// Special debugging logger
return LogManager.GetLogger("DEBUG_MODE");
}
else
{
// Root logger
return LogManager.GetLogger(declaringType);
}
}
}
}
I'm utilizing this code like so..
private static readonly Type declaringType =
MethodBase.GetCurrentMethod().DeclaringType;
private static readonly ILog log =
Log4NetThreadContext.Initialize(declaringType);
...
log.Info("Something useful");
[EDIT]:
This is my AfterBuild target
<Target Name="AfterBuild">
<CreateItem Include="#(ReferenceCopyLocalPaths)" Condition="'%(Extension)'=='.dll'">
<Output ItemName="AssembliesToMerge" TaskParameter="Include" />
</CreateItem>
<Message Text="MERGING: #(AssembliesToMerge->'%(Filename)')" Importance="High" />
<Exec Command=""$(ProgramFiles)\Microsoft\Ilmerge\Ilmerge.exe" /targetplatform:v2 /log /internalize:"ilmerge.excludes.txt" /keyfile:$(AssemblyOriginatorKeyFile) /out:#(MainAssembly) "#(IntermediateAssembly)" #(AssembliesToMerge->'"%(FullPath)"', ' ')" />
<Delete Files="#(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />
Is there just a better way in general to debug protection level issues?
Log4NetThreadContext.Initialize(System.Type)' is inaccessible due to its protection level
Ultimately the easiest thing to do is to exclude log4net completely from the ilmerge process and maintain it as a dependent assembly.
So after much torture here's the "not-so-obvious" solution..
The excludes were not required after all, the real answer is to use the /lib:[path] switch in ilmerge.
I updated the AfterBuild target to remove the excludes from the /internalize switch. Next I added the /lib switch to pass in the location of the log4net dll as a dependent reference. It looks like this:
<Target Name="AfterBuild">
<CreateItem Include="#(ReferenceCopyLocalPaths)" Condition="'%(Extension)'=='.dll'">
<Output ItemName="AssembliesToMerge" TaskParameter="Include" />
</CreateItem>
<Message Text="MERGING: #(AssembliesToMerge->'%(Filename)')" Importance="High" />
<Exec Command=""$(ProgramFiles)\Microsoft\Ilmerge\Ilmerge.exe" /lib:..\packages\log4net.2.0.0\lib\net35-full\ /targetplatform:v2 /log /internalize /keyfile:$(AssemblyOriginatorKeyFile) /out:#(MainAssembly) "#(IntermediateAssembly)" #(AssembliesToMerge->'"%(FullPath)"', ' ')" />
<Delete Files="#(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />
</Target>
In addition, I've added another target to restrict the list of assemblies included in the merge by adding a unique <ILMerge /> element to the references located in my .csproj file
<Target Name="AfterResolveReferences">
<Message Text="Filtering out ILMerge assemblies from ReferenceCopyLocalPaths..." Importance="High" />
<ItemGroup>
<ReferenceCopyLocalPaths Remove="#(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.ILMerge)'=='false'" />
</ItemGroup>
</Target>
Thus the reference elements were listed like so to accommodate:
...
<Reference Include="Ionic.Zip">
<HintPath>..\packages\DotNetZip.1.9.1.8\lib\net20\Ionic.Zip.dll</HintPath>
<ILMerge>True</ILMerge>
</Reference>
<Reference Include="log4net">
<HintPath>..\packages\log4net.2.0.0\lib\net35-full\log4net.dll</HintPath>
<ILMerge>False</ILMerge>
...
There's probably a better (programmatic) alternative for explicitly adding ILMerge=False values to the /lib switch but, in my case it is sufficient due to there being only one excluded item. Otherwise you may need to add additional paths manually.
Credit for the 'AfterResolveReferences' technique I've listed goes to http://www.hanselman.com/blog/MixingLanguagesInASingleAssemblyInVisualStudioSeamlesslyWithILMergeAndMSBuild.aspx
Hopefully this helps someone!
This usually happens to me when I am running the code from a remote location. .NET has permission issues when accessing the config file from a remote location. For example, a code repository setup on a network share.
I've only every been able to overcome it by moving my code to a local harddrive (as I don't have admin rights to elevate the security of the network share to "Trusted", etc).
(Note that this is primarily an issue with versions of Visual Studio prior to 2005. I think this was fixed in 2008 onwards, so it may not affect you. You don't say what version you are using, though.)

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