Adding custom field to log4j json layout breaks spring logging structure - json

I have a Spring Boot (2.2.6) application that uses Log4j2 (with Slf4j). Log4j is configured to use the json layout and in the end I want to ingest the logs in Datadog. For that the 'serviceName' is important as a field in the json.
Now according to the log4j docu (https://logging.apache.org/log4j/2.x/manual/layouts.html#JSONLayout) one can add a custom field with the 'KeyValuePair' tags and that works. Unfortunately this breaks the normal structure of the spring logs.
Log4j2.xml config:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<!-- Write logs to stdout, JSON, one line per log event -->
<Console name="Console" target="SYSTEM_OUT">
<JSONLayout compact="true" eventEol="true" includeStacktrace="true" locationInfo="true"
stacktraceAsString="true" properties="true">
<KeyValuePair key="serviceName" value="$${env:APPLICATION_NAME:-local}-sidecar"/> <!-- fine w/o this line -->
</JSONLayout>
</Console>
</Appenders>
<Loggers>
<Logger name="my.service" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.apache" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
Log w/o custom field:
{
"thread": "main",
"level": "INFO",
"loggerName": "com.nginxtrafficsidecar.ApplicationKt",
"message": "Starting ApplicationKt on xxx with PID 17300 (C:\\Users\\Felix\\Documents\\code\\nginx-traffic-sidecar\\build\\classes\\kotlin\\main started by Felix in C:\\Users\\Felix\\Documents\\code\\nginx-traffic-sidecar)",
"endOfBatch": false,
"loggerFqcn": "org.apache.commons.logging.LogAdapter$Log4jLog",
"threadId": 1,
"instant": {
"epochSecond": 1587975181,
"nanoOfSecond": 331370300
},
"source": {
"class": "org.springframework.boot.StartupInfoLogger",
"method": "logStarting",
"file": "StartupInfoLogger.java",
"line": 55,
"classLoaderName": "app"
},
"contextMap": {},
"threadPriority": 5
}
log w/ custom field:
{
"logEvent": "Starting ApplicationKt on xxx with PID 9732 (C:\\Users\\Felix\\Documents\\code\\nginx-traffic-sidecar\\build\\classes\\kotlin\\main started by Felix in C:\\Users\\Felix\\Documents\\code\\nginx-traffic-sidecar)",
"serviceName": "local-sidecar"
}
Spring docu mentions how this might work with logback, but not log4j (https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-custom-log-configuration, end of chapter)
I've searched but couldn't find anything useful. Any ideas how i can add a custom field to the json log while still preserving all fields coming from Spring?
Thanks,
Felix

download Jackson Annotations and import it because JsonLayout use Jackson Annotations internal code.
in my case, download and import these
jackson-annotations-2.10.3.jar
jackson-core-2.10.3.jar
jackson-databind-2.10.3.jar
add <JsonLayout>...</JsonLayout> in your config(in my case log4j2.xml) like below
<JsonLayout>
<KeyValuePair key="testKey" value="testValue"/>
</JsonLayout>
Replace key and value to what you want to use. In my case key is "testKey" and value is "testValue"
run and check your log!
it it my full sample code and xml configuration info.
code
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
public class LogTest{
private static final Logger logger = LogManager.getLogger(LogTest.class.getName());
public static void main(String[] args) {
ThreadContext.put("logFileName", "testFile1" );
logger.info("log printed! - testFile1");
ThreadContext.put("logFileName", "testFile2" );
logger.info("log printed! - testFile2");
ThreadContext.remove("logFileName");
}
}
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Routing name="RoutingAppender">
<Routes pattern="${ctx:logFileName}">
<Route>
<RollingFile name="Rolling-${ctx:logFileName}"
fileName="./logs/${ctx:logFileName}.log"
filePattern="./logs/${ctx:logFileName}.%i.log.gz">
<JsonLayout>
<KeyValuePair key="testKey" value="testValue"/>
</JsonLayout>
<SizeBasedTriggeringPolicy size="512" />
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<Root level="all">
<AppenderRef ref="RoutingAppender" />
</Root>
</Loggers>
</Configuration>
output
{
"instant" : {
"epochSecond" : 1588590944,
"nanoOfSecond" : 469000000
},
"thread" : "main",
"level" : "INFO",
"loggerName" : "com.test.LogTest",
"message" : "log printed! - testFile2",
"endOfBatch" : false,
"loggerFqcn" : "org.apache.logging.log4j.spi.AbstractLogger",
"threadId" : 1,
"threadPriority" : 5,
"testKey" : "testValue"
}

Related

SLF4J Logging Exception as JSON or Single-Line String

I need to be able to log an Exception as a single record in my logs, which will make it much easier to investigate issues in Kibana / Elasticsearch. From what I can tell from the documentation for slf4j, the Logger interface requires messages to be Strings. Is my only option to remove newline characters from the Exception message before passing it to the Logger?
For context, I am using the following:
.m2/repository/org/slf4j/slf4j-api/1.7.28/slf4j-api-1.7.28.jar
Java 11
Sprint Boot version 2.1.8.RELEASE
This is a trimmed down version of my custom exception handler :
import my.error.Error; // custom Error class
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private void logError(Error error, Exception ex){
logger.error(String.format("id: %s, message: %s", error.getId(), ex.getMessage()), ex);
}
}
Initially I had attempted to alter the logging behavior with changes to my logback.xml file, within src/main/java/resources. Unfortunately, this appears to do nothing, so my assumption now is that the CustomExceptionHandler that I am creating is overruling the specification set in the logback.xml file. Specifically, the <pattern> of the <encoder> has been changed based on other research. It's attempting to replace all newline characters.
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" debug="true">
<include resource="org/springframework/boot/logging/logback/base.xml" />
<appender name="FILE-ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/gateway.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/archived/gateway/gateway.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<!-- each archived file, size max 5MB -->
<maxFileSize>5MB</maxFileSize>
<!-- total size of all archive files, if total size > 10GB, it will delete old archived file -->
<totalSizeCap>10GB</totalSizeCap>
<!-- 30 days to keep -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d %p %c{1.} [%t] %m MULTIEXCEPTION %replace(%xException){'\n','\u2028'}%nopex%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE-ROLLING" level="DEBUG" additivity="false"/>
</root>
<springProfile name="local">
<logger name="my.gateway" level="TRACE" additivity="false">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE-ROLLING" />
</logger>
<logger name="com.netflix" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
<logger name="org.springframework" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
<logger name="com" level="INFO" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
<logger name="gov" level="INFO" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
<logger name="org" level="INFO" additivity="false">
<appender-ref ref="CONSOLE" />
</logger>
</springProfile>
</configuration>
Links
Logback Docs
SLF4J Docs
SLF4J Manual
Combine Logback and SLF4J
Logback and SLF4J - StructuredArguments
JSON Logging w/ Logback
Collapse Logs - Multiple Lines into One
Apache Log4j Layouts
SO : Override Logback 1
SO : Override Logback 2
SO : Make Logback Output JSON
Format SLF4J
Collapse multi-line logs into one with Logback or Log4j2
Baeldung Log4j2 JSON Logging
Java Logging Guide
I changed my logError method to this:
import org.slf4j.MDC; // new import to add property
// ... other imports from before
private void logError(Error error, Exception exception){
MDC.put("error id", error.getId().toString()); // add a new property to the thread context
LOGGER.error(exception.getMessage(), exception);
MDC.clear(); // remove all new properties after logging the error
}
My new pom.xml File Dependencies:
<dependencies>
<!-- logging -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-jackson</artifactId>
<version>0.1.5</version>
</dependency>
<!-- ch.qos.logback.contrib.json.classic.JsonLayout -->
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-json-classic</artifactId>
<version>0.1.5</version>
</dependency>
<!-- Other dependencies -->
<!-- ... -->
<!-- ... -->
</dependencies>
New logback.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE-ROLLING">
<!-- Old encoder is removed:
<encoder>
<pattern>%d %p %c{1.} [%t] %m%n</pattern>
</encoder>
-->
<file>${LOG_PATH}/data.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/archived/data/data.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<!-- each archived file, size max 10MB -->
<maxFileSize>10MB</maxFileSize>
<!-- total size of all archive files, if total size > 20GB, it will delete old archived file -->
<maxHistory>60</maxHistory>
<!-- 60 days to keep -->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!-- TODO : configure a pretty printer -->
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<provider class="net.logstash.logback.composite.loggingevent.ArgumentsJsonProvider"/>
</encoder>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- TODO : configure a pretty printer -->
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<provider class="net.logstash.logback.composite.loggingevent.ArgumentsJsonProvider"/>
</encoder>
</appender>
<!-- The base logback configuration is removed, but note that this will also remove the default Spring start-up messages, so those need to be added back into the <root>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
-->
<root level="INFO">
<appender-ref ref="FILE-ROLLING"/>
<appender-ref ref="CONSOLE"/> <!-- This adds Spring start-up messages back into the logs -->
</root>
<springProfile name="local">
<logger additivity="false" level="TRACE" name="org.springframework.web">
<appender-ref ref="CONSOLE"/>
</logger>
</springProfile>
<springProfile name="dev">
<logger additivity="false" level="DEBUG" name="org.springframework">
<appender-ref ref="FILE-ROLLING"/>
</logger>
</springProfile>
<springProfile name="prod">
<logger additivity="false" level="DEBUG" name="my.app.path">
<appender-ref ref="FILE-ROLLING"/>
</logger>
<logger additivity="false" level="DEBUG" name="org.jooq">
<appender-ref ref="FILE-ROLLING"/>
</logger>
<logger additivity="false" level="DEBUG" name="org.springframework">
<appender-ref ref="FILE-ROLLING"/>
</logger>
</springProfile>
</configuration>
SLF4J Docs - Mapped Diagnostic Context (MDC)
Baeldung Example - MDC + Log4J2

