Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/Elastic.CommonSchema.NLog/EcsLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ public NLogEcsDocument RenderEcsDocument(LogEventInfo logEvent)
ecsEvent.Event = GetEvent(logEvent);
ecsEvent.Process = GetProcess(logEvent);
ecsEvent.Tags = GetTags(logEvent);
#pragma warning disable CS0618 // Obsolete Labels
ecsEvent.Labels = GetLabels(logEvent);
#pragma warning restore CS0618
ecsEvent.Http = GetHttp(logEvent);
ecsEvent.Url = GetUrl(logEvent);

Expand Down
3 changes: 2 additions & 1 deletion src/Elastic.CommonSchema/EcsDocument.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ public partial class EcsDocument : BaseFieldSet , IAs, ICodeSignature, IElf, IEn
/// Container for additional metadata against this event.
/// <para/>
/// When working with unknown fields use <see cref="AssignField"/>. <br/>
/// <para> This will try to assign valid ECS fields to their respective property
/// <para> This will try to assign valid ECS fields to their respective property
/// Failing that it will assign strings to <see cref="Labels"/> and everything else to <see cref="Metadata"/> </para>
/// </summary>
[Obsolete("Use Attributes instead. Metadata values will be merged into Attributes during serialization.")]
[JsonPropertyName("metadata"), DataMember(Name = "metadata")]
[JsonConverter(typeof(MetadataDictionaryConverter))]
public MetadataDictionary? Metadata { get; set; }
Expand Down
41 changes: 41 additions & 0 deletions src/Elastic.CommonSchema/EcsDocument.OTel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

namespace Elastic.CommonSchema;

public partial class EcsDocument
{
/// <summary>
/// Passthrough key-value store for OTel semantic convention attributes.
/// In Elasticsearch, <c>attributes</c> is a passthrough field type where the prefix can be omitted during queries.
/// In JSON source, data is nested under an <c>attributes</c> object.
/// </summary>
[JsonPropertyName("attributes"), DataMember(Name = "attributes")]
public MetadataDictionary? Attributes { get; set; }

/// <summary>
/// Assigns an OTel attribute value. Always stores in <see cref="Attributes"/>.
/// If the OTel name maps to an ECS field (via <see cref="OTelMappings.OTelToEcs"/>),
/// also sets the corresponding ECS property via <see cref="AssignField"/>.
/// </summary>
/// <param name="otelName">The OTel semantic convention attribute name</param>
/// <param name="value">The value to assign</param>
public void AssignOTelField(string otelName, object value)
{
// Always store in Attributes
Attributes ??= new MetadataDictionary();
Attributes[otelName] = value;

// If there's a mapping to an ECS field, also set it
if (OTelMappings.OTelToEcs.TryGetValue(otelName, out var ecsPath))
AssignField(ecsPath, value);
else
// For match relations, the OTel name IS the ECS name — try direct assignment
AssignField(otelName, value);
}
}
45 changes: 45 additions & 0 deletions src/Elastic.CommonSchema/EcsDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public static TEcsDocument CreateNewWithDefaults<TEcsDocument>(
if (options?.IncludeActivityData is null or true)
SetActivityData(doc);

// Process remaining OTel resource attributes through the mapping table
ApplyOTelResourceAttributes(doc, initialCache.OTelResourceAttributes);

return doc;
}

Expand All @@ -121,6 +124,48 @@ private static IDictionary<string, string> ParseOTelResourceAttributes(string? r
return keyValues;
}

// Attributes already handled by specific code in GetService/GetHost
private static readonly HashSet<string> HandledResourceAttributes = new()
{
"service.name", "service.version", "service.instance.id",
"deployment.environment",
"host.name", "host.type", "host.arch"
};

private static void ApplyOTelResourceAttributes(EcsDocument doc, IDictionary<string, string>? resourceAttributes)
{
if (resourceAttributes == null || resourceAttributes.Count == 0) return;

foreach (var kvp in resourceAttributes)
{
var attrName = kvp.Key;
var attrValue = kvp.Value;

// Skip attributes already handled by specific code
if (HandledResourceAttributes.Contains(attrName)) continue;

// Check if this OTel attribute name maps to an ECS field
if (OTelMappings.OTelToEcs.TryGetValue(attrName, out var ecsPath))
{
// Equivalent mapping: set ECS property and store in Attributes
doc.AssignField(ecsPath, attrValue);
doc.Attributes ??= new MetadataDictionary();
doc.Attributes[attrName] = attrValue;
}
else if (OTelMappings.AllBidirectionalEcsFields.Contains(attrName))
{
// Match relation: OTel name IS the ECS name
doc.AssignField(attrName, attrValue);
}
else
{
// Unknown attribute: store in Attributes only
doc.Attributes ??= new MetadataDictionary();
doc.Attributes[attrName] = attrValue;
}
}
}


/// <summary>
/// Create an instance of <see cref="Agent"/> that defaults to the assembly from
Expand Down
2 changes: 2 additions & 0 deletions src/Elastic.CommonSchema/FieldSets.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public abstract class BaseFieldSet {
/// Example: `docker` and `k8s` labels.</para>
/// <example>{"application": "foo-bar", "env": "production"}</example>
///</summary>
[Obsolete("Use EcsDocument.Attributes instead. Labels values will be merged into Attributes during serialization.")]
[JsonPropertyName("labels"), DataMember(Name = "labels")]
public Labels? Labels { get; set; }
}
Expand Down Expand Up @@ -605,6 +606,7 @@ public abstract class ContainerFieldSet {
/// <para>Image labels.</para>
/// <example></example>
///</summary>
[Obsolete("Use EcsDocument.Attributes instead. Labels values will be merged into Attributes during serialization.")]
[JsonPropertyName("labels"), DataMember(Name = "labels")]
public ContainerLabels? Labels { get; set; }
}
Expand Down
7 changes: 5 additions & 2 deletions src/Elastic.CommonSchema/IndexComponents.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ public static class IndexComponents
""@timestamp"": {
""type"": ""date""
},
""attributes"": {
""type"": ""passthrough"",
""priority"": 10
},
""labels"": {
""type"": ""object""
},
Expand All @@ -359,8 +363,7 @@ public static class IndexComponents
}
}
}
}
"
}"
},
{
"ecs_9.3.0_client",
Expand Down
4 changes: 2 additions & 2 deletions src/Elastic.CommonSchema/IndexTemplates.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public static string GetIndexTemplateForElasticsearchComposable(string indexPatt
""codec"": ""best_compression"",
""mapping"": {
""total_fields"": {
""limit"": 3000
""limit"": 3212
}
}
}
Expand Down Expand Up @@ -9562,7 +9562,7 @@ public static string GetIndexTemplateForElasticsearchLegacy(string indexPattern
""index"": {
""mapping"": {
""total_fields"": {
""limit"": 10000
""limit"": 3212
}
},
""refresh_interval"": ""5s""
Expand Down
Loading
Loading