Skip to content

Commit d8f3bec

Browse files
Write new Custom Fields JSON Serialization
The JSON format of custom fields was changed from ``` { /* ... */ "custom_fields": [{"label1":"value1"}, {"label2":"value2"}], /* ... */ } ``` to the new format ``` { /* ... */ "#cf":{"strings":[{"l":"label1","v":"value1","i":0}, {"l":"label2":"v":"value2","i":1}]} /* ... */ } ``` Indices of custom fields are determined by the index during declaration in the log config.
1 parent 17a64cb commit d8f3bec

File tree

22 files changed

+558
-377
lines changed

22 files changed

+558
-377
lines changed
Lines changed: 93 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,124 @@
11
package com.sap.hcp.cf.logging.common.converter;
22

3-
import java.util.ArrayList;
3+
import java.io.IOException;
44
import java.util.Collections;
5+
import java.util.HashMap;
56
import java.util.List;
67
import java.util.Map;
78

89
import org.slf4j.LoggerFactory;
910

11+
import com.fasterxml.jackson.core.JsonProcessingException;
1012
import com.fasterxml.jackson.jr.ob.JSON;
1113
import com.fasterxml.jackson.jr.ob.JSONComposer;
14+
import com.fasterxml.jackson.jr.ob.JSONObjectException;
15+
import com.fasterxml.jackson.jr.ob.comp.ArrayComposer;
1216
import com.fasterxml.jackson.jr.ob.comp.ObjectComposer;
1317
import com.sap.hcp.cf.logging.common.customfields.CustomField;
1418

1519
public class DefaultCustomFieldsConverter {
1620

17-
private String fieldName = null;
18-
private boolean embed = true;
21+
private String fieldName = null;
22+
private boolean embed = true;
23+
private List<String> customFieldKeyNames;
1924

20-
public void setFieldName(String fieldName) {
21-
if (fieldName != null) {
22-
this.fieldName = fieldName;
23-
embed = false;
24-
}
25-
}
25+
public void setFieldName(String fieldName) {
26+
if (fieldName != null) {
27+
this.fieldName = fieldName;
28+
embed = false;
29+
}
30+
}
31+
32+
public void setCustomFieldKeyNames(List<String> customFieldKeyNames) {
33+
this.customFieldKeyNames = customFieldKeyNames;
34+
}
2635

27-
public void convert(StringBuilder appendTo, Map<String, String> mdcCustomFields, Object... arguments) {
28-
List<CustomField> customFields = getCustomFields(arguments);
36+
public void convert(StringBuilder appendTo, Map<String, String> mdcPropertiesMap, Object... arguments) {
37+
if (customFieldKeyNames.isEmpty()) {
38+
return;
39+
}
40+
Map<String, CustomField> customFields = getRegisteredCustomFields(arguments);
41+
Map<String, String> mdcCustomFields = getRegisteredMdcCustomFields(mdcPropertiesMap);
2942
if (!customFields.isEmpty() || !mdcCustomFields.isEmpty()) {
3043
try {
31-
if (!embed) {
32-
appendTo.append(JSON.std.asString(fieldName)).append(":");
33-
}
34-
/*
35-
* -- no matter whether we embed or not, it seems easier to
36-
* compose -- a JSON object from the key/value pairs. -- if we
37-
* embed that object, we simply chop off the outermost curly
38-
* braces.
39-
*/
40-
ObjectComposer<JSONComposer<String>> oc = JSON.std.composeString().startObject();
41-
for (CustomField cf : customFields) {
42-
oc.putObject(cf.getKey(), cf.getValue());
43-
}
44-
for (Map.Entry<String, String> mdcField : mdcCustomFields.entrySet()) {
45-
oc.put(mdcField.getKey(), mdcField.getValue());
46-
}
47-
String result = oc.end().finish().trim();
48-
if (embed) {
49-
/* -- chop off curly braces -- */
50-
appendTo.append(result.substring(1, result.length() - 1));
51-
} else {
52-
appendTo.append(result);
53-
}
44+
ArrayComposer<ObjectComposer<JSONComposer<String>>> oc = startJson(appendTo);
45+
addCustomFields(oc, customFields, mdcCustomFields);
46+
finishJson(oc, appendTo);
5447
} catch (Exception ex) {
5548
/* -- avoids substitute logger warnings on startup -- */
5649
LoggerFactory.getLogger(DefaultCustomFieldsConverter.class).error("Conversion failed ", ex);
5750
}
5851
}
5952
}
6053

61-
private List<CustomField> getCustomFields(Object[] arguments) {
62-
if (arguments == null || arguments.length == 0) {
63-
return Collections.emptyList();
54+
private Map<String, String> getRegisteredMdcCustomFields(Map<String, String> mdcPropertiesMap) {
55+
if (mdcPropertiesMap.isEmpty()) {
56+
return Collections.emptyMap();
57+
}
58+
Map<String, String> mdcCustomFields = new HashMap<>(mdcPropertiesMap.size());
59+
for (Map.Entry<String, String> current : mdcPropertiesMap.entrySet()) {
60+
if (customFieldKeyNames.contains(current.getKey())) {
61+
mdcCustomFields.put(current.getKey(), current.getValue());
62+
}
63+
}
64+
return mdcCustomFields;
65+
}
66+
67+
private Map<String, CustomField> getRegisteredCustomFields(Object... arguments) {
68+
if (arguments == null) {
69+
return Collections.emptyMap();
6470
}
65-
List<CustomField> customFields = new ArrayList<CustomField>();
66-
for (Object argument : arguments) {
67-
if (argument instanceof CustomField) {
68-
customFields.add((CustomField) argument);
71+
Map<String, CustomField> result = new HashMap<>();
72+
for (Object current : arguments) {
73+
if (current instanceof CustomField) {
74+
CustomField field = (CustomField) current;
75+
if (customFieldKeyNames.contains(field.getKey())) {
76+
result.put(field.getKey(), field);
77+
}
6978
}
7079
}
71-
return customFields;
80+
return result;
81+
}
82+
83+
private ArrayComposer<ObjectComposer<JSONComposer<String>>> startJson(StringBuilder appendTo)
84+
throws IOException, JSONObjectException, JsonProcessingException {
85+
if (!embed) {
86+
appendTo.append(JSON.std.asString(fieldName)).append(":");
87+
}
88+
/*
89+
* -- no matter whether we embed or not, it seems easier to compose -- a JSON
90+
* object from the key/value pairs. -- if we embed that object, we simply chop
91+
* off the outermost curly braces.
92+
*/
93+
return JSON.std.composeString().startObject().startArrayField("string");
94+
}
95+
96+
private void addCustomFields(ArrayComposer<ObjectComposer<JSONComposer<String>>> oc,
97+
Map<String, CustomField> customFields, Map<String, String> mdcCustomFields)
98+
throws IOException, JsonProcessingException {
99+
for (int i = 0; i < customFieldKeyNames.size(); i++) {
100+
String key = customFieldKeyNames.get(i);
101+
String value = mdcCustomFields.get(key);
102+
// Let argument CustomField take precedence over MDC
103+
CustomField field = customFields.get(key);
104+
if (field != null) {
105+
value = field.getValue();
106+
}
107+
if (value != null) {
108+
oc.startObject().put("k", key).put("v", value).put("i", i).end();
109+
}
110+
}
111+
}
112+
113+
private void finishJson(ArrayComposer<ObjectComposer<JSONComposer<String>>> oc, StringBuilder appendTo)
114+
throws IOException, JsonProcessingException {
115+
ObjectComposer<JSONComposer<String>> end = oc.end();
116+
String result = end.end().finish().trim();
117+
if (embed) {
118+
/* -- chop off curly braces -- */
119+
appendTo.append(result.substring(1, result.length() - 1));
120+
} else {
121+
appendTo.append(result);
122+
}
72123
}
73124
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.sap.hcp.cf.logging.common.converter;
2+
3+
import java.util.Map;
4+
5+
import org.hamcrest.Matcher;
6+
import org.hamcrest.Matchers;
7+
8+
public final class CustomFieldMatchers {
9+
10+
private CustomFieldMatchers() {
11+
}
12+
13+
public static Matcher<Map<? extends String, ? extends Object>> hasCustomField(String key, String value,
14+
int index) {
15+
return Matchers.both(Matchers.<String, Object>hasEntry("k", key))
16+
.and(Matchers.<String, Object>hasEntry("v", value)).and(Matchers.<String, Object>hasEntry("i", index));
17+
}
18+
}

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

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package com.sap.hcp.cf.logging.common.converter;
22

3-
import static com.sap.hcp.cf.logging.common.converter.UnmarshallUtilities.unmarshal;
4-
import static com.sap.hcp.cf.logging.common.converter.UnmarshallUtilities.unmarshalPrefixed;
3+
import static com.sap.hcp.cf.logging.common.converter.CustomFieldMatchers.hasCustomField;
4+
import static com.sap.hcp.cf.logging.common.converter.UnmarshallUtilities.unmarshalCustomFields;
55
import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField;
6-
import static org.hamcrest.Matchers.allOf;
7-
import static org.hamcrest.Matchers.hasEntry;
6+
import static org.hamcrest.Matchers.contains;
7+
import static org.hamcrest.Matchers.containsInAnyOrder;
88
import static org.hamcrest.Matchers.hasToString;
9+
import static org.hamcrest.Matchers.isEmptyString;
910
import static org.junit.Assert.assertThat;
1011

12+
import java.util.Arrays;
1113
import java.util.Collections;
1214
import java.util.HashMap;
1315
import java.util.Map;
@@ -17,12 +19,19 @@
1719

1820
public class DefaultCustomFieldsConverterTest {
1921

22+
private static final String CUSTOM_KEY_0 = "custom_key_0";
23+
private static final String CUSTOM_VALUE_0 = "custom_value_0";
24+
private static final String CUSTOM_KEY_1 = "custom_key_1";
25+
private static final String CUSTOM_VALUE_1 = "custom_value_1";
26+
private static final String UNREGISTERED_KEY = "unregistered_key";
27+
private static final String UNREGISTERED_VALUE = "unregistered_value";
2028
private static final String HACK_ATTEMPT = "}{:\",\"";
2129
private DefaultCustomFieldsConverter converter;
2230

2331
@Before
2432
public void initConverter() {
2533
this.converter = new DefaultCustomFieldsConverter();
34+
converter.setCustomFieldKeyNames(Arrays.asList(CUSTOM_KEY_0, CUSTOM_KEY_1));
2635
}
2736

2837
@Test
@@ -45,84 +54,121 @@ public void standardArgument() throws Exception {
4554
public void singleCustomFieldArgumentEmbedded() throws Exception {
4655
StringBuilder sb = new StringBuilder();
4756

48-
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
57+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0));
4958

50-
assertThat(unmarshal(sb), hasEntry("some key", "some value"));
59+
assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0)));
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
@Test
64+
public void multipleCustomFieldArgumentEmbedded() throws Exception {
65+
StringBuilder sb = new StringBuilder();
66+
67+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_1, CUSTOM_VALUE_1),
68+
customField(UNREGISTERED_KEY, UNREGISTERED_VALUE), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0));
69+
70+
assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0),
71+
hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1)));
5172
}
5273

