diff --git a/src/OpenTelemetry.Exporter.OneCollector/Internal/EventNameManager.cs b/src/OpenTelemetry.Exporter.OneCollector/Internal/EventNameManager.cs index 90a44634b2..b6b42edc36 100644 --- a/src/OpenTelemetry.Exporter.OneCollector/Internal/EventNameManager.cs +++ b/src/OpenTelemetry.Exporter.OneCollector/Internal/EventNameManager.cs @@ -12,6 +12,7 @@ internal sealed class EventNameManager // Note: OneCollector will silently drop events which have a name less than 4 characters. internal const int MinimumEventFullNameLength = 4; internal const int MaximumEventFullNameLength = 100; + private static readonly Regex EventNamespaceValidationRegex = new(@"^[A-Za-z](?:\.?[A-Za-z0-9]+?)*$", RegexOptions.Compiled); private static readonly Regex EventNameValidationRegex = new(@"^[A-Za-z][A-Za-z0-9]*$", RegexOptions.Compiled); @@ -20,6 +21,7 @@ internal sealed class EventNameManager private readonly IReadOnlyDictionary? eventFullNameMappings; private readonly ResolvedEventFullName defaultEventFullName; private readonly Hashtable eventNamespaceCache = new(StringComparer.OrdinalIgnoreCase); + private readonly Hashtable eventFullNameCache = new(StringComparer.OrdinalIgnoreCase); public EventNameManager( string defaultEventNamespace, @@ -43,15 +45,43 @@ public EventNameManager( #endif } - // Note: This is exposed for unit tests. + // Note: These caches are exposed for unit tests. internal Hashtable EventNamespaceCache => this.eventNamespaceCache; + internal Hashtable EventFullNameCache => this.eventFullNameCache; + public static bool IsEventNamespaceValid(string eventNamespace) => EventNamespaceValidationRegex.IsMatch(eventNamespace); public static bool IsEventNameValid(string eventName) => EventNameValidationRegex.IsMatch(eventName); + public ResolvedEventFullName ResolveEventFullName( + string eventFullName) + { + if (this.eventFullNameCache[eventFullName] is ResolvedEventFullName cachedEventFullName) + { + return cachedEventFullName; + } + + byte[] eventFullNameBlob = BuildEventFullName(string.Empty, eventFullName); + + var resolvedEventFullName = new ResolvedEventFullName( + eventFullNameBlob, + originalEventNamespace: null, + originalEventName: null); + + lock (this.eventFullNameCache) + { + if (this.eventFullNameCache[eventFullName] is null) + { + this.eventFullNameCache[eventFullName] = resolvedEventFullName; + } + } + + return resolvedEventFullName; + } + public ResolvedEventFullName ResolveEventFullName( string? eventNamespace, string? eventName) diff --git a/src/OpenTelemetry.Exporter.OneCollector/Internal/Serialization/LogRecordCommonSchemaJsonSerializer.cs b/src/OpenTelemetry.Exporter.OneCollector/Internal/Serialization/LogRecordCommonSchemaJsonSerializer.cs index 519a5e1d2c..2cdb647143 100644 --- a/src/OpenTelemetry.Exporter.OneCollector/Internal/Serialization/LogRecordCommonSchemaJsonSerializer.cs +++ b/src/OpenTelemetry.Exporter.OneCollector/Internal/Serialization/LogRecordCommonSchemaJsonSerializer.cs @@ -89,9 +89,23 @@ protected override void SerializeItemToJson(Resource resource, LogRecord item, C Debug.Assert(writer != null, "writer was null"); - var resolvedEventFullName = this.eventNameManager.ResolveEventFullName( - item.CategoryName, - item.EventId.Name); + int attributeStartIndex = 0; + EventNameManager.ResolvedEventFullName resolvedEventFullName; + if (item.Attributes != null + && item.Attributes.Count > 0 + && item.Attributes[0].Key == "{EventFullName}" + && item.Attributes[0].Value is string eventFullName + && !string.IsNullOrEmpty(eventFullName)) + { + attributeStartIndex++; + resolvedEventFullName = this.eventNameManager.ResolveEventFullName(eventFullName); + } + else + { + resolvedEventFullName = this.eventNameManager.ResolveEventFullName( + item.CategoryName, + item.EventId.Name); + } writer!.WriteStartObject(); @@ -150,7 +164,7 @@ protected override void SerializeItemToJson(Resource resource, LogRecord item, C if (item.Attributes != null) { - for (int i = 0; i < item.Attributes.Count; i++) + for (int i = attributeStartIndex; i < item.Attributes.Count; i++) { var attribute = item.Attributes[i]; diff --git a/test/OpenTelemetry.Exporter.OneCollector.Tests/EventNameManagerTests.cs b/test/OpenTelemetry.Exporter.OneCollector.Tests/EventNameManagerTests.cs index 74e5d2c94c..c1a211de74 100644 --- a/test/OpenTelemetry.Exporter.OneCollector.Tests/EventNameManagerTests.cs +++ b/test/OpenTelemetry.Exporter.OneCollector.Tests/EventNameManagerTests.cs @@ -103,6 +103,22 @@ public void EventNameCacheTest() Assert.Single((eventNameManager.EventNamespaceCache["Test"] as Hashtable)!); } + [Fact] + public void EventFullNameCacheTest() + { + var eventNameManager = BuildEventNameManagerWithDefaultOptions(); + + Assert.Empty(eventNameManager.EventFullNameCache); + + eventNameManager.ResolveEventFullName("Company_Product_EventName"); + + Assert.Single(eventNameManager.EventFullNameCache); + + eventNameManager.ResolveEventFullName("company_product_eventName"); + + Assert.Single(eventNameManager.EventFullNameCache); + } + [Fact] public void EventFullNameMappedWhenEventNamespaceMatchesTest() { diff --git a/test/OpenTelemetry.Exporter.OneCollector.Tests/LogRecordCommonSchemaJsonSerializerTests.cs b/test/OpenTelemetry.Exporter.OneCollector.Tests/LogRecordCommonSchemaJsonSerializerTests.cs index 87d557461e..0dfebc8a01 100644 --- a/test/OpenTelemetry.Exporter.OneCollector.Tests/LogRecordCommonSchemaJsonSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.OneCollector.Tests/LogRecordCommonSchemaJsonSerializerTests.cs @@ -204,7 +204,7 @@ public void LogRecordScopesJsonTest() } [Fact] - public void LogRecordStateValuesJsonTest() + public void LogRecordAttributesJsonTest() { string json = GetLogRecordJson(1, (index, logRecord) => { @@ -216,6 +216,24 @@ public void LogRecordStateValuesJsonTest() json); } + [Fact] + public void LogRecordAttributesWithEventFullNameJsonTest() + { + string json = GetLogRecordJson(1, (index, logRecord) => + { + logRecord.Attributes = new List> + { + new KeyValuePair("{EventFullName}", "company_Product_EventName"), + new KeyValuePair("stateKey1", "stateValue1"), + new KeyValuePair("stateKey2", "stateValue2"), + }; + }); + + Assert.Equal( + """{"ver":"4.0","name":"Company_Product_EventName","time":"2032-01-18T10:11:12Z","iKey":"o:tenant-token","data":{"severityText":"Trace","severityNumber":1,"stateKey1":"stateValue1","stateKey2":"stateValue2"}}""" + "\n", + json); + } + [Fact] public void LogRecordTraceContextJsonTest() {