From 62fc4fbe796fa09c83aabfcf50bcf8c52b6e0fde Mon Sep 17 00:00:00 2001 From: Sourabh Jain Date: Fri, 25 Oct 2024 21:56:56 +0530 Subject: [PATCH] first commit --- .../AppInsightClassicAttributeKeys.cs | 69 +++++++++++ .../OpenTelemetry/CosmosDbEventSource.cs | 5 + .../OpenTelemetryAttributeKeys.cs | 73 +++++++++++- .../OpenTelemetryCoreRecorder.cs | 108 +++++------------- .../OpenTelemetryStablityModes.cs | 5 + .../OpenTelemetry/TracesStabilityFactory.cs | 97 ++++++++++++++++ 6 files changed, 274 insertions(+), 83 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs index 49d3c4bc3b..1c4dd81d30 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/AppInsightClassicAttributeKeys.cs @@ -4,6 +4,9 @@ namespace Microsoft.Azure.Cosmos.Telemetry { + using System; + using global::Azure.Core; + internal sealed class AppInsightClassicAttributeKeys { /// @@ -90,5 +93,71 @@ internal sealed class AppInsightClassicAttributeKeys /// Represents the item count in the operation. /// public const string ItemCount = "db.cosmosdb.item_count"; + + /// + /// Represents the type of exception. + /// + public const string ExceptionType = "exception.type"; + + /// + /// Represents the message of the exception. + /// + public const string ExceptionMessage = "exception.message"; + + /// + /// Represents the stack trace of the exception. + /// + public const string ExceptionStacktrace = "exception.stacktrace"; + + public static void PopulateAttributes(DiagnosticScope scope, + string operationName, + string databaseName, + string containerName, + string accountName, + string userAgent, + string machineId, + string clientId, + string connectionMode) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.DbOperation, operationName); + scope.AddAttribute(AppInsightClassicAttributeKeys.DbName, databaseName); + scope.AddAttribute(AppInsightClassicAttributeKeys.ContainerName, containerName); + scope.AddAttribute(AppInsightClassicAttributeKeys.ServerAddress, accountName); + scope.AddAttribute(AppInsightClassicAttributeKeys.UserAgent, userAgent); + scope.AddAttribute(AppInsightClassicAttributeKeys.MachineId, machineId); + scope.AddAttribute(AppInsightClassicAttributeKeys.ClientId, clientId); + scope.AddAttribute(AppInsightClassicAttributeKeys.ConnectionMode, connectionMode); + } + + public static void PopulateAttributes(DiagnosticScope scope, Exception exception) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.ExceptionStacktrace, exception.StackTrace); + scope.AddAttribute(AppInsightClassicAttributeKeys.ExceptionType, exception.GetType().Name); + + // If Exception is not registered with open Telemetry + if (!OpenTelemetryCoreRecorder.IsExceptionRegistered(exception, scope)) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.ExceptionMessage, exception.Message); + } + } + + public static void PopulateAttributes(DiagnosticScope scope, string operationType, OpenTelemetryAttributes response) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.OperationType, operationType); + if (response != null) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.RequestContentLength, response.RequestContentLength); + scope.AddAttribute(AppInsightClassicAttributeKeys.ResponseContentLength, response.ResponseContentLength); + scope.AddIntegerAttribute(AppInsightClassicAttributeKeys.StatusCode, (int)response.StatusCode); + scope.AddIntegerAttribute(AppInsightClassicAttributeKeys.SubStatusCode, response.SubStatusCode); + scope.AddIntegerAttribute(AppInsightClassicAttributeKeys.RequestCharge, (int)response.RequestCharge); + scope.AddAttribute(AppInsightClassicAttributeKeys.ItemCount, response.ItemCount); + scope.AddAttribute(AppInsightClassicAttributeKeys.ActivityId, response.ActivityId); + + if (response.Diagnostics != null) + { + scope.AddAttribute(AppInsightClassicAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(response.Diagnostics.GetContactedRegions())); + } + } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbEventSource.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbEventSource.cs index 4cd3b8ffa4..0d6aaac76e 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbEventSource.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/CosmosDbEventSource.cs @@ -35,6 +35,11 @@ public static void RecordDiagnosticsForRequests( Documents.OperationType operationType, OpenTelemetryAttributes response) { + if (response.Diagnostics == null) + { + return; + } + if (CosmosDbEventSource.IsEnabled(EventLevel.Warning)) { if (!DiagnosticsFilterHelper.IsSuccessfulResponse( diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs index 2d01b8864d..0f7f3d38a3 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryAttributeKeys.cs @@ -4,6 +4,9 @@ namespace Microsoft.Azure.Cosmos.Telemetry { + using System; + using global::Azure.Core; + /// /// Contains constant string values representing OpenTelemetry attribute keys for monitoring and tracing Cosmos DB operations. /// These keys follow the OpenTelemetry conventions and the Cosmos DB semantic conventions as outlined in the OpenTelemetry specification. @@ -115,7 +118,7 @@ internal sealed class OpenTelemetryAttributeKeys /// /// Represents the item count in the operation. /// - public const string ItemCount = "db.cosmosdb.item_count"; + public const string ItemCount = "db.cosmosdb.row_count"; /// /// Represents the activity ID for the operation. @@ -158,5 +161,73 @@ internal sealed class OpenTelemetryAttributeKeys /// Represents the stack trace of the exception. /// public const string ExceptionStacktrace = "exception.stacktrace"; + + public static void PopulateAttributes(DiagnosticScope scope, + string operationName, + string databaseName, + string containerName, + string accountName, + string userAgent, + string clientId, + string connectionMode) + { + scope.AddAttribute(OpenTelemetryAttributeKeys.DbOperation, operationName); + scope.AddAttribute(OpenTelemetryAttributeKeys.DbName, databaseName); + scope.AddAttribute(OpenTelemetryAttributeKeys.ContainerName, containerName); + scope.AddAttribute(OpenTelemetryAttributeKeys.ServerAddress, accountName); + scope.AddAttribute(OpenTelemetryAttributeKeys.UserAgent, userAgent); + scope.AddAttribute(OpenTelemetryAttributeKeys.ClientId, clientId); + scope.AddAttribute(OpenTelemetryAttributeKeys.ConnectionMode, connectionMode); + } + + public static void PopulateAttributes(DiagnosticScope scope, Exception exception) + { + scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionStacktrace, exception.StackTrace); + scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionType, exception.GetType().Name); + + // If Exception is not registered with open Telemetry + if (!OpenTelemetryCoreRecorder.IsExceptionRegistered(exception, scope)) + { + scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.Message); + } + } + + public static void PopulateAttributes(DiagnosticScope scope, QueryTextMode? queryTextMode, OpenTelemetryAttributes response) + { + if (response == null) + { + return; + } + + if (response.BatchSize is not null) + { + scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.BatchSize, (int)response.BatchSize); + } + + scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.StatusCode, (int)response.StatusCode); + scope.AddAttribute(OpenTelemetryAttributeKeys.RequestContentLength, response.RequestContentLength); + scope.AddAttribute(OpenTelemetryAttributeKeys.ResponseContentLength, response.ResponseContentLength); + scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.SubStatusCode, response.SubStatusCode); + scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.RequestCharge, (int)response.RequestCharge); + scope.AddAttribute(OpenTelemetryAttributeKeys.ItemCount, response.ItemCount); + scope.AddAttribute(OpenTelemetryAttributeKeys.ActivityId, response.ActivityId); + scope.AddAttribute(OpenTelemetryAttributeKeys.CorrelatedActivityId, response.CorrelatedActivityId); + scope.AddAttribute(OpenTelemetryAttributeKeys.ConsistencyLevel, response.ConsistencyLevel); + + if (response.QuerySpec is not null) + { + if (queryTextMode == QueryTextMode.All || + (queryTextMode == QueryTextMode.ParameterizedOnly && response.QuerySpec.ShouldSerializeParameters())) + { + scope.AddAttribute(OpenTelemetryAttributeKeys.QueryText, response.QuerySpec?.QueryText); + } + } + + if (response.Diagnostics != null) + { + scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(response.Diagnostics.GetContactedRegions())); + } + + } } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs index f606fef57e..8f917b3612 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryCoreRecorder.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Telemetry using System.Diagnostics; using global::Azure.Core; using Microsoft.Azure.Cosmos.Telemetry.Diagnostics; + using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry; using Microsoft.Azure.Documents; /// @@ -19,8 +20,6 @@ internal struct OpenTelemetryCoreRecorder : IDisposable { private const string CosmosDb = "cosmosdb"; - private static readonly string otelStabilityMode = Environment.GetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN"); - private readonly DiagnosticScope scope = default; private readonly CosmosThresholdOptions config = null; private readonly Activity activity = null; @@ -69,13 +68,21 @@ private OpenTelemetryCoreRecorder( this.config = config; this.operationType = operationType; - this.connectionModeCache = Enum.GetName(typeof(ConnectionMode), clientContext.ClientOptions.ConnectionMode); + this.connectionModeCache = clientContext.ClientOptions.ConnectionMode switch + { + ConnectionMode.Direct => "direct", + ConnectionMode.Gateway => "gateway", + _ => throw new NotImplementedException() + }; + this.queryTextMode = queryTextMode; if (scope.IsEnabled) { this.scope.Start(); + this.scope.AddAttribute(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb); + this.Record( operationName: operationName, containerName: containerName, @@ -151,28 +158,15 @@ public void Record( { if (this.IsEnabled) { - if (otelStabilityMode == OpenTelemetryStablityModes.DatabaseDupe) - { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.DbOperation, operationName); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.DbName, databaseName); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ContainerName, containerName); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ServerAddress, clientContext.Client?.Endpoint?.Host); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.UserAgent, clientContext.UserAgent); - } - else - { - // Classic Appinsights Support - this.scope.AddAttribute(AppInsightClassicAttributeKeys.DbOperation, operationName); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.DbName, databaseName); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.ContainerName, containerName); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.ServerAddress, clientContext.Client?.Endpoint?.Host); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.UserAgent, clientContext.UserAgent); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.MachineId, VmMetadataApiHandler.GetMachineId()); - } - - this.scope.AddAttribute(OpenTelemetryAttributeKeys.DbSystemName, OpenTelemetryCoreRecorder.CosmosDb); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ClientId, clientContext?.Client?.Id); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ConnectionMode, this.connectionModeCache); + TracesStabilityFactory.SetAttributes(this.scope, + operationName, + databaseName, + containerName, + clientContext.Client?.Endpoint?.Host, + clientContext.UserAgent, + VmMetadataApiHandler.GetMachineId(), + clientContext?.Client?.Id, + this.connectionModeCache); } } @@ -196,14 +190,7 @@ public void MarkFailed(Exception exception) { if (this.IsEnabled) { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionStacktrace, exception.StackTrace); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionType, exception.GetType().Name); - - // If Exception is not registered with open Telemetry - if (!OpenTelemetryCoreRecorder.IsExceptionRegistered(exception, this.scope)) - { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ExceptionMessage, exception.Message); - } + TracesStabilityFactory.SetAttributes(this.scope, exception); if (exception is not CosmosException || (exception is CosmosException cosmosException && !DiagnosticsFilterHelper @@ -242,58 +229,15 @@ public void Dispose() { OperationType operationType = (this.response == null || this.response?.OperationType == OperationType.Invalid) ? this.operationType : this.response.OperationType; - if (otelStabilityMode != OpenTelemetryStablityModes.DatabaseDupe) - { - string operationName = Enum.GetName(typeof(OperationType), operationType); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.OperationType, operationName); - } - - if (this.response != null) - { - if (this.response.BatchSize is not null) - { - this.scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.BatchSize, (int)this.response.BatchSize); - } + string operationTypeName = Enum.GetName(typeof(OperationType), operationType); - if (otelStabilityMode == OpenTelemetryStablityModes.DatabaseDupe) - { - this.scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.StatusCode, (int)this.response.StatusCode); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.RequestContentLength, this.response.RequestContentLength); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ResponseContentLength, this.response.ResponseContentLength); - } - else - { - this.scope.AddAttribute(AppInsightClassicAttributeKeys.RequestContentLength, this.response.RequestContentLength); - this.scope.AddAttribute(AppInsightClassicAttributeKeys.ResponseContentLength, this.response.ResponseContentLength); - this.scope.AddIntegerAttribute(AppInsightClassicAttributeKeys.StatusCode, (int)this.response.StatusCode); - } + TracesStabilityFactory.SetAttributes(this.scope, operationTypeName, this.queryTextMode, this.response); - this.scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.SubStatusCode, this.response.SubStatusCode); - this.scope.AddIntegerAttribute(OpenTelemetryAttributeKeys.RequestCharge, (int)this.response.RequestCharge); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ItemCount, this.response.ItemCount); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ActivityId, this.response.ActivityId); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.CorrelatedActivityId, this.response.CorrelatedActivityId); - this.scope.AddAttribute(OpenTelemetryAttributeKeys.ConsistencyLevel, this.response.ConsistencyLevel); + CosmosDbEventSource.RecordDiagnosticsForRequests(this.config, operationType, this.response); - if (this.response.QuerySpec is not null) - { - if (this.queryTextMode == QueryTextMode.All || - (this.queryTextMode == QueryTextMode.ParameterizedOnly && this.response.QuerySpec.ShouldSerializeParameters())) - { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.QueryText, this.response.QuerySpec?.QueryText); - } - } - - if (this.response.Diagnostics != null) - { - this.scope.AddAttribute(OpenTelemetryAttributeKeys.Region, ClientTelemetryHelper.GetContactedRegions(this.response.Diagnostics.GetContactedRegions())); - CosmosDbEventSource.RecordDiagnosticsForRequests(this.config, operationType, this.response); - } - - if (!DiagnosticsFilterHelper.IsSuccessfulResponse(this.response.StatusCode, this.response.SubStatusCode)) - { - this.scope.Failed($"{(int)this.response.StatusCode}/{this.response.SubStatusCode}"); - } + if (!DiagnosticsFilterHelper.IsSuccessfulResponse(this.response.StatusCode, this.response.SubStatusCode)) + { + this.scope.Failed($"{(int)this.response.StatusCode}/{this.response.SubStatusCode}"); } this.scope.Dispose(); diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryStablityModes.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryStablityModes.cs index b73b40fd00..8b31b1015c 100644 --- a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryStablityModes.cs +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/OpenTelemetryStablityModes.cs @@ -18,5 +18,10 @@ internal sealed class OpenTelemetryStablityModes /// emit both the old and the stable database conventions, allowing for a seamless transition. /// public const string DatabaseDupe = "database/dup"; + + /// + /// Environment Variable to support the classic AppInsight conventions + /// + public const string ClassicAppInsights = "appinsightssdk"; } } diff --git a/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs new file mode 100644 index 0000000000..42faceaaab --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Telemetry/OpenTelemetry/TracesStabilityFactory.cs @@ -0,0 +1,97 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry +{ + using System; + using global::Azure.Core; + + /// + /// Factory for handling telemetry trace stability modes, allowing attribute settings + /// based on environment-specified stability mode configurations. + /// + internal class TracesStabilityFactory + { + // Specifies the stability mode for telemetry attributes, configured via the OTEL_SEMCONV_STABILITY_OPT_IN environment variable. + private static readonly string otelStabilityMode = Environment.GetEnvironmentVariable("OTEL_SEMCONV_STABILITY_OPT_IN"); + + /// + /// Sets trace attributes based on stability mode for standard telemetry events. + /// + /// The diagnostic scope to be enriched with attributes. + /// The name of the operation being traced. + /// The name of the database in context. + /// The name of the container in context. + /// The account name associated with the operation. + /// The user agent performing the operation. + /// The machine identifier (optional). + /// The client identifier performing the operation. + /// The connection mode in use. + public static void SetAttributes(DiagnosticScope scope, + string operationName, + string databaseName, + string containerName, + string accountName, + string userAgent, + string machineId, + string clientId, + string connectionMode) + { + PopulateAttributesBasedOnMode( + () => AppInsightClassicAttributeKeys.PopulateAttributes(scope, operationName, databaseName, containerName, accountName, userAgent, machineId, clientId, connectionMode), + () => OpenTelemetryAttributeKeys.PopulateAttributes(scope, operationName, databaseName, containerName, accountName, userAgent, clientId, connectionMode)); + } + + /// + /// Sets trace attributes for telemetry events related to an exception. + /// + /// The diagnostic scope to be enriched with attributes. + /// The exception that occurred. + public static void SetAttributes(DiagnosticScope scope, Exception exception) + { + PopulateAttributesBasedOnMode( + () => AppInsightClassicAttributeKeys.PopulateAttributes(scope, exception), + () => OpenTelemetryAttributeKeys.PopulateAttributes(scope, exception)); + } + + /// + /// Sets trace attributes for telemetry events + /// + /// The diagnostic scope to be enriched with attributes. + /// The type of operation being traced. + /// The query text mode in use. + /// The telemetry attributes for the response. + public static void SetAttributes(DiagnosticScope scope, + string operationType, + QueryTextMode? queryTextMode, + OpenTelemetryAttributes response) + { + PopulateAttributesBasedOnMode( + () => AppInsightClassicAttributeKeys.PopulateAttributes(scope, operationType, response), + () => OpenTelemetryAttributeKeys.PopulateAttributes(scope, queryTextMode, response)); + } + + /// + /// Executes attribute population actions based on the specified stability mode. + /// + /// Action to populate AppInsightClassic attributes. + /// Action to populate OpenTelemetry attributes. + private static void PopulateAttributesBasedOnMode(Action populateClassicAttributes, Action populateOpenTelemetryAttributes) + { + switch (otelStabilityMode) + { + case OpenTelemetryStablityModes.Database: + populateOpenTelemetryAttributes(); + break; + case OpenTelemetryStablityModes.DatabaseDupe: + populateClassicAttributes(); + populateOpenTelemetryAttributes(); + break; + default: + populateClassicAttributes(); + break; + } + } + } +}