5374
@Test
5475
public void singleCustomFieldArgumentPrefix() throws Exception {
5576
converter.setFieldName("prefix");
5677
StringBuilder sb = new StringBuilder();
5778

58-
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
79+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0));
5980

60-
assertThat(unmarshalPrefixed(sb, "prefix"), hasEntry("some key", "some value"));
81+
assertThat(unmarshalCustomFields(sb, "prefix"), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0)));
6182
}
6283

6384
@Test
6485
public void singleMdcField() throws Exception {
6586
StringBuilder sb = new StringBuilder();
66-
87+
6788
@SuppressWarnings("serial")
6889
Map<String, String> mdcFields = new HashMap<String, String>() {
6990
{
70-
put("some key", "some value");
71-
}};
91+
put(CUSTOM_KEY_0, CUSTOM_VALUE_0);
92+
}
93+
};
7294

7395
converter.convert(sb, mdcFields);
7496

75-
assertThat(unmarshal(sb), hasEntry("some key", "some value"));
97+
assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0)));
7698
}
7799

100+
@SuppressWarnings("unchecked")
78101
@Test
79-
public void mergesMdcFieldsAndArguments() throws Exception {
102+
public void multipleMdcFields() throws Exception {
80103
StringBuilder sb = new StringBuilder();
81104

82105
@SuppressWarnings("serial")
83106
Map<String, String> mdcFields = new HashMap<String, String>() {
84107
{
85-
put("mdc key", "mdc value");
108+
put(CUSTOM_KEY_1, CUSTOM_VALUE_1);
109+
put(UNREGISTERED_KEY, UNREGISTERED_VALUE);
110+
put(CUSTOM_KEY_0, CUSTOM_VALUE_0);
86111
}
87112
};
88113

89-
converter.convert(sb, mdcFields, customField("some key", "some value"));
114+
converter.convert(sb, mdcFields);
90115

91-
assertThat(unmarshal(sb),
92-
allOf(hasEntry("some key", "some value"), hasEntry("mdc key", "mdc value")));
116+
assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0),
117+
hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1)));
93118
}
94119

