Skip to content

Commit 83e8664

Browse files
authored
Validate against shared spec (#111)
closes #110
1 parent 1b66122 commit 83e8664

File tree

5 files changed

+169
-63
lines changed

5 files changed

+169
-63
lines changed

ecs-logging-core/src/test/java/co/elastic/logging/AbstractEcsLoggingTest.java

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
* the Apache License, Version 2.0 (the "License"); you may
1212
* not use this file except in compliance with the License.
1313
* You may obtain a copy of the License at
14-
*
14+
*
1515
* http://www.apache.org/licenses/LICENSE-2.0
16-
*
16+
*
1717
* Unless required by applicable law or agreed to in writing,
1818
* software distributed under the License is distributed on an
1919
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -27,37 +27,95 @@
2727

2828
import com.fasterxml.jackson.databind.JsonNode;
2929
import com.fasterxml.jackson.databind.ObjectMapper;
30+
import org.junit.jupiter.api.BeforeEach;
3031
import org.junit.jupiter.api.Test;
3132

3233
import java.io.IOException;
3334
import java.time.Instant;
3435
import java.time.temporal.ChronoUnit;
36+
import java.util.ArrayList;
37+
import java.util.Iterator;
38+
import java.util.List;
39+
import java.util.Map;
3540
import java.util.stream.Collectors;
3641
import java.util.stream.StreamSupport;
3742

3843
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.assertj.core.api.Assertions.assertThatCode;
3945
import static org.assertj.core.api.Assertions.within;
4046

4147
public abstract class AbstractEcsLoggingTest {
4248

4349
protected ObjectMapper objectMapper = new ObjectMapper();
50+
private JsonNode spec;
51+
52+
@BeforeEach
53+
final void setUpSpec() throws Exception {
54+
spec = objectMapper.readTree(getClass().getClassLoader().getResource("spec/spec.json"));
55+
}
4456

4557
@Test
4658
void testMetadata() throws Exception {
4759
debug("test");
48-
assertThat(getLastLogLine().get("process.thread.name").textValue()).isEqualTo(Thread.currentThread().getName());
49-
assertThat(getLastLogLine().get("service.name").textValue()).isEqualTo("test");
50-
assertThat(Instant.parse(getLastLogLine().get("@timestamp").textValue())).isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
51-
assertThat(getLastLogLine().get("log.level").textValue()).isIn("DEBUG", "FINE");
52-
assertThat(getLastLogLine().get("log.logger")).isNotNull();
53-
assertThat(getLastLogLine().get("event.dataset").textValue()).isEqualTo("testdataset.log");
54-
assertThat(getLastLogLine().get("ecs.version").textValue()).isEqualTo("1.2.0");
60+
assertThat(getAndValidateLastLogLine().get("process.thread.name").textValue()).isEqualTo(Thread.currentThread().getName());
61+
assertThat(getAndValidateLastLogLine().get("service.name").textValue()).isEqualTo("test");
62+
assertThat(Instant.parse(getAndValidateLastLogLine().get("@timestamp").textValue())).isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
63+
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isIn("DEBUG", "FINE");
64+
assertThat(getAndValidateLastLogLine().get("log.logger")).isNotNull();
65+
assertThat(getAndValidateLastLogLine().get("event.dataset").textValue()).isEqualTo("testdataset.log");
66+
assertThat(getAndValidateLastLogLine().get("ecs.version").textValue()).isEqualTo("1.2.0");
67+
validateLog(getAndValidateLastLogLine());
5568
}
5669

5770
@Test
5871
void testSimpleLog() throws Exception {
5972
debug("test");
60-
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("test");
73+
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("test");
74+
}
75+
76+
void validateLog(JsonNode logLine) {
77+
Iterator<Map.Entry<String, JsonNode>> specFields = spec.get("fields").fields();
78+
Iterator<String> iterator = logLine.fieldNames();
79+
List<String> logFieldNames = new ArrayList<>();
80+
iterator.forEachRemaining(logFieldNames::add);
81+
82+
while (specFields.hasNext()) {
83+
Map.Entry<String, JsonNode> specField = specFields.next();
84+
String specFieldName = specField.getKey();
85+
JsonNode specForField = specField.getValue();
86+
JsonNode fieldInLog = logLine.get(specFieldName);
87+
88+
validateRequiredField(logLine, specFieldName, specForField.get("required").booleanValue());
89+
if (fieldInLog != null) {
90+
validateIndex(logLine, logFieldNames, specFieldName, specForField.get("index"));
91+
validateType(fieldInLog, specForField.get("type").textValue());
92+
}
93+
}
94+
}
95+
96+
private void validateRequiredField(JsonNode logLine, String specFieldName, boolean required) {
97+
if (required) {
98+
assertThat(logLine.get(specFieldName))
99+
.describedAs(logLine.toString())
100+
.isNotNull();
101+
}
102+
}
103+
104+
private void validateIndex(JsonNode logLine, List<String> logFieldNames, String specFieldName, JsonNode index) {
105+
if (index != null) {
106+
assertThat(logFieldNames.get(index.intValue()))
107+
.describedAs(logLine.toString())
108+
.isEqualTo(specFieldName);
109+
}
110+
}
111+
112+
private void validateType(JsonNode fieldInLog, String type) {
113+
switch (type) {
114+
case "datetime":
115+
assertThatCode(() -> Instant.parse(fieldInLog.textValue())).doesNotThrowAnyException();
116+
case "string":
117+
assertThat(fieldInLog.isTextual()).isTrue();
118+
}
61119
}
62120

63121
@Test
@@ -75,22 +133,22 @@ void testSimpleParameterizedLog() throws Exception {
75133
debug("{} is not {}", 1, 2);
76134
}
77135

78-
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("1 is not 2");
136+
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("1 is not 2");
79137
}
80138

81139
@Test
82140
void testThreadContext() throws Exception {
83141
if (putMdc("foo", "bar")) {
84142
debug("test");
85-
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
143+
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
86144
}
87145
}
88146

89147
@Test
90148
void testThreadContextStack() throws Exception {
91149
if (putNdc("foo")) {
92150
debug("test");
93-
assertThat(getLastLogLine().get("tags").iterator().next().textValue()).isEqualTo("foo");
151+
assertThat(getAndValidateLastLogLine().get("tags").iterator().next().textValue()).isEqualTo("foo");
94152
}
95153
}
96154

@@ -100,21 +158,21 @@ void testMdc() throws Exception {
100158
putMdc("span.id", "foo");
101159
putMdc("foo", "bar");
102160
debug("test");
103-
assertThat(getLastLogLine().get("labels.transaction.id")).isNull();
104-
assertThat(getLastLogLine().get("transaction.id").textValue())
161+
assertThat(getAndValidateLastLogLine().get("labels.transaction.id")).isNull();
162+
assertThat(getAndValidateLastLogLine().get("transaction.id").textValue())
105163
.isEqualTo("0af7651916cd43dd8448eb211c80319c");
106-
assertThat(getLastLogLine().get("span.id").textValue()).isEqualTo("foo");
107-
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
164+
assertThat(getAndValidateLastLogLine().get("span.id").textValue()).isEqualTo("foo");
165+
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
108166
}
109167
}
110168

