Log4j2: How to append to HTML log using HtmlLayout - log4j2

I am creating html log files by using log4j2. When I am executing my code the first time the logs are formatted as a table. However, while executing a second time the data is not appended to the table, it is instead stored line by line under the table. Is there a way to append the logs to the existing table?
<Appenders>
<RollingFile name="fileLogger" fileName="/home/developers/Desktop/exam/app-info.html"
filePattern="app-info-%d{yyyy-MM-dd}.html">
<HTMLLayout charset="UTF-8" title="Howtodoinjava Info Logs" locationInfo="true" />
<Policies>
<TimeBasedTriggeringPolicy interval="10" modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="com.howtodoinjava" level="info" additivity="true">
<appender-ref ref="fileLogger" level="info" />
</Logger>
<Root level="debug" additivity="true">
<appender-ref ref="console" />
<appender-ref ref="fileLogger" />
</Root>
</Loggers>
This is log file
Screenshot of log file

Unfortunately I think the answer is that you can't do what you want with the HtmlLayout provided by log4j2. It looks like the HtmlLayout was intended to generate a single html file per execution.
If you take a look at the source code for HtmlLayout, you'll see code like following:
#Override
public byte[] getHeader() {
final StringBuilder sbuf = new StringBuilder();
append(sbuf, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" ");
...
appendLs(sbuf, "<html>");
...
appendLs(sbuf, "<table ...");
...
return sbuf.toString().getBytes(getCharset());
}
....
#Override
public byte[] getFooter() {
final StringBuilder sbuf = new StringBuilder();
appendLs(sbuf, "</table>");
appendLs(sbuf, "<br>");
appendLs(sbuf, "</body></html>");
return getBytes(sbuf.toString());
}
The code is clearly expecting to create a new html document each time as you can see the opening html tags in getHeader and closing tags in the getFooter.
You could write your own layout to do what you want but the problem you're going to face is determining when to write your footer. You don't want to write the footer until you're completely done with the file, so you will have to somehow implement a way to detect this situation (assuming you want to have clean HTML in your log).
If you don't care about having clean HTML then just don't write any footer. In this case you could simply copy the HtmlLayout class and change it slightly to create a new layout that does not write any footer. For example:
//your package and imports go here
...
#Plugin(name = "NoFooterHtmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public final class NoFooterHtmlLayout extends AbstractStringLayout {
//Various other methods copied from HtmlLayout go here
...
#Override
public byte[] getFooter() {
return new byte[0];
}
//Various other methods copied from HtmlLayout go here
...
}
and then in your log4j2 configuration you would use this:
<NoFooterHtmlLayout charset="UTF-8" title="My Title"
locationInfo="true" />
Hope this helps!

Related

Using RoutingAppender in commerical product programmatically

