Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
30b0ed3
Adding EcsNamespacingProcessor
eyalkoren Mar 26, 2025
b96cc0c
Adding module-info
eyalkoren Mar 27, 2025
469df61
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren Mar 27, 2025
2bd819f
Exposing and testing the processor
eyalkoren Mar 27, 2025
1c2a670
Add test and some algorithm fixes
eyalkoren Mar 27, 2025
904c19c
Making scope non-mandatory
eyalkoren Mar 27, 2025
bd75b06
Minimize dependencies
eyalkoren Mar 27, 2025
50f3c4d
Extending REST tests
eyalkoren Mar 27, 2025
cb0dcee
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren Mar 27, 2025
f68cf93
Update docs/changelog/125699.yaml
eyalkoren Mar 27, 2025
9dbe94b
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren Mar 30, 2025
79bf683
instanceOf with pattern matching
eyalkoren Mar 30, 2025
dbc4d4a
instanceOf with pattern matching
eyalkoren Mar 30, 2025
d160fe6
revert constants usage
eyalkoren Mar 30, 2025
dfe33fc
Merge remote-tracking branch 'eyalkoren/ECS-namespacing-processor' in…
eyalkoren Mar 30, 2025
2b77e3c
Complete review change proposals
eyalkoren Mar 30, 2025
4773cf6
fix typo in test name
eyalkoren Mar 30, 2025
054a76f
[CI] Auto commit changes from spotless
Mar 30, 2025
4afacd0
Applying review suggestions
eyalkoren Mar 30, 2025
d67fa2c
Merge remote-tracking branch 'eyalkoren/ECS-namespacing-processor' in…
eyalkoren Mar 30, 2025
bef3643
Silence a warning from Intellij
joegallo Apr 3, 2025
49d439f
Rename this variable
joegallo Apr 3, 2025
e15984b
Fix some typos and reflow a comment
joegallo Apr 3, 2025
e09fa14
Use ofEntries for increased clarity
joegallo Apr 3, 2025
b71fa3f
Save a rehash and some traversals
joegallo Apr 3, 2025
e8338f8
Merge branch 'main' into ECS-namespacing-processor
joegallo Apr 3, 2025
98880ca
This can be static
joegallo Apr 9, 2025
d37587d
Rely on mutability for these tests
joegallo Apr 9, 2025
21f2f9e
Rename some variables
joegallo Apr 9, 2025
b0037c3
Drop the top-level warnings suppression
joegallo Apr 9, 2025
96ff183
Merge branch 'main' into ECS-namespacing-processor
joegallo Apr 9, 2025
65f7ea2
Rewrite this test
joegallo Apr 9, 2025
3639c77
Merge branch 'main' into ECS-namespacing-processor
joegallo Apr 25, 2025
f9421ac
Drop yaml-rest-compat-test
joegallo Apr 25, 2025
b2dd61d
Refactor ECS Namespacing to Normalize to OTel
eyalkoren Apr 28, 2025
c66663b
Apply review comments
eyalkoren Apr 28, 2025
7d536a7
Adding accurate resource attributes handling
eyalkoren May 5, 2025
9493a73
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 5, 2025
bb538a6
Suppress warning for forbidden usage of System.out in tests
eyalkoren May 5, 2025
e7c1f9d
Eliminating some more forbidden APIs
eyalkoren May 5, 2025
605bdc9
[CI] Auto commit changes from spotless
May 5, 2025
e65c0ef
Reverting shameful refactoring errors
eyalkoren May 6, 2025
916eaca
Merge remote-tracking branch 'eyalkoren/ECS-namespacing-processor' in…
eyalkoren May 6, 2025
07e78a2
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 6, 2025
5c34f94
Disabling ResourceAttributesTests
eyalkoren May 6, 2025
e11ea51
Adding local disk crawler
eyalkoren May 8, 2025
c85ddf1
Disabling tests with @LuceneTestCase.Nightly()
eyalkoren May 8, 2025
717472b
Disabling forbidden APIs
eyalkoren May 8, 2025
d93f043
Disabling test and adding javadoc
eyalkoren May 8, 2025
1c4bcaa
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 8, 2025
9d0da6a
Fix Logger#warn usage
eyalkoren May 8, 2025
0a6a5d4
Adding documentation
eyalkoren May 11, 2025
0f12253
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 11, 2025
cd90ca9
Fix typo
eyalkoren May 12, 2025
c1c4d1f
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 21, 2025
a259ce6
Disabling the tests temporarily with @Ignore
eyalkoren May 22, 2025
9d77fcd
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 22, 2025
45f02b6
Adding to toc
eyalkoren May 22, 2025
675ef90
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 27, 2025
2b8441d
Remove GitHub-API-based crawler
eyalkoren May 27, 2025
bf906e2
Merge remote-tracking branch 'upstream/main' into ECS-namespacing-pro…
eyalkoren May 29, 2025
f279b7a
Refactor: renaming to normalize_for_stream
eyalkoren May 29, 2025
44ac64b
Merge branch 'main' into ECS-namespacing-processor
joegallo May 29, 2025
55885a8
Merge branch 'main' into ECS-namespacing-processor
joegallo May 30, 2025
978b163
Merge branch 'main' into ECS-namespacing-processor
joegallo Jun 2, 2025
bc4a015
Merge branch 'main' into ECS-namespacing-processor
eyalkoren Jun 3, 2025
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
5 changes: 5 additions & 0 deletions docs/changelog/125699.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 125699
summary: Adding `EcsNamespacingProcessor`
area: Ingest Node
type: feature
issues: []
22 changes: 22 additions & 0 deletions modules/ingest-ecs/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

