Create multiple Logfiles with dynamic Names with log4net - 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.

Related

How to migrate Log4j Filter to Log4j 2

I got a Java Webapplication that starts an asynchron server side "job".
The application creates a directory for each job and logs in a file in this directory.
My implementation with log4j is:
import org.apache.log4j.*;
public class ThreadLogger {
String sThreadName;
String sLogfilePath;
RollingFileAppender rfaJob;
PatternLayout plJobLog;
public ThreadLogger(){}
public void start(String sThreadId, String sLogfilePath){
this.sThreadName = sThreadId;
this.sLogfilePath = sLogfilePath;
// Create Logfilter and LogAppender for thread based logging
ThreadLoggingFilter ThreadLogFilter = new ThreadLoggingFilter(this.sThreadName);
plJobLog = new PatternLayout("[%x - %t][%d / %p / %c] - %m%n");
this.rfaJob = new RollingFileAppender();
this.rfaJob.setLayout(plJobLog);
this.rfaJob.setFile(sLogfilePath);
this.rfaJob.setEncoding("UTF-8");
this.rfaJob.activateOptions();
this.rfaJob.setMaxBackupIndex(9);
this.rfaJob.setMaxFileSize("10MB");
this.rfaJob.setThreshold(Level.ALL);
this.rfaJob.addFilter(ThreadLogFilter);
Logger.getRootLogger().addAppender(this.rfaJob);
}
public void stop(){
Logger.getRootLogger().removeAppender(this.rfaJob);
this.rfaJob.close();
}
}
and the ThreadLogginFilter is:
import org.apache.log4j.spi.*;
public class ThreadLoggingFilter extends Filter {
String threadName;
public ThreadLoggingFilter(String _threadName){
this.threadName = _threadName;
}
#Override
public int decide(final LoggingEvent event) {
if (event.getNDC() != null && event.getNDC().equals(this.threadName)) {
return ACCEPT;
}
return DENY;
}
}
No I want to implement this with log4j2 and don´t know how to do the filter.
I know the documentation on https://logging.apache.org/log4j/2.0/manual/filters.html but I cant find a way to do this.
Is it possible to do this without a configuration?
Update: The goal...
My webapplication starts threads. Every thread produces several files to a folder that will be send to the user at the end. Within the folder there has to be the log file. So every thread need his own appender with a foldername.

Convert log level (log4net)

I want to convert an INFO log level to a WARN if the INFO log message contains an exception. Is there anyway I can accomplish this? (I am integrating log4net in a .NET application)
Unless you already wrap your logging calls, in which case you could intercept the messages before passing them to log4net, your best bet would be to create your own appenders which promote log events as appropriate. As each appender subclass would need the exact same code I've created an extension method which does the actual promotion:
public static class AppenderExtensions
{
public static LoggingEvent Promote(this LoggingEvent loggingEvent)
{
if (loggingEvent.Level != Level.Info
|| loggingEvent.ExceptionObject == null)
{
return loggingEvent;
}
var data = loggingEvent.GetLoggingEventData(FixFlags.All);
data.Level = Level.Warn;
return new LoggingEvent(data);
}
}
public class PromotingAdoNetAppender : AdoNetAppender
{
protected override void Append(LoggingEvent loggingEvent)
{
base.Append(loggingEvent.Promote());
}
}
public class PromotingRollingFileAppender : RollingFileAppender
{
protected override void Append(LoggingEvent loggingEvent)
{
base.Append(loggingEvent.Promote());
}
}
Then all you need to do is to declare these appender types in your config:
<appender name="DatabaseAppender"
type="Your.Namespace.Here.PromotingAdoNetAppender">
…

"Manually" rolling a log4net RollingFileAppender log file?