I have a commerical product, using log4j2 internally.
I can add java/groovy script as an internal api. Here I can also use log4j2 as logger
Each script is running within a thread.
The logging for each of the scripts, I want have a own logfile (and all subordinate loggers)
Before I start within the product, I was playing with Log4J2 locally, using the RoutingAppender examples.
RoutingAppender: it works as expected when I have a log4j2.xml during startup.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout
pattern="myCONSOLE - [%d] [%-5p] [%-7t] - %m%n" />
</Console>
<Routing name="Routing">
<Routes pattern="$${ctx:ROUTINGKEY}">
<Route>
<RollingFile name="Rolling-${ctx:ROUTINGKEY}"
fileName="logs/test-${ctx:ROUTINGKEY}.log"
filePattern="./logs/test-${ctx:ROUTINGKEY}-%d{yyyyMMdd}-%i.log.gz">
<PatternLayout>
<pattern>%d{ISO8601} [%t] %p %c{3} - %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="6"
modulate="true" />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Root level="ALL">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="Routing" />
</Root>
</Loggers>
</Configuration>
In the code I use parts as:
In the script script (simulated), I do ThreadContext.put("ROUTINGKEY", "exampleA"); , in a second script I use "exampleB".
In main programm I set:
static {
System.setProperty("isThreadContextMapInheritable", "true"); // So all objects/loggers in same thread will inherit the ThreadC
}
Other classes/objects are created as usual with normal logger technique: private static Logger logger = LogManager.getLogger(Grassfrosch.class);; and using logger.info....
All logging was in correct logfile including the subordinate elements.
Back to the commerical product. Here I want to add the RoutingAppender (and the RollingFileAppender) dynamically with programming code in the groovy scripts.
Script "exampleA" should add the route for the own part by itself
Script "exampleB" should add the route for the own part by itself
I don't want to use the product config file itself due to product upgrades/patches, overwritten by the manufacturer.
So, I also implemented locally such logic "programmatically approach".
I create a FileAppender/RollingFileAppender
I create a Routes plus Route(s) object
I create a RoutingAppender, adding the routes etc.
Simplified code is:
private void fragment() {
Configuration cfg;
org.apache.logging.log4j.core.Logger rl = (org.apache.logging.log4j.core.Logger) LogManager.getRootLogger();
cfg = rl.getContext().getConfiguration();
String name = "exampleA";
PatternLayout layout = PatternLayout.newBuilder().withPattern("DYNAMIC[" + name + "] - %d [%p] - %m%n").build();
FileAppender fileAppender = FileAppender.newBuilder() //
.setName("FileAppender-" + name).withFileName("logs/FileAppender-" + name + ".log") //
.withAppend(true).setLayout(layout) //
// .setConfiguration(cfg)
.build();
fileAppender.start();
rl.addAppender(fileAppender);
Route route01 = Route.createRoute("FileAppender-" + name , name, null);
Routes routes = Routes.newBuilder().withPattern("$${ctx:ROUTINGKEY}").withRoutes(new Route[] { route01}).build();
RoutingAppender routingAppender = RoutingAppender.newBuilder().setName("EPI-Routing").withRoutes(routes) //
.setConfiguration(cfg) //
.build();
routingAppender.start();
rl.addAppender(routingAppender);
rl.getContext().updateLoggers();
}
With this code, both scripts "exampleA" and "exampleB", will written to both logfiles "FileAppender-exampleA" and "FileAppender-exampleB" and not as expected in the "log4j2.xml" example.
I guess, the problem is:
I have to add a FileAppender to existing configuration
due to I have to "reference" Route.createRoute.
Finally the FileAppender I have added to rootlogger (wrong).
I also try to add to script logger, but no success.
I stuck. Any hints?
Uwe

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!

how to change htmllayout in log4j2

