API Logging in Json format - json

I would like to log API request/response as json format.
The expected LogEntry is something like
{
"timestamp" : "...",
"level" : "DEBUG",
"headers" : [
"header1" : "value1",
"header2" : "value2",
"header3" : "value3"
],
"requestPayload" : "<Request Json>" // prefereablly as sub-document, worst case string is fine.
"labels" : [ //key fields which can be used for searching the logentry
"searchField1" : "....",
"searchField2" : "....",
"searchField3" : "...."
]
}
My Question is :
Using Logback, How to log nested fields (e.g. headers, labels, requestPaylod in above example), as json sub-document. I tried MDC, but its limited to
Map of 'String, String' only, and considers all fields after first level as String.
I hate to write my custom logger for this, and would like to use the goodness of proven logging frameworks(logback/log4j) to control the logging level, time stamping log event, etc.

For request/response logging try Logbook and logstash-logback-encoder. You'll be able to use Markers to add structured contents.
For adding 'your own' fields in a JSON structure, I've written a code generator you might find interesting, it adds builder support: json-log-domain

You can use logback-contrib's JsonLayout inside any Logback appender. For example:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
<prettyPrint>false</prettyPrint>
</jsonFormatter>
<timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
<appendLineSeparator>true</appendLineSeparator>
</layout>
</appender>
Given log statements like this ...
MDC.put("header1", "headerValue1");
logger.info("hello!");
logger.info("good bye!");
... the use of JsonLayout would result in Logback writing this:
{"timestamp":"2017-08-15 09:06:41.813","level":"INFO","thread":"main","mdc":{"header1":"headerValue1"},"logger":"com.stackoverflow.logback.LogbackTest","message":"hello!","context":"default"}
{"timestamp":"2017-08-15 09:06:41.887","level":"INFO","thread":"main","mdc":{"header1":"headerValue1"},"logger":"com.stackoverflow.logback.LogbackTest","message":"good bye!","context":"default"}
I think this ticks the boxes of writing your log events as JSON documents whilst still retaining Logback's behaviour such as "control the logging level, time stamping log event, etc". There is some built-in support for changing the JSON format (e.g. you can include/exclude context, logger name etc) but the JsonLayout class provides an extension point which would allow you to change the attribute names in the resulting JSON by extending JsonLayout and overriding toJsonMap().
Edit 1: addressing your reply ("my question was more on how to achieve json subdocument/nesting as mentioned. MDC is limited to map of only") ... you could serialise your complex MDC values to JSON and add the serialised JSON representations to MDC. For example:
Map<String, Object> complexMdcValue = new HashMap<>();
Map<String, Object> childMdcValue = new HashMap<>();
childMdcValue.put("name", "Joe");
childMdcValue.put("type", "Martian");
complexMdcValue.put("child", childMdcValue);
complexMdcValue.put("category", "etc");
MDC.put("complexNestedValue", objectMapper.writeValueAsString(complexMdcValue));
logger.info("hello!");
Would produce this output (in which the MVC logged key "complexNestedValue" is JSON containing a sub document):
{"timestamp":"2017-08-27 18:03:46.706","level":"INFO","thread":"main","mdc":{"complexNestedValue":"{\"category\":\"etc\",\"child\":{\"name\":\"Joe\",\"type\":\"Martian\"}}"},"logger":"com.stackoverflow.logback.LogbackTest","message":"hello!","context":"default"}

Related

How to split the data of NodeObject in Apache Flink

I'm using Flink to process the data coming from some data source (such as Kafka, Pravega etc).
In my case, the data source is Pravega, which provided me a flink connector.
My data source is sending me some JSON data as below:
{"key": "value"}
{"key": "value2"}
{"key": "value3"}
...
...
Here is my piece of code:
PravegaDeserializationSchema<ObjectNode> adapter = new PravegaDeserializationSchema<>(ObjectNode.class, new JavaSerializer<>());
FlinkPravegaReader<ObjectNode> source = FlinkPravegaReader.<ObjectNode>builder()
.withPravegaConfig(pravegaConfig)
.forStream(stream)
.withDeserializationSchema(adapter)
.build();
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<ObjectNode> dataStream = env.addSource(source).name("Pravega Stream");
dataStream.map(new MapFunction<ObjectNode, String>() {
#Override
public String map(ObjectNode node) throws Exception {
return node.toString();
}
})
.keyBy("word") // ERROR
.timeWindow(Time.seconds(10))
.sum("count");
As you see, I used the FlinkPravegaReader and a proper deserializer to get the JSON stream coming from Pravega.
Then I try to transform the JSON data into a String, KeyBy them and count them.
However, I get an error:
The program finished with the following exception:
Field expression must be equal to '*' or '_' for non-composite types.
org.apache.flink.api.common.operators.Keys$ExpressionKeys.<init>(Keys.java:342)
org.apache.flink.streaming.api.datastream.DataStream.keyBy(DataStream.java:340)
myflink.StreamingJob.main(StreamingJob.java:114)
It seems that KeyBy threw this exception.
Well, I'm not a Flink expert so I don't know why. I've read the source code of the official example WordCount. In that example, there is a custtom splitter, which is used to split the String data into words.
So I'm thinking if I need to use some kind of splitter in this case too? If so, what kind of splitter should I use? Can you show me an example? If not, why did I get such an error and how to solve it?
I guess you have read the document about how to specify keys
Specify keys
The example codes use keyby("word") because word is a field of POJO type WC.
// some ordinary POJO (Plain old Java Object)
public class WC {
public String word;
public int count;
}
DataStream<WC> words = // [...]
DataStream<WC> wordCounts = words.keyBy("word").window(/*window specification*/);
In your case, you put a map operator before keyBy, and the output of this map operator is a string. So there is obviously no word field in your case. If you actually want to group this string stream, you need to write it like this .keyBy(String::toString)
Or you can even implement a customized keySelector to generate your own key.
Customized Key Selector

