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
Related
I want to include the class and method name in our log output with a combined fixed width to look something like;
ClassA.MethodA - message
AnotherClass.AnotherMethod- message
WhateeverClass.Meth - message
SomeReallyLongClass.AndAreallyLongMethod- message
Is it possible to somehow combine the 2 wildcards say like %-50class.method?
Thanks!
There's no builtin support for padding a combination of conversion patterns.
Your best option is to implement a custom PatternLayoutConverter returning the combined result of %class and %method. On this single one, you can apply a padding specifier in the regular way.
Pattern layout
namespace PFX
{
public class ClassAndMethodPatternConverter : PatternLayoutConverter
{
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
var info = loggingEvent.LocationInformation;
var text = $"{info.ClassName}.{info.MethodName}";
writer.Write(text);
}
}
}
Registration and usage in a Log4net configuration file
PFX.ClassAndMethodIdPatternConverter, MyLibrary represents the full name of the pattern converter above, composed of the namespace, class and assembly .
<appender name="consoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="classandmethod" />
<type value="PFX.ClassAndMethodPatternConverter, MyLibrary" />
</converter>
<conversionPattern value="%date | %logger | %level | %-50classandmethod | %message | %exception%n" />
</layout>
</appender>
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
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.
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
Here's my appender configuration from my app.config. This just prints out the literal string instead of translating it to the date (i.e., it literally prints "[START: %date{MM/dd/yy HH:mm} ]").
<appender name="RollingLogFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="C:\somelog" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="-yyyy-MM-dd'.txt'" />
<layout type="log4net.Layout.PatternLayout">
<header value="[START: %date{MM/dd/yy HH:mm} ]
" />
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} - %message" />
<footer value="[END]
" />
</layout>
</appender>
How can I get this to print the date/time in the header?
Answer from here.
<layout type="log4net.Layout.DynamicPatternLayout">
...
<header value="[BEGIN LOGGING AT %date]%newline"/>
<footer value="[END LOGGING AT %date]%newline"/>
...
</layout>
Works for me like a charm. No need to write a piece of code.
Also in projects we usually use:
<header type="log4net.Util.PatternString" value="Our Application Name version %property{Assembly.Version}, .NET version %property{Runtime.Version}, %date ***%newline"/>
Take a look at PatternString doc also.
Also I've noticed that log file won't appear until you write first log statement.
An easy way to do this is to subclass log4net.Layout.PatternLayout and override Header and Footer. Then you can add whatever you want to your Header: date stamp, machine name, user name, assembly version, or whatever your heart desires:
using System;
using log4net.Layout;
namespace MyAssembly
{
class MyPatternLayout : PatternLayout
{
public override string Header
{
get
{
var dateString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
return string.Format("[START: {0} ]\r\n", dateString);
}
}
}
}
Include this new class in your assembly, and use the new type in your file, like this:
<layout type="MyAssembly.MyPatternLayout">
<conversionPattern value="%date{yyyy-MM-dd HH:mm:ss} - %message" />
</layout>
Since you overrode Header and Footer, you don't even need to add it here. The Header will be added every time your application starts, and every time the file rolls over.
Building on #Wizou's comment. See the DynamicPatternLayout Class documentation.
I'm actually using this difference in behaviour to have a start and end time
One important difference between PatternLayout and DynamicPatternLayout is the treatment of the Header and Footer parameters in the configuration. The Header and Footer parameters for DynamicPatternLayout must be syntactically in the form of a PatternString, but should not be marked as type log4net.Util.PatternString. Doing so causes the pattern to be statically converted at configuration time and causes DynamicPatternLayout to perform the same as PatternLayout.
Setting the type on Header to PatternString but leaving Footer as dynamic
<layout type="log4net.Layout.DynamicPatternLayout">
<param name="Header" value="%newline**** Trace Opened Local: %date{yyyy-MM-dd HH:mm:ss.fff} UTC: %utcdate{yyyy-MM-dd HH:mm:ss.fff} ****%newline" type="log4net.Util.PatternString" />
<param name="Footer" value="**** Trace Closed %date{yyyy-MM-dd HH:mm:ss.fff} ****%newline" />
</layout>
I encountered this problem and a quick workaround I used is to just use a fixed header and footer. Then do this as soon as you have the ILog object:
log.Info(string.Format("[START: {0} ]\r\n", dateString))
So just under the header you will get the information you desire.
E.g
===Start===
[START: 2012-02-23 12:12:12 ]
logs...