How to log specific HTTP header using logback.xml

I would like to create a console appender that displays some log info, and also prints out a particular http header, similar to this:
> [INFO] { "time": "2017-08-31 12:14:32,583", "app-id": "my-app", "my-header": "my-header-value" } -- "Hello, World"
I have created a logback-spring.xml file like below, but "my-header" just prints out blank.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty name="appId" source="spring.app.application_id"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>INFO</level>
</filter>
<encoder>
<pattern>
%clr(%5p) %clr({ "time": "%date{ISO8601}", "app-id": "${appId}", "my-header": "%X{my-header}"}){faint} -- %msg%n
</pattern>
<charset>utf8</charset>
</encoder>
</appender>
​
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>
I have read that using logback-access gives you access to HTTP request/response properties, but when I try setting the encoder class I cannot use any of the classic logback conversion words:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- Appender to log to console -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- Minimum logging level to be presented in the console logs-->
<level>INFO</level>
</filter>
<encoder class="ch.qos.logback.access.PatternLayoutEncoder">
<pattern class="ch.qos.logback.access.PatternLayoutEncoder">
%clr(%5p) %clr({ "time": "%date{ISO8601}", "app-id": "${appId}", "my-header": "%header{my-header}"}){faint} -- %msg%n
</pattern>
<charset>utf8</charset>
</encoder>
</appender>
​
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>
The logback above gives these errors:
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:1.4.0.RELEASE:run (default-cli) on project pd-thundera-server: An exception occurred while running. null: InvocationTargetException: Logback configuration error detected:
[ERROR] ERROR in ch.qos.logback.core.pattern.parser.Compiler#4782f0a4 - There is no conversion class registered for conversion word [p]
[ERROR] ERROR in ch.qos.logback.core.pattern.parser.Compiler#4782f0a4 - [p] is not a valid conversion word
[ERROR] ERROR in ch.qos.logback.core.pattern.parser.Compiler#6071227e - There is no conversion class registered for conversion word [msg]
[ERROR] ERROR in ch.qos.logback.core.pattern.parser.Compiler#6071227e - [msg] is not a valid conversion word
How can I access a request header?
This configuration worked for me:
<encoder>
<charset>utf-8</charset>
<pattern>%t{yyyy-MM-dd HH:mm:ss,SSS} %h X-Forwarded-For: %header{X-Forwarded-For} "%r", Response status:%s, Bytes sent:%b, Response time:%D</pattern>
</encoder>
Name of the header goes in between curly braces, e.g. %header{Content-type} or %header{My-header}, or just %header if you want all headers to be logged.
Source:
https://logback.qos.ch/access.html#configuration
https://logback.qos.ch/manual/layouts.html#AccessPatternLayout
Please find below example with Spring
If you want to log http headers then you need to use MDC feature: http://logback.qos.ch/manual/mdc.html
create Filter:
#Component
class RequestHeaderFilterConfig : Filter {
private val xRequestId = "X-Request-Id"
override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {
val httpRequest = request as HttpServletRequest
MDC.put(xRequestId, httpRequest.getHeader(xRequestId))
chain?.doFilter(request, response)
}
override fun destroy() = MDC.remove(xRequestId)
}
Controller:
#PostMapping
fun operation(#RequestHeader(value = "X-Request-Id", required = false) xRequestId: String? = null): ResponseEntity<Output> {
....
}
logback.xml
<Pattern>%d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level [X-Request-Id: %X{X-Request-Id}] %logger{36}.%M:%line - %msg%n</Pattern>

