-
Notifications
You must be signed in to change notification settings - Fork 41.1k
ECS structure logging is not compatible with all collectors as it does not use the nested format #45063
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
Comments
UPDATE: My previous statement was incorrect.
For your specific case, you should create a custom implementation of For more details, please refer to the Spring Boot documentation. The following package task.gh45063;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import org.slf4j.event.KeyValuePair;
import org.springframework.boot.json.JsonWriter;
import org.springframework.boot.json.JsonWriter.PairExtractor;
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties.Service;
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
import org.springframework.core.env.Environment;
import java.util.Objects;
class NestedElasticCommonSchemaStructuredLogFormatter extends
JsonWriterStructuredLogFormatter<ILoggingEvent> {
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of(
(pair) -> pair.key,
(pair) -> pair.value);
NestedElasticCommonSchemaStructuredLogFormatter(Environment environment,
ThrowableProxyConverter throwableProxyConverter,
StructuredLoggingJsonMembersCustomizer<?> customizer) {
super((members) -> jsonMembers(environment, throwableProxyConverter, members), customizer);
}
private static void jsonMembers(Environment environment,
ThrowableProxyConverter throwableProxyConverter,
JsonWriter.Members<ILoggingEvent> members) {
members.add("@timestamp", ILoggingEvent::getInstant);
members.add("log").usingMembers((logMembers) -> {
logMembers.add("logger", ILoggingEvent::getLoggerName);
logMembers.add("level", ILoggingEvent::getLevel);
}
);
members.add("process").usingMembers((processMembers) -> {
processMembers.add("pid", environment.getProperty("spring.application.pid", Long.class))
.when(Objects::nonNull);
processMembers.add("thread")
.usingMembers((threadMembers) -> threadMembers.add("name",
ILoggingEvent::getThreadName));
});
Service service = ElasticCommonSchemaProperties.get(environment).service();
members.add("service").usingMembers((serviceMembers) -> {
serviceMembers.add("name", service::name).whenHasLength();
serviceMembers.add("version", service::version).whenHasLength();
serviceMembers.add("environment", service::environment).whenHasLength();
serviceMembers.add("node.name", service::nodeName).whenHasLength();
});
members.add("message", ILoggingEvent::getFormattedMessage);
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);
members.from(ILoggingEvent::getKeyValuePairs)
.whenNotEmpty()
.usingExtractedPairs(Iterable::forEach, keyValuePairExtractor);
members.add("error").whenNotNull(ILoggingEvent::getThrowableProxy)
.usingMembers((throwableMembers) -> {
throwableMembers.add("type", ILoggingEvent::getThrowableProxy)
.as(IThrowableProxy::getClassName);
throwableMembers.add("message", ILoggingEvent::getThrowableProxy)
.as(IThrowableProxy::getMessage);
throwableMembers.add("stack_trace", throwableProxyConverter::convert);
});
members.add("ecs").usingMembers((ecsMembers) -> ecsMembers.add("version", "8.11"));
}
} {
"@timestamp": "2025-04-10T14:35:20.081935Z",
"log": {
"logger": "task.gh45063.Gh45063Application",
"level": "INFO"
},
"process": {
"pid": 25264,
"thread": {
"name": "main"
}
},
"service": {
"name": "gh-45063"
},
"message": "Started Gh45063Application in 0.257 seconds (process running for 0.418)",
"ecs": {
"version": "8.11"
}
}
|
This comment has been minimized.
This comment has been minimized.
Hello, thanks for looking at this :) ECS stores data in a nested format, our example will be saved as For reference, you can check the mapping representation of the ECS standard for our example here: https://github.com/elastic/ecs/blob/main/generated/elasticsearch/composable/component/ecs.json The ECS documentation https://www.elastic.co/guide/en/ecs/current/ecs-ecs.html refers to attributes like If you use another agent than the elastic-agent (e.g.fluent-bit) to process logs and spring delivers them as e.g. Spring doesn't deliver the logs according to the ECS standard format and it will only work without problems if you collect these logs with the elastic-agent that will convert them to the right ECS format before sending them to storage. regards, |
I set up Elasticsearch via https://www.elastic.co/cloud and performed another round of testing.
However, in practice, the JSON format currently produced by Flat JSON example: {
"@timestamp": "2025-04-10T18:03:42.230354Z",
"log.level": "ERROR",
"process.pid": 30859,
"process.thread.name": "main",
"service.name": "gh-45063",
"log.logger": "task.gh45063.Gh45063Application",
"message": "MyError",
"error.type": "java.lang.IllegalStateException",
"error.message": "Error Test",
"error.stack_trace": "java.lang.IllegalStateException: Error Test\n\tat task.gh45063.Gh45063Application.main(Gh45063Application.java:14)\n",
"ecs.version": "8.11"
}
Additionally, Elasticsearch fully supports and parses nested JSON structures, like the one below: Nested JSON example: {
"@timestamp": "2025-04-10T18:04:14.257693Z",
"log": {
"logger": "task.gh45063.Gh45063Application",
"level": "ERROR"
},
"process": {
"pid": 30877,
"thread": {
"name": "main"
}
},
"service": {
"name": "gh-45063"
},
"message": "MyError",
"error": {
"type": "java.lang.IllegalStateException",
"message": "Error Test",
"stack_trace": "java.lang.IllegalStateException: Error Test\n\tat task.gh45063.Gh45063Application.main(Gh45063Application.java:14)\n"
},
"ecs": {
"version": "8.11"
}
} In summary, both flat and nested formats are compatible. |
Thank you, @nosan, for investigating this issue further. "...In summary, both flat and nested formats are compatible ....", as a source, but only when using Elasticsearch as the storage infrastructure because elasticsearch transforms flat format to the standard nested format before saving the log. UPDATE: If elasticsearch works as opensearch this statement is not true: "If you examine the JSON code of the document saved by Elasticsearch after sending the flat version, you'll see it has been also converted into a nested json document to adhere to the standard" OpenSearch accepts both flat and nested JSON with a nested mapping definition now, but the saved flat JSON document is not converted to nested format. Check #45063 (comment) for full details. The ECS standard is now utilized by many products following the merger of OpenTelemetry and Elastic standards to create a unified open standard. Refer to the following links for more information:
It would greatly enhance the Spring project if it could deliver logs in the ECS nested format. This would enable standardized log storage without the need to convert from flat to nested format before saving the log, when used with products other than Elasticsearch, such as Opensearch. The conversion from flat to nested format to be able to save the standardized log, can be quite tedious and resource-intensive when processing millions of logs if you don't use elasticsearch. regards |
According to the ECS Reference:
I also checked the ECS Logging Java repository elastic/ecs-logging-java and found they have a similar issues:
However, the ECS Java logging library continues to use dot notation and relies on this processor afterward. It seems they chose to keep dot notation because they can't ensure that all fields are correctly nested especially considering fields provided through {
"geo": {
"continent_name": "North America"
}
} Personally, if Spring Boot chooses to support the nested JSON format, I don't see any advantage in maintaining support for the current format, as only the nested JSON format fully complies with the ECS specification.
I set up OpenSearch using Docker Compose and submitted several JSON documents to it. Both formats are supported by OpenSearch. I hope I haven't overlooked anything, and I apologize once again for my earlier incorrect comment: #45063 (comment) |
No problem at all! My original description of this issue was incomplete. I'm a java developer and my knowledge of Opensearch is very limited. @rafaelma is one of our log experts, and I'm very grateful he stepped in an explained the problem. |
Thanks for letting us know. I have totally overlooked the sentence from the docs when implementing this format:
|
Hei @nosan and thanks again for your feedbak. Interesting, last year, we faced mapping issues with OpenSearch when using the flat format and the nested mapping definition from ECS. It appears that this has been resolved, and it now behaves like Elasticsearch. I have tested it again, and while OpenSearch accepts both flat and nested JSON with a nested mapping definition, the saved JSON document is not converted to nested format. This is unfortunate, as you can have both flat and nested documents within the same index when the same type of logs comes from different sources. When operating solely within Elasticsearch or OpenSearch, it works because they have implemented an abstraction that facilitates the conversion between formats, allowing for searches across different formats. However, this approach fails when using data from these indexes for further external processing in another system, as those documents can contain both formats. Here is a simple check I ran with
The two formats are not compatible; you must search using different methods because they have different structures.
As you can see, these are two incompatible representations of the same type of object, resulting in a lack of unified results. You have to search in different ways because they are different data structures. We could continue discussing the pros and cons of using flat versus nested JSON formats when defining a JSON structure. Personally, I prefer the nested format because it eliminates the need to interpret the document's structure and how to use it. One thing is certain: one should never mix flat and nested formats in the same document, as it can lead to confusion. Although both formats conform to the JSON RFC 8259 standard (see: https://www.rfc-editor.org/rfc/rfc8259.txt), the entire issue surrounding flat versus nested formats contributes to a lack of standardization and various problems. This is primarily because interpreting a JSON dataset with attributes that contain dots (.) and nested format is arbitrary and depends on the developer's implementation. Although it may be an unspoken convention that dots (.) signify the flat version of a nested structure, it's crucial to recognize that this is not a formal standard. The consequences of this ambiguity contribute to the challenges we are discussing. In our case, processing of logs in flat format with the Fluent Bit agent and Logstash is problematic, as they deliver JSON in nested format. If the flat parts are not converted to nested format before saving, the resulting data structure will become messy, which will lead to issues depending on the software used for further data processing. regards and good weekend :) |
Just want to mention for those interested that there's additional interesting info about this case in the closed duplicate issue ECS standard |
Good morning, We have further investigated this issue and uncovered some important findings. First, consider this statement: "Elasticsearch and its derivatives, such as OpenSearch, do not permit single attributes with dots This is highly unfortunate, and we are still very surprised that this has gone on for so long without more reactions from other communities and projects adhering to the standard. This is a summary of the consequences, which include the following:
[1] Kubernetes Annotations documentation: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ This is an example for the item
regards, |
@rafaelma I think we should consider this a bug, although it's too risky to fix in 3.4. I've pushed something to |
It would be nice with an option to get ecs logging as nested json like
instead of what we get today
The reason for asking is that collecting and processing of logs would be easier.
We are using Spring Boot 3.4.4.
The text was updated successfully, but these errors were encountered: