Log4j2 JsonTemplateLayout - log message field as json - log4j2

I have this log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="JsonAppender" target="SYSTEM_OUT">
<JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json" />
</Console>
</Appenders>
<Loggers>
<Logger name="JsonLogger" level="INFO" additivity="false">
<AppenderRef ref="JsonAppender"/>
</Logger>
<Root level="info">
<AppenderRef ref="JsonAppender"/>
</Root>
</Loggers>
</Configuration>
In my code I have a logging statement like:
HashMap logMap = new HashMap<>();
logMap.put("appId", "123456789");
logMap.put("action", "Received request");
logger.info(new ObjectMessage(logMap));
In my log I get this:
{
"#timestamp": "2022-02-02T10:52:56.100Z",
"ecs.version": "1.2.0",
"log.level": "INFO",
"message": "{appId=123456789, action=Received request}",
"process.thread.name": "main",
"log.logger": "org.xxxx.App"
}
That's ok but I want the message to be in json, so would like:
"message": {
"appId": "123456789",
"action": "Received request"
}
I know using JsonLayout you had to specify objectMessageAsJsonObject="true", but suspect it's a bit more nuanced when using JsonTemplateLayout.
TIA.

Upon reading the JSON Template Layout docs further, you can modify the event template fields by leveraging the eventTemplateAdditionalField param.
There seems to be a few ways to approach this but the option I went with is to modify the log4j2.xml:
<Appenders>
<Console name="JsonAppender" target="SYSTEM_OUT">
<JsonTemplateLayout eventTemplateUri="classpath:LambdaEcsLayout.json">
<EventTemplateAdditionalField
key="message"
format="JSON"
value='{"$resolver": "message", "field": "name"}'/>/>
</JsonTemplateLayout>
</Console>
</Appenders>
And when logging, have something like this:
var logMap = new HashMap<>();
logMap.put("id", "123456789");
logMap.put("action", "Received request");
var om = new ObjectMessage(logMap);
log.info(om);
There is probably a much neater way to deal with the latter, some kind of wrapper etc - but you get the idea.

I was able to get #Hurricane's method to work, but in trying to simplify it, I found that you can omit the <EventTemplateAdditionalField> element and add
"message": {
"$resolver": "message",
"stringified": false
}
to the template layout instead. You can also omit the 'stringified' attribute as it defaults to 'false'. The OP didn't include the layout, but I suspect he had 'stringified' set to 'true'

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

Log4j2: How to append to HTML log using HtmlLayout

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!

log4j2 - duplicating logs in Console & RollingFile

hey I was wondering if it possible to have the same output in Console as in the file output.
Here is my XML config.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" name="log4j2 Logs">
<Properties>
<Property name="basePath">./logs</Property>
</Properties>
<Appenders>
<RollingFile name="file" fileName="${basePath}/ActivateMaintenancePage.logs"
filePattern="${basePath}/ActivateMaintenancePage-%d{yyyy-MM-dd}">
<PatternLayout header="LOGGING START%n%n" footer="%n%nLOGGING END"
pattern="%3sn %30d{DEFAULT} [%M] %-7level %c{30} - %m%n" />
<Policies>
<OnStartupTriggeringPolicy minSize="0"/>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="500 MB"/>
</Policies>
</RollingFile>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout header="LOGGING START%n%n" footer="%n%nLOGGING END"
pattern="%3sn %30d{DEFAULT} [%M] %-7level %c{30} - %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console" level="error"/>
<AppenderRef ref="file" level="trace"/>
</Root>
</Loggers>
</Configuration>
Output in RollingFile
1 2017-07-25 11:16:36,762 [initializeChrome] INFO class testNG.SimSettings - Web Chrome driver is now initilized.
2 2017-07-25 11:16:36,762 [lambda$0] INFO class testNG.SimSettings - Opening... http://msrvaq11vm.technomedia.ca/sigal_60/2017sp1/sp_polymont/_sim/PROD Sp2 2016 Sim...
3 2017-07-25 11:16:47,926 [initilizeAllElements] INFO class testNG.SimSettings - All #FindBy elements have been initilized.
4 2017-07-25 11:16:48,006 [change1stLevelFrame] INFO class testNG.SimSettings - Changing target to 1st level frame.
5 2017-07-25 11:16:49,719 [enterCredentials] INFO class testNG.SimSettings - UserName & Password: Success!
and empty in Console. But now if I change
<AppenderRef ref="console" level="error"/>
to "trace"
It will be 2,4,6....in console and in my file it will be 1,3,5,7... which is easily understandable.
But my question is how can we have both the same log-level (trace) output in Console and File?
(adding tag with package name and level did not work)
Related to this question: log4j2 xml configuration - Log to file and console (with different levels)
I'm not sure if I read your question correctly but it seems that you want to render some unique value in the log output, such that the same log event has the same unique value in the Console log and the File log output.
The sequence number pattern converter will increment every time a log event is rendered. The same log event is rendered separately for each Appender, so different Appenders will never have the same sequence number.
There are a number of alternatives. One idea is to include %nano nanotime in the pattern layout. This value is captured when the application makes the logging call and will be the same for all appenders. An alternative is to create a custom Log4j2 pattern converter or lookup.

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/>

Log4j2 Appender attributes with strict xml config

I'm using log4j2-beta9 and want to configure it using a log4j2.xml in strict mode.
My issue is: how do I specify attributes that are not in the shipped schema file?
An Example:
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration
status="DEBUG"
strict="true"
monitorInterval="5"
name="TestingAttributes"
verbose="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="Log4j-config.xsd">
<Properties>
</Properties>
<Appenders>
<Appender
type="Console"
name="SYSERR"
target="SYSTEM_ERR"> <!-- cvc-complex-type.3.2.2: Attribute 'target' is not allowed to appear in element 'Appender'. -->
<Layout Type="PatternLayout">
<Pattern>%date{dd.MM.yyyy HH:mm:ss,SSS} %5p %logger %m%n</Pattern>
</Layout>
<Filters>
<Filter
type="MarkerFilter"
marker="FLOW"
onMatch="DENY"
onMismatch="NEUTRAL" />
<Filter
type="MarkerFilter"
marker="EXCEPTION"
onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
</Appender>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="SYSERR" />
</Root>
</Loggers>
</Configuration>
Notice that I want to set the appender to have the target SYSTEM_ERR but the attribute is not allowed in strict mode.
target="SYSTEM_ERR"> <!-- cvc-complex-type.3.2.2: Attribute 'target' is not allowed to appear in element 'Appender'. -->
I could always edit the Log4j-config.xsd and allow that attribute there but that would be kind of wrong also because not all appenders have a target attribute.
As searching the web didn't help me so far, I'm asking you:
Is there anything I'm missing in configuring Log4j2 in strict XML mode?
Do I have to "patch" the XMLConfiguration and the schema file and commit a change to log4j or is there another way besides not using strict mode?
Thanks in advance.
Can you ask this on the log4j-user mailing list? It could be a bug in the schema, but I suspect the schema can do with more improvements and your feedback would be valuable.

Resources