log4j2 exception handling not working

I am using log4j2 with this 2 dependencies:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.6.2</version>
</dependency>
When I try to log for example an error with a throwable like:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.testng.annotations.Test;
public class Test {
private static final Logger logger = LogManager.getLogger(Test.class);
#Test
public void testSendMessage() throws Exception {
Exception exception = new Exception("some exception");
logger.error("error with exception", exception);
}
}
using patternlayout:
<Configuration>
<properties>
<property name="filters">org.testng,org.apache.maven,sun.reflect,java.lang.reflect</property>
</properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT" direct="true">
<PatternLayout pattern="%maxLen{%d{DEFAULT} [%p] %c{-3}:%L - %enc{%m} %xEx{filters(${filters})}%n}{200}"/>
</Console>
</Appenders>
<Loggers>
<logger name="my.test.class.path" level="trace" additivity="false">
<AppenderRef ref="ConsoleAppender" />
</logger>
</Loggers>
</Configuration>
Then the filtered packages won't disappear from the stacktrace, I can't even manipulate the stacktrace in any way like maximizing the lines:
%xEx{5}
Highlightning also don't work in eclipse nor in Kibana(ELK environment).
Can anybody help?
Could it be that the %xEx PatternLayout converter doesn't support property substitution in its options?
What if you put the packages you want to filter directly in the filters list?
It may be worth raising a Jira ticket on the Log4j 2 issue tracker for this.
try to remove the ending{200}. i think there is an issue using more sub parameters.
here is a snippet which works. please note if you add additional things like separator(|) it will stop working [tested in version 2.8.1 and 2.9.1]
<Properties>
<Property name="exfilters">org.jboss,java.lang.reflect,sun.reflect</Property>
<Property name="log-pattern">%d %-5p %m%n%xEx{filters(${exfilters})}/Property>
</Properties>
...
<PatternLayout pattern="${sys:log-pattern}"/>
Works for me on Log4j2 v 2.17 (and also for rThrowable):
<Properties>
<Property name="PACKAGE_FILTER">org.jboss,java.lang.reflect,sun.reflect</Property>
<Property name="LOG_PATTERN">%xThrowable{filters(${PACKAGE_FILTER})}</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>

