NLog: variable with cached layout yields different values if used multiple times - c#

I log to Azure Blob Storage and want to have one three logfiles (containing different things) per start and I use a cached timestamp for this. Ideally the timestamp should be the same in all three files, but they differ if I log to the specific targets at different times.
I set the variable like this
<variable name='blobprefix' value='${cached:cached=true:Inner=${date:format=yyyy-MM-dd\\THH.mm.ss}' />
And this is how I use it for every blob:
<target type="AzureBlobStorage"
name="general-azureblob"
connectionString="${blobConnection}"
blobName="${blobprefix}.log">
</target>
How can I have the variable evaluate exactly once.

You can use the ${processinfo:StartTime} like this:
<variable name='blobprefix' value='${processinfo:StartTime:format=yyyy-MM-dd\\THH.mm.ss:cached=true}' />
If you want to control the StartTime, then you can use NLog GDC and do the following at application startup (Before creating the first NLog Logger-object):
NLog.GlobalDiagnosticsContext.Set("StartupTime", DateTime.UtcNow);
And have the following in NLog.config (whenEmpty is only there as fallback):
<variable name='blobprefix' value='${gdc:StartupTime:format=yyyy-MM-dd\\THH.mm.ss:cached=true:whenEmpty:${date:format=yyyy-MM-dd\\THH.mm.ss:cached=true}}' />

Related

How to set log4net create filename with logger name and date?

I would like to have files named for example:
loggername_yyyy-MM-dd.log
I try to possible:
<file type="log4net.Util.PatternString" value="Log\%logger_%date{yyyy-MM-dd}.txt" />
But he only take effect %date{yyyy-MM-dd}
How is this possible with log4net?
I want my different job use different logger, like : ILog log = LogManager.GetLogger("LogName");, he should save to 'LogName_2019-03-07.log' file
The %property{LogName} only take effect in layout

How can I use Wix Properties

I'm working with Wix v4 to create a msi package. I had the problem, that I must set a Property in a CustomAction (C#) at the beginning of the installation.
This works fine, but now I'm a little bit confused. The property can't be used in all my cases.
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
<Property Id="ANUMMER" Value="A2000-0000" />
<Binary Id='CustomActionReadConfig' SourceFile='...'/>
<InstallUISequence>
<!-- Set the property over session["ANUMMER"] = "..." -->
<Custom Action="CustomActionReadConfig" Before="AppSearch" />
</InstallUISequence>
The ini file hasn't a problem
<IniFile Id="Programm.ini" Action="createLine" Key="ANUMMER" Name="Programm.ini" Section="Programm" Value="[ANUMMER]" Directory="Dir" />
For the directory I found the follow workaround
<SetDirectory Action="SetApplicationFolder" Id="APPLICATIONFOLDER" Value="[ProgramFilesFolder]\[COMPANYNAME]\[MYPROGRAMM]\[ANUMMER]" Sequence="ui"/>
But the shortcuts can' use it and I didn't find a workaround
<Shortcut Id="DesktopShortcut" Directory="DesktopFolder" Name="Programm [ANUMMER]" WorkingDirectory="Dir" Advertise="yes" Icon="DesktopIcon.exe" IconIndex="0" />
<Shortcut Id="DesktopShortcut" Directory="DesktopFolder" Name="Programm" WorkingDirectory="Dir" Advertise="yes" Icon="StartMenuIcon.exe" IconIndex="0">
<ShortcutProperty Key="Name" Value="Programm [ANUMMER]"/>
</Shortcut>
Like this, I need this property in some further cases. Do I use it wrong or do I have to use an special escape combination? Can't I use properties in Name attributes? Is there an other way, to use the input as variable witch I can set in the CustomAction? Or what is the basic problem, that I can't use such a custom runtime property in sutch ways?
Thanks for help
After searching for further options I found the reason for the problem for this in an other question here: Dynamically assigning name to shortcut at run time in WIX
The property value can be used in Formatted type. I wanted to use it in LongFileNameType (Simple Type) or in strings.
If someone knows a way, to fill a variable at runtime to solve this problem, it would be nice to share it with us.
Info: The value could also be a localization variable with the format !(loc.VARIABLE).

Write Properties (name=value) to a file from an MSBuild project

For an MSBuild project, I would like to output some kind of a .config file that would be redistributed along the generated binary so the parameters used at build time can be checked by the users of the binary, programmatically.
Output file format:
PropertyName1=ValueA
PropertyName2=ValueB
...
Ideally, the list of properties to write would contain just their names. Maybe like:
<ItemGroup>
<MyExposedDictionary Include="Configuration" />
<MyExposedDictionary Include="Platform" />
<MyExposedDictionary Include="PropertyName1" />
...
</ItemGroup>
With MyExposedDictionary being the argument to give to some DotConfigFileWriter task, as well as the path of the destination file.
I found several ways to write down values to a file, including a sub-target with some C# code in it, but I'm new to MSBuild and I'm not sure how I can merge those requirements into a single Target to make it re-usable.
In case someone comes here with the same requirement, here is what I ended up with. Not really happy with the result as I was hoping for something more generic but at least it does the job and blends well in my project:
<Target Name="WriteBuildProperties" BeforeTargets="PreBuildEvent">
<WriteLinesToFile File="$(DotConfigFile)" Overwrite="true" Lines="" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="ProjectName=$(ProjectName)" />
<WriteLinesToFile File="$(DotConfigFile)" Lines="Configuration=$(Configuration)" />
...
</Target>
If someone happen to have a more elegant solution, please jump in!
I am not sure where your problem is located. I have a similar requirement that a file is created by the program which just was compiled. I edited the properties of the project: in the build events enter a Post-build action like
REM create special file
"$(ProjectDir)$(OutDir)MyProgram.exe" /WriteFile MyFile.xml
Of course, you must also change your program such that it does the right thing when called with that parameter (and stops after having completed that action - does not show a GUI or start as a Windows Service).

log4net BufferingForwardingAppender performance issue

