how do you make log4net output to current working directory? - log4net

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

Related

No logging with configuration made in code using latest version of slf4net (1.0.0)

I'm using the slf4net.log4net nuget package to handle logging in a project. Because it must be possible for the loglevel to change at runtime, I made the configuration in code. The issue is that this code works fine in slf4net.log4net version 0.1.32.1 but when I upgrade it to version 1.0.0, the logfile is created, but the logs are not present on the logfile. I've created a dummy project to show this issue. I do not see how I can add a zip file here, so I'll just post the code here. It is a console app in net framework 4.7.2;
class Program
{
private static string GetLoggingPath()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData,
Environment.SpecialFolderOption.DoNotVerify), "LoggingTesting");
Directory.CreateDirectory(path);
return path;
}
static void Main(string[] args)
{
var layout = new PatternLayout
{
ConversionPattern = "%d{ABSOLUTE}: %message %newline"
};
layout.ActivateOptions();
var fileAppender = new RollingFileAppender();
fileAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
fileAppender.Layout = layout;
var path = GetLoggingPath();
fileAppender.File = path + System.IO.Path.DirectorySeparatorChar + "LISlogging_.txt";
fileAppender.AppendToFile = true;
fileAppender.PreserveLogFileNameExtension = true;
fileAppender.StaticLogFileName = false;
fileAppender.DatePattern = "yyyy-MM-dd";
fileAppender.MaxSizeRollBackups = 10;
fileAppender.ActivateOptions();
ILoggerRepository repository = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly());
BasicConfigurator.Configure(repository, fileAppender);
var root = (repository as Hierarchy)?.Root;
if (root == null) return;
root.Level = log4net.Core.Level.All;
// Create log4net ILoggerFactory and set the resolver
var factory = new slf4net.log4net.Log4netLoggerFactory();
var resolver = new SimpleFactoryResolver(factory);
slf4net.LoggerFactory.SetFactoryResolver(resolver);
// trigger logging
var log = slf4net.LoggerFactory.GetLogger(typeof(Program));
log.Info("log this line");
}
}
public class SimpleFactoryResolver : IFactoryResolver
{
private readonly slf4net.ILoggerFactory _factory;
public SimpleFactoryResolver(slf4net.ILoggerFactory factory)
{
_factory = factory;
}
public slf4net.ILoggerFactory GetFactory()
{
return _factory;
}
}
This dummy project was created in .net framework, but I need this in a .net core project. That is why I need to version 1.0.0 .
I've also post this issue on the github page of slf4net (because it looks like a bug) : https://github.com/ef-labs/slf4net/issues/6
My main question for here on stack overflow is if there is a workaround so this can work with slf4net.log4net version 1.0.0
I've found a workaround for this. Maybe not the cleanest solution but it works. If anyone knows a cleaner solution please add it here.
When looking at the slf4net.log4net code I found out that when it tries to configure log4net it uses xml files or config files, which is a nightmare if you want to set the loglevel at runtime. You can pass a customconfigurator as parameter of the Log4netLoggerFactory . This customconfigurator needs to implement IXmlConfigurator.
The CustomConfigurator I've made accepts an IAppender and a loglevel (log4net.Core.Level). In the implementation of the Configure(ICollection(ILoggerRepository repository) method. I've set the root log level and Configured with the BasicConfigurator.
The CustomConfigurator looks like this:
public class CustomConfigurator: IXmlConfigurator
{
private readonly IAppender _appender;
private readonly log4net.Core.Level _logLevel;
public CustomConfigurator(IAppender appender, log4net.Core.Level logLevel)
{
_appender = appender;
_logLevel = logLevel;
}
public ICollection Configure(ILoggerRepository repository)
{
var root = (repository as Hierarchy)?.Root;
if (root != null)
{
root.Level = _logLevel;
}
return BasicConfigurator.Configure(repository, _appender);
}
public ICollection Configure(ILoggerRepository repository, XmlElement element)
{
return XmlConfigurator.Configure(repository, element);
}
public ICollection Configure(ILoggerRepository repository, FileInfo configFile)
{
return XmlConfigurator.Configure(repository, configFile);
}
public ICollection ConfigureAndWatch(ILoggerRepository repository, FileInfo configFile)
{
return XmlConfigurator.ConfigureAndWatch(repository, configFile);
}
}
Now you can create an appender in code like shown in the question (until fileappender.ActivateOptions) Then when constructing the log4netLoggerFactory you pass an instance of CustomConfigurator which takes the fileAppender and a loglevel as parameter.
var factory = new slf4net.log4net.Log4netLoggerFactory(new CustomConfigurator(fileAppender, Level.All));
var resolver = new SimpleFactoryResolver(factory);
slf4net.LoggerFactory.SetFactoryResolver(resolver);
This should work.

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.

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.

"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

Resources