Log4j2: Empty log file in case of parallel tests - log4j2

I have in my test automation project problem with logging. I'm using log4j2 logger with FileAppender. The way I'm using it is:
Logger logger = (Logger) LogManager.getLogger(loggerName);
Appender appender = FileAppender.newBuilder()
.withAppend(false)
.withBufferedIo(true)
.withFileName(DIR_NAME + File.separator + loggerName + ".log")
.withIgnoreExceptions(false)
.withImmediateFlush(true)
.withLocking(false)
.withLayout(PatternLayout.newBuilder().withPattern("%d{HH:mm:ss.SSS} [%-5level] %msg%n").withCharset(Charset.forName("UTF-8")).build())
.withName(loggerName)
.build();
appender.start();
logger.addAppender(appender);
It works when I'm running single test. All data are visible in console, the file is created and test log is written in the file. Problem occurs in case of tests are running in parallel - in different threads.
In this case, two different loggers and file appenders are created. Log files from both file appenders are created too and logs from both tests are visible in console. Everything seems to be fine, but every time one of these log files is empty. The empty log belongs to test which started later.
I suspect problem with caching. The first file appender holds all cache for writing so the second one is not able to write. Am I right? What is the solution for this?
Thank you.

You should be able to achieve what you want without using programmatic configuration. There are many reasons not to configure log4j2 programmatically, but the best one, in my opinion, is that in doing so you would make your code dependent on aspects of log4j2 that are not part of the public API. This means that if the implementation of log4j2 changes your code has to change as well. This creates more work for you in the long run.
So, with that in mind I will provide a demo of how to set up log4j2 using an XML config file such that it will generate separate logs for each test. I am assuming, since it was not specified in your question, that your goal is to create a log for each method with a Test annotation and that each of these methods is executed in parallel.
First, here is my TestNG class:
package testpkg;
import java.lang.reflect.Method;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class NewTest {
private static final Logger log = LogManager.getLogger();
#BeforeMethod
public void setThreadName(Method method){
ThreadContext.put("threadName", method.getName());
}
#Test
public void test1() {
log.info("This is the first test!");
log.warn("Something may be wrong, better take a look.");
}
#Test
public void test2() {
log.info("Here's the second test!");
log.error("There's a problem, better fix it");
}
}
As you can see here I have two Test methods and a BeforeMethod called setThreadName. The setThreadName method is, obviously, executed before each of the Test methods. It places a key named threadName into the log4j2 ThreadContext using the name of the method that is about to be run. This will be used as part of the log file name in the log4j2 config file.
Here is the log4j2.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Routing name="MyRoutingAppender">
<Routes pattern="$${ctx:threadName}">
<Route>
<File
fileName="logs/${ctx:threadName}.log"
name="appender-${ctx:threadName}"
append="false">
<PatternLayout>
<Pattern>[%date{ISO8601}][%-5level][%t] %m%n</Pattern>
</PatternLayout>
</File>
</Route>
</Routes>
</Routing>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="[%date{ISO8601}][%-5level][%t] %m%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="testpkg" level="TRACE" additivity="false">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="MyRoutingAppender" />
</Logger>
<Root level="WARN">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
As you can see I've set up the config file to use a RoutingAppender to dynamically generate appenders at runtime based on the ThreadContext key threadName and that threadName is also used in the fileName attribute of the FileAppender.
Here is my testNG config file:
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="My suite" parallel="methods" thread-count="5" verbose="1">
<test name="testpkg" >
<classes>
<class name="testpkg.NewTest" />
</classes>
</test>
</suite>
As you can see here I've set it up so that each Test method within my class is run in parallel.
When executed this results in the following console output:
[RemoteTestNG] detected TestNG version 6.14.3
[2018-05-04T21:54:54,703][INFO ][TestNG-test=testpkg-2] Here's the second test!
[2018-05-04T21:54:54,703][INFO ][TestNG-test=testpkg-1] This is the first test!
[2018-05-04T21:54:54,709][WARN ][TestNG-test=testpkg-1] Something may be wrong, better take a look.
[2018-05-04T21:54:54,709][ERROR][TestNG-test=testpkg-2] There's a problem, better fix it
===============================================
My suite
Total tests run: 2, Failures: 0, Skips: 0
===============================================
You can clearly see that the output of the two methods is interleaved, so we know that the methods are indeed running in parallel.
The execution of the test class also creates two log files as expected. They are named test1.log and test2.log
Here are their contents:
test1.log:
[2018-05-04T21:54:54,703][INFO ][TestNG-test=testpkg-1] This is the first test!
[2018-05-04T21:54:54,709][WARN ][TestNG-test=testpkg-1] Something may be wrong, better take a look.
test2.log:
[2018-05-04T21:54:54,703][INFO ][TestNG-test=testpkg-2] Here's the second test!
[2018-05-04T21:54:54,709][ERROR][TestNG-test=testpkg-2] There's a problem, better fix it
So we see here that as expected the logs from the first method went to test1.log and the logs from the second method went to test2.log
Enjoy!

Related

Log4Net can't locate logfile anywhere

I have followed many different guides on how to configure the log4net, it is up and running but i can't find a log file anywhere ...
This is how my configuration look like:
Web.Config
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
<log4net debug="true">
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="C:\\temp\\Log.txt" />
<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.18M - %m%n" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="RollingLogFileAppender" />
</root>
</log4net>
Global.asax:
XmlConfigurator.Configure(new FileInfo(Server.MapPath("~/Web.config")));
//XmlConfigurator.Configure();
StartUp.cs
//[assembly: XmlConfigurator(ConfigFile = "Web.config", Watch = true)]
[assembly: XmlConfigurator(Watch = true)]
Declaration
readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
Logging
BasicConfigurator.Configure();
logger.Info("Info logging ...");
logger.Error("Homepage loading test logging ...");
Where my file value is: <file value="C:\\temp\\Log.txt" />
I have tried several paths, and commented out what above but no success.
What am i doing wrong?
UPDATE:
As suggested by John H i have tried configuring and calling the logger in the Application_Start method and tried several alternative configs with it but with no luck. Here are 2 screenshots of some debugging info:
Main properties:
Below are the Logger properties:
What am i doing wrong?
OK so i got it to work following this tutorial: log4net-guide-dotnet-logging
I have created a log4net.config file with content as showed in tutorial.
used [assembly: XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
Called it like this:
ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
logger.Info("Application started.");
file is created and content logged as well.
I am gonna compare the config files content and see if the difference is in there and go gradually comparing everything till i have found what caused it not to work.
Thank you for helping me!
Kind regards
From your screenshots, we can see that your logger is not being initialised with your configuration, because IsDebug is false. One thing I notice from your screenshot, is you're trying to pass the path to Web.config directly to the Configure() method. I realise that may be an attempt to solve the problem, so you may have already tried my next suggestion, but calling Configure() in the manner you currently have won't work because Web.config is not published to your bin\debug folder. It will called Web.projectname.config. Calling
XmlConfigurator.Configure()
with no parameters, will automatically resolve the correct configuration file in your output directory. I'm guessing you've tried that, but if that still doesn't work, try this as well:
using log4net;
protected void Application_Start(object sender, EventArgs e)
{
// Initialising configuration before requesting a logger.
XmlConfigurator.Configure();
// Requesting a logger only after the configuration has been initialised.
var logger = LogManager.GetLogger(typeof(Global));
logger.Info("Application started.");
}
I'm not sure it will make any difference, but your configuration looks fine to me.
But by inspecting the IsDebug property on the logger, you'll at least be able to tell if the configuration has even been read.
Edit: One other thing, make sure the application will have the permissions to write to the file. From the documentation:
The RollingFileAppender extends the FileAppender and has the same behavior when opening the log file. The appender will first try to open the file for writing when ActivateOptions() is called. This will typically be during configuration. If the file cannot be opened for writing the appender will attempt to open the file again each time a message is logged to the appender. If the file cannot be opened for writing when a message is logged then the message will be discarded by this appender.

Translating prometheus LOG4j conf from XML to properties file

I have used LOG4j many times on quite common basics, and mainly through a properties file. I am though very unfamiliar with the XML form and with uncommon features (such as third party lib custom logging).
The prometheus log4j2 configuration is written for xml conf files, and I actually don't understand it. Although I could use it as such, I would like to understand it by translating in a form I am confortable with : as a properties file .
<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="io.prometheus.client.log4j2">
<Appenders>
<Prometheus name="METRICS"/>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="METRICS"/>
</Root>
</Loggers>
</Configuration>
properties file :
name=PropertiesConfig
property.filename = /var/logs
appenders = console, METRICS?
appenders.METRICS?.
...?
rootLogger.appenderRefs = METRICS, console
...?
Can anyone help me on this one?
like this:
log4j.rootLogger=CONSOLE,METRICS
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c] \:%m%n
log4j.appender.METRICS=io.prometheus.client.log4j.InstrumentedAppender
after see log4j_appender_total flag in prometheus
converstion refer: https://www.journaldev.com/10698/log4j-properties-file-example

Modify existing log4j2 FileAppender configuration during runtime

I am migrating application (huge web application) from log4j1.x to log4j 2.11.0.
I need help/clarification to migrate following scenarios -
--------scenario1
log4j1.x:
<appender name="import_log_file" class="xxxx">
During runtime i want to change the file so i would just do appender.setFile(...new file...). And done.
log4j2:
how do I migrate above code?
Few ideas but not a straight answer:
Creating brand new appender via LoggerContext, Configuration might be a way but i want to update an existing appender's configuration and reload the log4j2 xml. How do I do that?
Another way could be redefining something like this
<appender name="import_log_file" class="xxxx">
And then setup "dynamic_name" property in threadcontext. But I am loosing original default file import.log
Any help is appreciated. Let me know if you have ideas.
During runtime i want to change the file
You can use the RoutingAppender together with a lookup to do this. See the log4j2 FAQ page for details.
Here is a very simple example of how to change the log file name at runtime:
package pkg;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
public class Log4j2DiffFilePerCtxVarMain {
private static final Logger LOG = LogManager.getLogger();
public static void main(String[] args){
ThreadContext.put("myFileNameVar", "file1");
LOG.info("This should appear in file1.log");
ThreadContext.put("myFileNameVar", "file2");
LOG.info("This should appear in file2.log");
}
}
The configuration looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Routing name="myAppender">
<Routes pattern="$${ctx:myFileNameVar}">
<Route>
<File
fileName="logs/${ctx:myFileNameVar}.log"
name="myAppender-${ctx:myFileNameVar}">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="myAppender" />
</Root>
</Loggers>
</Configuration>
The result of running the above code will be two files - file1.log an file2.log each with a single entry. The file1.log will contain the first log message and file2.log will contain the second message.
Hope this helps!

Log4J2 JSONLAYOUT How to add custom Parameters

