How to use dynamic file name in Log4j.xml? - log4j

how to read property from properties file and use in log4j.xml if not found then use default path.
For an instance
<appender name="FILE" class="org.apache.log4j.FileAppender">
<param name="File" value="logs/${logfilename}.log" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d::[%t]::%-5p::%c::%x - %m%n" />
</layout>
</appender>
In above code snippet, I want to retrieve value of logfilename from properties file.If I mentioned logfilename=abc_log in property file then abc_log.log should be generated inside logs folder. If I am unable to find logfilename properties then by default location should be called for an instance /logs/default.log
could you please help in this, How I can achieve the above approach?

Below is a code for using Log4J for dynamically generate filename. It changes its name according to input file name and current date-time. (So helpful in case you run same file multiple times.) (Found this in another thread)
public class LogClass {
private static Logger log = Logger.getLogger(LogClass.class);
private static boolean initializationFlag = false;
private static String fileName;
private static void intializeLogger(){
log.setLevel(Level.DEBUG);
DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
Date date = new Date();
RollingFileAppender appender = new RollingFileAppender();
appender.setAppend(true);
appender.setMaxFileSize("1MB");
appender.setMaxBackupIndex(1);
appender.setFile(fileName + "_" + dateFormat.format(date) + ".log");
PatternLayout layOut = new PatternLayout();
layOut.setConversionPattern("%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n");
appender.setLayout(layOut);
log.addAppender(appender);
}
public static Logger getLogger(){
if(initializationFlag == false){
intializeLogger();
initializationFlag = true;
return LogClass.log;
}
else{
return LogClass.log;
}
}
public static void setFileName(String fileName){
LogClass.fileName = fileName;
}

Related

Custom PatternLayoutConverter with log4net.Ext.Json?

I have the following log4net configuration:
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type='log4net.Layout.SerializedLayout, log4net.Ext.Json'>
<renderer type='log4net.ObjectRenderer.JsonDotNetRenderer, log4net.Ext.Json.Net'>
<DateFormatHandling value="IsoDateFormat" />
<NullValueHandling value="Ignore" />
</renderer>
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<default />
<remove value='message' />
<remove value='ndc' />
<member value='message:messageObject' />
<member value='details:preparedMessage' />
</layout>
</appender>
<appender name="Console2" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<converter>
<name value="preparedMessage" />
<type value="JsonLogs.CustomLayoutConverter" />
</converter>
<conversionPattern value="%level %thread %logger - %preparedMessage%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="Console" />
<appender-ref ref="Console2" />
</root>
</log4net>
with the following implementation of my custom PatternLayoutConverter:
namespace JsonLogs
{
using System.IO;
using log4net.Core;
using log4net.Layout.Pattern;
public class CustomLayoutConverter : PatternLayoutConverter
{
#region Methods
protected override void Convert(TextWriter writer, LoggingEvent loggingEvent)
{
if (loggingEvent.MessageObject is string stringMessage)
{
writer.Write(new { message = stringMessage });
}
else
{
writer.Write(loggingEvent.RenderedMessage);
}
}
#endregion
}
}
For some reason, the converter works perfectly fine with the Console2 appender(which is not JSON driven) but it doesn't work with the Console appender whose output is JSON.
Example of the output:
Console -> {"date":"2018-12-09T12:25:28.0529041+03:00","level":"INFO","appname":"JsonLogs.exe","logger":"JsonLogs.Program","thread":"1","message":"Test","details":"preparedMessage"}
Console2 -> INFO 1 JsonLogs.Program - { message = Test }
My goal is to have details always in JSON that's why I introduced my own converter to catch primitive values and wrap them in a custom object.
Is my configuration wrong? Or I'm missing something? Could you help me, please, to figure this out?
Thank you
The issue seems to be a bug of log4net.Ext.Json. I'm going to report it on their GitLab.
So far, I ended up with my custom log4net layout which looks like this
public class CustomLayout : PatternLayout
{
#region Public Methods and Operators
public override void Format(TextWriter writer, LoggingEvent loggingEvent)
{
var message = loggingEvent.MessageObject.GetType().IsPrimitive || loggingEvent.MessageObject is string || loggingEvent.MessageObject is decimal || loggingEvent.MessageObject is BigInteger
? new { message = loggingEvent.MessageObject }
: loggingEvent.MessageObject;
writer.WriteLine(JsonConvert.SerializeObject(new
{
timestamp = loggingEvent.TimeStampUtc,
threadId = loggingEvent.ThreadName,
details = message,
logger = loggingEvent.LoggerName,
level = loggingEvent.Level.DisplayName,
user = loggingEvent.UserName
}));
}
#endregion
}
it meets my needs and does exactly what I want.
The exact place of this problem is AddMember Method and its implementation. Here is SerializedLayout source code for that:
public virtual void AddMember(string value)
{
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[0]);
m_arrangement.AddArrangement(arrangement);
}
As you can see the second parameter of GetArrangment is empty array of ConverterInfo, Though there must be our custom attached ones (by AddConverter method or by xml).
As the solution you can implement your own subclass that will derive from SerializedLayout with overridden AddMember like this:
public override void AddMember(string value)
{
var customConverter = new ConverterInfo("lookup", typeof(CustomPatternConverter));
var arrangement = log4net.Util.TypeConverters.ArrangementConverter.GetArrangement(value, new ConverterInfo[] { customConverter });
m_arrangement.AddArrangement(arrangement);
}
Hope it helps as it did with my case!

