Log assembly version that contains the code that logs with log4net? - log4net

Is there a way to add the assembly version of assembly that is emiting the logging event?

I don't think there is anything built-in that will do this, so you will probably need to create your own custom PatternConverter and PatternLayout (which is very easy)
The biggest problem is speed, since this will require log4net to generate the caller information and (in their own words)
WARNING Generating the caller class information is slow. Thus, its use should be avoided unless execution speed is not an issue.
If speed is not an issue, then something like this should work.
public class AssemblyVersionPatternConverter : log4net.Util.PatternConverter
{
protected override void Convert(TextWriter writer, object state)
{
var le = state as log4net.Core.LoggingEvent;
if (le != null)
writer.Write(GetAssemblyVersion(le.LocationInformation.ClassName));
}
}
public class AssemblyVersionPatternLayout : log4net.Layout.PatternLayout
{
public AssemblyVersionPatternLayout()
{
AddConverter( new ConverterInfo
{
Name = "AssemblyVersion",
Type = typeof(AssemblyVersionPatternConverter)
});
}
}
Apart from writing the GetAssemblyVersion method, that is all it takes to implement your own customer PatternConverter and PatternLayout.
You then have to change your log4net configuration file to tell it to use your custom routines.
eg if you have something like
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%5level %message%newline" />
</layout>
you change it to something like
<layout type="MyNameSpace.AssemblyVersionPatternLayout, MyDllname">
<conversionPattern value="%5level %AssemblyVersion %message%newline" />
</layout>
The GetAssemblyVersion method, could be something like the following
private string GetAssemblyVersion(string className)
{
Type type = Type.GetType(className);
System.Reflection.Assembly assembly ;
if (type != null)
assembly = type.Assembly ;
else
assembly = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(a => a.GetType(className) != null);
if (assembly == null)
return String.Empty;
return assembly.FullName.Split(',')[1].Replace("Version=", "");
}
Note, it is not recommended to use assembly.GetName().Version as this can throw a SecurityException if the account has very local privileges.

Check this out: Log Event Context http://www.beefycode.com/post/Log4Net-Tutorial-pt-6-Log-Event-Context.aspx
At start set ThreadContext with Properties you need:
class Program
{
private static log4net.ILog Log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );
 
static void Main( string[] args )
{
            log4net.Config.XmlConfigurator.Configure();
            log4net.ThreadContext.Properties["myContext"] = "Logging from Main";
            log4net.ThreadContext.Properties["AssemblyVersion"] = GetType().Assembly.GetName().Version;
            Log.Info( "this is an info message" );
            Console.ReadLine();
        }
    }
}
Use this Properties in your appender conversionPattern:
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%logger (%property{myContext}) (%property{AssemblyVersion}) [%level]- %message%newline" />
</layout>
</appender>

Related

log4j2 Custom Filter Plugin is not working on websphere . Getting error : RollingFile contains an invalid element or attribute "UALevelMatchFilter

