Azure Cosmos DB SDK 可观测性

适用范围: NoSQL

Azure Cosmos DB .NET 和 Java SDK 支持分布式跟踪,以帮助你监视应用程序。 跟踪请求流有助于调试、分析延迟和性能,以及收集诊断数据。 使用 OpenTelemetry 检测应用程序的跟踪信息,该工具不区分供应商,它提供一组语义约定来确保标准化的数据格式,无论你选择了哪个导出程序;或者,可以使用 Application Insights SDK 或 Azure Monitor OpenTelemetry Distro 进行检测。

入门

以下 SDK 中提供了分布式跟踪:

SDK 支持的版本 备注
.NET v3 SDK >= 3.36.0 此功能在预览版和非预览版中均可用。 对于非预览版本,默认处于关闭状态。 可以通过在 CosmosClientOptions.CosmosClientTelemetryOptions 中设置 DisableDistributedTracing = false 来启用跟踪。
.NET v3 SDK 预览版 >= 3.33.0-preview 此功能在预览版和非预览版中均可用。 对于预览版本,默认处于开启状态。 可以通过在 CosmosClientOptions.CosmosClientTelemetryOptions 中设置 DisableDistributedTracing = true 来禁用跟踪。
Java v4 SDK >= 4.43.0

跟踪属性

Azure Cosmos DB 跟踪遵循 OpenTelemetry 数据库规范,同时提供多个自定义属性。 根据请求操作,你可以看到不同的属性,并且这些属性是所有请求的核心属性。

属性 类型 说明
net.peer.name 字符串 Azure Cosmos DB 主机名。
db.name 字符串 Azure Cosmos DB 数据库名称。
db.system 字符串 数据库服务的标识符。 对于所有请求均为 cosmosdb
db.operation 字符串 操作名称,例如 CreateItemAsync
db.cosmosdb.container 字符串 Azure Cosmos DB 容器名称。
db.cosmosdb.client_id 字符串 表示唯一客户端实例的标识符。
db.cosmosdb.operation_type 字符串 操作类型,例如 Create
db.cosmosdb.connection_mode 字符串 客户端连接模式。 directgateway
db.cosmosdb.status_code int 请求的状态代码。
db.cosmosdb.sub_status_code int 请求的子状态代码。
db.cosmosdb.request_charge Double 操作消耗的 RU 数。
db.cosmosdb.regions_contacted 字符串 在 Azure Cosmos DB 帐户中联系的区域列表。
user_agent.original 字符串 Azure Cosmos DB SDK 生成的完整用户代理字符串。

收集诊断信息

如果在跟踪提供程序中配置了日志,则可以自动获取失败或延迟较高的 Azure Cosmos DB 请求的诊断。 这些日志可帮助你诊断失败的和缓慢的请求,而无需编写任何自定义代码来捕获它们。

除了获取失败请求的诊断日志外,还可以配置不同的延迟阈值,来指定何时从成功的请求收集诊断数据。 对于点操作,默认值为 100 毫秒,对于非点操作,则为 500 毫秒。 可以通过客户端选项调整这些阈值。

CosmosClientOptions options = new CosmosClientOptions()
{
    CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions()
    {
        DisableDistributedTracing = false,
        CosmosThresholdOptions = new CosmosThresholdOptions()
        {
            PointOperationLatencyThreshold = TimeSpan.FromMilliseconds(100),
            NonPointOperationLatencyThreshold = TimeSpan.FromMilliseconds(500)
        }
    },
};

可以配置日志级别来控制要接收的诊断日志。

日志级别 说明
错误 仅错误日志。
警告 基于配置的阈值的错误和高延迟请求的日志。
信息 没有特定的信息级别日志。 此级别的日志与使用“警告”相同。

根据应用程序环境,可以通过不同的方法配置日志级别。 下面是 appSettings.json 中的示例配置:

{ 
    "Logging": {​
        "LogLevel": {​
            "Azure-Cosmos-Operation-Request-Diagnostics": "Information"​
        }​
    }
}

配置 OpenTelemetry

