log4net different folder based on cmdline data - log4net

Let's say I have an app that accepts a commandline argument of the state code. Each state has it's own job so the app itself can be running many times at the same time but only ever once per state at a time. I want to use this commandline argument to create/write to a folder with it's same value for logging so that they aren't tripping over each other. So at the end I'd have:
logfiles/WI/logfile.log
logfiles/MN/logfile.log
logfiles/MI/logfile.log
Where WI, MN, MI were commandline arguments to the 1 app.
What I currently tried to do was use the formatting of string.Format() in the app.config and in code loop over and use that cmdline value to create the log folder by state name.
<param name="File" value="./logfiles/{0}/logfile.txt" />
public static void SetupLogFileVendorPath(string state)
{
foreach (var fileAppender in LogManager.GetRepository().GetAppenders().OfType<FileAppender>())
{
// apply transformation to the filename
fileAppender.File = string.Format(fileAppender.File, state);
// notify the logging subsystem of the configuration change
fileAppender.ActivateOptions();
}
}
This sort of works as it does create the state folder and the log file in it, but it also creates a folder named:
logfiles/{0}/logfile.txt
Because it does this if 2 instances happen to run at the same exact time it could create an issue as both try to write to this file under {0} which they all do initially once the line LogManager.GetRepository().GetAppenders() is called. They just put header/footer but it does write to this one central place which is what I'm trying to avoid.
Is there any way to get the structure I'm looking for while avoiding this {0} with the way I'm doing it with string.Format()

Hook the state code commandline argument to a Log4net context property.
static void Main(string[] args)
{
string stateCode = args[0];
log4net.GlobalContext.Properties["stateCode"] = stateCode;
// ...
}
Include this context property in the output file path of your (Rolling)FileAppender configuration via %property{stateCode}.
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="logfiles\%property{stateCode}\logfile.log" />
<!-- More settings here -->
</parameters>
Doing so will create a subfolder with the name of the passed commandline argument.

Related

system property not replaced with the date and time for new log4j log filename for every run

I have the below lines as part of log4j.properties file.
property.filename=logs
appenders=file
appender.file.type=File
appender.file.name=LOGFILE
appender.file.fileName=${filename}/propertieslogs_${current.date}.log
appender.file.layout.type=PatternLayout
appender.file.layout.pattern=[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %m%n
appender.file.append=false
loggers=file
logger.file.name=com.utils
logger.file.level=info
logger.file.appenderRefs=file
logger.file.appenderRef.file.ref=LOGFILE
rootLogger.level=info
rootLogger.appenderRefs=stdout
rootLogger.appenderRef.stdout.ref=STDOUT
And, I have 3 java files. The first one has the main function. And, I have included the below lines to store the current date in the system property.
static {
SimpleDateFormat dateTimeFormat = new SimpleDateFormat("dd-MM-yyyy-hh-mm-ss");
System.setProperty("log4j.logger.dateTime", dateTimeFormat.format(new Date()));
}
The 2nd java file has the logger class defined.
public static Logger logger = LogManager.getLogger(CustomerDetails.class);
And, it contains the logger.info, logger.warn, and logger. error statements.
The 3rd file contains the reusable methods called from 2nd file.
Now, when the 1st file containing main function executed then the log file is created as expected. But, not with the required name. The log file name looks like this.
propertieslogs_${current.date}.log
Why the 'current.date' from system property not updated in the log file created.
Appreciate any help on this please...

TFS and MSBuild projects Logging for each project

I'm going build large number (300+) of projects on TFS. I'm considering a proj file to manage these as follows:
<ItemGroup Label="GGX_Projects" >
<MyProj Include="$(Source)Proj1.vcxproj" />
<MyProj Include="$(Source)Proj2.vcxproj" />
<MyProj Include="$(Source)Proj3.csproj" />
<MyProj Include="$(Source)Proj4.csproj" />
<MyProj Include="$(Source)Proj5.vcxproj" />
<MSBuild Projects="#(MyProj)"
Targets="ReBuid"
Properties="Configuration='$(Configuration)'"
RebaseOutputs="true"
ContinueOnError="true"
/>
This actually working fine, but one thing I'm unable to achieve i.e. to get Log for each of the projects mentioned in ItemGroup.
I need separate logs, so that if any project fails, that log can be sent via e-mail.
In addition, on msbuild command line, I tried /distributedFileLogger, but unable to understand how to use this to get log file for each project separately.
Is that a correct approach?
Can anyone please suggest some better solution which could provide separate logging for each project??
Distributed file logger is, as the name suggests, for logging from individual MSBuild nodes within a "distributed" build system, not for logging from individual projects, targets or tasks. You can try creating a simple custom logger, it is fairly trivial, I've written a sample below. Although, you might want to play around with event types for verbosity and use something like NLog rather than appending to files as you might get locks.
msbuild PerProjectLogger.sln /logger:PerProjectLogger\bin\Debug\PerProjectLogger.dll
using System.Collections.Concurrent;
using System.IO;
using Microsoft.Build.Framework;
namespace PerProjectLogger
{
public class Logger : Microsoft.Build.Utilities.Logger
{
public override void Initialize(IEventSource eventSource)
{
var loggers = new ConcurrentDictionary<string, FileInfo>();
eventSource.AnyEventRaised += (s, a) =>
{
var property = a.GetType().GetProperty("ProjectFile");
if (property == null)
return;
var project = property.GetValue(a) as string;
if (project == null)
return;
var logger = loggers.GetOrAdd(Path.GetFileName(project), name => new FileInfo(name + ".log"));
using (var writer = logger.AppendText())
writer.WriteLine(a.Message);
};
}
}
}
The simplest way to achieve this with TFS is to create a single build detention and then on the Process tab you should see an option to pick solutions to build. Here you can add each Project individually. When you do a build the default build process will do a foreach project and build them. You will get a log file in the drop location for each project.

How to Configure Log4Net to Enable Logging for a Particular Class

Given an assembly with 2 classes, Foo and Bar, via the configuration file, how to I enable logging at the Info Level for Foo, and the Warning level for Bar?
What configuration you use, depends on how the Logger is created. The basic configuration looks like this:
<logger name="(INSERT LOGGER NAME HERE)">
<level value="(WHATEVER LOG LEVEL TO APPLY FOR THIS PARTICULAR LOGGER)" />
</logger>
What determines your Logger Name is how you create it.
LogManager.GetCurrentClassLogger() // The name will be "namespace.name" of the current class.
LogManager.GetLogger<T>() // The name will be "namespace.name" of the Type T.
LogManager.GetLogger(Type type) // The name will be "namespace.name" of the type.
LogManager.GetLogger(string name) // The name will be name.
So if you have a particular process that you want to log in multiple classes / files, define the name of the process and use the string overload.
If you want to be able to turn on logging for a single class, use one of the other overloads.

How to use ExtendedLog4netLogger.cs to set logfile path for appender at runtime?

I am trying to figure out how to use the ExtendedLog4NetLogger.cs
to change the log file path dynamically at runtime or using the LoggingFacility?
This should be something similar to using log4net directly like this:
log4net.GlobalContext.Properties["LogName"] = logName;
How would I access the ExtendedLogger if I register log4net integration like this:
container.AddFacility<LoggingFacility>(f => f.UseLog4Net());
Update: I use the following code to register the Extended Logger
container.AddFacility<LoggingFacility>(LoggerImplementation.ExtendedLog4net).WithConfig(configFile).ToLog(Lo‌gger));
I get no runtime exceptions and the logger is not a null instance but I don't see the log file created at all using the global properties, I also set the config value to this for the appender: <file type="log4net.Util.PatternString" value="%property{LogName}" />
If I just set the file property in the config file to full path it does work. I am wondering if it is not working because configuration is done before setting the global variable.
extendedlogger.GlobalProperties["logName"] = logName;
To enable extended logger you need to do:
container.AddFacility<LoggingFacility>(f => f.LogUsing(LoggerImplementation.ExtendedLog4net));
1. How would I access the ExtendedLogger if I register log4net integration like this:
Using the dependency injection you can expect IExtendedLogger object in the place where you need it.
2. I am wondering if it is not working because configuration is done before setting the global variable.
That's right. You need to reconfigure log4net after setting a property.
Here is an example:
using Castle.Core.Logging;
using log4net.Config;
class MyClass {
private readonly IExtendedLogger _extendedLogger;
public MyClass(IExtendedLogger extendedLogger) {
_extendedLogger = extendedLogger;
}
public void MyFunction() {
_extendedlogger.GlobalProperties["logName"] = logName;
XmlConfigurator.Configure();
_extendedlogger.Error("my error message");
}
}