On Websphere 8.5x
I am getting following error
RollingFile contains an invalid element or attribute "UALevelMatchFilter"
**Configuration XML is as given below and it contains the package attribute which has the class **
<Configuration packages="com.unica.afc.logger" monitorInterval="60" status="trace">
<!-- ======================================================== -->
<!-- Console Log Appender -->
<!-- -->
<!-- ======================================================== -->
<Console name="CONSOLE_LOG" target="SYSTEM_OUT">
<PatternLayout pattern="%-5p %F.%M:%L: %m%n"/>
<!-- only the levels (comma separated) specified in LevelToMatch will be logged -->.
<UALevelMatchFilter levelToMatch="DEBUG, WARN,INFO, ERROR, FATAL" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<!-- =================== -->
<!-- System Log Appender -->
<!-- =================== -->
<RollingFile name="SYS_LOG" fileName="${sys:plan.home}/logs/system.log"
filePattern="${sys:plan.home}/logs/system.log.%d{yyyy-MM-dd}"
immediateFlush="false" append="true" >
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%X{user}] %-5p %F.%M:%L: %m%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<UALevelMatchFilter levelToMatch="DEBUG, WARN,INFO, ERROR, FATAL" onMatch="ACCEPT" onMismatch="DENY"/>
</RollingFile>
.
.
.
The implementation of class is given below
#Plugin(name = "UALevelMatchFilter", category = "Core", elementType = "filter", printObject = true)
public class UALevelMatchFilter extends AbstractFilter
{
private Vector<Level> levelToMatch;
private UALevelMatchFilter(Vector<Level> levels, Result onMatch, Result onMismatch) {
super(onMatch, onMismatch);
this.levelToMatch = levels;
}
public Result filter(Logger logger, Level level, Marker marker, String msg, Object[] params) {
return filter(level);
}
public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
return filter(level);
}
public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
return filter(level);
}
#Override
public Result filter(LogEvent event) {
return filter(event.getLevel());
}
private Result filter(Level level) {
//return level.isMoreSpecificThan(this.level) ? onMatch : onMismatch;
for(Level currentLevel:levelToMatch){
if(currentLevel.equals(level)){
return onMatch;
}
}
return onMismatch;
}
/**
* Create a ThresholdFilter.
* #param loggerLevel The log Level.
* #param match The action to take on a match.
* #param mismatch The action to take on a mismatch.
* #return The created ThresholdFilter.
*/
#PluginFactory
public static UALevelMatchFilter createFilter(#PluginAttribute (value = "LevelToMatch", defaultString = "DEBUG") String levelStr,
#PluginAttribute(value = "onMatch", defaultString = "NEUTRAL") Result onMatch,
#PluginAttribute(value = "onMismatch", defaultString = "DENY") Result onMismatch) {
String [] levelStrArr = levelStr.split(",");
Vector<Level> levels = new Vector<>();
for (int i = 0; i < levelStrArr.length; i++) {
levels.add(Level.toLevel(levelStrArr[i].trim()));
}
return new UALevelMatchFilter(levels, onMatch, onMismatch);
}
/**
* parameter level format: "DEBUG, INFO, SQL, ..."
*/
public void setLevelToMatch(String levels)
{
//System.out.println("UAPLevelMatchFilter.setLevelToMatch: setting levels to >>> " + levels);
if (levels != null && levels.trim().length() > 0)
{
if (levelToMatch == null) levelToMatch = new Vector();
StringTokenizer st = new StringTokenizer(levels,",");
String level = null;
while (st.hasMoreTokens())
{
level = st.nextToken();
//System.out.println("^^^^^^^ level ==== " + level);
levelToMatch.add(Level.toLevel(level.trim())); //TODO what about custom levels
}
}
}
Also, the class org.apache.logging.log4j.core.config.plugins.util.ResolveUtil has some debug statements but those are not getting printed even after adding status="trace"
Also, the console shows DEBUG Took 0.001806 seconds to load 0 plugins from package com.unica.afc.logger
Could you let me know if I am missing something
The classes in mentioned in the package are present in the product's jar file which is under WEB-INF/lib
Using the packages element is not recommended and should only be used as a last resort. Instead, you should ensure that annotation processing is enabled so that Log4j can add your plugin to a Log4jPlugins.dat file. That mechanism is much faster than resorting to classpath scanning at runtime.
If you add verbose="true" to your Configuration element you should see the output of scanning for plugins. You don't see the output of ResolverUtil because the log level isn't set until after plugin scanning.

How to make FluentFTP to log to log4net log?

