Log4Net param from Properties without programming - log4net

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

Related

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

How can I log to Special Folders with log4net?

How can I log to special folders (e.g. %APPDATA%) using the app.config file?
I can do it programmatically, but I need to be able to use the app.config file for configuration. I have seen a post of using %envFolderPath.It is not available in the latest released version, but only in their latest code.
Below is the code that I setting the log to special folders programmatically.
public void ExampleLog
{
XmlConfigurator.Configure();
var fileName = GetFileName();
var appender = new log4net.Appender.RollingFileAppender
{
Layout = new log4net.Layout.PatternLayout("%d - %m%n"),
File = fileName,
MaxSizeRollBackups = 10,
MaximumFileSize = "100MB",
AppendToFile = true,
Threshold = Level.Debug
};
appender.ActivateOptions();
BasicConfigurator.Configure(appender);
}
private static string GetFileName()
{
const string subPath = "MySubFolder";
var path = String.Format(#"{0}\{1}", Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), subPath);
const string logName = "Log.txt";
return Path.Combine(path, logName);
}
Pretty sure the syntax for this is available in the current release.
<file type="log4net.Util.PatternString" value="%env{APPDATA}\\MyApp\\Log.txt" />
If you need something more, you can look into option of subclassing the PatternString class, as described here: Log4Net can’t find %username property when I name the file in my appender
Check out the RollingFileAppender configuration sample on the log4net docs (for all the other parameters).
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="100KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.SimpleLayout" />
</layout>
</appender>
I've referenced environment variables (including special folders) with the basic log4net variable format ${NAME}. And the file tag doesn't need to have the PatternLayout specified, it's implied.
<file value="${APPDATA}\log.txt" />

How do I use a date pattern in a header/footer?

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...

Resources