Log4Net - how to properly use PropertyFilter - log4net

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>

Related

How to write log4net config into appsettings.json?

I have implemented log4net into .NET core 2.0, to log into a text file. As log4net have a config file, which is having XML configuration in it. So, I have created a separate file log4net.config to set its configuration and it is working fine. But I want to set its configuration into appsettings.json. Is it possible to write the log4net configuration into appsettings.json.
<appSettings>
<add key="IsLog" value="True" />
<add key="MaxThreads" value="3" />
</appSettings>
<log4net debug="false">
<root>
<level value="ALL" />
<appender-ref ref="RollingLogFileAppender" />
</root>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="./logs/logfile.txt" />
<appendToFile value="false" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="-1" />
<maximumFileSize value="50GB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
</log4net>
this is my XML configuration.
I have found this question in which it is mentioned that log4net don't support Json projects. Is it possible to write its configuration into appsettings.json.
Maybe it is not the right way but, anyhow I managed to use my log4net.config in appSettings.json. I am putting my answer here so it can help someone if they don't want to use an extra config file for there project.
So what I have done is like by converting my XML into JSON and used the JSON as a string on my appSettings.json after that I use the following code to read the string.
appSettings.json
{
"ConnectionStrings": {
"LoggerConfig": "Config string"
}
}
Json to XML Conversion using Newtonsoft.Json
string xmlElement = _configuration["ConnectionStrings:LoggerConfig"];
XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(xmlElement);
XmlConfigurator.Configure(logRepository, GenerateStreamFromString(doc.InnerXml));
This code is used to convert the JSON into XML but it will not provide the XML content as Element so, I used it as a stream.
For converting the string into a stream I used the following code.
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
and it works fine.
Here I used to convert first my XML to JSON and again JSON to XML to
use it as a log4net config, but if anyone wants then use XML
directly as a string, So it will reduce some code.
If you using Microsoft.Extensions.Logging.Log4Net.AspNetCore nuget package, there is a way keep log4net config in appsettings.json (but honestly not very usable).
You can write into appsettings.json (or appsettings.Environment.json for different environments) rules overriding nodes from log4net config file.
Documentation
Example of setting the logging level from appsettings.json.
appsettings.json:
{
"Log4NetCore": {
"PropertyOverrides": [
{
"XPath": "/log4net/root/level",
"Attributes": {
//"value": "ALL"
//"value": "DEBUG"
//"value": "INFO"
"value": "WARN"
//"value": "ERROR"
//"value": "FATAL"
//"value": "OFF"
}
}
]
}
}
You still needs log4net config file with nodes which you want override from appsettings.json:
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="DebugAppender" type="log4net.Appender.DebugAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
<file value="example.log" />
<appendToFile value="true" />
<maximumFileSize value="100KB" />
<maxSizeRollBackups value="2" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %5level %logger.%method [%line] - MESSAGE: %message%newline %exception" />
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="DebugAppender" />
<appender-ref ref="RollingFile" />
</root>
</log4net>
Registration in Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add these lines
var loggingOptions = this.Configuration.GetSection("Log4NetCore")
.Get<Log4NetProviderOptions>();
loggerFactory.AddLog4Net(loggingOptions);
app.UseMvc();
}
}

Change subject of SmtpAppender programmatically

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

Log4net custom json message in Console/FileAppender

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.

Can the log file path be changed on the fly (reguarly)

I've been experimenting with log4net as it appears that it does not support a particular logging feature my project needs. In short, I want to be able to control the log file path in code. This path will change constantly. The specific use case is a set of file system watchers, and a separate log file is required per instance.
Can this be done?
I want to be able to specify a variable that controls the logging destination in code.
For example:
var log4NetLogger1 = new Log4NetLogger("LogFileAppender1");
log4NetLogger1.InformationEvent("Log message 1");
var log4NetLogger2 = new Log4NetLogger("LogFileAppender2");
log4NetLogger2.InformationEvent("Log message 2");
In the above example, I am passing a string to the log4net wrapper, that matches an appender name in configuration. The idea is that the log4net wrapper changes the logging target by modifying the appender in use via GetLogger.
_log = log4net.LogManager.GetLogger(appenderName);
The result is that two different log files are created, but the log message is written to both. It appears that some aspect of the log4net configuration is global, and I am not able to change log path on the fly in this way.
Configuration file:
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
</configSections>
<log4net>
<root>
<appender-ref ref="LogFileAppender1"/>
<appender-ref ref="LogFileAppender2"/>
</root>
<appender name="LogFileAppender1" type="log4net.Appender.RollingFileAppender">
<threshold value="INFO"/>
<param name="File" value="C:\Tmp\EDP_TEST\LOG_DESTINATION\TestLog1.txt"/>
<param name="AppendToFile" value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n"/>
</layout>
</appender>
<appender name="LogFileAppender2" type="log4net.Appender.RollingFileAppender">
<threshold value="INFO"/>
<param name="File" value="C:\Tmp\EDP_TEST\LOG_DESTINATION\TestLog2.txt"/>
<param name="AppendToFile" value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n"/>
</layout>
</appender>
</log4net>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
Full wrapper class:
using System;
using log4net;
namespace Log4NetRunner
{
public enum LoggingLevel
{
Information,
Warning,
Error
}
public class Log4NetLogger
{
private readonly ILog _log;
public Log4NetLogger(Type type)
{
if (_log == null)
{
_log = log4net.LogManager.GetLogger(type);
}
}
public Log4NetLogger(string appenderName)
{
if (_log == null)
{
_log = log4net.LogManager.GetLogger(appenderName);
}
}
public void FatalErrorEvent(string messageText)
{
SendLog(messageText, LoggingLevel.Error);
}
public void WarningEvent(string messageText)
{
SendLog(messageText, LoggingLevel.Warning);
}
public void InformationEvent(string messageText)
{
SendLog(messageText, LoggingLevel.Information);
}
private void SendLog(string messageText, LoggingLevel logLevel)
{
ILog logger = _log;
switch (logLevel)
{
case LoggingLevel.Error:
logger.Error(messageText);
break;
case LoggingLevel.Warning:
logger.Warn(messageText);
break;
case LoggingLevel.Information:
logger.Info(messageText);
break;
default:
logger.Error("Unknown Logging level: " + messageText);
break;
}
}
}
}
Yes it can, you just need to use the loggers section of the config.
http://www.beefycode.com/post/Log4Net-Tutorial-pt-5-Using-Logger-Objects.aspx
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,Log4net"/>
</configSections>
<log4net>
<root>
<!--<appender-ref ref="LogFileAppender1"/>
<appender-ref ref="LogFileAppender2"/>-->
</root>
<logger name="Logger1">
<level value="ALL" />
<appender-ref ref="LogFileAppender1" />
</logger>
<logger name="Logger2">
<level value="ALL" />
<appender-ref ref="LogFileAppender2" />
</logger>
<appender name="LogFileAppender1" type="log4net.Appender.RollingFileAppender">
<threshold value="INFO"/>
<param name="File" value="C:\Tmp\EDP_TEST\LOG_DESTINATION\TestLog1.txt"/>
<param name="AppendToFile" value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n"/>
</layout>
</appender>
<appender name="LogFileAppender2" type="log4net.Appender.RollingFileAppender">
<threshold value="INFO"/>
<param name="File" value="C:\Tmp\EDP_TEST\LOG_DESTINATION\TestLog2.txt"/>
<param name="AppendToFile" value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%-5p%d{yyyy-MM-dd hh:mm:ss} – %m%n"/>
</layout>
</appender>
</log4net>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>

Log4Net Rolling File Appender Rolling issue

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

Resources