Knime JSON transformer - Adding an attribute to a JSON object

I have converted some columns to JSON using the columns to json node. The output from that is:
{
"Material" : 101,
"UOM" : "GRAM",
"EAN" : 7698,
"Description" : "CHALK BOX"
}
I would like to add the value of the material property as a key to each JSON object. So, my desired output is:
"101": {
"Material" : 101,
"UOM" : "GRAM",
"EAN" : 7698,
"Description" : "CHALK BOX"
}
I have tried entering the following expression in the JSON transformer node but all I get is a question mark in the new column it generates:
$Material$:{"Material":$Material$,"UOM":$UOM$,"EAN":$EAN$,"Description":$Description$}
I have also tried replacing the $Material$ with "Material" but got the same result.
How would I go about this, please?
In case you convert the Material column to String (for example with String Manipulator), you can easily configure the Columns to JSON:
As you can see the Data bound key is the important part.
The String Manipulator node configuration (string($Material$)):
I finally managed to solve this by a different method.
I split the JSON data into several columns, then used the join function to create a string in the required order. I put the resulting string through the string to JSON node to create the new JSON object.
Thanks for all your tips and comments !

Changing an immutable object F#

I think the title of this is wrong but can't create a title that reflects, in the abstract, what I want to achieve.
I am writing a function which calls a service and retrieves data as a JSON string. The function parses the string with a JSON type provider. Under certain conditions I want to amend properties on that JSON object and then return the string of the amended object. So if the response from the call was
{"property1" : "value1","property2" : "value2", "property3": "value3" }
I want to change property3 to a new value and then return the JSON string.
If the JsonProvider was mutable this would be an exercise like:
type JsonResponse =
JsonProvider<""" {"property1" : "value1",
"property2" : "value2",
"property3": "value3" } """>
let jsonResponse = JsonResponse.Parse(response)
jsonResponse.Property3 <- "new value"
jsonResponse.ToString()
However, this does not work as the property cannot be set. I am trying to ascertain the best way to resolve this. I am quite happy to initialise a new object based on the original response but with amended parameters but I am not sure if there is an easy way to achieve this.
For reference, the JSON object is much more involved than the flat example given and contains a deep hierarchy.
Yes, you would need to create a new object, changing the bits you want and using the existing object's values for the rest. We added write APIs for both the XML and JSON type providers a while back. You will notice the types representing your JSON have constructors on them. You can see an example of this in use at the bottom of this link

Can I get MOXy to not output an element when generating json?

An instance of my JAXB Object model contains an element that I want output when I generate Xml for the instance but not when I generate json
i.e I want
<release-group>
<type>Album</type>
<title>Fred</title>
</release-group>
and
"release-group" : {
"title" : "fred",
},
but have
"release-group" : {
"type" : "Album",
"title" : "fred"
},
Can I do this using the oxml.xml mapping file
This answer shows how I can do it for attributes using the transient keyword, Can I get MOXy to not output an attribute when generating json? but I cannot get that to work for an element.
Sorry problem solved, a bit of confusion on my part.
The example I gave above didn't actually match the true situation accurately, type was actually output as an attribute for Xml, but use of transient didnt work because it had been renamed in the JAXB
#XmlAttribute(name = "target-type", required = true)
#XmlSchemaType(name = "anyURI")
protected String targetType;
So adding
<java-type name="ReleaseGroup">
<java-attributes>
<xml-transient java-attribute="targetType"/>
</java-attributes>
</java-type>
worked, previously I was incorrectly doing
<java-type name="ReleaseGroup">
<java-attributes>
<xml-transient java-attribute="target-type"/>
</java-attributes>
</java-type>

How to use JMS Message Transformation in ActiveMQ with Stomp/JSON

I am sending messages in JSON format to an ActiveMQ server. I am trying to use JMS Transformation to transform the JSON encoded object into a true Java Object in hopes of being able to use selectors on the data inside.
Here is a link to the documentation on Stomp and Message Transformation.
Here is a link to a discussion on the patch where someone shows an example of a legal JSON object
The format of the JSON objects I am sending (in pretty print) are similar to this:
{
"msg": {
"flag1" : "value1",
"flag2" : "value2"
}
}
The messages arrive in the message queue, but with the transformation-error property set to 'msg : msg'.
The only format accepted by the transformation jms-map-json or jms-object-json is a simple Map format, which in JSON is:
{"map" :
{"entry" :
[
{ "string1": [ "key1", "value1" ] },
{ "string2": [ "key2", "value2" ] }
]
}
}
This is the same format shown in the discussion forum. This format represents a name/value pair map object in java.
Selectors are only usable on Properties and Headers.
you can use any JSON notation for your jms-object-json transformations as long as XStream can handle it. You can take a look at test cases for some examples. There, we use SamplePojo class:
https://svn.apache.org/repos/asf/activemq/trunk/activemq-stomp/src/test/java/org/apache/activemq/transport/stomp/SamplePojo.java
which is properly annotated so it can be represented with the following JSON
{"pojo":{
"name":"Dejan",
"city":"Belgrade"
}}
You can try using the same approach for your classes.
Hope this helps,
Dejan
It should be mentioned that ActiveMQ version must at least 5.8, because with 5.6 version I had problem when transformation just did not work.