Camel Fuse Route from JSON to JSON not working

I am playing with the Camel Fuse tooling to convert from JSON to JSON through a data mapper. I have been able to do conversions from XML to JSON using data mappers.
However, when I try to receive a json object and then data map it and then send it the data mapping fails with the below message.
Note that I am building something that will run on a Tomcat server, that is why I am using the camel-config.xml file.
Any thoughts on what might be amiss?
Patrik
java.lang.NullPointerException
at org.apache.camel.component.dozer.DozerProducer.process(DozerProducer.java:78)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:460)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:162)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
at org.eclipse.jetty.servlets.MultiPartFilter.doFilter(MultiPartFilter.java:146)
at org.apache.camel.component.jetty.CamelFilterWrapper.doFilter(CamelFilterWrapper.java:43)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Unknown Source)java.lang.NullPointerException
at org.apache.camel.component.dozer.DozerProducer.process(DozerProducer.java:78)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.processor.SendProcessor.process(SendProcessor.java:141)
at org.apache.camel.processor.RedeliveryErrorHandler.process(RedeliveryErrorHandler.java:460)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:121)
at org.apache.camel.processor.Pipeline.process(Pipeline.java:83)
at org.apache.camel.processor.CamelInternalProcessor.process(CamelInternalProcessor.java:190)
at org.apache.camel.component.jetty.CamelContinuationServlet.service(CamelContinuationServlet.java:162)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:808)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1669)
at org.eclipse.jetty.servlets.MultiPartFilter.doFilter(MultiPartFilter.java:146)
at org.apache.camel.component.jetty.CamelFilterWrapper.doFilter(CamelFilterWrapper.java:43)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:499)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Unknown Source)
Here is my input .json
{
"id": "138784",
"fields":
{
"description": "Maxed",
"summary": "Max is my name",
"created": "2015-09-28",
"duedate": "2015-09-28",
"updated": "2015-09-28"
}
}
Here is my expected output:
{
"theType" : "Transaction",
"theId" : "0",
"attributes" : {
"valuationDate" : "",
"amount" : "108.15",
"valueDate" : "",
"description" : "description 0",
"type" : "withdrawal",
"verificationId" : "verificationId 0"
},
"type" : "Transaction",
"id" : "0"
}
Here is my transformation map:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://dozer.sourceforge.net http://dozer.sourceforge.net/schema/beanmapping.xsd">
<configuration>
<wildcard>false</wildcard>
</configuration>
<mapping>
<class-a>input.Input</class-a>
<class-b>transaction1.Transaction1</class-b>
<field>
<a>id</a>
<b>attributes.verificationId</b>
</field>
</mapping>
<mapping>
<class-a>input.Fields</class-a>
<class-b>transaction1.Attributes</class-b>
<field>
<a>description</a>
<b>description</b>
</field>
<field>
<a>created</a>
<b>valuationDate</b>
</field>
<field>
<a>duedate</a>
<b>valueDate</b>
</field>
<field>
<a>summary</a>
<b>type</b>
</field>
<field>
<a>updated</a>
<b>amount</b>
</field>
</mapping>
<mapping>
<class-a>org.apache.camel.component.dozer.ExpressionMapper</class-a>
<class-b>transaction1.Transaction1</class-b>
<field custom-converter-id="_expressionMapping" custom-converter-param="constant:Transaction">
<a>expression</a>
<b>theType</b>
</field>
<field custom-converter-id="_expressionMapping" custom-converter-param="constant:0">
<a>expression</a>
<b>theId</b>
</field>
<field custom-converter-id="_expressionMapping" custom-converter-param="constant:2">
<a>expression</a>
<b>id</b>
</field>
</mapping>
</mappings>
And finally here is my route config in the file: camel-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- here we have the Camel route(s). -->
<!-- we must still use the http://camel.apache.org/schema/spring namespace so Camel can load the routes
though Spring JARs is not required -->
<!-- incoming requests from the servlet is routed -->
<!-- is there a header with the key name? -->
<!-- yes so return back a message to the user -->
<!-- if no name parameter then output a syntax to the user -->
<routes xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="jetty:http://localhost:8091/camel/input/input"/>
<log message="Log1 ${body}"/>
<to uri="dozer:transformation11?sourceModel=input.Input&targetModel=transaction0.Transaction0&mappingFile=transformation.xml"/>
<log message="Log2 ${body}"/>
<marshal>
<json library="Jackson"/>
</marshal>
<to uri="jetty:http://localhost:8088/camel/transaction/output?bridgeEndpoint=true&throwExceptionOnFailure=false"/>
</route>
</routes>
Although it is great that JBoss Fuse is getting a graphical mapper I think since it is a new component you should probably give it some time before they fix eventual bugs and optimise the performance. At least for me when I was testing it, it ran quit slowly.
For pure json-to-json mapping perhaps you can look at the component camel-jolt.
http://camel.apache.org/jolt.html

