Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions src/OpenTelemetry.Exporter.OneCollector/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

* Event full names supplied directly via the `{EventFullName}` log attribute are
now validated: invalid values are now discarded and the default event full name
is used instead.
([#4857](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4857))

## 1.17.0

Released 2026-Jul-17
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ public static bool IsEventNamespaceValid(string eventNamespace)
public static bool IsEventNameValid(string eventName)
=> EventNameValidationRegex().IsMatch(eventName);

public static bool IsEventFullNameValid(string eventFullName)
{
if (string.IsNullOrEmpty(eventFullName))
{
return false;
}

foreach (var c in eventFullName)
{
if (c is not ((>= 'A' and <= 'Z') or (>= 'a' and <= 'z') or (>= '0' and <= '9') or '.' or '_'))
{
return false;
}
}

return true;
}

public ResolvedEventFullName ResolveEventFullName(
string eventFullName)
{
Expand All @@ -58,7 +76,18 @@ public ResolvedEventFullName ResolveEventFullName(
return cachedEventFullName;
}

var eventFullNameBlob = BuildEventFullName(string.Empty, eventFullName);
byte[] eventFullNameBlob;

if (!IsEventFullNameValid(eventFullName) ||
eventFullName.Length is < MinimumEventFullNameLength or > MaximumEventFullNameLength)
{
OneCollectorExporterEventSource.Log.EventFullNameDiscarded(string.Empty, eventFullName);
eventFullNameBlob = this.defaultEventFullName.EventFullName;
}
else
{
eventFullNameBlob = BuildEventFullName(string.Empty, eventFullName);
}
Comment thread
martincostello marked this conversation as resolved.
Outdated

var resolvedEventFullName = new ResolvedEventFullName(
eventFullNameBlob,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections;
using System.Text;
using System.Text.Json;

namespace OpenTelemetry.Exporter.OneCollector.Tests;

Expand All @@ -15,9 +16,7 @@ public class EventNameManagerTests
[InlineData("company99.1product")]
[InlineData("c")]
public void ValidEventNamespaceTest(string eventNamespace)
{
Assert.True(EventNameManager.IsEventNamespaceValid(eventNamespace));
}
=> Assert.True(EventNameManager.IsEventNamespaceValid(eventNamespace));

[Theory]
[InlineData("9")]
Expand All @@ -26,19 +25,15 @@ public void ValidEventNamespaceTest(string eventNamespace)
[InlineData(".Company")]
[InlineData("")]
public void InvalidEventNamespaceTest(string eventNamespace)
{
Assert.False(EventNameManager.IsEventNamespaceValid(eventNamespace));
}
=> Assert.False(EventNameManager.IsEventNamespaceValid(eventNamespace));

[Theory]
[InlineData("Opened")]
[InlineData("closed")]
[InlineData("c")]
[InlineData("event9")]
public void ValidEventNameTest(string eventNamespace)
{
Assert.True(EventNameManager.IsEventNameValid(eventNamespace));
}
=> Assert.True(EventNameManager.IsEventNameValid(eventNamespace));

[Theory]
[InlineData("9")]
Expand All @@ -47,9 +42,7 @@ public void ValidEventNameTest(string eventNamespace)
[InlineData(".Event")]
[InlineData("")]
public void InvalidEventNameTest(string eventNamespace)
{
Assert.False(EventNameManager.IsEventNameValid(eventNamespace));
}
=> Assert.False(EventNameManager.IsEventNameValid(eventNamespace));

[Theory]
[InlineData(null, null, "DefaultNamespace.DefaultName")]
Expand Down Expand Up @@ -102,6 +95,67 @@ public void EventNameCacheTest()
Assert.Single((eventNameManager.EventNamespaceCache["Test"] as Hashtable)!);
}

[Theory]
[InlineData(126)]
[InlineData(127)]
[InlineData(1000)]
public void ResolveEventFullNameSingleArgumentBoundsLength(int length)
{
var eventNameManager = BuildEventNameManagerWithDefaultOptions();

var resolved = eventNameManager.ResolveEventFullName(new string('A', length));

if (length <= EventNameManager.MaximumEventFullNameLength)
{
Assert.Equal(Encoding.ASCII.GetBytes($"\"{new string('A', length)}\""), resolved.EventFullName);
}
else
{
Assert.Equal(Encoding.ASCII.GetBytes("\"DefaultNamespace.DefaultName\""), resolved.EventFullName);
}
}
Comment thread
martincostello marked this conversation as resolved.

[Theory]
[InlineData("A\",\"injected\":\"value")]
[InlineData("name\"with\"quotes")]
[InlineData("name\\with\\backslashes")]
[InlineData("name with spaces")]
public void ResolveEventFullNameSingleArgumentRejectsUnsafeCharacters(string payload)
{
var eventNameManager = BuildEventNameManagerWithDefaultOptions();

var resolved = eventNameManager.ResolveEventFullName(payload);

Assert.Equal(
Encoding.ASCII.GetBytes("\"DefaultNamespace.DefaultName\""),
resolved.EventFullName);

using var stream = new MemoryStream();
using (var writer = new Utf8JsonWriter(stream))
{
writer.WriteStartObject();
writer.WritePropertyName("name");
writer.WriteRawValue(resolved.EventFullName, skipInputValidation: true);
writer.WriteEndObject();
}

var json = Encoding.UTF8.GetString(stream.ToArray());
using var doc = JsonDocument.Parse(json);
Assert.Equal("DefaultNamespace.DefaultName", doc.RootElement.GetProperty("name").GetString());
}

[Fact]
public void ResolveEventFullNameSingleArgumentAllowsSafeCharacters()
{
var eventNameManager = BuildEventNameManagerWithDefaultOptions();

var resolved = eventNameManager.ResolveEventFullName("Company_Product_EventName");

Assert.Equal(
Encoding.ASCII.GetBytes("\"Company_Product_EventName\""),
resolved.EventFullName);
}

[Fact]
public void EventFullNameCacheTest()
{
Expand Down Expand Up @@ -190,17 +244,13 @@ public void EventFullNameMappedUsingPassthroughTest()
}

private static EventNameManager BuildEventNameManagerWithDefaultOptions()
{
return new EventNameManager("defaultNamespace", "defaultName");
}
=> new("defaultNamespace", "defaultName");

private static EventNameManager BuildEventNameManagerWithEventFullNameMappings(
params KeyValuePair<string, string>[] mappings)
{
return BuildEventNameManagerWithEventFullNameMappings(
params KeyValuePair<string, string>[] mappings) =>
BuildEventNameManagerWithEventFullNameMappings(
"defaultNamespace",
mappings);
}

private static EventNameManager BuildEventNameManagerWithEventFullNameMappings(
string defaultNamespace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
Expand Down Expand Up @@ -240,6 +241,26 @@ public void LogRecordAttributesWithEventFullNameJsonTest()
json);
}

[Fact]
public void LogRecordAttributesWithJsonEventFullNameIsNotInjected()
{
var json = GetLogRecordJson(1, (index, logRecord) =>
{
logRecord.Attributes =
[
new KeyValuePair<string, object?>("{EventFullName}", "bad\",\"injected\":\"json"),
];
});

Assert.Equal(
"""{"ver":"4.0","name":"Namespace.Name","time":"2032-01-18T10:11:12Z","iKey":"o:tenant-token","data":{"severityText":"Trace","severityNumber":1}}""" + "\n",
json);

using var doc = JsonDocument.Parse(json.Trim());
Assert.Equal("Namespace.Name", doc.RootElement.GetProperty("name").GetString());
Assert.False(doc.RootElement.TryGetProperty("injected", out _));
}

[Fact]
public void LogRecordTraceContextJsonTest()
{
Expand Down