Multiple instances of same process logging on different files - c#

I'm trying to log to multiple files with multiple instances of the same scheduled process, and the target file is chosen based on runtime parameters.
The problem here is that, while one instance is running, no other one is able to log, producing just empty files.
Here's a sample scenario:
The schedlued task program.exe A starts and begins to log to A_{currentDate}.log
The schedlued task program.exe B starts while program.exe A is still running and should start logging to B_{currentDate}.log
When they both end, the second file is empty while the first is not, and this is not the expected result as I'm 100% sure that the second instance has something to log. Basically it looks like that the first instance to run prevents all the other ones from logging, being the only one that can do it.
Given that we have about 20 scheduled tasks for the same program, this is causing a lot of issues as we're missing some important logs.
Here's a snippet of log4net.config file:
<appender name="A_appender" type="log4net.Appender.FileAppender">
<file type="log4net.Util.PatternString" value="A_%property{Date}.log" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="B_appender" type="log4net.Appender.FileAppender">
<file type="log4net.Util.PatternString" value="B_%property{Date}.log" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
...
<logger name="A.Logger">
<appender-ref ref="A_appender" />
<level value="DEBUG" />
</logger>
<logger name="B.Logger">
<appender-ref ref="B_appender" />
<level value="DEBUG" />
</logger>
...
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
</root>
and here's the initialization code for each instance:
protected Module(string logName)
{
ThreadContext.Properties["Date"] = GlobalContext.Properties["Date"] = DateTime.Now.ToString("yyyyMMdd");
XmlConfigurator.Configure(new System.IO.FileInfo(properties.get("log4net.config.file")));
log = LogManager.GetLogger(logName);
...
}
Can someone help me with that?

The way it looks to me is that both of your processes are trying to hold both files open (both appender A and appender B) so whichever one starts first blocks the second process from running. You may only be grabbing a reference to one or the other appender to use, but it is creating both of them even though it is only logging to whichever one you grab.
You will likely need to set a second GlobalContext property for each process that is unique and include that in the filename, since it sounds like both processes run on the same date.

Related

If maxSizeRollBackups is decreased in Log4Net config, it is not deleting extra log files

When maxSizeRollBackups is decreased (suppose 4 to 2), it is expected that, only 2 files should be kept and extra files should be deleted, but it is not happening.
However, In case I increase the maxSizeRollBackups value, it is working according to the count.
Log4Net setting:
<?xml version="1.0"?>
<configuration>
<log4net>
<logger name="ConsoleLog">
<level value="FATAL" />
<level value="ERROR" />
<level value="WARN" />
<level value="INFO" />
<level value="DEBUG" />
<appender-ref ref="ConsoleLogFile" />
</logger>
<appender name="ConsoleLogFile" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%property{ConsoleSetup}" />
<appendToFile value="true" />
<maxSizeRollBackups value="4" />
<maximumFileSize value="1KB" />
<rollingStyle value="Composite" />
<datePattern value="yyyyMMdd" />
<staticLogFileName value="true" />
<Encoding value="UTF-8" />
<layout type="log4net.Layout.PatternLayout">
<!-- %d:DateTime, %t:Thread, %-5p:Categories, %c:LoggerName, %m:ADKMessage, %n:line sepalator-->
<param name="ConversionPattern" value="%d ,%-5p, %m%n" />
</layout>
</appender>
Watcher in C# on the log config:
log4net.Config.XmlConfigurator.ConfigureAndWatch(new System.IO.FileInfo(sPath));
According to maxSizeRollBackups setting (4) above, below files are existing.
"Application_ConsoleSetup.log"
"Application_ConsoleSetup.log.1"
"Application_ConsoleSetup.log.2"
"Application_ConsoleSetup.log.3"
"Application_ConsoleSetup.log.4"
Then I set, maxSizeRollBackups to 2 then I think, below files should be deleted, but not getting deleted. I want to find out cause of this issue
"Application_ConsoleSetup.log.3"
"Application_ConsoleSetup.log.4"
Note, If I set, maxSizeRollBackups to 6 then files below are getting generated as expected.
"Application_ConsoleSetup.log.5"
"Application_ConsoleSetup.log.6"
the reason is that Log4net doesn't do a full inventory of its log files and deletes those that shouldn't be there according to the current configuration. It just observes the configuration settings while it is logging. Looking at the method RollOverRenameFiles in RollingFileAppender.cs, you can see that if a log file rollover happens, the oldest file is being deleted and the other files are renamed. "Oldest file" refers to the oldest file according to the current configuration. If your configuration states that there should only be 2 backups, the oldest file is <baseFilename>.2. Log4net never checks if there are other files (.3, .4 and so on) that were created earlier due to a different configuration.

Can log4net create log files in a yyyy\\MM\\ddMMyyyy_file.log structure?