若要将 OpenTelemetry 与 Azure Cosmos DB SDK 配合使用,请将 Azure.Cosmos.Operation 源添加到跟踪提供程序。 OpenTelemetry 与许多可以引入数据的导出程序兼容。 以下示例使用 Azure Monitor OpenTelemetry Exporter,但你可以选择配置所需的任何导出程序。 根据所选导出程序,引入数据时可能会出现最长几分钟的延迟。

提示

如果使用 Azure.Monitor.OpenTelemetry.Exporter 包,请确保使用 1.0.0-beta.11 或更高版本。 如果你使用 ASP.NET Core 和 Azure Monitor,我们建议改用 Azure Monitor OpenTelemetry Distro

此示例演示如何为 .NET 控制台应用配置 OpenTelemetry。 请参阅 GitHub 上的完整示例

namespace Cosmos.Samples.OpenTelemetry
{
    using global::OpenTelemetry;
    using global::OpenTelemetry.Trace;
    using global::OpenTelemetry.Resources;
    using System;
    using System.Threading.Tasks;
    using Newtonsoft.Json;
    using Microsoft.Azure.Cosmos;
    using Microsoft.Extensions.Azure;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Configuration;
    using Azure.Monitor.OpenTelemetry.Exporter;
    using System.Diagnostics;

    internal class Program
    {
        private static readonly string databaseName = "samples";
        private static readonly string containerName = "otel-sample";
        private static readonly string serviceName = "MySampleService";

        private static TracerProvider? _traceProvider;

        static async Task Main()
        {
            try
            {
                IConfigurationRoot configuration = new ConfigurationBuilder()
                                                            .AddJsonFile("AppSettings.json")
                                                            .Build();

                string endpoint = configuration["CosmosDBEndPointUrl"];
                if (string.IsNullOrEmpty(endpoint))
                {
                    throw new ArgumentNullException("Please specify a valid CosmosDBEndPointUrl in the appSettings.json");
                }

                string authKey = configuration["CosmosDBAuthorizationKey"];
                if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret key"))
                {
                    throw new ArgumentException("Please specify a valid CosmosDBAuthorizationKey in the appSettings.json");
                }

                string aiConnectionString = configuration["ApplicationInsightsConnectionString"];
                if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret connection string"))
                {
                    throw new ArgumentException("Please specify a valid ApplicationInsightsConnectionString in the appSettings.json");
                }

                // <SetUpOpenTelemetry>
                ResourceBuilder resource = ResourceBuilder.CreateDefault().AddService(
                            serviceName: serviceName,
                            serviceVersion: "1.0.0");

                // Set up logging to forward logs to chosen exporter
                using ILoggerFactory loggerFactory
                    = LoggerFactory.Create(builder => builder
                                                        .AddConfiguration(configuration.GetSection("Logging"))
                                                        .AddOpenTelemetry(options =>
                                                        {
                                                            options.IncludeFormattedMessage = true;
                                                            options.SetResourceBuilder(resource);
                                                            options.AddAzureMonitorLogExporter(o => o.ConnectionString = aiConnectionString); // Set up exporter of your choice
                                                        }));
                /*.AddFilter(level => level == LogLevel.Error) // Filter  is irrespective of event type or event name*/

                AzureEventSourceLogForwarder logforwader = new AzureEventSourceLogForwarder(loggerFactory);
                logforwader.Start();

                // Configure OpenTelemetry trace provider
                AppContext.SetSwitch("Azure.Experimental.EnableActivitySource", true);
                _traceProvider = Sdk.CreateTracerProviderBuilder()
                    .AddSource("Azure.Cosmos.Operation", // Cosmos DB source for operation level telemetry
                               "Sample.Application") 
                    .AddAzureMonitorTraceExporter(o => o.ConnectionString = aiConnectionString) // Set up exporter of your choice
                    .AddHttpClientInstrumentation() // Added to capture HTTP telemetry
                    .SetResourceBuilder(resource)
                    .Build();
                // </SetUpOpenTelemetry>

                ActivitySource source = new ActivitySource("Sample.Application");
                using (_ = source.StartActivity(".Net SDK : Azure Monitor : Open Telemetry Sample")) // Application level activity to track the entire execution of the application
                {
                    using (_ = source.StartActivity("GATEWAY MODE")) // Activity to track the execution of the gateway mode
                    {
                        await Program.RunCosmosDbOperation(ConnectionMode.Gateway, endpoint, authKey);
                    }
                    using (_ = source.StartActivity("DIRECT MODE")) // Activity to track the execution of the direct mode
                    {
                        await Program.RunCosmosDbOperation(ConnectionMode.Direct, endpoint, authKey);
                    }
                }
               
            }
            finally
            {
                _traceProvider?.Dispose();
                // Sleep is required for logging in console apps to ensure that telemetry is sent to the back-end even if application terminates.
                await Task.Delay(5000);

                Console.WriteLine("End of demo.");
            }
        }

