log4net closed appender error with config in code - log4net

I got in trouble with my l4n configuration.
Working on a big project, I try to configure my logger(s) via code.
So this is my only app.config-content:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<log4net>
<root>
<level value="DEBUG"/>
</root>
<logger name="NESSEEServerCoreLogger">
<level value="INFO"/>
</logger>
</log4net>
And the following is written in the Code, in this case in the Ctor of one class:
(Only change: The m_RFA member is a class member in productive code)
log4net.Filter.LevelRangeFilter lrf = new log4net.Filter.LevelRangeFilter();
lrf.LevelMin = log4net.Core.Level.Info;
lrf.LevelMax = log4net.Core.Level.Fatal;
//create and configure rolling file appender
log4net.Appender.RollingFileAppender m_Rfa = new log4net.Appender.RollingFileAppender();
m_Rfa.Name = "NESSEEServerCore_RFA";
m_Rfa.ImmediateFlush = true;
m_Rfa.AppendToFile = true;
m_Rfa.File = #"C:\Temp\NESSEEServerCore_Log.txt";
m_Rfa.MaxFileSize = 52428800; // 50 MiB
m_Rfa.MaxSizeRollBackups = 5;
m_Rfa.StaticLogFileName = true;
m_Rfa.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size;
var layout = new log4net.Layout.PatternLayout()
{
ConversionPattern = "[%utcdate{dd.MM.yyyy HH:mm:ss,fff}] %level\t%type{1}->%method:%line : %message %newline"
};
layout.ActivateOptions();
m_Rfa.Layout = layout;
m_Rfa.AddFilter(lrf);
m_Rfa.AddFilter(new log4net.Filter.DenyAllFilter());
m_Rfa.ActivateOptions();
log4net.Config.BasicConfigurator.Configure(m_Rfa);
This works fine so far.
The only problem is:
Sometimes, when an exception occures in the application (running as an windows service, I integrated a button to provoke an exception), the logger stopps writing to the file.
I already turned on internal debugging on l4n. This gives me the following output:
7/31/2013 1:42:30 PM: log4net:ERROR [RollingFileAppender] ErrorCode: GenericFailure. Attempted to append to closed appender named [NESSEEServerCore_RFA].
7/31/2013 1:42:30 PM: log4net:ERROR [FileAppender] ErrorCode: GenericFailure. Attempted to append to closed appender named [].
The second file Appender seems to be the one for internal debugging itself.
After 1:42:30, there are no log entries any more.
What do I have to do to fix this problem?
Do I have to reinitialize the logger completely?

Related

How can I take over writing an exception in log4net

We have a large Java application that we convert to .NET using IKVM. All of the log4j calls in it call a log4j wrapper we created that calls log4net. Mostly it works great.
But we have one problem - the logging does not give the stack trace or InnerException(s). I believe the problem is the .NET Exception.ToString() provides all that information while the Java Throwable.toString() is basically the Exception.Message.
So where log4net calls Exception.ToString(), I need to replace that for any exception that inherits from java.lang.Throwable to create the string. How can I do this?
Before calling Exception.ToString(), Log4net looks whether a custom IObjectRenderer has been registered for an Exception (as it also does for any other type).
In order to get a custom Exception output, you need to create and register a custom IObjectRenderer.
The one here below will output the Exception in uppercase.
You are free to build any error message string representation which you pass to writer.Write.
namespace PFX
{
public class MyExceptionRenderer : IObjectRenderer
{
public void RenderObject(RendererMap rendererMap, object obj, TextWriter writer)
{
Exception exception = obj as Exception;
// Format the error message as pleased here.
String error = exception.ToString().ToUpper();
writer.Write(error);
}
}
}
You register the MyExceptionRenderer in your Log4net configuration as shown below.
<log4net>
<appender name="consoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date | %logger | %level | %message | %exception%n" />
</layout>
</appender>
<root>
<level value="All" />
<appender-ref ref="consoleAppender" />
</root>
<renderer renderingClass="PFX.MyExceptionRenderer"
renderedClass="System.Exception" />
</log4net>
The code below
var logger = LogManager.GetLogger("AppLog");
var exception = new ApplicationException("Ooops");
logger.Error("An error occured.", exception);
will have the following log output.
Notice that the exception is in full caps.
2019-05-08 21:52:22,855 | AppLog | ERROR | An error occured. | SYSTEM.APPLICATIONEXCEPTION: OOOPS

Log4Net param from Properties without programming

I tried to setup my Log4Net dynamical. But some properties in my Appenders wont work.
I.e. in my Program.cs
GlobalContext.Properties["hostname"] = Environment.MachineName;
GlobalContext.Properties["serviceName"] = ServiceProperties.ServiceName;
GlobalContext.Properties["smtpHost"] = LoggingProperties.SMTPServer;
GlobalContext.Properties["maximumFileSize"] = LoggingProperties.MaximumFileSize;
log4net.Config.XmlConfigurator.Configure();
log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
log.Debug("Logger configured.");
log.Fatal("I am a Mail"); // Mail Logging Test
And the part of my app.config
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<param name="smtpHost" value="%property{smtpHost}" />
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="FATAL"/>
</evaluator>
....
</appender>
In my SmtpAppender, it literally says "%property{smtpHost}" als String, same with every other property.
Can I use the properties in my config without programmatically set everything or do I have to code the whole appenders?
Regards