I am trying to make log4net create it's log files in a folder structure, but so far I'm not successful.
What I'd like to accomplish is that log4net creates log files like this:
..\log\2017\10\30-10-2017_eva360Recorder.log
..\log\2017\10\31-10-2017_eva360Recorder.log
..\log\2017\11\01-11-2017_eva360Recorder.log
..\log\2017\11\02-11-2017_eva360Recorder.log
In my app.config I now have the following:
<!-- Log4net Logging Setup -->
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
</root>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender, log4net">
<file type="log4net.Util.PatternString" value="log\\%date{yyyy}\\%date{MM}\\%date{dd-MM-yyyy}_eva360Recorder.log" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<maxDateRollBackups value="60" />
<datePattern value="dd-MM-yyyy" />
<preserveLogFileNameExtension value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger %M - %message%newline" />
</layout>
</appender>
</log4net>
This, however, results in the following:
d:\Eva360\Recorder\log\2017\10\31-10-2017_eva360Recorder.log
d:\Eva360\Recorder\log\2017\10\31-10-2017_eva360Recorder31-10-2017.log
As you can see, this is not what I wanted. It did not create a month folder (..\2017\11..) and also did not change the name of the log file, but added a date within the file name.
Is what I want even possible?
If so, how should I do that?
And also importantly, where is this to be found in the documentation of log4net?
I realise that this conjecture, rather than a definitive answer, but this could probably be achieved through the use of a custom log4net appender. See IAppender in the log4net SDK.
Google is chock full of articles about creating custom log4net appenders, so no need to repeat anything here.

Different appenders based on build configuration in log4net

My main application either starts in a console environment or as a Windows Service, based on certain indicators (launch flags, #if directive,...). As long as the application runs in 'production mode', e.g. without a user context as a startup service, I want to log both in a file (info, warnings) as well as into the Windows event log. However when I'm debugging, I want log messages only been carried out to the console window.
Is there any way to achieve this using log4net that doesn't include touching the application configuration during runtime?
Frankly the code approach doesn't sound like a very good idea to me, since it hides some behavior to the person that will manage the system in the end. Since you probably have two build configurations (release and debug?) why not use them instead to change the configuration file depending on the build you're doing.
If you really don't want to do this, then here is how to do it: you can set a custom property in the global context of your log4net library
// declare this code as soon as you've determined what context you're in
log4net.GlobalContext.Properties["ApplicationMode"] = "Service";
// or 'Console", with an #IF directive
Then in your log4net configuration, you can filter out the messages that will be caught by your appenders by using the PropertyFilter of log4net
The console appender for your dev:
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender,log4net">
<filter type="log4net.Filter.PropertyFilter">
<key value="ApplicationMode" />
<stringToMatch value="Console" />
<acceptOnMatch value="true" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %-25.25logger{1} - %message%newline" />
</layout>
</appender>
and the file appender for your production
<appender name="FileAppender" type="log4net.Appender.FileAppender,log4net">
<filter type="log4net.Filter.PropertyFilter">
<key value="ApplicationMode" />
<stringToMatch value="Service" />
<acceptOnMatch value="true" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<file value="mylogfile.txt" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %-25.25logger{1} - %message%newline" />
</layout>
</appender>
Then declare all appenders for your logs:
<root>
<level value="DEBUG"/>
<appender-ref ref="FileAppender"/>
<appender-ref ref="ConsoleAppender"/>
</root>
This will dispatch your message to the correct appender depending on the value that is in the global property. The DenyAllFilter is important; log4net works by letting all messages go through by default, and you only want the messages matching your PropertyFilter
All the appenders can be created, modified, or removed programatically. Why not just remove the appenders you want to avoid? This allows you to use the same code for detecting you are debugging and/or running in console.
To programatically disable the appender:
log4net.Appender.IAppender[] appenders = log4net.LogManager.GetRepository().GetAppenders();
for (int i = 0; i < appenders.Length; ++i)
{
Log4net.Appender.FileAppender appender = appenders[i] as log4net.Appender.FileAppender;
if (appender != null && appender.Name == "RollingFile")
appender.Threshold = log4net.Core.Level.Off;
}

With log4net, do you specify which appender to use?

When you have 2 appenders, say one for debug errors and one for product errors, in your code, do you explicitly create 2 log classes or is an appender basically going to write any log messages that meet a certain criteria?
So in your code, you use a single log method, and depending on how your appenders are setup, it will log the message if it e.g. is logged from a specific namespace, or is a certain log level.
So its possible a single log entry is written to 2 log files?
Yes, you can have single log statements post to multiple appenders. As long as it meets the criteria for each, it will use each appender.
For example, take this config section, it logs all messages to file, and also logs warnings and errors to event viewer.:
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="c:\logs\MySite"/>
<appendToFile value="true"/>
<datePattern value=".yyyy-MM-dd.\l\o\g"/>
<rollingStyle value="Date"/>
<MaxSizeRollBackups value="14"/>
<param name="StaticLogFileName" value="false"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%thread] %-5level %logger{1} - %m%n"/>
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<applicationName value="Trading.Web"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%thread] %-5level %logger{1} - %m%n"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="WARN"/>
<param name="LevelMax" value="ERROR"/>
</filter>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
<appender-ref ref="EventLogAppender" />
</root>
</log4net>
So this:
_log.Debug("This is a debug message");
Will only appear in the log file (because it doesn't meet the event log appender's filter criteria).
But this:
_log.Error("This is an error message.");
Will log to both the log file and the event viewer.
UPDATE: On your filtering question, if you had:
<root>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
</root>
<!-- Print only messages of level WARN or above in the namespace Com.Foo -->
<logger name="Com.Foo">
<level value="WARN" />
</logger>
Then all things under Com.Foo will log if WARN or higher, but everything else would log at DEBUG or higher....

