Customizing the body of the email using Log4net Smtp appender - c#

How do I customize the body of the email using Log4net Smtp appender? I wanted to add custom message to the body.

Given you are using an appender similar this
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="to#domain.com" />
<from value="from#domain.com" />
<subject value="test logging message" />
<smtpHost value="SMTPServer.domain.com" />
<bufferSize value="512" />
<lossy value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%message" />
</layout>
</appender>
You should be able to format the message using StringBuilder before logging:
var sb = new StringBuilder();
sb.Append("Header");
sb.Append(Environment.NewLine);
sb.Append("Message");
...
var msg = sb.ToString();
ILog log = //resolve ILog
log.Debug(msg);
More config samples here search for SmtpAppender

You can create your own appender and inherit it from SmtpAppender. There you can override such methods as SendEmail and so on.
class MySmtpAppender : SmtpAppender
{
protected override void SendEmail(string messageBody)
{
string newmessageBody = messageBody + "...";
base.SendEmail(newmessageBody);
}
}
<appender name="MySmtpAppender" type="YourLib.MySmtpAppender">
You can also add some extra properties to this class and you will be able to use them in your config file.

When you use an SMTP Appender the message body contains the log event,
Which is formatted by the layout you specify when you configure the appender.
The most used one is PatternLayout so whatever you put in this layout's conventionPattern property will go into the email message body.
so you should do something like this:
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="to#domain.com" />
<from value="from#domain.com" />
<subject value="test logging message" />
<smtpHost value="SMTPServer.domain.com" />
<bufferSize value="512" />
<lossy value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="Hello, we got an error in the app. here are the details: %newline%date [%thread] %-5level %logger [%property{NDC}] - %message%newline%newline%newline" />
</layout>
</appender>

You can customize at layout level for example by adding an header and/or a footer:
<layout type="log4net.Layout.PatternLayout">
<header value="[Header]
" />
<footer value="[Footer]
" />
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>

The body is controlled using a conversion pattern (like any other appender).
<appender name="EmailAppender" type="log4net.Appender.SmtpAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%n%n%d{yyyy-MM-dd HH:mm:ss} %5p %10u %m" />
</layout>
</appender>

Related

How to log timestamp in EST format using log4net configuration ignoring the Server Timezone it runs on

I have added my log4net.config setting at the end of my question.
In "MyLog.txt" file I want to log timestamp in EST format using just the config file.
It should replace the %date in config below. I have seen some random articles suggest something like %d{yyyy-MM-dd HH:mm:ss}{GMT-4} but that didnt work.
I also read that Log4j has something called EnhancedPatternLayout which seems to have such date options. But Log4net has nothing equivalent.
Please share if you have some solution to show EST dynamically in Log irrespective of the Server timezone it runs on.
<appender name="ApiLoggerAppender" type="log4net.Appender.RollingFileAppender">
<lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
<file value="C:\MyLog.txt"/>
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="-ddMMyyyy" />
<PreserveLogFileNameExtension value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>

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

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

Categories