log4net Azure Blob Appender

When saving logs into Azure Blob Storage, is there a way to create a different folder per day?
Right now I'm using this configuration, and it works fine
<appender name="AzureAppender2" type="log4net.Appender.AzureBlobAppender, log4net.Appender.Azure">
<param name="ContainerName" value="testcon" />
<param name="DirectoryName" value="myfolder/logs.txt" />
<param name="ConnectionString" value="DefaultEndpointsProtocol=https;AccountName=testcon;AccountKey="rftgdfgdfgfdg78=="/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
I've already tried this
<param name="DirectoryName" value=%date/" />
But it doesn't work
What I want is to be able to dynamically use different folders per day: something like
DirectoryName = 2016-05-13
DirectoryName = 2016-05-12
DirectoryName = 2016-05-11
Is this achievable?
Thanks
Here's the code for the appender on GitHub.
Here's the DirectoryName class property that maps from the configuration value:
private string _directoryName;
public string DirectoryName
{
get
{
if (String.IsNullOrEmpty(_directoryName))
throw new ApplicationException(Resources.DirectoryNameNotSpecified);
return _directoryName;
}
set
{
_directoryName = value;
}
}
And the relevant Filename method that actually creates the file name for the blob:
private static string Filename(LoggingEvent loggingEvent, string directoryName)
{
return string.Format("{0}/{1}.{2}.entry.log.xml",
directoryName,
loggingEvent.TimeStamp.ToString("yyyy_MM_dd_HH_mm_ss_fffffff",
DateTimeFormatInfo.InvariantInfo),
Guid.NewGuid().ToString().ToLower());
}
So it looks like directoryName only accepts static values. Good thing it's open source ...

Logging only top-most ThreadContext stack item