log4net not working in dll

I'm currently having issues with getting log4net to work within a particular dll. I'm currently using log4net in other dlls being called by my test app and logging is working fine within those dlls and also within my test app. It's this one particular dll that I'm having trouble with. Here is snippet of code from the dll I'm having trouble with.
//This is from ABC.dll
public class SessionFactory
{
protected static ISessionFactory sessionFactory;
private static readonly ILog log = LogManager.GetLogger(typeof(SessionFactory));
private static void Init()
{
try
{
//Read the configuration from hibernate.xml.cfg or app.config
Configuration normalConfig = new Configuration().Configure();
ConfigureNhibernateValidator(normalConfig);
log.Debug("Initializing session factory");
sessionFactory = Fluently.Configure(normalConfig)
.Mappings(m =>
m.FluentMappings
.AddFromAssemblyOf<OrderHeaderMap>()
.Conventions.AddFromAssemblyOf<PascalCaseColumnNameConvention>())
.ProxyFactoryFactory("NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu")
.BuildSessionFactory();
log.Debug("Finished initializing the session factory");
}
catch(Exception ex)
{
//Code not shown
}
}
}
In my test app I am calling:
log4net.Config.XmlConfigurator.Configure();
Here is my log4net configuration in my App.config file:
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<!-- ALL|DEBUG|INFO|WARN|ERROR|FATAL|OFF -->
<root>
<level value="DEBUG"/>
<appender-ref ref="SpeedTest"/>
</root>
<!-- This is a default logger that nhibernate uses to push out all the SQL statements to-->
<logger name="NHibernate.SQL" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="NHibernateConsoleLog"/>
<appender-ref ref="NHibernateFileLog"/>
</logger>
<!-- This is a default logger that nhibernate uses to push out all the debugging type information to-->
<logger name="NHibernate" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="NHibernateFileLog"/>
</logger>
<appender name="NHibernateConsoleLog" type="log4net.Appender.TraceAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/>
</layout>
</appender>
<appender name="NHibernateFileLog" type="log4net.Appender.RollingFileAppender">
<file value="Logs/nhibernate.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="SpeedTest" type="log4net.Appender.RollingFileAppender">
<file value="Logs/SpeedTest.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n" />
</layout>
</appender>
</log4net>
</configuration>
Again logging is working fine within my test application using the SpeedTest appender but the logging within the above snippet of code is not working. I set breakpoints above on when it initializes the logger and it seems to hit it. I can post the log4net debug output if necessary but I didn't really see much. Just let me know if you need it and I will post.
Any suggestions as to why logging is not being recorded in the above snippet of code?
It seems that this issue was stemming from me changing the directory to all my external dependencies (log4net being one of them) awhile back in TFS. All I did was drop all my references in my visual studio project and re-add them from my new dependencies folder and everything worked as expected after this. Thanks for all those that helped here.
My suspicion would be that it isn't reading the configuration from the configuration file when you call configure.
If you add the following lines to your application, what do you see (either on the console, or in IDE output window, or by stepping through in the debugger):
var log4netConfig = ConfigurationManager.GetSection("log4net");
var log4netConfigIsNull = log4netConfig == null;
Console.WriteLine(log4netConfigIsNull);
Does it look like the configuration is actually available from the configuration file?
[Edit: in response to your comment]
If you now add another line of debug code to your app:
Console.WriteLine(log.IsDebugEnabled);
what output do you get? Does the logger think it is configured for debug logging? (I'm particularly interested in the behaviour of this in, or after, SessionFactory.Init).
The immediate thought would be that your logging code isn't getting hit, possibly due to an exception being thrown prior to your first log4net call. Can you put a log entry into a finally block, just to test that the logger works?

Categories

Resources