I couldn't find any information on how to do it. Basically FluentFTP is using System.Diagnostics to log their messages.
FluentFtp expose the following static method:
FtpTrace.AddListener(TraceListener listener);
However I don't know if there is any way to implement (or use existing implementation, which?) TraceListener in the way it relays everything to log4net engine.
Any hints or ideas?
Thanks, Radek
You can attach a listener to the OnLogEvent method that FluentFTP exposes.
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public static void UploadFTP(FileInfo localFile, string remoteFileLocation, string remoteServer, NetworkCredential credentials)
{
FtpClient client = new FtpClient(remoteServer, credentials);
client.RetryAttempts = 3;
client.OnLogEvent = OnFTPLogEvent;
client.Connect();
if (!client.UploadFile(localFile.FullName, remoteFileLocation, FtpExists.Overwrite, false, FtpVerify.Retry | FtpVerify.Throw))
{
throw new Exception($"Could not Upload File {localFile.Name}. See Logs for more information");
}
}
private static void OnFTPLogEvent(FtpTraceLevel ftpTraceLevel, string logMessage)
{
switch (ftpTraceLevel)
{
case FtpTraceLevel.Error:
Log.Error(logMessage);
break;
case FtpTraceLevel.Verbose:
Log.Debug(logMessage);
break;
case FtpTraceLevel.Warn:
Log.Warn(logMessage);
break;
case FtpTraceLevel.Info:
default:
Log.Info(logMessage);
break;
}
}
The method OnFTPLogEvent will be called every-time the OnLogEvent action will be called allowing you to extend any logging you have already built into your application.
Basically FluentFTP is using System.Diagnostics.TraceListener so in order to make it logging to your log4net log you need to write your own simple class that would redirect logs to log4net logger. Like the following:
using System.Diagnostics;
using log4net;
namespace YourApp.Logging
{
public class Log4NetTraceListener : TraceListener
{
private readonly ILog _log;
public Log4NetTraceListener(string provider)
{
_log = LogManager.GetLogger(provider);
}
public override void Write(string message)
{
if(_log == null)
return;
if(!string.IsNullOrWhiteSpace(message))
_log.Info(message);
}
public override void WriteLine(string message)
{
if(_log == null)
return;
if (!string.IsNullOrWhiteSpace(message))
_log.Info(message);
}
}
}
Then, in your app.config file add the following entry:
<system.diagnostics>
<trace autoflush="true"></trace>
<sources>
<source name="FluentFTP">
<listeners>
<clear />
<add name="FluentLog" />
</listeners>
</source>
</sources>
<sharedListeners>
<add name="FluentLog" type="YourApp.Logging.Log4NetTraceListener, YourApp" initializeData="FluentLog" />
</sharedListeners>
</system.diagnostics>
That should enable FluentFtp logs and merge it with your application log4net log.

Log4net Appender to Azure Document Db