using Nlog and writing to file as json

I think I'm missing something as I can't seem to figure out how to have it write to a log file in json format using NLog setup in configuration file. The straight rolling file works fine, but not the json. The json target only outputs the message (not in json).
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true">
<target xsi:type="File" name="rollingFile" fileName="${basedir}/logs/${shortdate}.log" archiveFileName="${basedir}/logs/{shortdate}_Archive{###}.log" archiveAboveSize="1000000" archiveNumbering="Sequence" layout="${longdate} ${uppercase:${level}} ${callsite} ${message}" />
<target xsi:type="File"
name="rollingFileJson"
fileName="${basedir}/logs/${shortdate}.json"
archiveFileName="${basedir}/logs/{shortdate}_Archive{###}.json"
archiveAboveSize="1000000"
archiveNumbering="Sequence"
layout="${json-encode} ${message}">
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="rollingFile" />
<logger name="*" minlevel="Trace" writeTo="rollingFileJson" />
</rules>
</nlog>
As of the release of NLog 4.0.0 it is possible to use a layout that renders events as structured JSON documents.
Example taken from NLog project site:
<target name="jsonFile" xsi:type="File" fileName="${logFileNamePrefix}.json">
<layout xsi:type="JsonLayout">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:upperCase=true}"/>
<attribute name="message" layout="${message}" />
</layout>
</target>
Edit:
You can also create it in code.
Here is the link to the specific part of documentation.
And here the copied example:
var jsonLayout = new JsonLayout
{
Attributes =
{
new JsonAttribute("type", "${exception:format=Type}"),
new JsonAttribute("message", "${exception:format=Message}"),
new JsonAttribute("innerException", new JsonLayout
{
Attributes =
{
new JsonAttribute("type", "${exception:format=:innerFormat=Type:MaxInnerExceptionLevel=1:InnerExceptionSeparator=}"),
new JsonAttribute("message", "${exception:format=:innerFormat=Message:MaxInnerExceptionLevel=1:InnerExceptionSeparator=}"),
}
},
//don't escape layout
false)
}
};
For more info read the docs.
As per NLog documentation: json-encode will only escape output of another layout using JSON rules. It will not "convert" the output to JSON. You'll have to do that yourself.
'{ "date":"${longdate}","level":"${level}","message":${message}}'
Take a look at this question for more details.