MemoryAppender.GetEvents : Events Null - c#

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();

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 - How to get calling method name when using wrapper

I have a log4net wrapper for logging error messages in web api application. It is working fine but i am not able to log calling method name. It only display's top level method name. Lets say, i have a method a of class A which calls method b of a class B and b logs error message. Log4net only displays Class A and method a but i want to display either full calling chain A-a-B-b or at least B-b
private static readonly ILog LoggerObject = LogManager.GetLogger("ErrorLog");
log4net config
<log4net>
<appender name="ErrorLog" type="log4net.Appender.RollingFileAppender">
<file value="LogBackUp2.log" />
<staticLogFileName value="false" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyy-MM-dd.'Err'" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%M %C] - %message%newline" />
</layout>
</appender>
<logger name="ErrorLog">
<maximumFileSize value="15MB" />
<appender-ref ref="ErrorLog" />
</logger>
</log4net>
If i use this then log4net doesn't create any log file.
LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType)
use %stacktrace{5} to show 5 levels of the stacktrace which lead to the call of the log method. Replace the 5 with the level you want to have. example:
<conversionPattern value="%date [%thread] %-5level %logger [%stacktrace{5}] - %message%newline" />
When you use a log4net wrapper you may want to apply this to get rid of the wrapper in the stacktrace

How to configure logging via log4net in an UWP App

I have an UWP app that uses some of my libraries.
Such libraries use log4net for logging purpose, and are shared across a number of projects, not only UWP.
I'd like to configure log4net via the usual confi section in the XML config file, but I cannot find a way to do this in an UWP project, since there isn't an app.config file.
Where should I put the following section?
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="log\mylog.log" />
<appendToFile value="true" />
<maximumFileSize value="2000KB" />
<maxSizeRollBackups value="20" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="Console" />
<appender-ref ref="RollingFile" />
</root>
</log4net>
Thank you!
To complete pfx answer, if you are using netStandard, you will have to use Configure overloads with an additional parameter log4net.Repository.ILoggerRepository.
I haven't been able to use the ConsoleAppender and switch to the DebugAppender.
You cannot use relative path in UWP with RollingFileAppender since log4net will not have permission to create file in the install location of your application. I think it could work with a full path but I have seen some permissions issues (you should activate debug mode of log4net for this).
Finally, I also made a Custom Appender which writes file in the Local Storage of your application. Here is the code which should be enhanced for production use.
namespace AppWithLog4net
{
public class LocalStorageFileAppender : log4net.Appender.TextWriterAppender
{
private Stream m_stream;
public LocalStorageFileAppender() : base() { }
protected override void PrepareWriter()
{
IAsyncOperation<Windows.Storage.StorageFile> task = Windows.Storage.ApplicationData.Current.LocalCacheFolder.CreateFileAsync("localStorage.log",
Windows.Storage.CreationCollisionOption.GenerateUniqueName);
Windows.Storage.StorageFile file = task.GetAwaiter().GetResult();
m_stream = file.OpenStreamForWriteAsync().Result;
QuietWriter = new log4net.Util.QuietTextWriter(new StreamWriter(m_stream, Encoding.UTF8), ErrorHandler);
WriteHeader();
}
protected override void Reset()
{
m_stream.Dispose();
m_stream = null;
base.Reset();
}
}
}
With the following config file:
<log4net debug="true">
<appender name="Console" type="log4net.Appender.DebugAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<appender name="LocalStorageFile" type="AppWithLog4net.LocalStorageFileAppender, AppWithLog4net">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="Console" />
<appender-ref ref="LocalStorageFile" />
</root>
</log4net>
Because there is no App.config file, you are going to have to configure Log4net programmatically.
You can store the settings in a local file (or embedded resource) and read these at application startup; reference: Create and read a local file.
Log4net's XmlConfigurator class can accept these settings as a Stream, FileInfo or XmlElement via one of its Configure overloads.
log4net.Config.XmlConfigurator.Configure(XmlElement config);
log4net.Config.XmlConfigurator.Configure(Stream config);
log4net.Config.XmlConfigurator.Configure(FileInfo config);

log4net multiple loggers - properties

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}");

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.

Categories