Skip to content

Validate against shared spec #111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
Expand All @@ -27,37 +27,95 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.within;

public abstract class AbstractEcsLoggingTest {

protected ObjectMapper objectMapper = new ObjectMapper();
private JsonNode spec;

@BeforeEach
final void setUpSpec() throws Exception {
spec = objectMapper.readTree(getClass().getClassLoader().getResource("spec/spec.json"));
}

@Test
void testMetadata() throws Exception {
debug("test");
assertThat(getLastLogLine().get("process.thread.name").textValue()).isEqualTo(Thread.currentThread().getName());
assertThat(getLastLogLine().get("service.name").textValue()).isEqualTo("test");
assertThat(Instant.parse(getLastLogLine().get("@timestamp").textValue())).isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
assertThat(getLastLogLine().get("log.level").textValue()).isIn("DEBUG", "FINE");
assertThat(getLastLogLine().get("log.logger")).isNotNull();
assertThat(getLastLogLine().get("event.dataset").textValue()).isEqualTo("testdataset.log");
assertThat(getLastLogLine().get("ecs.version").textValue()).isEqualTo("1.2.0");
assertThat(getAndValidateLastLogLine().get("process.thread.name").textValue()).isEqualTo(Thread.currentThread().getName());
assertThat(getAndValidateLastLogLine().get("service.name").textValue()).isEqualTo("test");
assertThat(Instant.parse(getAndValidateLastLogLine().get("@timestamp").textValue())).isCloseTo(Instant.now(), within(1, ChronoUnit.MINUTES));
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isIn("DEBUG", "FINE");
assertThat(getAndValidateLastLogLine().get("log.logger")).isNotNull();
assertThat(getAndValidateLastLogLine().get("event.dataset").textValue()).isEqualTo("testdataset.log");
assertThat(getAndValidateLastLogLine().get("ecs.version").textValue()).isEqualTo("1.2.0");
validateLog(getAndValidateLastLogLine());
}

@Test
void testSimpleLog() throws Exception {
debug("test");
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("test");
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("test");
}

void validateLog(JsonNode logLine) {
Iterator<Map.Entry<String, JsonNode>> specFields = spec.get("fields").fields();
Iterator<String> iterator = logLine.fieldNames();
List<String> logFieldNames = new ArrayList<>();
iterator.forEachRemaining(logFieldNames::add);

while (specFields.hasNext()) {
Map.Entry<String, JsonNode> specField = specFields.next();
String specFieldName = specField.getKey();
JsonNode specForField = specField.getValue();
JsonNode fieldInLog = logLine.get(specFieldName);

validateRequiredField(logLine, specFieldName, specForField.get("required").booleanValue());
if (fieldInLog != null) {
validateIndex(logLine, logFieldNames, specFieldName, specForField.get("index"));
validateType(fieldInLog, specForField.get("type").textValue());
}
}
}

private void validateRequiredField(JsonNode logLine, String specFieldName, boolean required) {
if (required) {
assertThat(logLine.get(specFieldName))
.describedAs(logLine.toString())
.isNotNull();
}
}

private void validateIndex(JsonNode logLine, List<String> logFieldNames, String specFieldName, JsonNode index) {
if (index != null) {
assertThat(logFieldNames.get(index.intValue()))
.describedAs(logLine.toString())
.isEqualTo(specFieldName);
}
}

private void validateType(JsonNode fieldInLog, String type) {
switch (type) {
case "datetime":
assertThatCode(() -> Instant.parse(fieldInLog.textValue())).doesNotThrowAnyException();
case "string":
assertThat(fieldInLog.isTextual()).isTrue();
}
}

@Test
Expand All @@ -75,22 +133,22 @@ void testSimpleParameterizedLog() throws Exception {
debug("{} is not {}", 1, 2);
}

assertThat(getLastLogLine().get("message").textValue()).isEqualTo("1 is not 2");
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("1 is not 2");
}

@Test
void testThreadContext() throws Exception {
if (putMdc("foo", "bar")) {
debug("test");
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
}
}

@Test
void testThreadContextStack() throws Exception {
if (putNdc("foo")) {
debug("test");
assertThat(getLastLogLine().get("tags").iterator().next().textValue()).isEqualTo("foo");
assertThat(getAndValidateLastLogLine().get("tags").iterator().next().textValue()).isEqualTo("foo");
}
}