When I use log4j, I can create a new class extends org.apache.log4j.HTMLLayout and override it to make a new format of the log. When using log4j2, I can only use the layout but not override it. In my log4j2.xml writing
<Appenders>
<File name="log" fileName="D:\log.html" append="false">
<HTMLLayout/>
</File>
</Appenders>
in log4j I may use layout class="log.FormatHTMLLayout" (log.FormatHTMLLayout is my new class which extends the HTMLLayout), but now I can only use HTMLLayout.
Is there any way to override the HTMLayout? I need to do a lot of things, like changing the output table, the title and so on.
Here is what i did,
I needed to add <img> HTML tags in logs generated by log4j2 and by default HTML elements like <, >, " are replaced by escape characters like lt; , gt; , quot;.
(Ref:https://issues.apache.org/jira/browse/LOG4J2-439)
So i just copied whole HTMLLayout class from source, which is available at
log4j-core / src / main / java / org / apache / logging / log4j / core / layout
and changed its name to "CustomHTMLLayout" and updated it wherever required (you can choose any name), now your custom layout class is as good as HTMLLayout class.
there is method called toSerializable which contains actual formatting of each record, so you can manipulate it as per your need.
once class is modified, you need to provide the new layout in your log4j2.html
as following:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration packages="com.redknee.bssauto.helpers">
<Appenders>
<RollingFile name="Rolling-default" fileName="logs/bssauto.html" filePattern="logs/$${date:yyyy-MM}/bssauto-%d{MM-dd-yyyy}-%i.log.gz">
<CustomHTMLLayout charset="UTF-8" title="BSSAuto Logs" locationInfo="true" />
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="Rolling-default"/>
</Root>
</Loggers>
</Configuration>
Notice following
<Configuration packages="com.bssauto.helpers">
here packages should have all packages containing custom class for layouts. so here com.bssauto.helpers is package under which i have CustomHTMLLayout class.
<CustomHTMLLayout charset="UTF-8" title="BSSAuto Logs" locationInfo="true" />
and CustomHTMLLayout is custom layout class created by extending AbstractStringLayout
Make sure you are using latest log4j2 version, I used log4j 2.2

log4j2: registering custom TriggeringPolicy

I have written a custom TriggeringPolicy for log4j2 that is suppose to roll-over .log file at the end of every hour/day/your_interval following advices from this SO post.
Though I followed TimeBasedTriggeringPolicy conventions (naming, etc) I am not able to see my policy being instantiated and used.
Solution comprise of 3 java files + a maven file and is available at the github.
Here you can find main lines from the policy itself:
#Plugin(name = "FTimeBasedTriggeringPolicy", category = "Core", printObject = true)
public class FTimeBasedTriggeringPolicy implements TriggeringPolicy {
private final TimeBasedTriggeringPolicy timeBasedTriggeringPolicy;
private RollingFileManager manager;
private FTimeBasedTriggeringPolicy(final int interval, final boolean modulate) {
timeBasedTriggeringPolicy = TimeBasedTriggeringPolicy.createPolicy(String.valueOf(interval), String.valueOf(modulate));
LogRotateThread.registerPolicy(this);
}
public void checkRollover(final LogEvent event) {
this.manager.checkRollover(event);
}
#Override
protected void finalize() throws Throwable {
LogRotateThread.unregisterPolicy(this);
super.finalize();
}
#Override
public void initialize(final RollingFileManager manager) {
this.manager = manager;
timeBasedTriggeringPolicy.initialize(manager);
}
#Override
public boolean isTriggeringEvent(final LogEvent event) {
return timeBasedTriggeringPolicy.isTriggeringEvent(event);
}
#Override
public String toString() {
return "FTimeBasedTriggeringPolicy";
}
#PluginFactory
public static FTimeBasedTriggeringPolicy createPolicy(
#PluginAttribute("interval") final String interval,
#PluginAttribute("modulate") final String modulate) {
final int increment = Integers.parseInt(interval, 1);
final boolean mod = Boolean.parseBoolean(modulate);
return new FTimeBasedTriggeringPolicy(increment, mod);
}
}
The log4j2.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" name="RoutingLoggingConfiguration" packages="org.log4j2plugin" verbose="true">
<Properties>
<Property name="routing_filename">${log.path}/table-$${sd:type}.log</Property>
</Properties>
<Appenders>
<Console name="STDOUT">
<PatternLayout pattern="%d{yyyyMMddHH}{GMT+0} %m%n"/>
</Console>
<Routing name="Routing">
<Routes pattern="$${sd:type}">
<Route>
<RollingFile name="RollingFile-${sd:type}"
fileName="${routing_filename}"
filePattern="${log.path}/%d{yyyyMMdd}{GMT+0}/%d{yyyyMMddHH}{GMT+0}-${sd:type}-${hostName}.%i.log.gz">
<PatternLayout>
<Pattern>%d{yyyyMMddHH}{GMT+0},'%d{yyyy-MM-dd HH:mm:ss}{GMT+0}',%K{v}%n</Pattern>
</PatternLayout>
<Policies>
<FTimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="64 MB"/>
</Policies>
<DefaultRolloverStrategy max="999"/>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Logger name="EventLogger" level="debug" additivity="false">
<AppenderRef ref="Routing"/>
</Logger>
<Root level="warn">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
EDIT:
During debugging I understood that Routes appenders (defined by the $${sd:type} in my case) are not known during log4j2.xml parsing. Thus - their creation/initialization is delayed in time to the moment when the first message for destination $${sd:type} arrives. My next plan is to:
add a StructuredDataFilter to the Routes appender
provide an empty message at the system start-up to all known $${sd:type}, which on one hand should initialize Route appender and cause FTimeBasedTriggeringPolicy to register itself in the LogRotateThread, but on other - should be discarded by the StructuredDataFilter
allow LogRotateThread to query registered FTimeBasedTriggeringPolicy and rotate logs if needed
While debugging, it appeared that Routes appenders (defined by the $${sd:type} in my case) are not known during log4j2.xml parsing. Thus, their creation/initialization is delayed in time to the moment when the first message for the destination $${sd:type} arrives.
Thus, the original subject of the post could be answered as "custom policy is registered, however not always instantly (or during the .xml parse time)".
The original problem, however, was to force .log files to roll-over at the end of the time period (an hour in my case). To address it, I have implemented following algorithm:
wrote a thin wrapper around TimeBasedTriggeringPolicy - FTimeBasedTriggeringPolicy that registers itself on instantiation at LogRotateThread
wrote a simple LogRotateThread that queries registered FTimeBasedTriggeringPolicy once every few minutes and makes them rotate .log if needed
added a StructuredDataFilter to the Routes appender, so that they discard specific messages (with id=SKIP in my case)
provide an empty message at the system start-up to all known $${sd:type}, which:
a. instantiate Route appender and cause FTimeBasedTriggeringPolicy to register itself in the LogRotateThread
b. are discarded by the StructuredDataFilter
Solution is published under Apache 2.0 license, and is available at github

Log4j2 (beta9) How to configure custom layout for FileAppender?

Can someone tell me, How to configure custom layout for FileAppender?
Can anyone tell me, how to configure custom layout for FileAppender?
I'm created copy of HTMLLayout and made some changes there (it's cannot be extends because it's final class) and now I want use this layout, but I don't know how :(
This error is showed with bellow listed configuration:
ERROR File contains an invalid element or attribute "ibtrader.log4j2.MYHTMLLayout"
Here is my log4j2.xml configuration file
<?xml version="1.0" encoding="UTF-8"?>
<configuration strict="true" monitorInterval="30">
<appenders>
<appender name="Console" type="Console" target="SYSTEM_OUT">
<layout type="PatternLayout" pattern="%highlight{%d{ISO8601} [%t] %-5level %logger{36} - %msg%n}" />
</appender>
<appender name="DEBUG_FILE" type="File" fileName="logs/errors.txt" >
<layout type="PatternLayout" pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n}" />
</appender>
<appender name="HTMLAppender" type="File" fileName="logs/mainlog.html">
<layout type="ibtrader.log4j2.MYHTMLLayout" charset="UTF-8" title="IBTRader logs" locationInfo="true" />
</appender>
</appenders>
<loggers>
<root level="trace">
<appender-ref ref="Console"/>
<appender-ref ref="DEBUG_FILE" level="WARN" />
<appender-ref ref="HTMLAppender" />
</root>
</loggers>
</configuration>
Thanks for help!
The comments mention this is already fixed but just for completeness, a (simplified) configuration with a custom MYHTMLLayout plugin could look like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- the packages attribute contains a comma-separated list
of the packages that Log4J2 will scan for custom plugins. -->
<configuration packages="ibtrader.log4j2">
<appenders>
<appender name="HTMLAppender" type="File" fileName="logs/mainlog.html">
<!-- Each plugin has a *name*, declared with annotation on the
plugin implementation class.
The plugin name does not need to match the class name.
The plugin name needs to match the *type* attribute in the config.
For example:
package ibtrader.log4j2;
import ...;
#Plugin(name="MYHTMLLayout", category="Core",
elementType="layout", printObject=true)
public class MyHTMLLayoutImpl extends AbstractStringLayout {...
-->
<layout type="MYHTMLLayout" charset="UTF-8" title="IBTRader logs" locationInfo="true" />
</appender>
</appenders>
<loggers>
<root level="trace">
<appender-ref ref="HTMLAppender" />
</root>
</loggers>
</configuration>
You have to extend AbstractStringLayout, setting the #Plugin properties with category = "Core", elementType = "layout" and name="The name of the class itself that should be refered from the configuration file" (i.e.: "ExtHtmlLayout")
You could just copy the whole class HtmlLayout from the sources and change whatever you want, for example, the time in millis column to the time in hours.
Define the package property in "Configuration" tag to use the package to the extended class you have created.
Finally, just call the new layout from the configuration file:
<ExtHtmlLayout/>

Resources