Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e652e34
Scaffolding
MattKotsenas Nov 8, 2025
851079c
Add initial implementation
MattKotsenas Nov 9, 2025
222f964
Add basic tests
MattKotsenas Nov 9, 2025
84eb6b3
Add option for query text
MattKotsenas Nov 9, 2025
4cddfea
Add metrics listener and split trace/metric providers
MattKotsenas Nov 13, 2025
786ad66
Add tests for exceptions
MattKotsenas Nov 15, 2025
0462f33
Refactor to use a single registration
MattKotsenas Nov 19, 2025
4056495
Add query summarization and sanitization
MattKotsenas Nov 19, 2025
cef4a17
Add Benchmarks project
MattKotsenas Nov 21, 2025
b421e46
Use record structs
MattKotsenas Nov 24, 2025
b4b64a2
Perf: Use a truncating string builder
MattKotsenas Nov 24, 2025
71b7629
Add option to disable summarization
MattKotsenas Nov 26, 2025
b87fe93
Extract TraceRecord parsing to own class and add tests
MattKotsenas Dec 1, 2025
6b0e908
Reduce dependency to Kusto.Cloud.Platform
MattKotsenas Dec 1, 2025
6bfd45d
Add database to attributes
MattKotsenas Dec 1, 2025
229dafb
Add enrichment callback
MattKotsenas Dec 1, 2025
c64ee82
Update README to document options
MattKotsenas Dec 1, 2025
1514528
Separate server.address from server.port
MattKotsenas Dec 1, 2025
84c085e
Add full instrumentation benchmark
MattKotsenas Dec 2, 2025
d649f86
Update README with metrics
MattKotsenas Dec 2, 2025
0090c35
Add error.type as attribute on trace
MattKotsenas Dec 3, 2025
12fad87
Unify metrics and trace attribute handling
MattKotsenas Dec 5, 2025
067f0e6
Add metrics-only and trace-only integration tests
MattKotsenas Dec 6, 2025
42697db
Add additional logging for errors
MattKotsenas Dec 6, 2025
bfb8eca
Split InstrumentationOptions for Trace and Meter
MattKotsenas Dec 8, 2025
3a97216
Add CI workflow, issue templates, and codecov config
MattKotsenas Dec 8, 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
4 changes: 4 additions & 0 deletions .gitignore
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some other settings we should add for Verify, such as changes to .editorconfig settings. See here for some of the missing parts.

Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,7 @@ test/**/BenchmarkResults/**
!test/**/BenchmarkResults/results/
# Do NOT ignore files ending with -report-github.md anywhere under BenchmarkResults
!test/**/BenchmarkResults/results/*-report-github.md

# Ignore Verify received files
*.received.*
*.received/
7 changes: 7 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
<PackageVersion Include="Confluent.Kafka" Version="[2.4.0,)" />
<PackageVersion Include="Grpc.Core.Api" Version="[2.46.6,)" />
<PackageVersion Include="InfluxDB.Client" Version="[4.18.0,)" />
<PackageVersion Include="Microsoft.Azure.Kusto.Cloud.Platform" Version="[14.0.3,)" />
<PackageVersion Include="Microsoft.Azure.Kusto.Data" Version="14.0.3" />
<PackageVersion Include="Microsoft.Azure.Kusto.Language" Version="[12.3.1,)" />
<PackageVersion Include="Microsoft.ServiceFabric.Actors" Version="[7.1.2448,)" />
<PackageVersion Include="Microsoft.ServiceFabric.Services.Remoting" Version="[7.1.2448,)" />
<PackageVersion Include="Quartz" Version="[3.6.3,)" />
Expand Down Expand Up @@ -122,9 +125,11 @@
<PackageVersion Include="Microsoft.Data.SqlClient" Version="7.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.25" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.25" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.3.36714.1" />
<PackageVersion Include="Microsoft.Web.Xdt" Version="3.2.5" />
<PackageVersion Include="MinVer" Version="7.0.0" />
<PackageVersion Include="MessagePack" Version="3.1.4" />
Expand All @@ -140,9 +145,11 @@
<!-- These WCF dependencies are pinned until we drop support for net8.0 and net9.0 -->
<PackageVersion Include="System.ServiceModel.Http" Version="[8.1.2,)" />
<PackageVersion Include="System.ServiceModel.NetTcp" Version="[8.1.2,)" />
<PackageVersion Include="Testcontainers.Kusto" Version="4.11.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.11.0" />
<PackageVersion Include="Testcontainers.MySql" Version="4.11.0" />
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.11.0" />
<PackageVersion Include="Verify.Xunit" Version="31.4.3" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="[2.8.2,)" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
Expand Down
1 change: 1 addition & 0 deletions opentelemetry-dotnet-contrib.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@
<Project Path="test/OpenTelemetry.Instrumentation.Hangfire.Tests/OpenTelemetry.Instrumentation.Hangfire.Tests.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Http.Benchmarks/OpenTelemetry.Instrumentation.Http.Benchmarks.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Http.Tests/OpenTelemetry.Instrumentation.Http.Tests.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Kusto.Benchmarks/OpenTelemetry.Instrumentation.Kusto.Benchmarks.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Kusto.Tests/OpenTelemetry.Instrumentation.Kusto.Tests.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Owin.Tests/OpenTelemetry.Instrumentation.Owin.Tests.csproj" />
<Project Path="test/OpenTelemetry.Instrumentation.Process.Tests/OpenTelemetry.Instrumentation.Process.Tests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions.KustoMeterInstrumentationOptions() -> void
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions.RecordQuerySummary.get -> bool
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions.RecordQuerySummary.set -> void
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions.RecordQueryText.get -> bool
OpenTelemetry.Metrics.KustoMeterInstrumentationOptions.RecordQueryText.set -> void
OpenTelemetry.Metrics.MeterProviderBuilderExtensions
OpenTelemetry.Trace.KustoTraceInstrumentationOptions
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity!, Kusto.Cloud.Platform.Utils.TraceRecord!>?
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.KustoTraceInstrumentationOptions() -> void
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.RecordQuerySummary.get -> bool
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.RecordQuerySummary.set -> void
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.RecordQueryText.get -> bool
OpenTelemetry.Trace.KustoTraceInstrumentationOptions.RecordQueryText.set -> void
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddKustoInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.AddKustoInstrumentation(this OpenTelemetry.Metrics.MeterProviderBuilder! builder, System.Action<OpenTelemetry.Metrics.KustoMeterInstrumentationOptions!>? configureKustoMeterInstrumentationOptions) -> OpenTelemetry.Metrics.MeterProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddKustoInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder) -> OpenTelemetry.Trace.TracerProviderBuilder!
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddKustoInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder! builder, System.Action<OpenTelemetry.Trace.KustoTraceInstrumentationOptions!>? configureKustoTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder!
3 changes: 2 additions & 1 deletion src/OpenTelemetry.Instrumentation.Kusto/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

## Unreleased

For more details, please refer to the [README](README.md).
* Initial implementation.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Initial implementation.
* Initial implementation.
([#3591](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3591))

([#3591](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/3591))
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

namespace OpenTelemetry.Instrumentation.Kusto.Implementation;

/// <summary>
/// Provides extension methods for <see cref="InstrumentationHandleManager" />.
/// </summary>
internal static class InstrumentationHandleManagerExtensions
{
/// <summary>
/// Returns <see langword="true"/> if tracing is active (i.e., there is at least one tracing handle); otherwise, <see langword="false"/>.
/// </summary>
/// <param name="handleManager">
/// The <see cref="InstrumentationHandleManager"/> to check for active tracing handles.
/// </param>
/// <returns><see langword="true"/> if tracing is active; otherwise, <see langword="false"/>.</returns>
public static bool IsTracingActive(this InstrumentationHandleManager handleManager) => handleManager.TracingHandles > 0;

/// <summary>
/// Returns <see langword="true"/> if metrics is active (i.e., there is at least one metrics handle); otherwise, <see langword="false"/>.
/// </summary>
/// <param name="handleManager">
/// The <see cref="InstrumentationHandleManager"/> to check for active metrics handles.
/// </param>
/// <returns><see langword="true"/> if metrics is active; otherwise, <see langword="false"/>.</returns>
public static bool IsMetricsActive(this InstrumentationHandleManager handleManager) => handleManager.MetricHandles > 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Reflection;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Instrumentation.Kusto.Implementation;

/// <summary>
/// Helper class to hold common properties used by Kusto instrumentation.
/// </summary>
internal static class KustoActivitySourceHelper
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this contains activities and meters, I would give it a different name.

{
public const string DbSystem = "azure.kusto";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConiderDbSystemNameValue / KustoDbSystemNameValue / AzureKustoDbSystemNameValue

public const string ClientRequestIdTagKey = $"{DbSystem}.client_request_id";

public static readonly Assembly Assembly = typeof(KustoActivitySourceHelper).Assembly;
public static readonly AssemblyName AssemblyName = Assembly.GetName();
public static readonly string PackageVersion = Assembly.GetPackageVersion();

public static readonly string ActivitySourceName = AssemblyName.Name!;
public static readonly ActivitySource ActivitySource = new(ActivitySourceName, PackageVersion);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also include a schema URL like we do here:

private static (ActivitySource ActivitySource, Meter Meter) CreateTelemetry()
{
const string telemetrySchemaUrl = "https://opentelemetry.io/schemas/1.36.0";
var assembly = typeof(AspNetInstrumentation).Assembly;
var assemblyName = assembly.GetName();
#pragma warning disable IDE0370 // Suppression is unnecessary
var name = assemblyName.Name!;
#pragma warning restore IDE0370 // Suppression is unnecessary
var version = assembly.GetPackageVersion();
var activitySourceOptions = new ActivitySourceOptions(name)
{
Version = version,
TelemetrySchemaUrl = telemetrySchemaUrl,
};
var meterOptions = new MeterOptions(name)
{
Version = version,
TelemetrySchemaUrl = telemetrySchemaUrl,
};
return (new ActivitySource(activitySourceOptions), new Meter(meterOptions));
}


public static readonly string MeterName = AssemblyName.Name!;
public static readonly Meter Meter = new(MeterName, PackageVersion);

public static readonly Histogram<double> OperationDurationHistogram = Meter.CreateHistogram(
"db.client.operation.duration",
unit: "s",
advice: new InstrumentAdvice<double>() { HistogramBucketBoundaries = [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10] },
description: "Duration of database client operations");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using Kusto.Cloud.Platform.Utils;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Instrumentation.Kusto.Implementation;

/// <summary>
/// Class to hold the singleton instances used for Kusto instrumentation.
/// </summary>
internal static class KustoInstrumentation
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to design this without needing statics? Then the tests can be simpler too.

{
private static readonly Lazy<ITraceListener> Listener = new(() =>
{
Environment.SetEnvironmentVariable("KUSTO_DATA_TRACE_REQUEST_BODY", "1");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should be changing process env vars internally. If this is some internal implementation detail like how the Azure SDKs need (needed?) an opt-in to emit OTel metrics, I would instead document that the user is required to set that themselves.

It's also messy for tests as it changes the test environment.


var listener = new KustoTraceRecordListener();
TraceSourceManager.AddTraceListener(listener, startupDone: true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do exactly?


return listener;
});

/// <summary>
/// Gets or sets the post-configured trace options for Kusto instrumentation.
/// </summary>
public static KustoTraceInstrumentationOptions TraceOptions { get; set; } = new();

/// <summary>
/// Gets or sets the post-configured meter options for Kusto instrumentation.
/// </summary>
public static KustoMeterInstrumentationOptions MeterOptions { get; set; } = new();

/// <summary>
/// Gets the <see cref="InstrumentationHandleManager"/> that tracks if there are any active listeners for <see cref="KustoTraceRecordListener"/>.
/// </summary>
public static InstrumentationHandleManager HandleManager { get; } = new();

/// <summary>
/// Initializes the Kusto instrumentation by ensuring the listener is created and registered with the client library.
/// </summary>
public static void Initialize() => _ = Listener.Value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Tracing;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Instrumentation.Kusto.Implementation;

/// <summary>
/// EventSource for Kusto instrumentation.
/// </summary>
[EventSource(Name = "OpenTelemetry-Instrumentation-Kusto")]
internal sealed class KustoInstrumentationEventSource : EventSource
{
public static KustoInstrumentationEventSource Log { get; } = new();

[NonEvent]
public void EnrichmentException(Exception ex)
{
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
{
this.EnrichmentException(ex.ToInvariantString());
}
}

[Event(1, Message = "Enrichment exception: {0}", Level = EventLevel.Error)]
public void EnrichmentException(string exception) => this.WriteEvent(1, exception);

[Event(2, Message = "Trace record payload is NULL or has NULL message, record will not be processed.", Level = EventLevel.Warning)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Event(2, Message = "Trace record payload is NULL or has NULL message, record will not be processed.", Level = EventLevel.Warning)]
[Event(2, Message = "Trace record payload is null or has a null message, record will not be processed.", Level = EventLevel.Warning)]

public void NullPayload() => this.WriteEvent(2);

[Event(3, Message = "Failed to find context for activity ID '{0}', operation data will not be recorded.", Level = EventLevel.Warning)]
public void ContextNotFound(string activityId) => this.WriteEvent(3, activityId);

[NonEvent]
public void UnknownErrorProcessingTraceRecord(Exception ex)
{
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
{
this.UnknownErrorProcessingTraceRecord(ex.ToInvariantString());
}
}

[Event(4, Message = "Unknown error processing trace record, Exception: {0}", Level = EventLevel.Error)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[Event(4, Message = "Unknown error processing trace record, Exception: {0}", Level = EventLevel.Error)]
[Event(4, Message = "Unknown error processing trace record. Exception: {0}", Level = EventLevel.Error)]

public void UnknownErrorProcessingTraceRecord(string exception) => this.WriteEvent(4, exception);
}
Loading
Loading