How to add customized parameters to Log4j2’s JSONLAYOUT?
Also is there a way to add pattern to the JSONLAYOUT’s message element?
I have tried the options listed here ->
logging.apache.org/log4j/2.x/manual/layouts.html#JSONLayout
Please help!
Solution is to explicitly add log4j2’s 2.10.0 version. This version supports custom parameter in JSONLayout!
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JSONLayout compact="true" eventEol="true">
<KeyValuePair key="application" value="${sys:com.example.appname}"/>
</JSONLayout>
</Console>
<Async name="AsyncAppender" bufferSize="80">
<AppenderRef ref="Console"/>
</Async>
</Appenders>
As I understand it you're looking for a way to customize the format of the JSON output from the JSONLayout in a manner similar to how you can customize the PatternLayout by specifying "Conversion Patterns".
I believe the answer is that you can't customize the JSONLayout in the same manner. You can select the various pieces of information you want to be included in the message. For example, the documentation shows parameters like properties:
If true, the appender includes the thread context map in the generated JSON. Defaults to false.
So you can set various parameters to include certain kinds of information, but you don't have the direct control over the specific items that are included.
What you could do instead is to use ObjectMessage along with a JSON library to generate a JSON message. However, this would generate JSON within JSON (assuming you still wish to use JSONLayout with this approach). Here is some sample code to illustrate:
A class with a main method to generate a log message:
package example;
import java.util.HashMap;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
public class JsonMessageExample {
private static final Logger log = LogManager.getLogger();
public static void main(String[] args) {
Map<String,String> msgMap = new HashMap<>();
msgMap.put("myKey", "myValue");
JSONObject message = new JSONObject(msgMap);
log.info(message);
}
}
The log4j2.xml config file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JSONLayout/>
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
The output from the above:
{
"timeMillis" : 1510429852038,
"thread" : "main",
"level" : "INFO",
"loggerName" : "example.JsonMessageExample",
"message" : "{\"myKey\":\"myValue\"}",
"endOfBatch" : false,
"loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger",
"threadId" : 1,
"threadPriority" : 5
}
As you can see, the message name value pair has a value that is a JSON string. In order to parse this you would have to parse the outer object as JSON, pull the message field and then parse its value as JSON as well.
However, if you use a different layout such as a very basic PatternLayout like this: <PatternLayout pattern="%m%n"/>
You will be able to generate just one level of JSON output and therefore only have to parse once. However, you would have to write your logic to obtain all of the data that you need in your message and stuff it into your map (and JSON object) because now you're simply dumping the contents of the map.
Sample output using same java code with the layout changed to PatternLayout as described above:
{"myKey":"myValue"}
EDIT:
You could even do something like the following if you want to use the "Conversion Patterns" of PatternLayout, output logs in JSON format, and not have to write the logic to obtain some of the specifics:
<PatternLayout>
<pattern>{"timeMillis":"%d{UNIX_MILLIS}","thread":"%t","level":"%p","loggerName":"%c","message":%m}%n</pattern>
</PatternLayout>
Sample output:
{"timeMillis":"1510455694601","thread":"main","level":"INFO","loggerName":"example.JsonMessageExample","message":{"myKey":"myValue"}}

Disable freemarker logs from logs4j

Similar question but i'm using log4j2.
I need a way to disable All logs from freemarker, in their documentation they say we can do it by calling Logger.selectLoggerLibrary(Logger.LIBRARY_NONE) but they say
selectLoggerLibrary must be called early, before FreeMarker could log anything, or else it will not have (consistent) effect.
Where do I call this in a struts2 application? (I tried calling it in prepare() method in my action class but its not working.) or is there any other way to disable the logs?
Question is, why do you need to disable it like that?
You shouldn't need that, so I guess that's where the real problem lies. Is there some kind of malfunction? Because if there isn't, why not just set the freemarker logger category to be ignored in your logger configuration? That's the normal way of doing this, FreeMarker or not.
Anyway, in 2.3.22 (release expected in early 2015) you can use the -Dorg.freemarker.loggerLibrary=none where you start the JVM (that is, you set the org.freemarker.loggerLibrary system property). Otherwise, if you could call that method in a ServletContextListener that's certainly early enough.
Update:
Reacting to the comments... in most applications you will have 3rd party libraries that use various logging "frameworks", like SLF4J, commons-logging, JUL, Log4j, Log4j2. Thus you have to ensure that all these get redirected into the same logger library, which is certainly Log4j2 in your case. I suspect that wasn't properly done in your case, so now multiple logger libraries log to the console, each with its own configuration settings.
FreeMarker 2.3.x uses Log4j 1.x if it detects that org.apache.log4j.Logger is present. Other logger libraries that it also can detect and use (Log4j2 is not amongst them) have lower priority. (FreeMarker 2.4.x will always use SLF4J if it's present.) Thus, if you add org.apache.logging.log4j:log4j-1.2-api to your dependencies, then FM will use org.apache.log4j.Logger, and so log4j-1.2.-api will redirect the FM log messages to Log4j2. That worked for me, with this Log4j2 configuration:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="Console" />
</Root>
<Logger name="freemarker" level="off">
<AppenderRef ref="Console" />
</Logger>
</Loggers>
</Configuration>
This came up as the first search result for "disable freemarker logging" which I searched for because I got double error logs for template errors, one from within the Freemarker library and one from my own code catching the same exception and logging it. The solution to this is simple and different from the answers already given: call setLogTemplateExceptions(false) on the Freemarker Configuration. In full:
Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
configuration.setLogTemplateExceptions(false);
The default behavior of logging the exception even though it propagates out of the Freemarker library is mentioned as a quirk on the Freemarker Logging documentation.
Use this statement:
freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE);

Resources