Skip to content

Commit fdd2624

Browse files
Add MDC fields to custom fields for logback
This change introduces a new configuration tag for the encoder: `<customFieldMdcKeyName>`. An example configuration is ``` <encoder class="com.sap.hcp.cf.logback.encoder.JsonEncoder"> <customFieldMdcKeyName>custom-field</customFieldMdcKeyName> <customFieldMdcKeyName>test-field</customFieldMdcKeyName> </encoder> ``` During the marshalling of a log messages the MDC will be inspected for the fields *custom-field* and *test-field*. If present, they will be written in the nested *custom_fields* object of the output json. This feature allows for special parsing of this nested data structure.
1 parent 5653275 commit fdd2624

File tree

15 files changed

+726
-431
lines changed

15 files changed

+726
-431
lines changed

cf-java-logging-support-core/src/main/java/com/sap/hcp/cf/logging/common/converter/DefaultArgsConverter.java

Lines changed: 0 additions & 69 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.sap.hcp.cf.logging.common.converter;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.fasterxml.jackson.jr.ob.JSON;
11+
import com.fasterxml.jackson.jr.ob.JSONComposer;
12+
import com.fasterxml.jackson.jr.ob.comp.ObjectComposer;
13+
import com.sap.hcp.cf.logging.common.customfields.CustomField;
14+
15+
public class DefaultCustomFieldsConverter {
16+
17+
private String fieldName = null;
18+
private boolean embed = true;
19+
20+
public void setFieldName(String fieldName) {
21+
if (fieldName != null) {
22+
this.fieldName = fieldName;
23+
embed = false;
24+
}
25+
}
26+
27+
public void convert(Object[] arguments, StringBuilder appendTo) {
28+
convert(appendTo, Collections.emptyMap(), arguments);
29+
}
30+
31+
public void convert(StringBuilder appendTo, Map<String, String> mdcCustomFields, Object... arguments) {
32+
List<CustomField> customFields = getCustomFields(arguments);
33+
if (!customFields.isEmpty() || !mdcCustomFields.isEmpty()) {
34+
try {
35+
if (!embed) {
36+
appendTo.append(JSON.std.asString(fieldName)).append(":");
37+
}
38+
/*
39+
* -- no matter whether we embed or not, it seems easier to
40+
* compose -- a JSON object from the key/value pairs. -- if we
41+
* embed that object, we simply chop off the outermost curly
42+
* braces.
43+
*/
44+
ObjectComposer<JSONComposer<String>> oc = JSON.std.composeString().startObject();
45+
for (CustomField cf : customFields) {
46+
oc.putObject(cf.getKey(), cf.getValue());
47+
}
48+
for (Map.Entry<String, String> mdcField : mdcCustomFields.entrySet()) {
49+
oc.put(mdcField.getKey(), mdcField.getValue());
50+
}
51+
String result = oc.end().finish().trim();
52+
if (embed) {
53+
/* -- chop off curly braces -- */
54+
appendTo.append(result.substring(1, result.length() - 1));
55+
} else {
56+
appendTo.append(result);
57+
}
58+
} catch (Exception ex) {
59+
/* -- avoids substitute logger warnings on startup -- */
60+
LoggerFactory.getLogger(DefaultCustomFieldsConverter.class).error("Conversion failed ", ex);
61+
}
62+
}
63+
}
64+
65+
private List<CustomField> getCustomFields(Object[] arguments) {
66+
if (arguments == null || arguments.length == 0) {
67+
return Collections.emptyList();
68+
}
69+
List<CustomField> customFields = new ArrayList<CustomField>();
70+
for (Object argument : arguments) {
71+
if (argument instanceof CustomField) {
72+
customFields.add((CustomField) argument);
73+
}
74+
}
75+
return customFields;
76+
}
77+
}

cf-java-logging-support-core/src/test/java/com/sap/hcp/cf/logging/common/converter/AbstractConverterTest.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@
1111

1212
import com.fasterxml.jackson.jr.ob.JSON;
1313
import com.fasterxml.jackson.jr.ob.JSONObjectException;
14-
import com.sap.hcp.cf.logging.common.customfields.CustomField;
1514