apply plugin: 'elasticsearch.internal-yaml-rest-test'
apply plugin: 'elasticsearch.yaml-rest-compat-test'

esplugin {
description = 'Ingest processor that applies ECS namespacing'
classname ='org.elasticsearch.ingest.ecs.EcsNamespacingPlugin'
}

restResources {
restApi {
include '_common', 'indices', 'index', 'cluster', 'nodes', 'get', 'ingest'
}
}
13 changes: 13 additions & 0 deletions modules/ingest-ecs/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module org.elasticsearch.ingest.ecs {
requires org.elasticsearch.base;
requires org.elasticsearch.server;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.ingest.ecs;

import org.elasticsearch.ingest.Processor;
import org.elasticsearch.plugins.IngestPlugin;
import org.elasticsearch.plugins.Plugin;

import java.util.Map;

public class EcsNamespacingPlugin extends Plugin implements IngestPlugin {

@Override
public Map<String, Processor.Factory> getProcessors(Processor.Parameters parameters) {
return Map.of(EcsNamespacingProcessor.TYPE, new EcsNamespacingProcessor.Factory());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.ingest.ecs;

import org.elasticsearch.cluster.metadata.ProjectId;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.ingest.AbstractProcessor;
import org.elasticsearch.ingest.IngestDocument;
import org.elasticsearch.ingest.Processor;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class EcsNamespacingProcessor extends AbstractProcessor {

public static final String TYPE = "ecs_namespacing";

private static final Map<String, String> RENAME_KEYS = Map.of(
"span.id",
"span_id",
"message",
"body.text",
"log.level",
"severity_text",
"trace.id",
"trace_id"
);

private static final Set<String> KEEP_KEYS;
static {
Set<String> keepKeys = new HashSet<>(Set.of("@timestamp", "attributes", "resource"));
Set<String> renamedTopLevelFields = new HashSet<>();
for (String value : RENAME_KEYS.values()) {
int dotIndex = value.indexOf('.');
if (dotIndex != -1) {
renamedTopLevelFields.add(value.substring(0, dotIndex));
} else {
renamedTopLevelFields.add(value);
}
}
keepKeys.addAll(renamedTopLevelFields);
KEEP_KEYS = Set.copyOf(keepKeys);
}

private static final String AGENT_PREFIX = "agent.";
private static final String CLOUD_PREFIX = "cloud.";
private static final String HOST_PREFIX = "host.";

private static final String ATTRIBUTES_KEY = "attributes";
private static final String RESOURCE_KEY = "resource";
private static final String SCOPE_KEY = "scope";
private static final String BODY_KEY = "body";
private static final String TEXT_KEY = "text";
private static final String STRUCTURED_KEY = "structured";

EcsNamespacingProcessor(String tag, String description) {
super(tag, description);
}

@Override
public String getType() {
return TYPE;
}

@Override
public IngestDocument execute(IngestDocument document) {
Map<String, Object> source = document.getSource();

boolean isOTel = isOTelDocument(source);
if (isOTel) {
return document;
}

// non-OTel document

Map<String, Object> newAttributes = new HashMap<>();
// The keep keys indicate the fields that should be kept at the top level later on when applying the namespacing.
// However, at this point we need to move their original values to the new attributes namespace, except for the @timestamp field.
for (String keepKey : KEEP_KEYS) {
if (keepKey.equals("@timestamp")) {
continue;
}
Object value = source.remove(keepKey);
if (value != null) {
newAttributes.put(keepKey, value);
}
}

Map<String, Object> newResource = new HashMap<>();
Map<String, Object> newResourceAttributes = new HashMap<>();
newResource.put(ATTRIBUTES_KEY, newResourceAttributes);

source.put(ATTRIBUTES_KEY, newAttributes);
source.put(RESOURCE_KEY, newResource);

renameSpecialKeys(document);

// Iterate through all top level keys and move them to the appropriate namespace
for (String key : Sets.newHashSet(source.keySet())) {
if (KEEP_KEYS.contains(key)) {
continue;
}
if (shouldMoveToResourceAttributes(key)) {
Object value = source.remove(key);
newResourceAttributes.put(key, value);
} else {
Object value = source.remove(key);
newAttributes.put(key, value);
}
}

// Flatten attributes
source.replace(ATTRIBUTES_KEY, Maps.flatten(newAttributes, false, false));
newResource.replace(ATTRIBUTES_KEY, Maps.flatten(newResourceAttributes, false, false));

return document;
}

@SuppressWarnings("unchecked")
boolean isOTelDocument(Map<String, Object> source) {
Object resource = source.get(RESOURCE_KEY);
if (resource instanceof Map == false) {
return false;
} else {
Object resourceAttributes = ((Map<String, Object>) resource).get(ATTRIBUTES_KEY);
if (resourceAttributes != null && (resourceAttributes instanceof Map) == false) {
return false;
}
}

Object scope = source.get(SCOPE_KEY);
if (scope != null && scope instanceof Map == false) {
return false;
}

Object attributes = source.get(ATTRIBUTES_KEY);
if (attributes != null && attributes instanceof Map == false) {
return false;
}

Object body = source.get(BODY_KEY);
if (body != null) {
if (body instanceof Map == false) {
return false;
}
Object bodyText = ((Map<String, Object>) body).get(TEXT_KEY);
if (bodyText != null && (bodyText instanceof String) == false) {
return false;
}
Object bodyStructured = ((Map<String, Object>) body).get(STRUCTURED_KEY);
return (bodyStructured instanceof String) == false;
}
return true;
}

private void renameSpecialKeys(IngestDocument document) {
RENAME_KEYS.forEach((nonOtelName, otelName) -> {
// first look assuming dot notation for nested fields
Object value = document.getFieldValue(nonOtelName, Object.class, true);
if (value != null) {
document.removeField(nonOtelName);
// recursively remove empty parent fields
int lastDot = nonOtelName.lastIndexOf('.');
while (lastDot > 0) {
String parentName = nonOtelName.substring(0, lastDot);
// parent should never be null and must be a map if we are here
@SuppressWarnings("unchecked")
Map<String, Object> parent = (Map<String, Object>) document.getFieldValue(parentName, Map.class);
if (parent.isEmpty()) {
document.removeField(parentName);
} else {
break;
}
lastDot = parentName.lastIndexOf('.');
}
} else if (nonOtelName.contains(".")) {
// look for dotted field names
value = document.getSource().remove(nonOtelName);
}
if (value != null) {
document.setFieldValue(otelName, value);
}
});
}

private boolean shouldMoveToResourceAttributes(String key) {
return key.startsWith(AGENT_PREFIX)
|| key.equals("agent")
|| key.startsWith(CLOUD_PREFIX)
|| key.equals("cloud")
|| key.startsWith(HOST_PREFIX)
|| key.equals("host");
}

public static final class Factory implements Processor.Factory {
@Override
public Processor create(
Map<String, Processor.Factory> processorFactories,
String tag,
String description,
Map<String, Object> config,
ProjectId projectId
) throws Exception {
return new EcsNamespacingProcessor(tag, description);
}
}
}
Loading
Loading