120+
@SuppressWarnings("unchecked")
95121
@Test
96-
public void properlyEscapesValues() throws Exception {
122+
public void argumentsTakePrecendenceOverMdc() throws Exception {
123+
StringBuilder sb = new StringBuilder();
124+
125+
@SuppressWarnings("serial")
126+
Map<String, String> mdcFields = new HashMap<String, String>() {
127+
{
128+
put(CUSTOM_KEY_0, CUSTOM_VALUE_0);
129+
put(CUSTOM_KEY_1, CUSTOM_VALUE_1);
130+
}
131+
};
132+
133+
converter.convert(sb, mdcFields, customField(CUSTOM_KEY_0, "preferred value"));
134+
135+
assertThat(unmarshalCustomFields(sb), containsInAnyOrder(hasCustomField(CUSTOM_KEY_0, "preferred value", 0),
136+
hasCustomField(CUSTOM_KEY_1, CUSTOM_VALUE_1, 1)));
137+
}
138+
139+
@Test
140+
public void doesNotWriteJsonWhenNoFieldKeysAreConfigured() throws Exception {
97141
StringBuilder sb = new StringBuilder();
98142

99-
converter.convert(sb, Collections.emptyMap(), customField("some key", HACK_ATTEMPT));
143+
converter.setCustomFieldKeyNames(Collections.emptyList());
144+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0));
100145

101-
assertThat(unmarshal(sb), hasEntry("some key", HACK_ATTEMPT));
146+
assertThat(sb.toString(), isEmptyString());
102147
}
103148

104149
@Test
105-
public void properlyEscapesKeys() throws Exception {
150+
public void properlyEscapesValues() throws Exception {
106151
StringBuilder sb = new StringBuilder();
107152

108-
converter.convert(sb, Collections.emptyMap(), customField(HACK_ATTEMPT, "some value"));
153+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, HACK_ATTEMPT));
109154

110-
assertThat(unmarshal(sb), hasEntry(HACK_ATTEMPT, "some value"));
155+
assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, HACK_ATTEMPT, 0)));
111156
}
112157

113158
@Test
114159
public void properlyEscapesMdcFields() throws Exception {
115160
StringBuilder sb = new StringBuilder();
116-
161+
117162
@SuppressWarnings("serial")
118163
Map<String, String> mdcFields = new HashMap<String, String>() {
119164
{
120-
put(HACK_ATTEMPT, HACK_ATTEMPT);
121-
}};
165+
put(CUSTOM_KEY_0, HACK_ATTEMPT);
166+
}
167+
};
122168

123169
converter.convert(sb, mdcFields);
124170

125-
assertThat(unmarshal(sb), hasEntry(HACK_ATTEMPT, HACK_ATTEMPT));
171+
assertThat(unmarshalCustomFields(sb), contains(hasCustomField(CUSTOM_KEY_0, HACK_ATTEMPT, 0)));
126172

127173
}
128174

@@ -131,10 +177,10 @@ public void properlyEscapesFieldNames() throws Exception {
131177
converter.setFieldName(HACK_ATTEMPT);
132178
StringBuilder sb = new StringBuilder();
133179

134-
converter.convert(sb, Collections.emptyMap(), customField("some key", "some value"));
180+
converter.convert(sb, Collections.emptyMap(), customField(CUSTOM_KEY_0, CUSTOM_VALUE_0));
135181

136-
assertThat(unmarshalPrefixed(sb, HACK_ATTEMPT), hasEntry("some key", "some value"));
182+
assertThat(unmarshalCustomFields(sb, HACK_ATTEMPT),
183+
contains(hasCustomField(CUSTOM_KEY_0, CUSTOM_VALUE_0, 0)));
137184
}
138185

139186
}
140-

0 commit comments

Comments
 (0)