We're using Log4net's ThreadContext.Stacks and it is mostly working well. My problem comes if there have been multiple ThreadContext.Stacks["key"].Push(...).
With a simple ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key} ..."/>
I see log entries like:
... topProp=first second third ...
I'd really like to see only the most recently pushed value rather than all the values. I had hoped I could put something like the following in my appender/layout/ConversionPattern:
<param name="ConversionPattern value="... topProp=%properties{key}{1} ..."/>
but that doesn't work. I can kludge it by assuming/requiring all values to be the same length (say 5) and doing:
<param name="ConversionPattern value="... topProp=%5.5properties{key} ..."/>
But that isn't real attractive. Any ideas?
Thanks!
[Edit to add very simple example]
using System;
using System.IO;
using log4net;
using log4net.Config;
namespace ThreadLocalExample {
class Program {
private const string PropJobId = "Example:JobId";
static void Main() {
XmlConfigurator.Configure(new FileInfo("log4net.cfg"));
var log = LogManager.GetLogger(typeof(Program));
ThreadContext.Stacks[PropJobId].Push("Old");
log.Debug("Enter using");
using (ThreadContext.Stacks[PropJobId].Push("New")) {
log.Debug("stuff");
}
log.Debug("Out of using");
log.Debug("done.");
Console.ReadKey();
}
}
}
With the log4net configuration:
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<threshold value="ALL" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="[jobId=%P{Example:JobId}]: %m%n" />
</layout>
</appender>
Produces:
[jobId=Old]: Enter using
[jobId=Old New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
But I'd like:
[jobId=Old]: Enter using
[jobId=New]: stuff
[jobId=Old]: Out of using
[jobId=Old]: done.
I had the same problem, and it was not only about 'bad formatting', because I used database appender (AdoNetAppender) which expects integers. So after joining all stacked values together the result is not an integer any more. Consider an appender like this:
<appender name="DbAppender" type="log4net.Appender.AdoNetAppender">
...
<commandText value="INSERT INTO Log ([Id]) VALUES (#Id)" />
<parameter>
<parameterName value="#Id" />
<dbType value="Int32" />
<layout type="log4net.Layout.PatternLayout" value="%P{someId}" />
</parameter>
This appender will not accept any log message, where 'someId' is stacked twice or more - no logs in database ...
So, to solve this problem I gave up with stacks and turned back to flat properties.
I've coded some short extension:
public static class Log4NetExt {
public static IDisposable ThreadContextPush(string key, object value) {
object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
ThreadContext.Properties[key] = oldVal;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable {
public event Action EvDispose;
public void Dispose() {
if (EvDispose != null) EvDispose();
}
}
}
And now, instead of:
using (ThreadContext.Stacks[PropJobId].Push("New")) {
write:
using (Log4NetExt.ThreadContextPush(PropJobId, "New")) {
and it works ok ;)
(This short code does not follow all best practises of building disposable objects, deleting event handlers and all this stuff, but it is short and I think it is safe in this simple case).
I have Done it via Filip solution modified as
public static class Log4NetExt
{
public static IDisposable ThreadContextSet(string key, object value)
{
//object oldVal = ThreadContext.Properties[key];
ThreadContext.Properties[key] = value;
var topMostCleaner = new DispCleaner();
topMostCleaner.EvDispose += () => {
// Pop = restore old value
//ThreadContext.Properties[key] = oldVal;
ThreadContext.Properties[key] = null;
};
return topMostCleaner;
}
private class DispCleaner : IDisposable
{
public event Action EvDispose;
public void Dispose()
{
if (EvDispose != null)
{
EvDispose();
}
}
}
}
And used it like
using (Log4NetExt.ThreadContextSet("ContextId", "Feed"))
using (Log4NetExt.ThreadContextPush("ContextValue", objFeed.FeedId))
{
}

Write Log4J to jTextArea

I'm an inexperienced Java developer writing an application that is handling a backup.
My application opens a gui (StepTwoBackup written using NetBeans Template), which gathers some information from the user, then when I press on the "Next" button, the gui passes these information to an object (BackupRestore which is logging all the operation using Log4J), and then opens another window (StepThreeBackup) and passes the object to it.
In this new window(StepThreeBackup), I automatically launch a method on the object passed (BackupRestore.execute()) which performs the backup.
In this last window (StepThreeBackup) I created a JTextArea where I would like to show the output of the Log4J (which currently writes to a log file and outputs to console).
Is there a way to do this? I've read that I should use an appender, but cannot figure out how to do this correctly.
For the time being I've created the following entry in my working Log4J property file:
<appender name="guiAppender" class="BackupAppGui.StatusMessageAppender">
<param name="Threshold" value="INFO" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{dd MMM yyyy HH:mm:ss} %5p %c{1} - %m%n"/>
</layout>
</appender>
Then the following Class in my Package (following another post):
package BackupAppGui;
/**
*
* #author MSTPA
*/
import javax.swing.JTextArea;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.spi.LoggingEvent;
public class StatusMessageAppender extends AppenderSkeleton {
private final JTextArea jTextA;
public StatusMessageAppender() {
jTextA = StepThreeBackup.getJTextA();
}
protected void append(LoggingEvent event)
{
if(event.getLevel().equals(Level.INFO)){
jTextA.append(event.getMessage().toString());
}
}
public void close()
{
}
public boolean requiresLayout()
{
return false;
}
}
But nothing is written to the jTextArea.
What am I doing wrong? Can someone help me solving this? Thank you for all the help you can give me.
You need to make sure that the instance of JTextArea is not null. Yoy can try adding the appender programmatically (e.g. in the constructor of StepThreeBackup after create the components):
StatusMessageAppender appender = new StatusMessageAppender();
LogManager.getRootLogger().addAppender(appender);
Don't forget delete the entry in the log4j.xml file.

DailyRollingFileAppender not work

I use Log4j to write some log my program.
I find and read many question and answer in this site, but i can't solve my problem.
Here my code:
1. log4j.xml
<appender name="rollingfileAppender" class="org.apache.log4j.DailyRollingFileAppender">
<param name="append" value="true"/>
<param name="file" value="logs/process.log"/>
<param name="DatePattern" value="'.'yyyy-MM-dd-HH"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss:SSS} %-5p [%c{1}] %m%n"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="rollingfileAppender"/>
<appender-ref ref="stdout"/>
</root>
2. My java code
package TestPacket;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
public class TestLog4jXML {
static Logger logger = org.apache.log4j.Logger.getLogger(TestLog4jXML.class.getName());
public TestLog4jXML() {
}
public static void main(String[] args) {
try {
DOMConfigurator.configure("log4j1.xml");
logger.trace("Entering application.");
logger.debug("Debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
logger.fatal("fatal");
lungtng();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void lungtng()
{
logger.fatal("some text here");
logger.info("hello picaso");
}
}
And i run my program, with eclipse, windows os.
But the log file name only: process.log not in daily format: process.log.yyyy-MM-dd-HH
Who can explain this to me?
It appears that if you're running windows this is a known bug:
see here: http://do.whileloop.org/2014/02/14/log4j-rolling-file-appenders-in-windows/
See here: https://issues.apache.org/bugzilla/show_bug.cgi?id=29726
And also here: http://www.coderanch.com/t/424837/java/java/Log-log-file-rolled-day
The solutions seems to be to use the extras here:
http://logging.apache.org/log4j/extras/
Try using this appender with the correct policies defined:
http://logging.apache.org/log4j/extras/apidocs/org/apache/log4j/rolling/RollingFileAppender.html
The org.apache.log4j.DailyRollingFileAppender will create new log file for each day, each hour or each minute but file name of the current log always will be in the format that you've specified in the "file" parameter. In your example it's "process.log". The file names of the logs for the previous hours will be in format "process.log.yyyy-MM-dd-HH".

Resources