I would like to send an email only after 100 ERRORS were written as a html table(That's the purpose of the SmtpExtendedAppender, iterate over all the messages that were saved) but I am not sure how to access them.
This is my app.config:
<appender name="SmtpAppender" type="log4net.Appender.SmtpExtendedAppender">
<authentication value="Basic" />
<password value="xxxxxx"/>
<username value="xxxxxxxx"/>
<from value="myemail#gmail.com" />
<to value="toemail#gmail.com" />
<smtpHost value="smtp.gmail.com" />
<isBodyHtml value="true" />
<bufferSize value="100" />
<EnableSsl value="true"/>
<subject value="test logging message" />
<lossy value="true" />
<evaluator type="log4net.Core.LevelEvaluator">
<threshold value="WARN"/>
</evaluator>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date{ABSOLUTE} [%logger]%newlineUsername: %property{username}%newline%level - %message%newline%exception" />
</layout>
</appender>
This is my console application:
static void Main(string[] args)
{
Console.WriteLine("hello world");
log.Error("This is my first error message");
log.Error("This is my second error message");
Console.ReadLine();
}
I am not fully understand how the log4net buffer size works, even though I gave it a value of 100, it sends me 2 separated emails every time I run my console application.
If I will run this application 50 times to get to 100 ERRORS, would it be possible to accumulate them and then send them in one email?
No that is not possible. The appender works not over multiple instances of your application. Every time you restart your application, all buffers are empty.
Related
I have an SmtpAppender like this:
<appender name="SmtpAppender" type="log4net.Appender.SmtpAppender">
<to value="MyEmail#example.org" />
<from value="SenderEmail#example.org" />
<subject type="log4net.Util.PatternString" value="This is a subject" />
<smtpHost value="smtp.host.value" />
<authentication value="Basic" />
<port value="587" />
<username value="Username" />
<password value="Password" />
<bufferSize value="1" />
<EnableSsl value="true"/>
<lossy value="false" />
<threshold value="ERROR" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%3thread] %-5level [%class].[%method] - %message%newline" />
</layout>
</appender>
I would like to change the subject of the log mail in my code to a string I would specify so it suits my needs better than a standard subject.
Right now I achieve it like this. First, I find the SmtpAppender by name:
public static IAppender FindAppenderByName(string name)
{
ILoggerRepository rootRep = LogManager.GetRepository();
foreach (IAppender iApp in rootRep.GetAppenders()) {
if (string.Compare(name, iApp.Name, true) == 0) {
return iApp;
}
}
return null;
}
and then I can change the subject like this:
IAppender appender = FindAppenderByName("SmtpAppender");
SmtpAppender smtpAppender = (SmtpAppender)appender;
smtpAppender.Subject = "An error occured trying to convert the values";
log.Error("Error message");
But this seems to be a bit too complicated since every time I send an error mail I first have to find the appender by name and I don't want to use a global IAppender object as well as that seems like bad practice to me.
Is there a way to use the user-specified error message as a subject or is there an extension method I could use?
My proposal is as follows :
Create a new class extending SmtpAppender
Override SendBuffer
Based on your LoggingEvent, dynamically change the base.Subject
public class CustomSmtpAppender : SmtpAppender
{
protected override void SendBuffer(LoggingEvent[] events)
{
PrepareSubject(events);
base.SendBuffer(events);
}
protected virtual void PrepareSubject(IEnumerable<LoggingEvent> events)
{
//here you can eval events and set base.Subject
}
}
I need to log with log4net into multiple targets (loggly, console, file). For loggly I need to log in JSON for some metadata to find logs. I am altering the messageobject for this by adding the metadata to the dynamic messageobject like this (example subject).
public void Info(string message)
{
log.Info(GetLogObject(message));
}
private object GetLogObject(string message, Exception ex = null)
{
dynamic obj = new ExpandoObject();
obj.message = message;
obj.exception = ex;
obj.subject = _configuration.SubjectId;
return obj;
}
app.config
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="LogglyAppender" />
</root>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date - %message%newline" />
</layout>
</appender>
<appender name="LogglyAppender" type="log4net.loggly.LogglyAppender, log4net-loggly">
<rootUrl value="https://logs-01.loggly.com/" />
<inputKey value="*" />
<tag value="AppTag" />
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Logs/rolling.log" />
<appendToFile value="true" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="10MB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level - %message%newline" />
</layout>
</appender>
Now the Console and FileAppenders are logging JSON as this:
2017-06-12 15:28:10,236 INFO - {[message, Will collect every 1 Hours, 0 Minutes and 0 Seconds], [exception, ], [subject, 11111111-1111-1111-1111-111111111111]}
2017-06-12 15:28:10,271 INFO - {[message, Collection Round started at 12.06.2017 13:28:10], [exception, ], [subject, 11111111-1111-1111-1111-111111111111]}
I want these appenders just to log the message itself (obj.message) to look like this:
2017-06-12 15:28:10,236 INFO - Will collect every 1 Hours, 0 Minutes and 0 Seconds
2017-06-12 15:28:10,271 INFO - Collection Round started at 12.06.2017 11:17:29
How can I do this?
Thank you very much :)
What would happen if you created your own type for the message object (instead of using a dynamic object), and override the ToString() method? I suspect, LogglyAppender would render the graph while the regular PatternLayout would resort to ToString(). I haven't tried this, just a hint. You could perhaps subclass the ExpandoObject not to loose that dynamicity.
Ok, here's the appender:
<appender name="DebugFileAppender" type="log4net.Appender.FileAppender">
<file value="debug.log" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<filter type="log4net.Filter.PropertyFilter">
<Key value="ApplicationName" />
<StringToMatch value="Test Application" />
</filter>
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="DEBUG" />
<levelMax value="DEBUG" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="Date:%date Thread:[%thread] Level:%-5level Logger:%logger - ApplicationName:%property{ApplicationName}; Message:%message%newline" />
</layout>
</appender>
Now, it seems this would check the ThreadContext.Properties["ApplicationName"] string and if it finds 'Test Application' it would log.
Well, it's logging everything, even if ApplicationName="DoNotLog".
Now, I will freely admit this may be due to the way I'm (attempting) to use Log4Net - I need to expose it to COM, so I've wrapped up a singleton Log4Net instance, and call this from COM:
public void LogDebug(LogCodeSource codeSource, LogExecutionSource execSource, object message, Exception exception = null)
{
// Add the thread context properties for appender filtering purposes
log4net.ThreadContext.Properties["ApplicationName"] = codeSource.ApplicationName;
log4net.ThreadContext.Properties["ComponentName"] = codeSource.ComponentName;
log4net.ThreadContext.Properties["MethodName"] = codeSource.MethodName;
log4net.ThreadContext.Properties["ClientMachineName"] = execSource.ClientMachineName;
log4net.ThreadContext.Properties["ServerMachineName"] = execSource.ServerMachineName;
log4net.ThreadContext.Properties["UserName"] = execSource.UserName;
if (exception == null)
{
LoggerContext.GetInstance.MainLogger.Debug(message);
}
else
{
LoggerContext.GetInstance.MainLogger.Debug(message, exception);
}
}
In my tests, if I call:
var logger = new MyLogger();
logger.LogDebug(new LogCodeSource { ApplicationName = "Test Application", ComponentName = "Test component", MethodName = "Test method" }, new LogExecutionSource { ClientMachineName = "Test client", ServerMachineName = "Test server", UserName = "Test user" }, "Test message");
logger.LogDebug(new LogCodeSource { ApplicationName = "DoNotLog", ComponentName = "Test component", MethodName = "Test method" }, new LogExecutionSource { ClientMachineName = "Test client", ServerMachineName = "Test server", UserName = "Test user" }, "Test message");
In root, I've tried:
<root>
<appender-ref ref="DebugFileAppender" />
</root>
As well as:
<logger name="MyLogger">
<level value="DEBUG" />
<appender-ref ref="DebugFileAppender" />
</logger>
Both logs are showing up in my debug.log file.
Any idea why the property filter isn't preventing DoNotLog from going to debug.log?
Here's what I'm seeing in the log:
Date:2015-12-31 11:11:00,928 Thread:[14] Level:DEBUG Logger:MyLogger - ApplicationName:Test Application; Message:Test message
Date:2015-12-31 11:11:00,942 Thread:[14] Level:DEBUG Logger:MyLogger - ApplicationName:DoNotLog; Message:Test message
What you are after is an AND filter since you want to log all message where ApplicationName matches a particular name AND the level is DEBUG. Unfortunately log4net doesn't ship AND filters out of the box however this SO answer details how to implement one.
Property filter are also available when using Microsoft.Extensions.Logging.Abstraction instead if log4Net directly.
Example:
using (_logger.BeginScope(new[] { new KeyValuePair<string, object>("YourPropertyKey", "YourPropertyValue") }))
{
_logger.LogDebug("My special log with context property set");
}
Then in the config an appender has to be setup for the property and key.
Note there is rexexToMatch in oder to stringToMacth if you want to allow multiple values to a key.
<log4net>
<root>
<level value="ALL" />
<appender-ref ref="property_logger" />
</root>
<appender name="property_logger" type="log4net.Appender.RollingFileAppender">
<file value="logs/my_service.log" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="-yyyyMMdd" />
<maxSizeRollBackups value="5" />
<maximumFileSize value="5MB" />
<preserveLogFileNameExtension value="true" />
<staticLogFileName value="false" />
<filter type="log4net.Filter.PropertyFilter">
<Key value="YourPropertyKey" />
<StringToMatch value="YourPropertyValue" />
</filter>
<filter type="log4net.Filter.DenyAllFilter" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%2thread] %-5level %.50logger - %message%newline" />
</layout>
</appender>
</log4net>
Can you point me to some log4net tutorial or samples that logs all unhandled exceptions occurring on my MVC Web Api when using OWIN?
I have a Startup class that configures Castle Windsor and OAuth and I'm not sure how application errors are handled for OWIN enabled applications.
I have tried putting the log4net code in the Application_Error event in global.asax but it never gets executed.
public class WebApiApplication : System.Web.HttpApplication {
protected void Application_Start() {
log4net.Config.XmlConfigurator.Configure();
}
protected void Application_Error(object sender,EventArgs e) {
Exception lastException = Server.GetLastError();
var logger = log4net.LogManager.GetLogger(typeof(WebApiApplication));
logger.Fatal(lastException);
}
}
Any ideas why this is?
EDIT: web.config with log4net configuration:
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net debug="true">
<logger name="SiteWorksAPI">
<level value="Debug" />
<appender-ref ref="EventLogAppender" />
</logger>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="C:\ProgramData\Site\SiteWorksAPILog.txt" />
<appendToFile value="true" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<immediateFlush value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="============================================================%nLogger = %logger.%nLocation = %location.%nUnique Token = %property{x-fcc-uniquetoken}.%nDate = %date{dd MMM yyyy - HH:mm:ss}.%nMessage = %message%n" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender">
<applicationName value="SiteWorks.API" />
<logname value="SiteWorks API Log" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="Date: %date{dd MMM yyyy - HH:mm:ss} %nMessage: %message %nIdentity: %identity %nUnique Token: %property{x-fcc-uniquetoken} %nChange Time Stamp: %property{x-fcc-changedatetime} %nOriginal Time Stamp: %property{x-fcc-originaltimestamp} %nOriginating System: %property{x-fcc-originatingsystem} %nOriginating User: %property{x-fcc-originatingusername} %nRequest Uri: %property{Request} %nResponse Headers: %property{ResponseHeaders}%nLogger: %logger %nThread: %thread %n%n%exception" />
</layout>
</appender>
</log4net>
You're not seeing any log events because you haven't defined a root logger in your config - the only logger you've defined is called SiteWorksAPI so any loggers you create with any other name - for example, the one named after typeof(WebApiApplication) - will not log anything. If you add a breakpoint on the logging statement you'll see that all log levels are disabled.
The appenders defined in a root logger are inherited by all named appenders (unless the child appenders are defined with additivity = false) - but child appenders set their own log level.
<root>
<level value="Debug" />
<appender-ref ref="EventLogAppender" />
</root>
Edit: there's also another thing to consider - your defined logger only uses the EventLogAppender - note that in order to log to the Event Log, the documentation states EventLogAppender will fail if you try to write using an event source that doesn't exist unless it is running with local administrator privileges but the event source needs to be created by an elevated process.
For testing purposes, you might want to add your FileAppender to the logger.
I have the following log4net config:
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="Logs/%date{yyyy-MM-dd} Service.log" />
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<rollingStyle value="Date"/>
<datePattern value="yyyy-MM-dd"/>
<maxSizeRollBackups value="100"/>
<maximumFileSize value="15MB"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger: %message%newline" />
</layout>
</appender>
Data will be logged constantly to the log file and rolls OK but I end up having rolled files like this:
2009-12-21 Service.log2009-12-22 (this is what it will write tonight)
2009-12-21 Service.log <-- this being the latest file
2009-12-21 Service.log2009-12-21 <-- last updated 23:59
I want the files to be like:
2009-12-21 Service.log
2009-12-22 Service.log
2009-12-23 Service.log
Get rid of the file name and the type in the file element:
<file value="Logs\" />
Then change your datePattern to (note: make sure you escape the letters in 'Service' appropriately, like the g in 'log' is a special format, so you need to escape it with the '\'):
<datePattern value="yyyy-MM-dd Service.lo\g"/>
I think the following should be what you need.
Add in the following element within your <appender />.
<staticLogFileName value="true" />
You can use the function below.
In this function first get file location that you set in webconfig and after that you can add any path that you want! (like Date, our Customer, or...)
WebConfig:
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C:\\t4\\"/>
<appendToFile value="true"/>
<rollingStyle value="Composite"/>
<datePattern value="_yyyy-MM-dd.lo'g'"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="1MB"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date User:%identity IP:%X{addr} Browser: %X{browser} Url: %X{url} [%thread] %-5level %c:%m%n"/>
</layout>
</appender>
Function:
public static void ChangeFileLocation(string _CustomerName,string _Project)
{
XmlConfigurator.Configure();
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy)LogManager.GetRepository();
foreach (IAppender a in h.Root.Appenders)
{
if (a is FileAppender)
{
FileAppender fa = (FileAppender)a;
string sNowDate= DateTime.Now.ToLongDateString();
// Programmatically set this to the desired location here
string FileLocationinWebConfig = fa.File;
string logFileLocation = FileLocationinWebConfig + _Project + "\\" + _CustomerName + "\\" + sNowDate + ".log";
fa.File = logFileLocation;
fa.ActivateOptions();
break;
}
}
}
and result like this: C:\Logs\TestProject\Customer1\Saturday, August 31, 2013.log