How to access log4net log file in Azure worker role?

I have setup log4net fileappender for my worker role as below using .NET 4.5 & VS2013.
<configSections> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a" /> </configSections>
<log4net>
<appender name="FileAppender" type="log4net.Appender.FileAppender,log4net">
<file value="Log.txt" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="FileAppender"/>
</root>
</log4net>
Then setting the log file path at run time using the below steps (briefly).
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Set the log file path - FileAppender.File = RoleEnvironment.GetLocalResource("LogFileStore").RootPath + "Log.txt"
Everything works fine and log file generated successfully and messages were written into it in worker role storage folder (LogFileStore - 10MB defined in .csdef).
But when I tried to attach the log file to a mail attachment in the code, it throws exception - "You can not access the file as it is being used by another process. How to fix it ?
MailMessage mail = new MailMessage(FromAddress, ToAddress);
SmtpClient client = new SmtpClient();
client.Port = 25;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = true;
client.Host = Host;
mail.Subject = "Error in the Data Load";
mail.IsBodyHtml = true;
var htmlBody = string.Empty;
htmlBody = "Error in Data Load and the Error is : </br></br>";
htmlBody += ErrorMessage;
mail.Body = htmlBody;
mail.Attachments.Add(new Attachment(RoleEnvironment.GetLocalResource("LogFileStore").RootPath + "Log.txt"));
client.Send(mail);
Please help.
Thanks
Bhanu
You will need to use File.Copy and create a copy of the log file. After sending the email, delete the temporary file that you created.
File.Copy(RoleEnvironment.GetLocalResource("LogFileStore").RootPath + "Log.txt", RoleEnvironment.GetLocalResource("LogFileStore").RootPath + "LogTemp.txt", true);
Edit:
If File.Copy doesn't work, you can try one of two other options:
1 - In the "appender" section of the log4net configuration in the app.config, add
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
This has some performance overhead, as it will need to open and lock the file each time it writes instead of just holding the lock the entire time.
2 - Copy the file contents to a stream using FileShare.ReadWrite and attach the stream to the email or write to new file and attach
string logFileText using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader streamReader = new StreamReader(fileStream))
{
//read stream to file or directly to memory stream and attach
}

log4net generates wrong log file name after rolling log files

Basically I have this config for log4net:
<?xml version="1.0" encoding="utf-8"?>
<log4net>
<root>
<level value="INFO" />
<appender-ref ref="RollingFileAppenderWithDeletion" />
</root>
<appender name="RollingFileAppenderWithDeletion" type="Namespace.RollingFileAppenderWithDeletion">
<file type="log4net.Util.PatternString" value="Logs/%property{LogName}/log.%property{ServiceName}-PID-%processid_%date{yyyyMMdd}.log" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMMdd" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="50MB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level %date{dd MMM yyyy HH:mm:ss,ffff} [%thread] %exception - %message%newline"/>
</layout>
</appender>
</log4net>
Now I have a problem. Everytime log4net rolls log file, it doesn't change the date part of log file. For example if today's log file is - log.MyServiceName-PID-1234_20131208.log, tomorrow after rolling the file, name of the file won't change, so I will end up having a rolled back log file and active log file like this
log.QAService-PID-17584_20131208.log
log.QAService-PID-17584_20131208.log2013Dec08
What I would like to have is
log.QAService-PID-17584_20131209.log - active log file
log.QAService-PID-17584_20131208.log2013Dec08 - rolled back
Now I came across this post, but it didn't help me. In particular, if I remove date pattern from file, and set preserveLogFileNameExtension to true, I can't see date part on active log file anymore. If I go further and set staticLogFileName to false, I don't have active log files anymore, instead active log files are having the rolled back log file name pattern.
What am I missing here? How can I have right log file names generated after roll back.
I'm using log4net version 1.2.10.0. Unfortunately I can't upgrade it to newer version.
Update
This is custom implementation of RollingFileAppenderWithDeletion. It just cleans up old rolled back files after log file rolling took place.
public class RollingFileAppenderWithDeletion :RollingFileAppender
{
private IFileBurner m_fileBurner;
private const int checkMinutes = 1664;
public RollingFileAppenderWithDeletion()
{
m_fileBurner = FileBurner.Instance;
}
protected override void AdjustFileBeforeAppend()
{
base.AdjustFileBeforeAppend();
string path = base.File;
string directoryPath = Path.GetDirectoryName(path);
IDeletionRequirements requirements = new DeletionRequirements();
requirements.CheckEveryMinutes = checkMinutes;
requirements.DayLimit = MaxSizeRollBackups;
requirements.Directories = new List<string> { directoryPath };
m_fileBurner.ClearLogFiles(requirements);
}
}
You are using the type Namespace.RollingAppenderWithDeletion, which is definately not a standard appender. Try use the type log4net.Appender.RollingFileAppender and see if that works. When you use your custom appender you need to post some code, or find why the filename code is not called.

log4net MemoryAppender not working

I'm using log4net to log in my app. My FileAppender is working fine, but I'm having problems with MemoryAppender.
Here is my config file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
<param name="File" value="Envision.log" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="" />
<param name="Footer" value="" />
<param name="ConversionPattern" value="%d [%t] %-5p %m%n" />
</layout>
</appender>
<appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="MemoryAppender" />
</root>
</log4net>
</configuration>
I use this code to setup the config file.
FileInfo file = new FileInfo(configPath);
log4net.Config.XmlConfigurator.Configure(file);
file = null;
Like I said, the FileAppender works great. But I can't seem to get any events.
I've tried using something like this to get the MemoryAppender.
Hierarchy hierarchy = LogManager.GetRepository() as Hierarchy;
MemoryAppender mappender = hierarchy.Root.GetAppender("MemoryAppender") as MemoryAppender;
I've tried using:
var events = mappender.GetEvents()
after logging something, and events is always empty. I've tried setting up the FileAppender and MemoryAppender in code instead of using the config file, and I get the same, the FileAppender works fine, but can't seem to get any events from MemoryAppender. Curious if I'm understanding MemoryAppender right? I also tried setting up a thread that loops checking for the GetEvents to not be empty, and while logging away it always comes back empty. I've tried setting the Threshold to Core.Level.All on the MemoryAppender but that did not change anything.
Thanks for any direction. I've looked around, and from the sites I've seen, I can't tell what I'm doing different.
Even something as simple as this does not work. events length is always zero;
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
MemoryAppender appender = new MemoryAppender();
ILog logger = LogManager.GetLogger("foo");
BasicConfigurator.Configure(appender);
logger.Error("Should work");
var events = appender.GetEvents();
}
}
For those that need it, here's how to do it programmatically in C#:
var memoryAppender = new MemoryAppender();
var repository = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
repository.Root.AddAppender(memoryAppender);
var events = memoryAppender.GetEvents();
I used Ralph's code above in my unit testing:
using log4net;
using log4net.Appender;
// ...
internal static MemoryAppender GetMemoLog<T>() where T: class
{
var memoLog = new MemoryAppender();
ILog appendableLog = LogManager.GetLogger(typeof(T).Assembly, typeof(T));
var repository = (log4net.Repository.Hierarchy.Hierarchy)appendableLog.Logger.Repository;
repository.Root.AddAppender(memoLog);
var logField = typeof(T).GetField("Log", BindingFlags.Static | BindingFlags.NonPublic);
if (logField != null) logField.SetValue(null, appendableLog);
return memoLog;
}
This assumes you have a private static Log field on your class:
private static readonly ILog Log = LogManager.GetLogger(typeof(MyClass));
So, in the test, it's just:
var memoLog = GetMemoLog<MyClass>();
// followed by test logic, and then...
var events = memoLog.GetEvents();
The simple sample code you posted works fine for me using log4net 1.2.10.0.
I would recommend downloading the source and stepping through it in a debugger. It may seem a little daunting at first, but you get used to their code pretty quickly and it's not hard to follow. I've done this many times when I had problems with custom constraints and appenders. It really helps solve problems quickly and gives you a much better understanding of how log4net works.
I figured it out. I was using the Compact Framework .dll by mistake. Once I realized that I switched to the .net 2.0 version, which caused a problem with log4net namespace not being found, so I did a search on that and realized I needed to change my .net Framework 4 client Profile to .net Framework 4. I'm now getting the events as expected.
I adapted CZahrobsky's answer. Had to tweak slightly, since my class cannot have static logger by design.
Class under test has the log field declared like:
private ILog Logger = Log4netFactory.GetLogger(typeof(MyClass));
In the GetMemLog logic I have to first create an instance of MyClass and change the logField look up to getField by name 'Logger' and BindingFlags.Instance instead of BindingFlags.Static
//create an instance of the class
var myObject = new MyClass(context);
var memoryLog = new MemoryAppender();
ILog appendableLog = LogManager.GetLogger(typeof(JobQueue).Assembly, typeof(MyClass));
var repository = (log4net.Repository.Hierarchy.Hierarchy)appendableLog.Logger.Repository;
repository.Root.AddAppender(memoryLog);
var logField = typeof(MyClass).GetField("Logger", BindingFlags.NonPublic | BindingFlags.Instance);
if (logField != null)
{
//set logfield property value for the instance
logField.SetValue(myObject, appendableLog);
}
Examples on SetValue() for PropertyInfo is here

Resources