        private static async Task RunCosmosDbOperation(ConnectionMode connMode, string endpoint, string authKey)
        {
            // <EnableDistributedTracing>
            CosmosClientOptions options = new CosmosClientOptions()
            {
                CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions()
                {
                    DisableDistributedTracing = false
                },
                ConnectionMode = connMode
            };
            // </EnableDistributedTracing>

            using (CosmosClient client = new CosmosClient(endpoint, authKey, options))
            {
                Console.WriteLine($"Getting container reference for {containerName}.");

                ContainerProperties properties = new ContainerProperties(containerName, partitionKeyPath: "/id");

                await client.CreateDatabaseIfNotExistsAsync(databaseName);
                Container container = await client.GetDatabase(databaseName).CreateContainerIfNotExistsAsync(properties);

                await Program.RunCrudDemo(container);
            }
        }

        public static async Task RunCrudDemo(Container container)
        {
            // Any operations will automatically generate telemetry 

            for(int i = 1; i <= 5; i++)
            {
                await container.CreateItemAsync(new Item { Id = $"{i}", Status = "new" }, new PartitionKey($"{i}"));
                Console.WriteLine($"Created document with id: {i}");
            }

            for (int i = 1; i <= 5; i++)
            {
                await container.ReadItemAsync<Item>($"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Read document with id: {i}");
            }

            try
            {
                await container.ReadItemAsync<Item>($"random key", new PartitionKey($"random partition"));
            }
            catch(Exception)
            {
                Console.WriteLine("Generate exception by reading an invalid key");
            }
            
            for (int i = 1; i <= 5; i++)
            {
                await container.ReplaceItemAsync(new Item { Id = $"{i}", Status = "updated" }, $"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Updated document with id: {i}");
            }

            for (int i = 1; i <= 5; i++)
            {
                await container.DeleteItemAsync<Item>($"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Deleted document with id: {i}");
            }
        }
    }

    internal class Item
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        public string Status { get; set; }
    }
}

配置 Application Insights SDK

根据编写应用程序所用的语言和你的计算环境,可以通过许多不同的方法配置 Application Insights。 有关详细信息,请参阅 Application Insights 文档。 将数据引入 Application Insights 最多可能需要几分钟时间。

注意

为目标 .NET 环境使用 2.22.0-beta2 或更高版本的 Application Insights 包。

以下示例演示如何为 .NET 控制台应用配置 Application Insights。 请参阅 GitHub 上的完整示例

namespace Cosmos.Samples.ApplicationInsights
{
    using System;
    using System.Threading.Tasks;
    using Newtonsoft.Json;
    using Microsoft.Azure.Cosmos;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.ApplicationInsights;
    using Microsoft.ApplicationInsights.WorkerService;
    using Microsoft.ApplicationInsights.DataContracts;

    internal class Program
    {
        private static readonly string databaseName = "samples";
        private static readonly string containerName = "ai-sample";

        private static TelemetryClient telemetryClient;