Rather than roll a log after a date/time or specified maximum size, I want to be able to call a method like "ResetLog" that copies my "log.txt" to "log.txt.1" and then clears log.txt.
I've tried to implement that by doing something like this with a FileAppender, rather than a RollingFileAppender:
var appenders = log4net.LogManager.GetRepository().GetAppenders();
foreach (var appender in appenders) {
var fa = appender as log4net.Appender.FileAppender;
if (fa != null) {
string logfile = fa.File;
fa.Close();
string new_file_path = CreateNextLogFile(logfile);
fa.File = new_file_path;
fa.ActivateOptions();
}
}
The file is closed and CreateNextLogFile() renames it. I then create a new log file and set the FileAppender to use it. However, I thought that ActivateOptions would go ahead and reconfigure the FileAppender with the desired settings. I've looked over the log4net documentation and don't see any other public methods that allow me to reopen the FileAppender after closing it. Can anyone recommend a way to implement the rollover? It would be nice if the RollingFileAppender had something like this, but I didn't see anything useful it its documentation, either.
If we look at the RollingFileAppender we can see that the mechanism for rolling over consists of closing the file, renaming existing files (optional) and opening it again:
// log4net.Appender.RollingFileAppender
protected void RollOverSize()
{
base.CloseFile();
// debug info removed
this.RollOverRenameFiles(this.File);
if (!this.m_staticLogFileName && this.m_countDirection >= 0)
{
this.m_curSizeRollBackups++;
}
this.SafeOpenFile(this.m_baseFileName, false);
}
Unfortunately the CloseFile/SafeOpenFile methods are protected which means you cannot access it from the outside (not easily). So your best bet would be to write an appender inheriting from RollingFileAppender, and to override the virtual AdjustFileBeforeAppend which is called before any logging event is added to the appender.
There you can decide what are the conditions of the roll if any must occur. An idea would be to create a static event that your custom rolling appender suscribes to: when the event is triggered the appender makes a note of it (rollBeforeNextAppend = true;). As soon as you try and log next entry the appender will roll.
public class CustomRollingAppender: RollingFileAppender
{
public CustomRollingAppender()
{
MyStaticCommandCenter.RollEvent += Roll;
}
public void Roll()
{
rollBeforeNextAppend = true;
}
public bool rollBeforeNextAppend {get; set;}
public override void AdjustFileBeforeAppend()
{
if (rollBeforeNextAppend) {
CloseFile();
RollOverRenameFiles(File);
SafeOpenFile(Filename, false);
rollBeforeNextAppend = false;
}
}
}

log4j custom log level and properties file

I have the following custom logging level
public class SAMLLoggingLevel extends Level{
public static final String SAMLLOGGING_LEVEL = "SAMLLOGGING";
public static final Level SAML_EXCEPTION_LOGGING = new SAMLLoggingLevel(FATAL_INT + 1, SAMLLOGGING_LEVEL, 7);
public static final Level SAML_DEBUG_LOGGING = new SAMLLoggingLevel(DEBUG_INT+1, SAMLLOGGING_LEVEL, 7);
public static final Level SAML_ERROR_LOGGING = new SAMLLoggingLevel(ERROR_INT+1, SAMLLOGGING_LEVEL, 7);
protected SAMLLoggingLevel(int level, String levelStr, int syslogEquivalent) {
super(level, levelStr, syslogEquivalent);
}
}
I try to log using the following
public static Logger logger = Logger.getLogger(xyz.class);
logger.log(SAMLLoggingLevel.SAML_DEBUG_LOGGING, "logger SAML_DEBUG_LOGGING");
logger.log(SAMLLoggingLevel.SAML_ERROR_LOGGING, "logger SAML_ERROR_LOGGING");
logger.log(SAMLLoggingLevel.SAML_EXCEPTION_LOGGING, "logger SAML_EXCEPTION_LOGGING");
my log4j.properteis file looks like this
log4j.logger.com.xyz=SAML_DEBUG_LOGGING, SAMLLoggingLevel
log4j.appender.SAMLLoggingLevel=org.apache.log4j.RollingFileAppender
log4j.appender.SAMLLoggingLevel.File=/SAML.log
log4j.appender.SAMLLoggingLevel.layout=org.apache.log4j.PatternLayout
log4j.appender.SAMLLoggingLevel.layout.ConversionPattern=%d{MMM dd yyyy HH:mm:ss,SSS zzz} %5p %c{1}:%L - %m%n
I get all the debub messages from all classes in com.xyz. I want to restrict only to the once that are logged using logger.log(SAMLLoggingLevel....)
Change you code from
log4j.logger.com.xyz=SAML_DEBUG_LOGGING, SAMLLoggingLevel
to
log4j.logger.com.xyz=SAML_DEBUG_LOGGING#com.yourpackagename.SAMLLoggingLevel, SAMLLoggingLevel

how do you make log4net output to current working directory?

Is it possible to have log4net put its log files relative to the current working directory instead of the directory where the application resides?
In other words, if I run ..\myapp.exe, I don't want the log files in ..\ I want them in .\
I ended up looking at the log4net source and determined I can implement my own appender that extends FileAppender and overrides the File property.
class CWDFileAppender : FileAppender
{
public override string File
{
set
{
base.File = Path.Combine(Directory.GetCurrentDirectory(), value);
}
}
}
I just use CWDFileAppender in my configuration.
Not possible from the config file, as per here. It may be possible if you are configuring it manually from inside your program though:
public static log4net.Appender.IAppender CreateFileAppender(string name,
string fileName)
{
log4net.Appender.FileAppender appender = new
log4net.Appender.FileAppender();
appender.Name = name;
appender.File = fileName;
appender.AppendToFile = true;
log4net.Layout.PatternLayout layout = new
log4net.Layout.PatternLayout();
layout.ConversionPattern = "%d [%t] %-5p %c [%x] - %m%n";
layout.ActivateOptions();
appender.Layout = layout;
appender.ActivateOptions();
return appender;
}
You can then associate it with the logger as follows:
AddAppender("Log4net.MainForm", CreateFileAppender("FileAppender",
Path.Combine(Directory.GetCurrentDirectory(), "foo.log")));

Resources