I am trying to write a custom appender for Log4net by referring the article http://korrozia.blogspot.in/2016/11/how-to-store-log4net-messages-to-azure.html. Documents are getting created in Azure Doc DB, but the log message is not appearing in document. Below is my custom appender code
public class DocumentDbAppender: AppenderSkeleton
{
private DocumentClient client;
public string DatabaseId
{
get;
set;
}
public string CollectionId
{
get;
set;
}
public string EndpointUrl
{
get;
set;
}
public string AuthKey
{
get;
set;
}
protected override void Append(LoggingEvent loggingEvent)
{
try
{
using (client = new DocumentClient(new Uri(EndpointUrl), AuthKey))
{
var database = RetrieveOrCreateDatabaseAsync(DatabaseId).Result;
var collection = RetrieveOrCreateCollectionAsync(database.SelfLink,
CollectionId).Result;
var document = CreateDocumentAsync(client, collection.SelfLink, loggingEvent).Result;
}
}
catch (DocumentClientException de)
{
Exception baseException = de.GetBaseException();
Debug.Print("Status code {0} error occurred: {1}, Message: {2}", de.StatusCode,
de.Message, baseException.Message);
}
catch (Exception e)
{
Exception baseException = e.GetBaseException();
Debug.Print("Error: {0}, Message: {1}", e.Message, baseException.Message);
}
}
private async Task<Database> RetrieveOrCreateDatabaseAsync(string id)
{
// Try to retrieve the database (Microsoft.Azure.Documents.Database) whose Id is equal to databaseId
var database = client.CreateDatabaseQuery().Where(db => db.Id == DatabaseId).
AsEnumerable().FirstOrDefault();
// If the previous call didn't return a Database, it is necessary to create it
if (database == null)
{
database = await client.CreateDatabaseAsync(new Database { Id = DatabaseId }).ConfigureAwait(false);
Debug.Print("Created Database: id - {0} and selfLink - {1}", database.Id, database.SelfLink);
}
return database;
}
private async Task<DocumentCollection> RetrieveOrCreateCollectionAsync(string databaseSelfLink,
string id)
{
// Try to retrieve the collection (Microsoft.Azure.Documents.DocumentCollection) whose
// Id is equal to collectionId
var collection = client.CreateDocumentCollectionQuery(databaseSelfLink).
Where(c => c.Id == id).ToArray().FirstOrDefault();
// If the previous call didn't return a Collection, it is necessary to create it
if (collection == null)
{
collection = await client.CreateDocumentCollectionAsync(databaseSelfLink,
new DocumentCollection { Id = id }).ConfigureAwait(false);
}
return collection;
}
private async Task<Document> CreateDocumentAsync(DocumentClient client,
string collectionSelfLink, LoggingEvent loggingEvent)
{
var doc = await client.CreateDocumentAsync(collectionSelfLink,
loggingEvent).ConfigureAwait(false);
return doc;
}
}
My Web.config looks like below
<log4net>
<!--This is for local testing. Comment out this appender when moved to azure cloud-->
<!--<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value=".\\Logs\\WebApi.log"/>
<appendToFile value="true"/>
<rollingStyle value="Size"/>
<maxSizeRollBackups value="10"/>
<maximumFileSize value="10MB"/>
<staticLogFileName value="true"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.25M - %m%n"/>
</layout>
</appender>-->
<appender name="DocumentDbAppender"
type="MyApp.Infra.Logger.DocumentDbAppender, MyApp.Infra.Logger">
<DatabaseId>Diagnostics</DatabaseId>
<CollectionId>Logs</CollectionId>
<EndpointUrl>https://mydb.documents.azure.com:443/</EndpointUrl>
<AuthKey>UB0w==</AuthKey>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5p %d %5rms %-22.22c{1} %-18.25M - %m%n"/>
<!--<conversionPattern value="%m"/>-->
</layout>
</appender>
I am creating logger instance as below
log4net.Util.LogLog.InternalDebugging = true;
log4net.Config.XmlConfigurator.Configure();
log4netLogger = log4net.LogManager.GetLogger(
System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
If I simply writes a info message, it looks like below in Document DB
{
"$type": "log4net.Core.LoggingEvent, log4net",
"LoggerName": MyApp.Infra.Logger.Log4NetLogger",
"Level": {
"$type": "log4net.Core.Level, log4net",
"Name": "INFO",
"Value": 40000,
"DisplayName": "INFO"
},
"Message": null,
"ThreadName": null,
"TimeStamp": "2017-03-20T15:57:17.7133358+05:30",
"LocationInfo": null,
"UserName": null,
"ExceptionString": null,
"Properties": null,
"Domain": null,
"Identity": null,
"id": "23071629-d896-4812-9a87-89871d969780"
}
Please note that Message is null. I am not sure why it is happening. If I use a Rolling File Appender, I am able to get the message without any issue.
From the link that you provided, we could find it overrides Append method and create a document for LoggingEvent object by using the CreateDocumentAsync method in Append method. Please try to check if Exception object ex is null when you write info message.
LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType).Info("info mes", ex);
Besides, please set breakpoint inside Append(LoggingEvent loggingEvent) method to watch the properties of loggingEvent before you call CreateDocumentAsync method to create the document.

Create multiple Logfiles with dynamic Names with log4net

I'm using log4net in a Windows Service. This Service processes some RFID Reader. Currently we are logging all tasks of all Reader in one Logfile. This works fine.
But now I want to log the tasks of each Reader in a separate File. The Readers are identified by their IP Address. So I want to take the IP Address as part of the Filename.
The option in log4net to create dynamic file appenders seems not to fit for me, because I would have to manage the assignment from Reader to log file, each time I write a log.
Is there an appropriate way to do this in log4net, or is it not possible?
In my Logclass I used a Dictionary<string, ILog> for my Loggers. I've overloaded methods, either they use the Default-Logger or they get the Key for the Dictionary to use the specified Logger.
public static class Log
{
private static readonly Dictionary<string, ILog> loggers = new Dictionary<string, ILog>();
static Log()
{
XmlConfigurator.Configure();
}
public static void Debug(string message)
{
Debug(Logger.Default, message);
}
public static void Debug(string readerIp, string message)
{
GetLoggerInternal(readerIp).Debug(message);
}
private static ILog GetLoggerInternal(string logger)
{
if (!loggers.ContainsKey(logger))
{
var appender = CreateRollingFileAppender(logger);
appender.ActivateOptions();
loggers.Add(logger, LogManager.GetLogger(logger));
((log4net.Repository.Hierarchy.Logger)loggers[logger].Logger).AddAppender(appender);
}
return loggers[logger];
}
private static RollingFileAppender CreateRollingFileAppender(string readingPointIp)
{
var layout = new PatternLayout
{
ConversionPattern = "%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"
};
layout.ActivateOptions();
return new RollingFileAppender
{
Name = readingPointIp,
AppendToFile = true,
DatePattern = "yyyyMMdd",
MaximumFileSize = "1MB",
MaxSizeRollBackups = 10,
RollingStyle = RollingFileAppender.RollingMode.Composite,
File = $"..\\Log\\{readingPointIp}_log.txt",
Layout = layout
};
}
}
It is important to call the .ActivateOptions(); methods, they instantiate the Appender and Layout Classes. I use LogManager.GetLogger to create a new Logger. To add the appender I've to cast the logger, to use AddAppender.
Now I just have to call Log.Debug(readingPoint.IpAddress, "Some readingpoint specific log message."); and I've this message in a file, with the IP Address in it's name.

Unable to filter on class name (in NLog.config) when using anotar catel nlog logging

I am using anotar catel fody for logging in my application.
In NLog.config I want to use different levels for certain classes. Example config
<logger name="SpaceA.*"
minlevel="Info"
writeTo="file"
final="true" />
<logger name="*"
minlevel="Debug"
writeTo="file" />
I have created a NLogListener class which derives from catel's LogListenerBase.
public class NLogListener : LogListenerBase
{
private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();
protected override void Debug(ILog log, string message, object extraData)
{
Log.Debug(message);
}
protected override void Info(ILog log, string message, object extraData)
{
Log.Info(message);
}
protected override void Warning(ILog log, string message, object extraData)
{
Log.Warn(message);
}
protected override void Error(ILog log, string message, object extraData)
{
Log.Error(message);
}
#endregion Methods
}
In my code I use Catel Anotar Fody:
LogTo.Debug("Starting something...");
Now no matter where I use the logging, it is all being displayed as coming from the namespace where I have defined the LogListerer.
What am I doing wrong and ergo do I have to change to be able to filter the NLog on class names like it normally should?
The problem is that you get the current class logger in the LogListener:
private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();
That way, you always log to the NLogListener type. What you should do is get the right logger type for each entry:
protected override void Debug(ILog log, string message, object extraData)
{
var nlog = NLog.LogManager.GetClassLogger(log.TargetType);
nlog.Debug(message);
}

Resources