        static async Task Main()
        {
            try
            {
                IConfigurationRoot configuration = new ConfigurationBuilder()
                            .AddJsonFile("AppSettings.json")
                            .Build();

                string endpoint = configuration["CosmosDBEndPointUrl"];
                if (string.IsNullOrEmpty(endpoint))
                {
                    throw new ArgumentNullException("Please specify a valid CosmosDBEndPointUrl in the appSettings.json");
                }

                string authKey = configuration["CosmosDBAuthorizationKey"];
                if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret key"))
                {
                    throw new ArgumentException("Please specify a valid CosmosDBAuthorizationKey in the appSettings.json");
                }

                string aiConnectionString = configuration["ApplicationInsightsConnectionString"];
                if (string.IsNullOrEmpty(authKey) || string.Equals(authKey, "Super secret connection string"))
                {
                    throw new ArgumentException("Please specify a valid ApplicationInsightsConnectionString in the appSettings.json");
                }

                // <SetUpApplicationInsights>
                IServiceCollection services = new ServiceCollection();
                services.AddApplicationInsightsTelemetryWorkerService((ApplicationInsightsServiceOptions options) => options.ConnectionString = aiConnectionString);

                IServiceProvider serviceProvider = services.BuildServiceProvider();
                telemetryClient = serviceProvider.GetRequiredService<TelemetryClient>();
                // </SetUpApplicationInsights>

                var infoOperation = telemetryClient.StartOperation<DependencyTelemetry>(".Net SDK : ApplicationInsights SDK"); // Application level activity to track the entire execution of the application

                var gops = telemetryClient.StartOperation<DependencyTelemetry>("GATEWAY MODE"); // Activity to track the execution of the gateway mode
                await Program.RunCosmosDbOperation(ConnectionMode.Gateway, endpoint, authKey);
                telemetryClient.StopOperation(gops);

                var dops = telemetryClient.StartOperation<DependencyTelemetry>("DIRECT MODE"); // Activity to track the execution of the direct mode
                await Program.RunCosmosDbOperation(ConnectionMode.Direct, endpoint, authKey); 
                telemetryClient.StopOperation(dops);

                telemetryClient.StopOperation(infoOperation);
            }
            finally
            {
                // Explicitly calling Flush() followed by sleep is required for Application Insights logging in console apps to ensure that telemetry is sent to the back-end even if application terminates.
                telemetryClient?.Flush();
                await Task.Delay(5000);

                Console.WriteLine("End of demo.");
            }
        }

        private static async Task RunCosmosDbOperation(ConnectionMode connMode, string endpoint, string authKey)
        {
            // <EnableDistributedTracing>
            CosmosClientOptions options = new CosmosClientOptions()
            {
                CosmosClientTelemetryOptions = new CosmosClientTelemetryOptions()
                {
                    DisableDistributedTracing = false
                },
                ConnectionMode = connMode
            };
            // </EnableDistributedTracing>

            using (CosmosClient client = new CosmosClient(endpoint, authKey, options))
            {
                Console.WriteLine($"Getting container reference for {containerName}.");

                ContainerProperties properties = new ContainerProperties(containerName, partitionKeyPath: "/id");

                await client.CreateDatabaseIfNotExistsAsync(databaseName);
                Container container = await client.GetDatabase(databaseName).CreateContainerIfNotExistsAsync(properties);

                await Program.RunCrudDemo(container);
            }
        }

        public static async Task RunCrudDemo(Container container)
        {
            // Any operations will automatically generate telemetry 

            for (int i = 1; i <= 5; i++)
            {
                await container.CreateItemAsync(new Item { Id = $"{i}", Status = "new" }, new PartitionKey($"{i}"));
                Console.WriteLine($"Created document with id: {i}");
            }

            for (int i = 1; i <= 5; i++)
            {
                await container.ReadItemAsync<Item>($"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Read document with id: {i}");
            }

            for (int i = 1; i <= 5; i++)
            {
                await container.ReplaceItemAsync(new Item { Id = $"{i}", Status = "updated" }, $"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Updated document with id: {i}");
            }

            for (int i = 1; i <= 5; i++)
            {
                await container.DeleteItemAsync<Item>($"{i}", new PartitionKey($"{i}"));
                Console.WriteLine($"Deleted document with id: {i}");
            }
        }
    }

    internal class Item
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        public string Status { get; set; }
    }
}

将跟踪数据引入 Application Insights 后,可以在 Azure 门户中将其可视化,以了解应用程序中的请求流。 下面是在 Azure 门户左侧导航栏上的事务搜索中运行跨分区查询后获得的跟踪数据示例。

在 Application Insights 事务搜索中运行 Azure Cosmos DB 跨分区查询后获得的分布式跟踪的屏幕截图。

后续步骤