Skip to content

Commit d92e8f8

Browse files
Merge pull request #67 from SAP/custom-fields-string
Migrate Custom Fields JSON Serialization
2 parents 4a65ad3 + 93feaba commit d92e8f8

File tree

27 files changed

+608
-385
lines changed

27 files changed

+608
-385
lines changed

cf-java-logging-support-core/beats/app-logs/docs/fields.asciidoc

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,23 @@ required: False
336336
A list of names to further categorize this log message.
337337

338338

339-
=== custom_fields Fields
339+
==== #cf
340340

341-
A collection of non-standard fields as key-value pairs.
341+
type: object
342+
343+
example: "#cf": {
344+
"string": [
345+
{"l":"some_label", "v":"some_value", "i": 0},
346+
{"l":"other_label", "v":"other_value", "i": 1}
347+
]
348+
}
349+
350+
351+
required: False
352+
353+
An object containing collections of non-standard fields.
354+
The field "string" contains custom fields with label "l", value "v" and an index "i".
355+
The index can be used for field order during parsing.
342356

343357
NOTE: As this is "custom" there are no predefined fields here!
344358

cf-java-logging-support-core/beats/app-logs/etc/fields.yml

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,20 @@ app-logs:
249249
description: |
250250
A list of names to further categorize this log message.
251251
252-
- name: "custom_fields"
253-
type: group
252+
- name: "#cf"
253+
java_alias: "CUSTOM_FIELDS"
254+
type: object
254255
required: false
255-
example: "cutom_fields: {\"some_key\": \"some_value\"}"
256+
example: |
257+
"#cf": {
258+
"string": [
259+
{"l":"some_label", "v":"some_value", "i": 0},
260+
{"l":"other_label", "v":"other_value", "i": 1}
261+
]
262+
}
256263
description: |
257-
A collection of non-standard fields as key-value pairs.
264+
An object containing collections of non-standard fields.
265+
The field "string" contains custom fields with label "l", value "v" and an index "i".
266+
The index can be used for field order during parsing.
258267
259268
NOTE: As this is "custom" there are no predefined fields here!

cf-java-logging-support-core/beats/scripts/gen_java_fields.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
spec.keys.each do |k|
1818
if spec[k].class == Hash && spec[k].has_key?('fields')
1919
spec[k]['fields'].each do |f|
20-
fields.add(f['name'])
20+
if fields.select { |c| c['name'].upcase == f['name'].upcase}.empty?
21+
fields.add(f)
22+
end
2123
end
2224
end
2325
end
@@ -37,7 +39,8 @@
3739
EOD
3840

3941
fields.each do |f|
40-
puts " public String #{f.upcase} = \"#{f}\";"
42+
key = f.has_key?('java_alias') ? f['java_alias'] : f['name']
43+
puts " public String #{key.upcase} = \"#{f['name']}\";"
4144
end
4245

4346
puts "}"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public interface Fields {
3232
public String MSG = "msg";
3333
public String STACKTRACE = "stacktrace";
3434
public String CATEGORIES = "categories";
35-
public String CUSTOM_FIELDS = "custom_fields";
35+
public String CUSTOM_FIELDS = "#cf";
3636
public String REQUEST = "request";
3737
public String REQUEST_SENT_AT = "request_sent_at";
3838
public String REQUEST_RECEIVED_AT = "request_received_at";
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
}
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+
}

0 commit comments

Comments
 (0)