log4net multiple loggers - properties - c#

log4net v. 2.0.8
I've got some problems in multiple logging with log4net. I'm developing an application that works with n devices and I would like to have a single log file for each device.
log4net.config
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%envFolderPath{LocalApplicationData}/Foo/Device%property{DeviceID}/log.txt"/>
<appendToFile value="true"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="BEGIN LOGGING AT %date *** %property *** %newline" type="log4net.Util.PatternString" />
<param name="Footer" value="END LOGGING AT %date *** %property *** %newline" type="log4net.Util.PatternString" />
<param name="ConversionPattern" value="%date [%thread] %-5level %-5class{1} %-5method(%line) %message %newline" />
</layout>
<filter type="log4net.Filter.PropertyFilter">
<key value="Version" />
<stringToMatch value="1" />
</filter>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
</root>
and this is the code
public DeviceClass(string deviceID)
{
InitializeComponent();
GlobalContext.Properties["DeviceID"] = deviceID;
logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
logger.Debug("Hello world");
The problem is that if I have for example two devices I got just the first log file with the messages from all devices.

I'm not a Log4Net expert, but I believe that the log manager is going to initialize the logger and all of its appenders the first time that you call GetLogger for the given type/name (type under covers is translated to a string name). That being the case, it wouldn't re-initialize one per device. My suggestion for you is to try creating a composite of the type name and device ID using something like:
logger = LogManager.GetLogger($"{System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName}{deviceId}");

Related

How to filter log records in an application?

I have a WinForms application with log4net.
The app.config has a log4net configuration section. It contains settings like this:
<appender name="coloredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="INFO" />
<levelMax value="FATAL" />
</filter>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %m%n" />
</layout>
</appender>
But user wants to change levelMin and levelMax via GUI menu because edit app.config file is not comfortable.
How can I change the levelMin and levelMax values in application?
log4net can be configured by config file and later the application can correct parameters like this:
Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository();
hierarchy.Root.Level = Level.Error;
hierarchy.Configured = true;

log4net not appending to azure blob

I want to append logs to azure blob storage. So i added nuget package called log4net.appender.azure.
Wrote following in app.config
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="AzureBlobAppender" />
</root>
<appender name="AzureBlobAppender" type="log4net.Appender.AzureBlobAppender, log4net.Appender.Azure">
<param name="ContainerName" value="myConatiner"/>
<param name="DirectoryName" value="myFolder/logs.txt"/>
<param name="ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=XXXX;AccountKey=Ngqa/KvLxL4zpxdPDv8Opm29JCOXTJuJsF8FrzFQZpWCOcoFm1EI+mvFu+7AJvaWEU3jDffYrf4rGOKPJu/ObA==;EndpointSuffix=core.windows.net" />
<param name="AsText" value="true" />
</appender>
this is how i defined connection string
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
then i wrote below in class file
log4net.ILog log =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
log.Info("I am being tested");
but when i run program i dont see anything logged in azure blob. What am i missing?
Looks to me like your configuration is wrong. According to the examples in the README...
<appender name="AzureBlobAppender" type="log4net.Appender.AzureBlobAppender, log4net.Appender.Azure">
<param name="ContainerName" value="testloggingblob"/>
<param name="DirectoryName" value="logs"/>
<!-- You can either specify a connection string or use the ConnectionStringName property instead -->
<param name="ConnectionString" value="UseDevelopmentStorage=true"/>
<!--<param name="ConnectionStringName" value="GlobalConfigurationString" />-->
<param name="AsText" value="true" />
</appender>
myContainer should be ContainerName
MyFolder should be DirectoryName
AzureBlobConnectionString should be ConnectionStringName
Try following steps and proceed to next only if previous one is fine.
You need to check if the Logger property in debugger mode and examine the ConfigurationMessages property to see if any errors are reported.
First try adding a file appender to see if log4net is working fine or not.
<appender name="fileAppender" type="log4net.Appender.RollingFileAppender">
<file value="log-file.txt" />
<appendToFile value="true" />
<maximumFileSize value="1000KB" />
<maxSizeRollBackups value="10" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger - %message%newline" />
</layout>
</appender>
With this, you can go to Kudo in Azure portal's webapp and see the physical file being written atleast.
Second, see if you are initializing the config properly with
log4net.Config.XmlConfigurator.Configure();
Third, check if the region of the storage account is the same as the web app or not.

MemoryAppender.GetEvents : Events Null

can anyone explain why I am not getting any events from the memoryAppender?
In other words, the events variable is Null.
public void Log(string message, Category category, Priority priority)
{
MemoryAppender memoryAppender = new MemoryAppender();
log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(#"C:\Users\Username\Documents\GitHub\MassSpecStudio\MassSpecStudio 2.0\source\MassSpecStudio\Core\app.config"));
bool log4netIsConfigured = log4net.LogManager.GetRepository().Configured;
switch(category)
{
case Category.Debug:
log.Debug(message);
break;
case Category.Warn:
log.Warn(message);
break;
case Category.Exception:
log.Error(message);
break;
case Category.Info:
log.Info(message);
break;
}
var events = memoryAppender.GetEvents(); // events is Null.
int esize = events.Length;
foreach (LoggingEvent loggingEvent in events)
{
LogItem logItem = new LogItem(loggingEvent.TimeStamp, loggingEvent.Level, loggingEvent.RenderedMessage);
LogItems.Add(logItem);
}
}
*UPDATE:
I have provided the following in the config file. How isn't my MemoryAppender hooking up into the logger properly?
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="C:\temp\Logger_.txt" />
<param name="AppendToFile" value="true"/>
<param name="RollingStyle" value="Once"/>
<param name="RollingStyle" value="Date"/>
<datePattern value="yyyy-MM-dd_HHmmss" />
<lockingModle type="log4net.Appender.FileAppender+MinimalLock"/>
<preserveLogFileNameExtension value="true"/>
<maxSizeRollBackups value="30" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="MemoryAppender" type="log4net.Appender.MemoryAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
<appender-ref ref="MemoryAppender" />
</root>
Log4Net works on 'Loggers' and 'Appenders'. You need to connect an Appender to a Logger in order for it to receive the messages being logged. This can be done in code or in a config file. You appear to be doing a bit of both, by initialising log4net using the config file, then creating a MemoryAppender in code.
Problems:
You're initialising log4net every time this function is called. You only need to do this once per process.
You have created a new instance of MemoryAppender with every call to the function, when you probable only want one.
THE MAIN ISSUE: the MemoryAppender is not hooked up to the logger. If you definitely want to do this in code, see here: Programmatically adding and removing log appenders in log4net
Updated to answer your revised question:
To get the events from the single MemoryAppender in your .config file:
var appender = log4net.LogManager.GetRepository().GetAppenders().OfType<log4net.Appender.MemoryAppender>().Single(); // Assumes exactly one MemoryAppender
var events = appender.GetEvents();

Configure Log4Net for the full project

I have the following code which Logs everything very nicely. Am I going to have to add this to the constructor of all of my classes?
Is there anyway to configure it for the full project? I suppose I could pass log around to all of the classes but that seams like a messy solution.
class Program
{
/// <summary>
/// Application start
/// <param name="settings"></param>
/// <returns></returns>
static int Main(string[] args)
{
string logFile = string.Format(#"{0}\Cloud\Cloud.Log", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData));
log4net.GlobalContext.Properties["LogFileName"] = logFile;
log4net.Config.XmlConfigurator.Configure();
ILog log = log4net.LogManager.GetLogger(typeof(Program));
log.Info("Console Applicatin Start");
}
}
How should can I configure Log4Net for the full solution?
I didn't think it was relevant to the question but here is my my App.config:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="%property{LogFileName}.txt" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="250KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="RollingFileAppender" />
</root>
</log4net>
I usually use a wrapper class. Something like:
public class Logger
{
private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(typeof(Logger));
static Logger()
{
log4net.Config.XmlConfigurator.Configure();
}
public void Error(string format, params object[] args)
{
_log.Error(string.Format(format, args));
}
//...
}
And then just instantiate that where I need it. Like:
private Logger _log = new Logger();
And the use it like:
_log.Error("Something went wrong, Exception: {0}, More parameters: {1}",
e.Message, "Another parameter");
The
log4net.Config.XmlConfigurator.Configure();
Should configure log4net according to your app.config.
Edit:
I thought I'd post my log4net config, in case you want to try one that has been confirmed working. It sets up a file logger and a console logger (useful to see logging i development environment):
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="DebugFileLogger" />
</root>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger: %message%newline" />
</layout>
</appender>
<appender name="DebugFileLogger" type="log4net.Appender.RollingFileAppender">
<param name="File" value="Logs/DebugLog.log" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="20MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d%t %m%n" />
</layout>
</appender>
</log4net>
One of the great features of log4net is the ability to configure filtering and processing differently depending on the logger name: any solution that wraps the logger in a wrapper, singleton or static object loses that as all logs will use the same logger name - and the useful ability to immediately see what class a log entry came from.
All you need in a class where you want to log is a field, eg:
private static ILog Log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
which sets the logger name to the name of the type it's contained in.
The solution I see most often, is to create a LogManager class, and a property in each class to fetch an instance of a logger, so this is the only lines of code needed in each class to get a logger:
private ILog _log;
public ILog Log
{
get { return _log ?? (_log = LogManager.GetLogger(LogName.WebServices)); }
}
The LogName.WebServices is an Enum value, and should be extended with more values, so you are able to filter your logs even more.

log4net GenericFailure. Unable to acquire lock on file

I am trying to configure log4net for the first time, I'm certain that I have everything configured properly, however, after getting empty log files I turned on the log4net debugger. I am now continually seeing the following error:
log4net:ERROR [RollingFileAppender] ErrorCode: GenericFailure. Unable to acquire lock on
file "file path\file name" The process cannot access the file "file path\file name" because
it is being used by another process.
I currently have log4net configured through my Web.config file thusly:
<log4net debug="true">
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs\\TransferPicturesApplicationLog.txt"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="5"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ERROR"/>
<param name="LevelMax" value="DEBUG"/>
</filter>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline"/>
</layout>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
</appender>
<root>
<level value="INFO"/>
<appender-ref ref="RollingFileAppender"/>
</root>
<logger>
<level value="DEBUG"/>
<appender-ref ref="RollingFileAppender"/>
</logger>
</log4net>
Help!!!
I also got this error because I specified the name of a directory as opposed to a file in the appender, the error message is cryptic:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<file value="C:\DirectoryNameHere\MyBad" />
My guess would be that you have multiple copies of the appender across different assemblies trying to access this path, so they all attempt to take hold of the appropriate log file at the same time. However, to be sure, I would recommend you use Unlocker to make sure your file isn't being held by something else you don't expect. If you are having multiple instances of this appender trying to open the same file, your problem would be that you are not using the appender correctly; if the appender is being used across assemblies then the two invocations of it will not play nicely with one another, leading to an already in use error like this. If this is the case, refactoring is likely your only option.
I got this problem when I tried to write the logfile in a subdirectory (same as #PeteN). When using the correct syntax this doens't occur anymore, in de appender config set:
<appender name="TestLogAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value=".\logDirectory\LogFileName.csv" />
<....>
hope this helps

Categories