1615
public abstract class AbstractConverterTest {
17-
protected static final String PREFIX = "prefix";
1816
protected static final String EMPTY = "";
1917
protected static final String SOME_KEY = "some_key";
2018
protected static final String SOME_VALUE = "some value";
2119
protected static final String STRANGE_SEQ = "}{:\",\"";
2220
protected static final String SOME_OTHER_KEY = "some_other_key";
2321
protected static final String SOME_OTHER_VALUE = "some other value";
2422
protected static final String TEST_MSG_NO_ARGS = "This is a test ";
25-
protected static final Object[] NO_ARGS = new Object[0];
26-
protected static final Object[] NON_CUSTOM_ARGS = new Object[] { new String("standard") };
2723

2824
protected String formatMsg(DefaultMessageConverter mc, String msg) {
2925
StringBuilder sb = new StringBuilder();
@@ -37,34 +33,12 @@ protected String formatProps(DefaultPropertiesConverter pc) {
3733
return sb.toString();
3834
}
3935

40-
protected String formatArgs(DefaultArgsConverter ac, Object[] args) {
41-
StringBuilder sb = new StringBuilder();
42-
ac.convert(args, sb);
43-
return sb.toString();
44-
}
45-
4636
protected String formatStacktrace(DefaultStacktraceConverter dstc, Throwable t) {
4737
StringBuilder sb = new StringBuilder();
4838
dstc.convert(t, sb);
4939
return sb.toString();
5040
}
5141

52-
protected Map<String, Object> makeMap(CustomField[] custFields) {
53-
Map<String, Object> map = new HashMap<String, Object>();
54-
for (CustomField cf: custFields) {
55-
map.put(cf.getKey(), cf.getValue());
56-
}
57-
return map;
58-
}
59-
60-
protected Map<String, Object> makeMap(String[] keys) {
61-
Map<String, Object> map = new HashMap<String, Object>();
62-
for (String key: keys) {
63-
map.put(key, MDC.get(key));
64-
}
65-
return map;
66-
}
67-
6842
protected Map<String, Object> mdcMap() {
6943
return mdcMap(null);
7044
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.sap.hcp.cf.logging.common.converter;
2+
3+
import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField;
4+
import static org.hamcrest.Matchers.allOf;
5+
import static org.hamcrest.Matchers.hasEntry;
6+
import static org.hamcrest.Matchers.hasToString;
7+
import static org.junit.Assert.assertThat;
8+
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
import org.junit.Before;
14+
import org.junit.Test;
15+
16+
import com.fasterxml.jackson.jr.ob.JSON;
17+
18+
public class DefaultCustomFieldsConverterTest {
19+
20+
private static final String HACK_ATTEMPT = "}{:\",\"";
21+
private DefaultCustomFieldsConverter converter;
22+
23+
@Before
24+
public void initConverter() {
25+
this.converter = new DefaultCustomFieldsConverter();
26+
}
27+
28+
@Test
29+
public void emptyMdcAndArguments() {
30+
StringBuilder sb = new StringBuilder();
31+
32+
converter.convert(sb, Collections.emptyMap());
33+
34+
assertThat(sb, hasToString(""));
35+
}
36+
37+
@Test
38+
public void standardArgument() throws Exception {
39+
StringBuilder sb = new StringBuilder();
40+
converter.convert(sb, Collections.emptyMap(), "an_argument");
41+
assertThat(sb, hasToString(""));
42+
}
43+
44+
@Test
45+
public void singleCustomFieldArgumentEmbedded() throws Exception {
46+
StringBuilder sb = new StringBuilder();
47+
48+
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
49+
50+
assertThat(unmarshal(sb), hasEntry("some key", "some value"));
51+
}
52+
53+
@Test
54+
public void singleCustomFieldArgumentPrefix() throws Exception {
55+
converter.setFieldName("prefix");
56+
StringBuilder sb = new StringBuilder();
57+
58+
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
59+
60+
assertThat(unmarshalPrefixed(sb, "prefix"), hasEntry("some key", "some value"));
61+
}
62+
63+
@Test
64+
public void singleMdcField() throws Exception {
65+
StringBuilder sb = new StringBuilder();
66+
67+
@SuppressWarnings("serial")
68+
Map<String, String> mdcFields = new HashMap<String, String>() {
69+
{
70+
put("some key", "some value");
71+
}};
72+
73+
converter.convert(sb, mdcFields);
74+
75+
assertThat(unmarshal(sb), hasEntry("some key", "some value"));
76+
}
77+
78+
@Test
79+
public void mergesMdcFieldsAndArguments() throws Exception {
80+
StringBuilder sb = new StringBuilder();
81+
82+
@SuppressWarnings("serial")
83+
Map<String, String> mdcFields = new HashMap<String, String>() {
84+
{
85+
put("mdc key", "mdc value");
86+
}
87+
};
88+
89+
converter.convert(sb, mdcFields, customField("some key", "some value"));
90+
91+
assertThat(unmarshal(sb), allOf(hasEntry("some key", "some value"), hasEntry("mdc key", "mdc value")));
92+
}
93+
94+
@Test
95+
public void properlyEscapesValues() throws Exception {
96+
StringBuilder sb = new StringBuilder();
97+
98+
converter.convert(sb, Collections.emptyMap(), customField("some key", HACK_ATTEMPT));
99+
100+
assertThat(unmarshal(sb), hasEntry("some key", HACK_ATTEMPT));
101+
}
102+
103+
@Test
104+
public void properlyEscapesKeys() throws Exception {
105+
StringBuilder sb = new StringBuilder();
106+
107+
converter.convert(sb, Collections.emptyMap(), customField(HACK_ATTEMPT, "some value"));
108+
109+
assertThat(unmarshal(sb), hasEntry(HACK_ATTEMPT, "some value"));
110+
}
111+
112+
@Test
113+
public void properlyEscapesMdcFields() throws Exception {
114+
StringBuilder sb = new StringBuilder();
115+
116+
@SuppressWarnings("serial")
117+
Map<String, String> mdcFields = new HashMap<String, String>() {
118+
{
119+
put(HACK_ATTEMPT, HACK_ATTEMPT);
120+
}};
121+
122+
converter.convert(sb, mdcFields);
123+
124+
assertThat(unmarshal(sb), hasEntry(HACK_ATTEMPT, HACK_ATTEMPT));
125+
126+
}
127+
128+
@Test
129+
public void properlyEscapesFieldNames() throws Exception {
130+
converter.setFieldName(HACK_ATTEMPT);
131+
StringBuilder sb = new StringBuilder();
132+
133+
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
134+
135+
assertThat(unmarshalPrefixed(sb, HACK_ATTEMPT), hasEntry("some key", "some value"));
136+
}
137+
138+
private static Map<String, Object> unmarshal(StringBuilder sb) throws Exception {
139+
return JSON.std.mapFrom("{" + sb.toString() + "}");
140+
}
141+
142+
@SuppressWarnings("unchecked")
143+
private static Map<String, Object> unmarshalPrefixed(StringBuilder sb, String prefix)
144+
throws Exception {
145+
return (Map<String, Object>) unmarshal(sb).get(prefix);
146+
}
147+
148+
}
149+

0 commit comments

Comments
 (0)