How do I make log4j create log files on demand only?

We have a modular application where modules have their own log4j logs (i.e. communication log and error log). The appenders and categories for these are all configured in the core log4j XML, but not all modules are always installed.
The DailyRollingFileAppender creates its file regardless of use and that exposes the full set of modules although not present and as some of them are customer specific we'd like to hide logs not in use.
Is there a way to make DailyRollingFileAppender create its file on first use instead of automatically at startup?
I had the same problem, so I have extended the standard FileAppender class and I have created a new LazyFileAppender that is a FileAppender that lazily initialize the log file(creates it only when the first write operation happens).
The LazyFileAppender and some other additions to the standard log4j library can be found into a simple library that I have created : log4j-additions .
You can look at the source to develop your own extension or you can use it as is ...
In Log4j 2, both FileAppender and RollingFileAppender has the parameter "createOnDemand" which can be used to configure to create the log file only when a log event passed to the appender.
Example:
<RollingFile name="LogFile" fileName="test.log" filePattern="test-%i.log.gz" createOnDemand="true">
<Policies>
<SizeBasedTriggeringPolicy size="1MB"/>
</Policies>
<DefaultRolloverStrategy max="5"/>
</RollingFile>
More details here: https://logging.apache.org/log4j/2.x/manual/appenders.html#RollingRandomAccessFileAppender
The file appenders have no option to lazily create the log files - the setFile method automatically creates the file if it doesn't already exist: ostream = new FileOutputStream(fileName, append);
You'll have to extend the appender and overwrite the file initialisation code yourself to get the behaviour you're after.
Extend the standard FileAppender class was unsuccessful for me. So I have found an other solution using appenders programmatically to create log files on demand only (and with timestamp in the name file). I have written these two methods :
public void startLog() {
SimpleDateFormat sdf_long = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
FileAppender fa = new FileAppender();
fa.setName("foo");
fa.setFile(sdf_long.format(new Date()) + ".log");
fa.setLayout(new PatternLayout("%d{HH:mm:ss.SSS} %m%n"));
fa.setThreshold(Level.DEBUG);
fa.setAppend(true);
fa.activateOptions();
Logger.getRootLogger().addAppender(fa);
}
public void stopLog() {
Logger.getRootLogger().getAppender("foo").close();
Logger.getRootLogger().removeAppender("foo");
}
My log4j.properties file only configures the console appender. When I want to start logging I call the startLog() method. When I want to log in an other file I call stopLog() first and then startLog() method.

Resources