Expand All @@ -100,21 +158,21 @@ void testMdc() throws Exception {
putMdc("span.id", "foo");
putMdc("foo", "bar");
debug("test");
assertThat(getLastLogLine().get("labels.transaction.id")).isNull();
assertThat(getLastLogLine().get("transaction.id").textValue())
assertThat(getAndValidateLastLogLine().get("labels.transaction.id")).isNull();
assertThat(getAndValidateLastLogLine().get("transaction.id").textValue())
.isEqualTo("0af7651916cd43dd8448eb211c80319c");
assertThat(getLastLogLine().get("span.id").textValue()).isEqualTo("foo");
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
assertThat(getAndValidateLastLogLine().get("span.id").textValue()).isEqualTo("foo");
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
}
}

@Test
void testLogException() throws Exception {
error("test", new RuntimeException("test"));
assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("ERROR");
assertThat(getLastLogLine().get("error.message").textValue()).isEqualTo("test");
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
String stackTrace = StreamSupport.stream(getLastLogLine().get("error.stack_trace").spliterator(), false)
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isEqualTo("ERROR");
assertThat(getAndValidateLastLogLine().get("error.message").textValue()).isEqualTo("test");
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
String stackTrace = StreamSupport.stream(getAndValidateLastLogLine().get("error.stack_trace").spliterator(), false)
.map(JsonNode::textValue)
.collect(Collectors.joining("\n", "", "\n"));
assertThat(stackTrace).contains("at co.elastic.logging.AbstractEcsLoggingTest.testLogException");
Expand All @@ -123,16 +181,16 @@ void testLogException() throws Exception {
@Test
void testLogExceptionNullMessage() throws Exception {
error("test", new RuntimeException());
assertThat(getLastLogLine().get("error.message")).isNull();
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
assertThat(getAndValidateLastLogLine().get("error.message")).isNull();
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
}

@Test
void testLogOrigin() throws Exception {
debug("test");
assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
assertThat(getLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
assertThat(getAndValidateLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.line").intValue()).isPositive();
}

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

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

public final JsonNode getAndValidateLastLogLine() throws IOException {
JsonNode log = getLastLogLine();
validateLog(log);
return log;
}

public abstract JsonNode getLastLogLine() throws IOException;
}
47 changes: 47 additions & 0 deletions ecs-logging-core/src/test/resources/spec/spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"version": 1.0,
"url": "https://www.elastic.co/guide/en/ecs/current/index.html",
"ecs": {
"version": "1.x"
},
"fields": {
"@timestamp": {
"type": "datetime",
"required": true,
"index": 0,
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html"
},
"log.level": {
"type": "string",
"required": true,
"index": 1,
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-log.html"
},
"message": {
"type": "string",
"required": true,
"index": 2,
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html"
},
"ecs.version": {
"type": "string",
"required": true,
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-ecs.html"
},
"labels": {
"type": "object",
"required": false,
"url": "https://www.elastic.co/guide/en/ecs/current/ecs-base.html",
"sanitization": {
"key": {
"replacements": [
".",
"*",
"\\"
],
"substitute": "_"
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@
*/
package co.elastic.logging.jul;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.within;
import co.elastic.logging.AbstractEcsLoggingTest;
import co.elastic.logging.ParameterizedLogSupport;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.MDC;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
Expand All @@ -41,14 +43,7 @@
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import co.elastic.logging.ParameterizedLogSupport;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.JsonNode;

import co.elastic.logging.AbstractEcsLoggingTest;
import org.slf4j.MDC;
import static org.assertj.core.api.Assertions.assertThat;

public class JulLoggingTest extends AbstractEcsLoggingTest {

Expand Down Expand Up @@ -132,10 +127,10 @@ void setUp() {
@Test
void testLogException() throws Exception {
error("test", new RuntimeException("test"));
assertThat(getLastLogLine().get("log.level").textValue()).isEqualTo("SEVERE");
assertThat(getLastLogLine().get("error.message").textValue()).isEqualTo("test");
assertThat(getLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
String stackTrace = StreamSupport.stream(getLastLogLine().get("error.stack_trace").spliterator(), false)
assertThat(getAndValidateLastLogLine().get("log.level").textValue()).isEqualTo("SEVERE");
assertThat(getAndValidateLastLogLine().get("error.message").textValue()).isEqualTo("test");
assertThat(getAndValidateLastLogLine().get("error.type").textValue()).isEqualTo(RuntimeException.class.getName());
String stackTrace = StreamSupport.stream(getAndValidateLastLogLine().get("error.stack_trace").spliterator(), false)
.map(JsonNode::textValue)
.collect(Collectors.joining("\n", "", "\n"));
assertThat(stackTrace).contains("at co.elastic.logging.jul.JulLoggingTest.testLogException");
Expand All @@ -144,8 +139,8 @@ void testLogException() throws Exception {
@Test
void testLogOrigin() throws Exception {
debug("test");
assertThat(getLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
assertThat(getLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
assertThat(getAndValidateLastLogLine().get("log.origin").get("file.name").textValue()).endsWith(".java");
assertThat(getAndValidateLastLogLine().get("log.origin").get("function").textValue()).isEqualTo("debug");
//No file.line for JUL
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ void testAdditionalFields() throws Exception {
putMdc("trace.id", "foo");
putMdc("foo", "bar");
debug("test");
assertThat(getLastLogLine().get("cluster.uuid").textValue()).isEqualTo("9fe9134b-20b0-465e-acf9-8cc09ac9053b");
assertThat(getLastLogLine().get("node.id").textValue()).isEqualTo("foo");
assertThat(getLastLogLine().get("empty")).isNull();
assertThat(getLastLogLine().get("emptyPattern")).isNull();
assertThat(getLastLogLine().get("clazz").textValue()).startsWith(getClass().getPackageName());
assertThat(getLastLogLine().get("404")).isNull();
assertThat(getLastLogLine().get("foo").textValue()).isEqualTo("bar");
assertThat(getAndValidateLastLogLine().get("cluster.uuid").textValue()).isEqualTo("9fe9134b-20b0-465e-acf9-8cc09ac9053b");
assertThat(getAndValidateLastLogLine().get("node.id").textValue()).isEqualTo("foo");
assertThat(getAndValidateLastLogLine().get("empty")).isNull();
assertThat(getAndValidateLastLogLine().get("emptyPattern")).isNull();
assertThat(getAndValidateLastLogLine().get("clazz").textValue()).startsWith(getClass().getPackageName());
assertThat(getAndValidateLastLogLine().get("404")).isNull();
assertThat(getAndValidateLastLogLine().get("foo").textValue()).isEqualTo("bar");
}

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

assertThat(getLastLogLine().get("tags")).contains(
assertThat(getAndValidateLastLogLine().get("tags")).contains(
TextNode.valueOf("parent"),
TextNode.valueOf("child"),
TextNode.valueOf("grandchild"));
Expand All @@ -79,38 +79,38 @@ void testMarker() throws Exception {
@Test
void testCustomPatternConverter() throws Exception {
debug("test");
assertThat(getLastLogLine().get("custom").textValue()).isEqualTo("foo");
assertThat(getAndValidateLastLogLine().get("custom").textValue()).isEqualTo("foo");
}

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

assertThat(getLastLogLine().get("message").isArray()).isFalse();
assertThat(getLastLogLine().get("message").textValue()).contains("foo", "bar");
assertThat(getAndValidateLastLogLine().get("message").isArray()).isFalse();
assertThat(getAndValidateLastLogLine().get("message").textValue()).contains("foo", "bar");
}

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

assertThat(getLastLogLine().get("message").textValue()).isEqualTo("foo");
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("foo");
}

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

assertThat(getLastLogLine().get("message").isNumber()).isFalse();
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("42");
assertThat(getAndValidateLastLogLine().get("message").isNumber()).isFalse();
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("42");
}

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

assertThat(getLastLogLine().get("message").isBoolean()).isFalse();
assertThat(getLastLogLine().get("message").textValue()).isEqualTo("true");
assertThat(getAndValidateLastLogLine().get("message").isBoolean()).isFalse();
assertThat(getAndValidateLastLogLine().get("message").textValue()).isEqualTo("true");
}

@Override
Expand Down
Loading