EDIT 2 : I have solved the problem (see answer below) Please note that the problem potentially affects all appenders decorated with BufferingForwardingAppender as well as all appenders inheriting from BufferingAppenderSkeleton (respectively : AdoNetAppender, RemotingAppender, SmtpAppender and SmtpPickupDirAppender) *
I was doing some very basic benchs of log4net and I tried to decorate a RollingFileAppender with a BufferingForwardingAppender.
I experience terrible performance going through the BufferingForwardingAppender instead of directly through the RollingFileAppender and I really don't get the reason.
Here is my configuration:
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="c:\" />
<appendToFile value="false" />
<rollingStyle value="Composite" />
<datePattern value="'.'MMdd-HH'.log'" />
<maxSizeRollBackups value="168" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender">
<bufferSize value="512" />
<appender-ref ref="RollingLogFileAppender" />
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="BufferingForwardingAppender" />
</root>
And here is the benchmark (very simple code):
var stopWatch = new Stopwatch();
stopWatch.Start();
for (int i = 0; i < 100000; i++)
{
Log.Debug("Hello");
}
stopWatch.Stop();
Console.WriteLine("Done in {0} ms", stopWatch.ElapsedMilliseconds);
Going directly through RollingFileAppender the output is:
Done in 511 ms
Whereas going through the BufferingForwardingAppender decorating the RollingFileAppender :
Done in 14261 ms
That's approx 30 times slower.
I thought I would gain some speed by buffering a certain amount of log before writing them to the file, however for some reason it gets things much worse.
Seems to me like the configuration is OK, so this is really weird.
Anyone got a clue?
Thanks!
EDIT 1 :
The behavior is strictly the same by wrapping/decorating a FileAppender or even ConsoleAppender (still there is an example of basic BufferingForwardingAppender wrapping/decorating ConsoleAppender in log4net official config samples .. and nothing specific mentioned dealing with performance).
After some investigation/profiling, I can see that the majority of the time is spoiled inside the BufferingForwardingAppender more specifically in a call to WindowsIdentity.GetCurrent() ... being called EACH time we make a call to Log.Debug() .. in the previous sample (100K times in the sample source above).
Calls to this method are known to be very costly and should be avoided or minimized, I really don't get why it gets called for each log event.
Am I really completely misconfiguring something / not seeing something evident, or is that a bug somehow somewhere, this is what I am trying to figure out now...
The partial call stack is :
AppenderSkeleton.DoAppend
BufferingAppenderSkeleton.Append
LoggingEvent.FixVolatileData
LoggingEvent.get_UserName()
A call to get_LocationInformation() is also done in FixVolatileData, incurring an high perf cost as well (capture the stack trace each time).
I am now trying to understand why this extremely costly FixVolatileData call (at least for the fix asked) happens for each log event in this context whereas going directly through the wrapped appender (directly through ConsoleAppender/FileAppender ..) does not perform this kind of operation.
Upcoming update to follow, unless someone got an answer to all of this ;)
Thanks!
I found out the issue.
The BufferingForwardingAppender is inheriting from BufferingAppenderSkeleton (as are other appenders making use of logging events buffering such as AdoNetAppender, RemotingAppender, SmtpAppender ..).
The BufferingAppenderSkeleton is actually buffering logging events before delivering them to the target appender, once a certain condition is met (buffer full for example).
According to documentation of the LoggingEvent class (representing a logging event, and containing all values (message, threadid ...) of the event) :
Some logging events properties are considered "volatile", that is the
values are correct at the time the event is delivered to appenders,
but will not be consistent at any time afterwards. If an event is to
be stored and the processed at a later time, these volatile values
must be fixed by calling FixVolatileData. There is a performance
penalty incurred by calling FixVolatileData but is is essential to
maintain data consistency
These "volatile" properties are represented by the FixFlags enumeration containing flags such as Message, ThreadName, UserName, Identity ... so all volatile properties.
It also contains the flag "None" (fix no properties), "All" (fix all properties) and "Partial" (fix only a certain predefine dset of properties).
When the BufferingAppenderSkeleton is instantiated, by DEFAULT it sets the fixing to "All" meaning that all "volatile" properties should be fixed.
In that context, for each LoggingEvent appended into the BufferingAppenderSkeleton, ALL "volatile" properties will be fixed before the event is inserted in the buffer. This includes the properties Identity (username) and LocationInformation (stack trace) even if these properties are not included in the layout (but I guess it makes some kind of sense if the layout is changed to include these properties at a later time while a buffer has been already been filled with LoggingEvents).
However in my case this really HURTS performance. I am not including the Identity and LocationInformation in my layout and don't plan to (mainly for performance issues)
Now for the solution ...
There are two properties in BufferingAppenderSkeleton which can be used to control the FixFlags flag value of the BufferingAppenderSkeleton (once again by default it is set to "ALL" which is not very nice !).
These two properties are Fix (FixFlags type) and OnlyFixPartialEventData (bool type).
For a fine tune of the flag value or to disable all fix, the Fix property should be used.
For a specific partial predefined combination of flags (not including Identity or LocationInfo), the OnlyFixPartialEventData can be used instead by setting it to "true".
If I reuse the configuration sample above (in my question), the only change made to the configuration to unleash performance is indicated below:
<appender name="BufferingForwardingAppender" type="log4net.Appender.BufferingForwardingAppender">
<bufferSize value="512" />
<appender-ref ref="RollingLogFileAppender" />
<Fix value="0"/> <!-- Set Fix flag to NONE -->
</appender>
Using this modified configuration, the benchmark code execution presented in my question above, is dropping from approx 14000ms to 230ms (60X faster) !
And if I use <OnlyFixPartialEventData value="true"/> instead of disabling all fix it is taking approx 350ms.
Sadly, this flag is not very well documented (except in the SDK documentation, a little bit) .. so I had to dig deep into log4net sources to find the issue.
This is particularly problematic especially in the "reference" configuration samples, this flag appears nowhere (http://logging.apache.org/log4net/release/config-examples.html).
So the samples provided for BufferingForwardingAppender, and AdoNetAppender (and other appenders inheriting from BufferingAppenderSkeleton) will give TERRIBLE performance to users, even if the layout they are using is pretty minimal.
Is it possible that it's because you aren't specifying a layout pattern in the BufferingForwardingAppender but you are in the RollingLogFileAppender therefore the BufferingForwardingAppender is including everything in it's output including the username (%username)
Below is an interesting blog article that has a list of the options in the pattern layouts and it looks like he has several of them marked as slow.
http://www.beefycode.com/post/Log4Net-Tutorial-pt-4-Layouts-and-Patterns.aspx

Passing array to custom MSBuild task

I thought this would be quite simple but then realised that I couldnt find any information on it anywhere.
I have a custom task like so:
public class MyCustomTask : Task
{
[Required]
public string[] SomeStrings {get;set;}
public override bool Execute()
{
// Do something with strings...
}
}
The matching MSBuild stuff is basically like so:
<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildBinPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<SomeStrings ParameterType="System.String[]" Required="true" />
</ParameterGroup>
<Task>
...
</Task>
</UsingTask>
<Target Name="DoSomething">
<MyCustomTask SomeStrings="????" />
</Target>
Dont have any idea of what to put in the SomeStrings parameter, thought maybe it would understand if I did "xxx,xxx,xxx" so can anyone shed any light on this. The basic scenario is alot like tokenizing so I require a list of strings then some comparison strings so I need to pass in 2 lists/arrays, but just stumped.
#BrianKretzler is exactly dead on in using ITaskItem, since it's what MSBuild uses when you declare an <ItemGroup>.
I just wanted to flush out the answer with a full working example, since I found this post while I was trying to accomplish the same thing and it helped me out. (It's very hard to search for these problems, because the keywords are used in different contexts, so hopefully this will help someone else).
<UsingTask TaskName="MyCustomTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<SomeStrings ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Class" Language="cs"><![CDATA[
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class MyCustomTask : Task
{
public ITaskItem[] SomeStrings { get; set; }
public override bool Execute()
{
foreach (var item in SomeStrings)
{
Log.LogMessage(MessageImportance.High,
"Got item {0}",
item.ItemSpec);
Log.LogMessage(" -> {0} -> {1}",
item.GetMetadata("Comparison"),
item.GetMetadata("MoreDetail"));
}
return true;
}
}
]]></Code>
</Task>
</UsingTask>
Now you can call this task with:
<Target Name="DoSomething">
<ItemGroup>
<SomeStrings Include="first string">
<Comparison>first</Comparison>
</SomeStrings>
<SomeStrings Include="second string">
<Comparison>2nd</Comparison>
<MoreDetail>this is optional</MoreDetail>
</SomeStrings>
</ItemGroup>
<MyCustomTask SomeStrings="#(SomeStrings)" />
</Target>
and the output is:
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.269]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 2012-10-19 5:41:22 PM.
Got first string
-> first ->
Got second string
-> 2nd -> this is optional
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.12
You can of course also use something like <ItemGroup><SomeStrings Include="**\*.txt" /></ItemGroup> and you'll get the list of filenames that are matched, and of course you can use GetMetadata() to access the well-known file metadata
It isn't quite clear what you are trying to do; you have the C# code for a custom task, but also the MSBuild code for the same task as an inline task -- you do realize you only need to do one of those, correct? If you are trying to create a task in an assembly, the <UsingTask> in your MSBuild should be an empty element, without the <ParameterGroup> and <Task> children. If you are trying to use an inline task, you don't need the C# code and need to specify your own assembly as the AssemblyFile, and not specify the TaskFactory as you have.
I'd declare the parameter as type ITaskItem[], so you can then pass in the value(s) as,
<MyCustomTask SomeStrings="#(SomeStrings)" />
You could set up the comparison strings as a second item array in a second parameter, or as metadata on the first parameter, e.g.
<ItemGroup>
<SomeStrings Include="first string">
<Comparison>first</Comparison>
</SomeStrings>
<SomeStrings Include="second string">
<Comparison>2nd</Comparison>
</SomeStrings>
</ItemGroup>
If you are using inline code, you'll need to <Reference> the proper MSBuild assemblies and fully qualify the ParameterType. Get it working in a compiled assembly first even if your eventual intent is to use inline code.
Since this is currently the first hit on Google, here's the other way of doing it (as alluded to by #alastair-maw's comment) as answered in another SO thread:
MSBuild tasks can accept ITaskItem, primitives, string or an array of any of those for parameters. You declare the type in your task and then the values will be converted before passed to the task. If the value cannot convert to the type, then an exception will be raised and the build will be stopped.
For example, if you have a task which accepts an int[] named Values then you could do:
<Target Name="MyTarget">
<MyTask Values="1;45;657" />
<!-- or you can do -->
<ItemGroup>
<SomeValues Include="7;54;568;432;79" />
</ItemGroup>
<MyTask Values="#(SomeValues) />
</Target>

Categories