111169
@Test
112170
void testLogException() throws Exception {
113171
error("test", new RuntimeException("test"));
114-
assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("ERROR");
115-
assertThat(getLastLogLine().get("error.message").textValue()).isEqualTo("test");
116-
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
117-
String stackTrace = StreamSupport.stream(getLastLogLine().get("error.stack_trace").spliterator(), false)
172+
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isEqualTo("ERROR");
173+
assertThat(getAndValidateLastLogLine().get("error.message").textValue()).isEqualTo("test");
174+
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
175+
String stackTrace = StreamSupport.stream(getAndValidateLastLogLine().get("error.stack_trace").spliterator(), false)
118176
.map(JsonNode::textValue)
119177
.collect(Collectors.joining("\n", "", "\n"));
120178
assertThat(stackTrace).contains("at co.elastic.logging.AbstractEcsLoggingTest.testLogException");
@@ -123,16 +181,16 @@ void testLogException() throws Exception {
123181
@Test
124182
void testLogExceptionNullMessage() throws Exception {
125183
error("test", new RuntimeException());
126-
assertThat(getLastLogLine().get("error.message")).isNull();
127-
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
184+
assertThat(getAndValidateLastLogLine().get("error.message")).isNull();
185+
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
128186
}
129187

130188
@Test
131189
void testLogOrigin() throws Exception {
132190
debug("test");
133-
assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
134-
assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
135-
assertThat(getLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
191+
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
192+
assertThat(getAndValidateLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
193+
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
136194
}
137195

138196
public boolean putMdc(String key, String value) {
@@ -153,5 +211,11 @@ public ParameterizedLogSupport getParameterizedLogSettings() {
153211

154212
public abstract void error(String message, Throwable t);
155213

214+
public final JsonNode getAndValidateLastLogLine() throws IOException {
215+
JsonNode log = getLastLogLine();
216+
validateLog(log);
217+
return log;
218+
}
219+
156220
public abstract JsonNode getLastLogLine() throws IOException;
157221
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"version": 1.0,
3+
"url": "https://www.elastic.co/guide/en/ecs/current/index.html",
4+
"ecs": {
5+
"version": "1.x"
6+
},
7+
"fields": {
8+
"@timestamp": {
9+
"type": "datetime",
10+
"required": true,
11+
"index": 0,
12+
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html"
13+
},
14+
"log.level": {
15+
"type": "string",
16+
"required": true,
17+
"index": 1,
18+
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-log.html"
19+
},
20+
"message": {
21+
"type": "string",
22+
"required": true,
23+
"index": 2,
24+
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html"
25+
},
26+
"ecs.version": {
27+
"type": "string",
28+
"required": true,
29+
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-ecs.html"
30+
},
31+
"labels": {
32+
"type": "object",
33+
"required": false,
34+
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html",
35+
"sanitization": {
36+
"key": {
37+
"replacements": [
38+
".",
39+
"*",
40+
"\\"
41+
],
42+
"substitute": "_"
43+
}
44+
}
45+
}
46+
}
47+
}

jul-ecs-formatter/src/test/java/co/elastic/logging/jul/JulLoggingTest.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@
2424
*/
2525
package co.elastic.logging.jul;
2626

27-
import static org.assertj.core.api.Assertions.assertThat;
28-
import static org.assertj.core.api.Assertions.within;
27+
import co.elastic.logging.AbstractEcsLoggingTest;
28+
import co.elastic.logging.ParameterizedLogSupport;
29+
import com.fasterxml.jackson.databind.JsonNode;
30+
import org.junit.jupiter.api.BeforeEach;
31+
import org.junit.jupiter.api.Test;
32+
import org.slf4j.MDC;
2933

3034
import java.io.ByteArrayOutputStream;
3135
import java.io.IOException;
3236
import java.io.OutputStream;
33-
import java.time.Instant;
34-
import java.time.temporal.ChronoUnit;
3537
import java.util.logging.Formatter;
3638
import java.util.logging.Handler;
3739
import java.util.logging.Level;
@@ -41,14 +43,7 @@
4143
import java.util.stream.Collectors;
4244
import java.util.stream.StreamSupport;
4345

44-
import co.elastic.logging.ParameterizedLogSupport;
45-
import org.junit.jupiter.api.BeforeEach;
46-
import org.junit.jupiter.api.Test;
47-
48-
import com.fasterxml.jackson.databind.JsonNode;
49-
50-
import co.elastic.logging.AbstractEcsLoggingTest;
51-
import org.slf4j.MDC;
46+
import static org.assertj.core.api.Assertions.assertThat;
5247

5348
public class JulLoggingTest extends AbstractEcsLoggingTest {
5449

@@ -132,10 +127,10 @@ void setUp() {
132127
@Test
133128
void testLogException() throws Exception {
134129
error("test", new RuntimeException("test"));
135-
assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("SEVERE");
136-
assertThat(getLastLogLine().get("error.message").textValue()).isEqualTo("test");
137-
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
138-
String stackTrace = StreamSupport.stream(getLastLogLine().get("error.stack_trace").spliterator(), false)
130+
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isEqualTo("SEVERE");
131+
assertThat(getAndValidateLastLogLine().get("error.message").textValue()).isEqualTo("test");
132+
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
133+
String stackTrace = StreamSupport.stream(getAndValidateLastLogLine().get("error.stack_trace").spliterator(), false)
139134
.map(JsonNode::textValue)
140135
.collect(Collectors.joining("\n", "", "\n"));
141136
assertThat(stackTrace).contains("at co.elastic.logging.jul.JulLoggingTest.testLogException");
@@ -144,8 +139,8 @@ void testLogException() throws Exception {
144139
@Test
145140
void testLogOrigin() throws Exception {
146141
debug("test");
147-
assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
148-
assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
142+
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
143+
assertThat(getAndValidateLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
149144
//No file.line for JUL
150145
}
151146

log4j2-ecs-layout/src/test/java/co/elastic/logging/log4j2/AbstractLog4j2EcsLayoutTest.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ void testAdditionalFields() throws Exception {
5353
putMdc("trace.id", "foo");
5454
putMdc("foo", "bar");
5555
debug("test");
56-
assertThat(getLastLogLine().get("cluster.uuid").textValue()).isEqualTo("9fe9134b-20b0-465e-acf9-8cc09ac9053b");
57-
assertThat(getLastLogLine().get("node.id").textValue()).isEqualTo("foo");
58-
assertThat(getLastLogLine().get("empty")).isNull();
59-
assertThat(getLastLogLine().get("emptyPattern")).isNull();
60-
assertThat(getLastLogLine().get("clazz").textValue()).startsWith(getClass().getPackageName());
61-
assertThat(getLastLogLine().get("404")).isNull();
62-
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
56+
assertThat(getAndValidateLastLogLine().get("cluster.uuid").textValue()).isEqualTo("9fe9134b-20b0-465e-acf9-8cc09ac9053b");
57+
assertThat(getAndValidateLastLogLine().get("node.id").textValue()).isEqualTo("foo");
58+
assertThat(getAndValidateLastLogLine().get("empty")).isNull();
59+
assertThat(getAndValidateLastLogLine().get("emptyPattern")).isNull();
60+
assertThat(getAndValidateLastLogLine().get("clazz").textValue()).startsWith(getClass().getPackageName());
61+
assertThat(getAndValidateLastLogLine().get("404")).isNull();
62+
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
6363
}
6464

6565
@Test
@@ -69,7 +69,7 @@ void testMarker() throws Exception {
6969
Marker grandchild = MarkerManager.getMarker("grandchild").setParents(child);
7070
root.debug(grandchild, "test");
7171

72-
assertThat(getLastLogLine().get("tags")).contains(
72+
assertThat(getAndValidateLastLogLine().get("tags")).contains(
7373
TextNode.valueOf("parent"),
7474
TextNode.valueOf("child"),
7575
TextNode.valueOf("grandchild"));
@@ -79,38 +79,38 @@ void testMarker() throws Exception {
7979
@Test
8080
void testCustomPatternConverter() throws Exception {
8181
debug("test");
82-
assertThat(getLastLogLine().get("custom").textValue()).isEqualTo("foo");
82+
assertThat(getAndValidateLastLogLine().get("custom").textValue()).isEqualTo("foo");
8383
}
8484

8585
@Test
8686
void testJsonMessageArray() throws Exception {
8787
root.info(new ObjectMessage(List.of("foo", "bar")));
8888

89-
assertThat(getLastLogLine().get("message").isArray()).isFalse();
90-
assertThat(getLastLogLine().get("message").textValue()).contains("foo", "bar");
89+
assertThat(getAndValidateLastLogLine().get("message").isArray()).isFalse();
90+
assertThat(getAndValidateLastLogLine().get("message").textValue()).contains("foo", "bar");
9191
}
9292

9393
@Test
9494
void testJsonMessageString() throws Exception {
9595
root.info(new ObjectMessage("foo"));
9696

97-
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("foo");
97+
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("foo");
9898
}
9999

100100
@Test
101101
void testJsonMessageNumber() throws Exception {
102102
root.info(new ObjectMessage(42));
103103

104-
assertThat(getLastLogLine().get("message").isNumber()).isFalse();
105-
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("42");
104+
assertThat(getAndValidateLastLogLine().get("message").isNumber()).isFalse();
105+
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("42");
106106
}
107107

108108
@Test
109109
void testJsonMessageBoolean() throws Exception {
110110
root.info(new ObjectMessage(true));
111111

112-
assertThat(getLastLogLine().get("message").isBoolean()).isFalse();
113-
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("true");
112+
assertThat(getAndValidateLastLogLine().get("message").isBoolean()).isFalse();
113+
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("true");
114114
}
115115

116116
@Override

0 commit comments

Comments
 (0)