Problematic N-Tier logging with log4net - c#

I am currently developing series of web services in WCF using .NET 4.5. For my logging I have chosen log4net framework but I found that its a big problem to use it in my project design.
I have everything separated into projects like this :
DataAccess
Common
etc. etc.
ExternalServicesContracts
ExternalServices
ExternalServicesProxies
InternalService
All of the services projects contains more services.
Situation
I need my log files to look like this ServiceName_YYYY_DDMM.log.
So in the log4net config file i need to use different loggers per namespace of the web service. I can achieve it by using loggers like this :
For the first service :
private static readonly ILog Log = LogManager.GetLogger(typeof(MyFirstService));
And the same for the second service :
private static readonly ILog Log = LogManager.GetLogger(typeof(MySecondService));
and then in config file I just point to them by loggers
<logger name="ExternalServices.MyFirstService">
<appender-ref ref="FirstServiceAppender"/>
</logger>
<logger name="ExternalServices.MySecondService">
<appender-ref ref="SecondServiceAppender"/>
</logger>
And in appenders
<appender type="log4net.Appender.RollingFileAppender" name="FirstServiceAppender">
<file value="c:\temp\FirstService"/>
<rollingStyle value="Composite" />
<datePattern value="_yyyy_MM_dd.lo\g"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="100MB"/>
....
</appender>
<appender type="log4net.Appender.RollingFileAppender" name="SecondServiceAppender">
<file value="c:\temp\SecondService"/>
<rollingStyle value="Composite" />
<datePattern value="_yyyy_MM_dd.lo\g"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="100MB"/>
....
</appender>
Problem
After this change, I can`t see anything from my DataAccess and Common because I had to do the same strategy with classes inside the layer.
So the logger will be loaded the same way like this:
private static readonly ILog Log = LogManager.GetLogger(typeof(UsersProvider));
I could add another logger for both projects but I need the logs from it to be written inside the same file (the log file used by the caller).
Question
I really cant separate the services into more projects because of the client.
Is there a way to use the same logger inside a library as the caller is using ? (So the DataAccess and Common writes logs into FirstServiceAppender when the service is using it.)
Or is there any pattern that would get me out of this situation ?

I don't have time to try this out right now, but this should be easier that you think it is. You can try configuring log4net to have a dynamic file name based on a GlobalContext.Properties value. In each service, put a value GlobalContext.Properties that "names" the service. This should be ok as each service is essentially a separate process, so using the GlobalContext should not result in any conflicts. This way you will need only one file target and your loggers can be configured very simply (just send the output to the one file target).
Configuration should be something like this:
<appender type="log4net.Appender.RollingFileAppender" name="RollingFileAppender">
<file type="log4net.Util.PatternString" value="C:\temp\%property{Service}"/>
<rollingStyle value="Composite" />
<datePattern value="_yyyy_MM_dd.log"/>
<maxSizeRollBackups value="-1"/>
<maximumFileSize value="100MB"/>
....
</appender>
<root>
<appender-ref ref="RollingFileAppender" />
</root>
In your code, when each service is initialized, put something like this:
GlobalContext.Properties["Service"] = "FirstService"; // for FirstService
GlobalContext.Properties["Service"] = "SecondService"; // for SecondService
Note that you must set the value into GlobalContext.Properties BEFORE anything else is done with log4net. The best place would be in the static constructor of your application/service.
This configuration should all logs generated in "FirstService" to be written to the FirstService log file and all logs generated in "SecondService" to be written to the SecondService log.
Update:
<appender name="DynamicRollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="c:\temp\%property{Service}" />
<datePattern value="_yyyy_MM_dd.lo\g" />
<appendToFile value="true" />
<maxSizeRollBackups value="-1" />
<maximumFileSize value="5000KB" />
<preserveLogFileNameExtension value="true"/>
<staticLogFileName value="false" />
<countDirection value="1"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%method] - %message%newline" />
</layout>
</appender>

You can pass logger as a parameter for classes. So maybe your DataAccess classes should take a logger as a constructor parameter and use it for loggin. Then you could give a desired logger from outside.

Related

How do I setup a FileAppender logger from log4net for a .NET Standard 2.0 class library?

How do I setup a File Appender logger from log4net for a class library using .NET Standard 2.0? I don't have an AssemblyInfo.cs. I think I have a fundamental misunderstanding that's leading to my confusion with this, so this may be a simple answer, but I just started a class library that I want to be accessible to as many projects as possible in our solutions and have internal logging from the get-go.
Any and all suggestions/help is much appreciated.
There is nothing special about the AssemblyInfo.cs file. The attribute to configure log4net can be put in any file in the assembly.
That being said I would recommend against using log4net directly and instead use a ILogger from the NuGet package Microsoft.Extensions.Logging.Abstractions. This allows the consumer of your library use any logging system they want instead of being tied to only using log4net.
Not sure if I understood what you need/want but this is an idea of how the config file should look like
<log4net>
<root>
<level value="INFO" />
<appender-ref ref="LogFileAppender" />
</root>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<param name="File" value="test.txt" />
<param name="AppendToFile" value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="5MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%date [%thread] %-5level %logger [%M %C] - %message%newline" />
</layout>
</log4net>
If i'm not mistaken, you don't need to add anything to your assemblyinfo (if you dont have one) but you need to add something like this on the startup
log4net.Config.XmlConfigurator.Configure();
(in my case webapi so I add it to my startup.cs)
I hope this helps!

Log4net sometimes creating two logs, one empty

Using the below configuration, about half of the time, I'm getting two logs per one, one empty. They differ in filename by one second (log.2015-03-09_11-50-25 vs log.2015-03-09_11-50-26)
I'm trying to have one log per run of the console application.
<log4net>
<appender name="Log" type="log4net.Appender.FileAppender">
<file type="log4net.Util.PatternString" value="C:\env\QA\Logs\consoleapp\log.%date{yyyy-MM-dd_HH-mm-ss}.txt"/>
<appendToFile value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %message%newline"/>
</layout>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="Log"/>
</root>
</log4net>
The latter log is the only one that's populated/written to.
Why is this happening? How do I fix it?
EDIT: Turns out i was instantiating a second logger in my code. When the instantiation occurred during a different second, a second log was created. The appender was working correctly.
I've seen something similar before - it's due to the second being specified in the filename, and the writes are occuring during the end of one second and the start of another.
<file type="log4net.Util.PatternString" value="C:\env\QA\Logs\consoleapp\log.%date{yyyy-MM-dd_HH-mm-ss}.txt"/>
should be
<file type="log4net.Util.PatternString" value="C:\env\QA\Logs\consoleapp\log.%date{yyyy-MM-dd}.txt"/>
In my last job, somebody set the second specifier in the log file name, and the production server came close to crashing trying the render the 150,000 files contained in the log folder.
Edit:
If you want to write once per run, you can add the following to your log4net configuration:
<rollingStyle value="Once" />
and you might need to set the appendToFile attribute to be false.
log4net one file per run
so your config would look like this:
<appender name="Log" type="log4net.Appender.FileAppender">
<file type="log4net.Util.PatternString" value="C:\env\QA\Logs\consoleapp\log.%date{yyyy-MM-dd_HH-mm-ss}.txt"/>
<appendToFile value="false" />
<maxSizeRollBackups value="-1" /> <!--infinite-->
<rollingStyle value="Once" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %message%newline"/>
</layout>
</appender>
I used user1666620's solution, but still got a .log.1 file.
Turns out I had the following line in my AssemblyInfo.cs file:
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
Removing the above line fixed my issue. By the way, my program.cs looks like:
public static class Program
{
static Program()
{
// Initialize logging
log4net.Config.XmlConfigurator.Configure();
}
private static readonly ILog log =
LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
...
}

log4net EventLog custom logname created, but not used

In a project, i'm using log4net to write down in the event log. I need to create a custom log so all will go there. I have tried to add the event to different logs, and it worked, but for some reasons, now it is impossible to change the logname from the config file. Even if I change it, it will keep the previous one and log into it. Here is a sample of my app.config:
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{yyyy-MMMM-dd HH:mm:ss, fff} [%thread] %level %message %exception%newline"/>
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG" />
<levelMax value="FATAL" />
</filter>
<ApplicationName>TestLoggerFichierConfig</ApplicationName>
<LogName>AgentLogger</LogName>
</appender>
I read that the event log must be restarted when adding a new log so the recent events will be displayed in it. I did this, and it allowed me to see the new log that has been created, but nothing will go there. Is it possible to force Log4Net to stop using the previous log to rather use the one I defined in the app.config?
Thanks for the help.
Update:
It seems that le EventLogs recognizes the source application and decide in wich log to put the new events since he remembers where it previously went. When I first set the logName, it works fine. If I stop the app, change the logname and restart it, the event will still go to the previous log. If the logName does not exists in the event log, it will be created, but not used! There might be something to do, but it is not in the side of log4net, and it may be dangerous to change the windows settings at this level. I created two eventLogAppender on the same app.config file, both pointing at different logs. the events got to the same log though. It is being obvious that the problem doesn't come from log4net, and the solution to my problem will not be solved by code. Thanks for the great advices though.
Something is probably wrong with your configuration and it is simply not telling you about it because log4net itself is designed to never, ever throw errors.
You can turn on internal debugging for log4net, but don't deploy to production with log4net debugging turned on. Weirdness will eventually happen if deploy your product with the log4net debugging switch on.
http://haacked.com/archive/2006/09/27/Log4Net_Troubleshooting.aspx
http://logging.apache.org/log4net/release/faq.html (see the troubleshooting section)
I ran into this and it wasn't down to log4net but the re-mapping of the source to a new custom log. I took log4net out of the equation and finding that the problem occurred usint the EventLog class directly, I eventually found EventLog.CreateEventSource is not creating a custom log
I think it's also the reason behind this Windows service always writes to custom log and application log
First and foremost, I'd encourage you to turn on internal debugging, as found in the link ntcolonel posted. Add this to your config file:
<add key="log4net.Internal.Debug" value="true"/>
Perhaps I am not understanding your question entirely, but I'd like to ensure you have both created the appender and properly tell log4net that you want to actually use that appender:
<log4net debug="true">
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs\SomeApplication.xml"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<countDirection value="1"/>
<maxSizeRollBackups value="30"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<layout type="log4net.Layout.XmlLayoutSchemaLog4j">
<locationInfo value="true"/>
</layout>
</appender>
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="SomeDistributionList#somehost.com"/>
<from value="SomeApplication#somehost.com"/>
<subject type="log4net.Util.PatternString" value="Some Application Error - %property{log4net:HostName}"/>
<smtpHost value="smtp.somehost.com"/>
<bufferSize value="1"/>
<threshold value="ERROR"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %property{log4net:HostName} %logger %level %newline%newline%property{ExceptionThrottleInformation}%newline%newline%message%newline"/>
</layout>
<filter type="SomeNamespace.SomeSubNamespace.Log4Net.ExceptionThrottleFilter, SomeSubNamespace">
<threshold value="10"/>
<thresholdTimeoutSeconds value="60"/>
<timeoutSecondsBetweenMessages value="600000"/>
<exceptionText value="Timeout expired"/>
</filter>
</appender>
<appender name="DatabaseAppender" type="SomeNamespace.SomeSubNamespace.DatabaseTraceAppender, SomeSubNamespace">
<hoursToKeepInformationalTraces value="48"/>
<hoursToKeepErrorTraces value="96"/>
<threshold value="INFO"/>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="RollingFileAppender"/>
<appender-ref ref="SmtpAppender"/>
<appender-ref ref="DatabaseAppender"/>
</root>
Note that while I have multiple appenders, I reference which to call within the root tag. Since you didn't post your whole config file, it's tough to tell if everything is matching as it should.

log4net - different log-files for assembly

I have a application which is loading PlugIn-Modules with reflection.
The Application is defining a log4net-log-appender in the app.config like:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="logs\xxx.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1MB" />
<staticLogFileName value="true" />
<threshold value="DEBUG" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger{2} - %message%newline" />
</layout>
</appender>
I get the Logger in the code like:
private static readonly ILog log = LogManager.GetLogger(typeof(Indexer));
Now, I am searching a way to declare different Log-Files for each Module (assembly), which is loaded as PlugIn with reflection.
The first problem is, that the modules are using Business-Library-Classes together (they are in a assembly which is used of all modules) which also generates log-entries. This entries should also be inserted in the log-file of the module.
The second problem is, I don't know the modules at developing-time. So I cannot insert some config in the app.config.
The second problem is, i don't know the modules at developing-time. So i cannot insert some config in the app.config.
This reveals that it might not be possible in the config file. When you add a new plugin, you can add code to add a new appender for the plugin.
Hers some (code) to get you inspired:
// Setup RollingFileAppender
log4net.Appender.RollingFileAppender fileAppender = new log4net.Appender.RollingFileAppender();
fileAppender.Layout = new log4net.Layout.PatternLayout("%d [%t]%-5p %c [%x] - %m%n");
fileAppender.MaximumFileSize = "100KB";
fileAppender.MaxSizeRollBackups = 5;
fileAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size;
fileAppender.AppendToFile = true;
fileAppender.File = fileName;
fileAppender.Name = "XXXRollingFileAppender";
log4net.Config.BasicConfigurator.Configure(fileAppender);
You need to add that it is only from the plugin-assembly you want to log, and maybe omit them from the already configures RollingFileAppender.

How to include log4net for a class library?

I want to implement logging function into a class library, which is itself referenced in a webservice. I tried to add app.config and did everything needed, but it seems that when an exception is thrown, log4net simply does nothing.
my app.config
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="D:\\mylogfile.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="test" />
</filter>
<filter type="log4net.Filter.StringMatchFilter">
<stringToMatch value="error" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline%exception" />
</layout>
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="RollingFileAppender"/>
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
in AssemblyInfo.cs:
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "app.config")]
in LogManager.cs:
private static readonly ILog Log = log4net.LogManager.GetLogger
(MethodBase.GetCurrentMethod().DeclaringType);
public static void WriteLog(Exception ex)
{
Log.Error(ex);
}
Can you please tell me what's wrong? How can I get log4net working for my class library?
Thank you
At runtime, config file is always used from host application, unless declared explicitly. Here in case, web.config is being used not app.cofig. Rather mention some other custom config file name and ensure that file is copied in virtual directory of web service. Also as chibacity said, ensure permission on log file. Better keep it in app_data folder for web service host.
You could use some kind of injection, either you build a simple one like:
public interface ILogger
{
void LogInfo(string message);
.
.
.
}
And then you just inject something that matches that interface, like a wrapper for log4net or such.
But, I think the most correct thing is to not log in the class library. Why I think so is because the library itself is not the application, your web service is so your web service should decide what to log. If you want to log exceptions just don't catch them in the class library and let your web service handle the logging. This will also make it easier to centralize the logging.
Please see my answer to the following question:
Use log4net in SDK
It has a log configuration routine that will construct a complete path to a log4net config file. As you are in a webservice it my not be looking where you expect for "app.config".
Also make sure that you have permission to write to "D:\mylogfile.txt" from your webservice.

Categories