Azure Cosmos DB SDK observability

APPLIES TO: NoSQL

The Azure Cosmos DB .NET and Java SDKs support distributed tracing to help you monitor your applications. Tracing the flow of requests is helpful in debugging, analyzing latency and performance, and gathering diagnostics. Instrument tracing for your applications using OpenTelemetry, which is vendor-neutral and has a set of semantic conventions to ensure a standardized data format regardless of your chosen exporter, or use the Application Insights SDK or Azure Monitor OpenTelemetry Distro.

Get started

Distributed tracing is available in the following SDKs:

SDK Supported version Notes
.NET v3 SDK >= 3.36.0 This feature is available in both preview and non-preview versions. For non-preview versions, it's off by default. You can enable tracing by setting DisableDistributedTracing = false in CosmosClientOptions.CosmosClientTelemetryOptions.
.NET v3 SDK preview >= 3.33.0-preview This feature is available in both preview and non-preview versions. For preview versions, it's on by default. You can disable tracing by setting DisableDistributedTracing = true in CosmosClientOptions.CosmosClientTelemetryOptions.
Java v4 SDK >= 4.43.0

Trace attributes

Azure Cosmos DB traces follow the OpenTelemetry database specification and also provide several custom attributes. You can see different attributes depending on the operation of your request, and these attributes are core attributes for all requests.

Attribute Type Description
net.peer.name string Azure Cosmos DB host name.
db.name string Azure Cosmos DB database name.
db.system string Identifier for the database service. Is cosmosdb for all requests.
db.operation string Operation name, ex. CreateItemAsync.
db.cosmosdb.container string Azure Cosmos DB container name.
db.cosmosdb.client_id string Identifier representing a unique client instance.
db.cosmosdb.operation_type string Operation type, ex. Create.
db.cosmosdb.connection_mode string Client connection mode. Either direct or gateway.
db.cosmosdb.status_code int Status code for the request.
db.cosmosdb.sub_status_code int Sub status code for the request.
db.cosmosdb.request_charge double RUs consumed for the operation.
db.cosmosdb.regions_contacted string List of regions contacted in the Azure Cosmos DB account.
user_agent.original string Full user-agent string generated by the Azure Cosmos DB SDK.

Gather diagnostics

If you configured logs in your trace provider, you can automatically get diagnostics for Azure Cosmos DB requests that failed or had high latency. These logs can help you diagnose failed and slow requests without requiring any custom code to capture them.

In addition to getting diagnostic logs for failed requests, you can configure different latency thresholds for when to collect diagnostics from successful requests. The default values are 100 ms for point operations and 500 ms for non point operations. These thresholds can be adjusted through client options.

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

You can configure the log level to control which diagnostics logs you receive.

Log Level Description
Error Logs for errors only.
Warning Logs for errors and high latency requests based on configured thresholds.
Information There are no specific information level logs. Logs in this level are the same as using Warning.

Depending on your application environment, there are different ways to configure the log level. Here's a sample configuration in appSettings.json:

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

Configure OpenTelemetry

To use OpenTelemetry with the Azure Cosmos DB SDKs, add the Azure.Cosmos.Operation source to your trace provider. OpenTelemetry is compatible with many exporters that can ingest your data. The following sample uses the Azure Monitor OpenTelemetry Exporter, but you can choose to configure any exporter you wish. Depending on your chosen exporter, you might see a delay ingesting data of up to a few minutes.

Tip

If you use the Azure.Monitor.OpenTelemetry.Exporter package, ensure you're using version >= 1.0.0-beta.11. If you're using ASP.NET Core and Azure Monitor, we recommend using the Azure Monitor OpenTelemetry Distro instead.

This sample shows how to configure OpenTelemetry for a .NET console app. See the complete sample on 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; }
    }
}

Configure the Application Insights SDK

There are many different ways to configure Application Insights depending on the language your application is written in and your compute environment. For more information, see the Application Insights documentation. Ingestion of data into Application Insights can take up to a few minutes.

Note

Use version >= 2.22.0-beta2 of the Application Insights package for your target .NET environment.

The following sample shows how to configure Application Insights for a .NET console app. See the complete sample on 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; }
    }
}

Once trace data is ingested into Application Insights, you can visualize it in the Azure portal to understand the request flow in your application. Here's an example of trace data from a cross partition query in the transaction search in the left navigation of the Azure portal.

Screenshot of distributed tracing of an Azure Cosmos DB cross-partition query in the Application Insights transaction search.

Next Steps