diff --git a/archetypes/blog.md b/archetypes/blog.md index 3fbeb86a57d3..2f1159bc6866 100644 --- a/archetypes/blog.md +++ b/archetypes/blog.md @@ -9,6 +9,8 @@ author: >- # If you have only one author, then add the single name on this line draft: true # TODO: remove this line once your post is ready to be published # canonical_url: http://somewhere.else/ # TODO: if this blog post has been posted somewhere else already, uncomment & provide the canonical URL here. body_class: otel-with-contributions-from # TODO: remove this line if there are no secondary contributing authors +issue: the issue ID for this blog post # TODO: See https://opentelemetry.io/docs/contributing/blog/ for details +sig: SIG Name # TODO: add the name of the SIG that sponsors this blog post --- diff --git a/content/en/docs/contributing/blog.md b/content/en/docs/contributing/blog.md index 1a245ec94364..cfe63df2c54c 100644 --- a/content/en/docs/contributing/blog.md +++ b/content/en/docs/contributing/blog.md @@ -6,13 +6,25 @@ weight: 30 The [OpenTelemetry blog](/blog/) communicates new features, community reports, and any news that might be relevant to the OpenTelemetry community. This -includes end users and developers. Anyone can write a blog post and submit it -for review. +includes end users and developers. Anyone can write a blog post, read below what +the requirements are. + +## Documentation or blog post? + +Before writing a blog post, ask yourself if your content also might be a good +addition to the documentation. If the answer is "yes", create a new issue or +pull request (PR) with your content to get it added to the docs. + +Note, that the focus of maintainers and approvers of the OpenTelemetry Website +is to improve the documentation of the project, so your blog post will have a +lower priority for review. ## Before submitting a blog post Blog posts should not be commercial in nature and should consist of original -content that applies broadly to the OpenTelemetry community. +content that applies broadly to the OpenTelemetry community. Blog posts should +follow the policies outlined in the +[Social Media Guide](https://github.com/open-telemetry/community/blob/main/social-media-guide.md). Verify that your intended content broadly applies to the OpenTelemetry Community . Appropriate content includes: @@ -27,15 +39,28 @@ Unsuitable content includes: - Vendor product pitches -To submit a blog post, +If your blog post fits into the list of appropriate content, [raise an issue](https://github.com/open-telemetry/opentelemetry.io/issues/new?title=New%20Blog%20Post:%20%3Ctitle%3E) -with the title and a short description of your blog post. If you are not a -[Member](https://github.com/open-telemetry/community/blob/main/community-membership.md#member), -you also need to provide a _sponsor_ for your blog post, who is a Member (by -that definition) and who is willing to provide a first review of your blog post. - -If you do not raise an issue before providing your PR, we may request you to do -so before providing a review. +with the following details: + +- Title of the blog post +- Short description and outline of your blog post +- If applicable, list the technologies used in your blog post. Make sure that + all of them are open source, and prefer CNCF projects over non-CNCF projects + (e.g. use Jaeger for trace visualization, and Prometheus for metric + visualization) +- Name of a [SIG](https://github.com/open-telemetry/community/), which is + related to this blog post +- Name of a sponsor (maintainer or approver) from this SIG, who will help to + review that PR + +Maintainers of SIG Communication will verify, that your blog post satisfies all +the requirements for being accepted. If you can not name a SIG/sponsor in your +initial issue details, they will also point you to an appropriate SIG, you can +reach out to for sponsorship. + +If your issue has everything needed, a maintainer will verify that you can go +ahead and submit your blog post. ## Submit a blog post @@ -44,10 +69,6 @@ locally or by using the GitHub UI. In both cases we ask you to follow the instructions provided by the [blog post template](https://github.com/open-telemetry/opentelemetry.io/tree/main/archetypes/blog.md). -**Note**: Before writing a blog post, ask yourself if your content also might be -a good addition to the documentation. If the answer is "yes", create a new issue -or pull request (PR) with your content to get it added to the docs. - ### Fork and write locally After you've set up the local fork you can create a blog post using a template. diff --git a/content/en/docs/languages/net/instrumentation.md b/content/en/docs/languages/net/instrumentation.md index bf8310bb189e..5cad2ffca78d 100644 --- a/content/en/docs/languages/net/instrumentation.md +++ b/content/en/docs/languages/net/instrumentation.md @@ -3,10 +3,24 @@ title: Instrumentation weight: 20 aliases: [manual] description: Instrumentation for OpenTelemetry .NET +cSpell:ignore: dicelib rolldice --- {{% docs/languages/instrumentation-intro %}} +{{% alert title="Note" color="info" %}} + +On this page you will learn how you can add traces, metrics and logs to your +code manually. You are not limited to using one kind of instrumentation: you can +also use [automatic instrumentation](/docs/languages/net/automatic/) to get +started and then enrich your code with manual instrumentation as needed. + +Also, for libraries your code depends on, you don't have to write +instrumentation code yourself, since they might be already instrumented or there +are [instrumentation libraries](/docs/languages/net/libraries/) for them. + +{{% /alert %}} + ## A note on terminology .NET is different from other languages/runtimes that support OpenTelemetry. The @@ -22,360 +36,641 @@ are covered here as well as the `System.Diagnostics` API. If you prefer to use OpenTelemetry APIs instead of `System.Diagnostics` APIs, you can refer to the [OpenTelemetry API Shim docs for tracing](../shim). -## Traces +## Example app preparation {#example-app} -### Initializing tracing +This page uses a modified version of the example app from +[Getting Started](/docs/languages/net/getting-started/) to help you learn about +manual instrumentation. -There are two main ways to initialize [tracing](/docs/concepts/signals/traces/), -depending on whether you're using a console app or something that's ASP.NET -Core-based. +You don't have to use the example app: if you want to instrument your own app or +library, follow the instructions here to adapt the process to your own code. -#### Console app +### Prerequisites {#example-app-prerequisites} -To start tracing in a console app, you need to create a tracer provider. +- [.NET SDK](https://dotnet.microsoft.com/download/dotnet) 6+ -First, ensure that you have the right packages: +### Create and launch an HTTP Server -```sh -dotnet add package OpenTelemetry -dotnet add package OpenTelemetry.Exporter.Console +To begin, set up an environment in a new directory called `dotnet-otel-example`. +Within that directory, execute following command: + +```shell +dotnet new web ``` -And then use code like this at the beginning of your program, during any -important startup operations. +To highlight the difference between instrumenting a library and a standalone +app, split out the dice rolling into a library file, which then will be imported +as a dependency by the app file. + +Create the library file named `Dice.cs` and add the following code to it: ```csharp -using OpenTelemetry; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; +/*Dice.cs*/ -// ... +public class Dice +{ + private int min; + private int max; -var serviceName = "MyServiceName"; -var serviceVersion = "1.0.0"; + public Dice(int min, int max) + { + this.min = min; + this.max = max; + } -using var tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddSource(serviceName) - .ConfigureResource(resource => - resource.AddService( - serviceName: serviceName, - serviceVersion: serviceVersion)) - .AddConsoleExporter() - .Build(); + public List rollTheDice(int rolls) + { + List results = new List(); + + for (int i = 0; i < rolls; i++) + { + results.Add(rollOnce()); + } -// ... + return results; + } + + private int rollOnce() + { + return Random.Shared.Next(min, max + 1); + } +} ``` -This is also where you can configure instrumentation libraries. +Create the app file `DiceController.cs` and add the following code to it: + +```csharp +/*DiceController.cs*/ + +using Microsoft.AspNetCore.Mvc; +using System.Net; -Note that this sample uses the Console Exporter. If you are exporting to another -endpoint, you'll have to use a different exporter. -#### ASP.NET Core +public class DiceController : ControllerBase +{ + private ILogger logger; -To start tracing in an ASP.NET Core-based app, use the OpenTelemetry extensions -for ASP.NET Core setup. + public DiceController(ILogger logger) + { + this.logger = logger; + } -First, ensure that you have the right packages: + [HttpGet("/rolldice")] + public List RollDice(string player, int? rolls) + { + if(!rolls.HasValue) + { + logger.LogError("Missing rolls parameter"); + throw new HttpRequestException("Missing rolls parameter", null, HttpStatusCode.BadRequest); + } + + var result = new Dice(1, 6).rollTheDice(rolls.Value); + + if (string.IsNullOrEmpty(player)) + { + logger.LogInformation("Anonymous player is rolling the dice: {result}", result); + } + else + { + logger.LogInformation("{player} is rolling the dice: {result}", player, result); + } + + return result; + } +} +``` + +Replace the content of the Program.cs file with the following code: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +app.MapControllers(); + +app.Run(); +``` + +In the `Properties` subdirectory, replace the content of `launchSettings.json` +with the following: + +```json +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:8080", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} +``` + +To ensure that it is working, run the application with the following command and +open in your web browser: + +```sh +dotnet run +``` + +You should get a list of 12 numbers in your browser window, for example: + +```text +[5,6,5,3,6,1,2,5,4,4,2,4] +``` + +## Manual instrumentation setup + +### Dependencies + +Install the following OpenTelemetry NuGet packages: + +[OpenTelemetry.Exporter.Console](https://www.nuget.org/packages/OpenTelemetry.Exporter.Console) + +[OpenTelemetry.Extensions.Hosting](https://www.nuget.org/packages/OpenTelemetry.Extensions.Hosting) ```sh -dotnet add package OpenTelemetry -dotnet add package OpenTelemetry.Extensions.Hosting dotnet add package OpenTelemetry.Exporter.Console +dotnet add package OpenTelemetry.Extensions.Hosting ``` -Then you can install the Instrumentation package +For ASP.NET Core-based applications, also install the AspNetCore instrumentation +package + +[OpenTelemetry.Instrumentation.AspNetCore](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.AspNetCore) ```sh -dotnet add package OpenTelemetry.Instrumentation.AspNetCore --prerelease +dotnet add package OpenTelemetry.Instrumentation.AspNetCore ``` -Note that the `--prerelease` flag is required for all instrumentation packages -because they are all dependent on naming conventions for attributes/labels -(Semantic Conventions) that aren't yet classed as stable. +### Initialize the SDK + +{{% alert title="Note" color="info" %}} If you’re instrumenting a library, you +don't need to initialize the SDK. {{% /alert %}} + +It is important to configure an instance of the OpenTelemetry SDK as early as +possible in your application. -Next, configure it in your ASP.NET Core startup routine where you have access to -an `IServiceCollection`. +To initialize the OpenTelemetry SDK for an ASP.NET Core app like in the case of +the example app, update the content of the `Program.cs` file with the following +code: ```csharp -using OpenTelemetry; +using OpenTelemetry.Logs; +using OpenTelemetry.Metrics; using OpenTelemetry.Resources; using OpenTelemetry.Trace; -// Define some important constants and the activity source. -// These can come from a config file, constants file, etc. -var serviceName = "MyCompany.MyProduct.MyService"; +// Ideally, you will want this name to come from a config file, constants file, etc. +var serviceName = "dice-server"; var serviceVersion = "1.0.0"; var builder = WebApplication.CreateBuilder(args); -// Configure important OpenTelemetry settings, the console exporter builder.Services.AddOpenTelemetry() - .WithTracing(b => - { - b - .AddSource(serviceName) - .ConfigureResource(resource => - resource.AddService( - serviceName: serviceName, - serviceVersion: serviceVersion)) - .AddAspNetCoreInstrumentation() - .AddConsoleExporter(); - }); + .ConfigureResource(resource => resource.AddService( + serviceName: serviceName, + serviceVersion: serviceVersion)) + .WithTracing(tracing => tracing + .AddSource(serviceName) + .AddAspNetCoreInstrumentation() + .AddConsoleExporter()) + .WithMetrics(metrics => metrics + .AddMeter(serviceName) + .AddConsoleExporter()); + +builder.Logging.AddOpenTelemetry(options => options + .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService( + serviceName: serviceName, + serviceVersion: serviceVersion)) + .AddConsoleExporter()); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +app.MapControllers(); + +app.Run(); ``` -This is also where you can configure instrumentation libraries. +If initializing the OpenTelemetry SDK for a console app, add the following code +at the beginning of your program, during any important startup operations. -Note that this sample uses the Console Exporter. If you are exporting to another -endpoint, you'll have to use a different exporter. +```csharp +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; -### Setting up an ActivitySource +//... -Once tracing is initialized, you can configure an -[`ActivitySource`](/docs/concepts/signals/traces/#tracer), which will be how you -trace operations with [`Activity`](/docs/concepts/signals/traces/#spans) -elements. +var serviceName = "MyServiceName"; +var serviceVersion = "1.0.0"; -Typically, an `ActivitySource` is instantiated once per app/service that is -being instrumented, so it's a good idea to instantiate it once in a shared -location. It is also typically named the same as the Service Name. +var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddSource(serviceName) + .ConfigureResource(resource => + resource.AddService( + serviceName: serviceName, + serviceVersion: serviceVersion)) + .AddConsoleExporter() + .Build(); -```csharp -using System.Diagnostics; +var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddMeter(serviceName) + .AddConsoleExporter() + .Build(); -public static class Telemetry +var loggerFactory = LoggerFactory.Create(builder => { - //... + builder.AddOpenTelemetry(logging => + { + logging.AddConsoleExporter(); + }); +}); - // Name it after the service name for your app. - // It can come from a config file, constants file, etc. - public static readonly ActivitySource MyActivitySource = new(TelemetryConstants.ServiceName); +//... - //... -} +tracerProvider.Dispose(); +meterProvider.Dispose(); +loggerFactory.Dispose(); ``` -You can instantiate several `ActivitySource`s if that suits your scenario, -although it is generally sufficient to just have one defined per service. +For debugging and local development purposes, the example exports telemetry to +the console. After you have finished setting up manual instrumentation, you need +to configure an appropriate exporter to +[export the app's telemetry data](/docs/languages/net/exporters/) to one or more +telemetry backends. -### Creating Activities +The example also sets up the mandatory SDK default attribute `service.name`, +which holds the logical name of the service, and the optional, but highly +encouraged, attribute `service.version`, which holds the version of the service +API or implementation. Alternative methods exist for setting up resource +attributes. For more information, see +[Resources](/docs/languages/net/resources/). -To create an [`Activity`](/docs/concepts/signals/traces/#spans), give it a name -and create it from your -[`ActivitySource`](/docs/concepts/signals/traces/#tracer). +To verify your code, build and run the app: -```csharp -using var myActivity = MyActivitySource.StartActivity("SayHello"); - -// do work that 'myActivity' will now track +```sh +dotnet build +dotnet run ``` -### Creating nested Activities - -If you have a distinct sub-operation you'd like to track as a part of another -one, you can create activities to represent the relationship. +## Traces -```csharp -public static void ParentOperation() -{ - using var parentActivity = MyActivitySource.StartActivity("ParentActivity"); +### Initialize Tracing - // Do some work tracked by parentActivity +{{% alert title="Note" color="info" %}} If you’re instrumenting a library, you +don't need to initialize a TraceProvider. {{% /alert %}} - ChildOperation(); +To enable [tracing](/docs/concepts/signals/traces/) in your app, you'll need to +have an initialized +[`TracerProvider`](/docs/concepts/signals/traces/#tracer-provider) that will let +you create a [`Tracer`](/docs/concepts/signals/traces/#tracer). - // Finish up work tracked by parentActivity again -} +If a `TracerProvider` is not created, the OpenTelemetry APIs for tracing will +use a no-op implementation and fail to generate data. -public static void ChildOperation() -{ - using var childActivity = MyActivitySource.StartActivity("ChildActivity"); +If you followed the instructions to [initialize the SDK](#initialize-the-sdk) +above, you have a `TracerProvider` setup for you already. You can continue with +[setting up an ActivitySource](#setting-up-an-activitysource). - // Track work in ChildOperation with childActivity -} -``` +### Setting up an ActivitySource -When you view spans in a trace visualization tool, `ChildActivity` will be -tracked as a nested operation under `ParentActivity`. +Anywhere in your application where you write manual tracing code should +configure an [`ActivitySource`](/docs/concepts/signals/traces/#tracer), which +will be how you trace operations with +[`Activity`](/docs/concepts/signals/traces/#spans) elements. -### Nested Activities in the same scope +It’s generally recommended to define `ActivitySource` once per app/service that +is been instrumented, but you can instantiate several `ActivitySource`s if that +suits your scenario. -You may wish to create a parent-child relationship in the same scope. Although -possible, this is generally not recommended because you need to be careful to -end any nested `Activity` when you expect it to end. +In the case of the example app, we will create a new file `Instrumentation.cs` +as a custom type to hold reference for the ActivitySource. ```csharp -public static void DoWork() -{ - using var parentActivity = MyActivitySource.StartActivity("ParentActivity"); +using System.Diagnostics; - // Do some work tracked by parentActivity +/// +/// It is recommended to use a custom type to hold references for ActivitySource. +/// This avoids possible type collisions with other components in the DI container. +/// +public class Instrumentation : IDisposable +{ + internal const string ActivitySourceName = "dice-server"; + internal const string ActivitySourceVersion = "1.0.0"; - using (var childActivity = MyActivitySource.StartActivity("ChildActivity")) + public Instrumentation() { - // Do some "child" work in the same function + this.ActivitySource = new ActivitySource(ActivitySourceName, ActivitySourceVersion); } - // Finish up work tracked by parentActivity again + public ActivitySource ActivitySource { get; } + + public void Dispose() + { + this.ActivitySource.Dispose(); + } } ``` -In the preceding example, `childActivity` is ended because the scope of the -`using` block is explicitly defined, rather than scoped to `DoWork` itself like -`parentActivity`. +Then we will update the `Program.cs` to add the Instrument object as a +dependency injection: + +```csharp +//... + +// Register the Instrumentation class as a singleton in the DI container. +builder.Services.AddSingleton(); + +builder.Services.AddControllers(); -### Creating independent Activities +var app = builder.Build(); -The previous examples showed how to create Activities that follow a nested -hierarchy. In some cases, you'll want to create independent Activities that are -siblings of the same root rather than being nested. +app.MapControllers(); + +app.Run(); +``` + +In the application file `DiceController.cs` we will reference that +activitySource instance and the same activitySource instance will also be passed +to the library file `Dice.cs` ```csharp -public static void DoWork() +/*DiceController.cs*/ + +using Microsoft.AspNetCore.Mvc; +using System.Diagnostics; +using System.Net; + +public class DiceController : ControllerBase { - using var parent = MyActivitySource.StartActivity("parent"); + private ILogger logger; + + private ActivitySource activitySource; - using (var child1 = DemoSource.StartActivity("child1")) + public DiceController(ILogger logger, Instrumentation instrumentation) { - // Do some work that 'child1' tracks + this.logger = logger; + this.activitySource = instrumentation.ActivitySource; } - using (var child2 = DemoSource.StartActivity("child2")) + [HttpGet("/rolldice")] + public List RollDice(string player, int? rolls) { - // Do some work that 'child2' tracks + List result = new List(); + + if (!rolls.HasValue) + { + logger.LogError("Missing rolls parameter"); + throw new HttpRequestException("Missing rolls parameter", null, HttpStatusCode.BadRequest); + } + + result = new Dice(1, 6, activitySource).rollTheDice(rolls.Value); + + if (string.IsNullOrEmpty(player)) + { + logger.LogInformation("Anonymous player is rolling the dice: {result}", result); + } + else + { + logger.LogInformation("{player} is rolling the dice: {result}", player, result); + } + + return result; } - - // 'child1' and 'child2' both share 'parent' as a parent, but are independent - // from one another } ``` -### Creating new root Activities +```csharp +/*Dice.cs*/ -If you wish to create a new root Activity, you'll need to "de-parent" from the -current activity. +using System.Diagnostics; -```csharp -public static void DoWork() +public class Dice { - var previous = Activity.Current; - Activity.Current = null; + public ActivitySource activitySource; + private int min; + private int max; - var newRoot = MyActivitySource.StartActivity("NewRoot"); + public Dice(int min, int max, ActivitySource activitySource) + { + this.min = min; + this.max = max; + this.activitySource = activitySource; + } - // Re-set the previous Current Activity so the trace isn't messed up - Activity.Current = previous; + //... } ``` -### Get the current Activity +### Create Activities -Sometimes it's helpful to access whatever the current `Activity` is at a point -in time so you can enrich it with more information. +Now that you have [activitySources](/docs/concepts/signals/traces/#tracer) +initialized, you can create [activities](/docs/concepts/signals/traces/#spans). + +The code below illustrates how to create an activity. ```csharp -var activity = Activity.Current; -// may be null if there is none -``` +public List rollTheDice(int rolls) +{ + List results = new List(); -Note that `using` is not used in the prior example. Doing so will end current -`Activity`, which is not likely to be desired. + // It is recommended to create activities, only when doing operations that are worth measuring independently. + // Too many activities makes it harder to visualize in tools like Jaeger. + using (var myActivity = activitySource.StartActivity("rollTheDice")) + { + for (int i = 0; i < rolls; i++) + { + results.Add(rollOnce()); + } -### Add tags to an Activity + return results; + } +} +``` -Tags (the equivalent of -[`Attributes`](/docs/concepts/signals/traces/#attributes) in OpenTelemetry) let -you attach key/value pairs to an `Activity` so it carries more information about -the current operation that it's tracking. +If you followed the instructions using the [example app](#example-app) up to +this point, you can copy the code above in your library file `Dice.cs`. You +should now be able to see activities/spans emitted from your app. -```csharp -using var myActivity = MyActivitySource.StartActivity("SayHello"); +Start your app as follows, and then send it requests by visiting + with your browser or curl. -activity?.SetTag("operation.value", 1); -activity?.SetTag("operation.name", "Saying hello!"); -activity?.SetTag("operation.other-stuff", new int[] { 1, 2, 3 }); +```sh +dotnet run ``` -We recommend that all Tag names are defined in constants rather than defined -inline as this provides both consistency and also discoverability. +After a while, you should see the spans printed in the console by the +`ConsoleExporter`, something like this: + +```json +Activity.TraceId: 841d70616c883db82b4ae4e11c728636 +Activity.SpanId: 9edfe4d69b0d6d8b +Activity.TraceFlags: Recorded +Activity.ParentSpanId: 39fcd105cf958377 +Activity.ActivitySourceName: dice-server +Activity.DisplayName: rollTheDice +Activity.Kind: Internal +Activity.StartTime: 2024-04-10T15:24:00.3620354Z +Activity.Duration: 00:00:00.0144329 +Resource associated with Activity: + service.name: dice-server + service.version: 1.0.0 + service.instance.id: 7a7a134f-3178-4ac6-9625-96df77cff8b4 + telemetry.sdk.name: opentelemetry + telemetry.sdk.language: dotnet + telemetry.sdk.version: 1.7.0 +``` -### Adding events +### Create nested Activities -An [event](/docs/concepts/signals/traces/#span-events) is a human-readable -message on an `Activity` that represents "something happening" during its -lifetime. +Nested [spans](/docs/concepts/signals/traces/#spans) let you track work that's +nested in nature. For example, the `rollOnce()` function below represents a +nested operation. The following sample creates a nested span that tracks +`rollOnce()`: ```csharp -using var myActivity = MyActivitySource.StartActivity("SayHello"); +private int rollOnce() +{ + using (var childActivity = activitySource.StartActivity("rollOnce")) + { + int result; -// ... + result = Random.Shared.Next(min, max + 1); -myActivity?.AddEvent(new("Gonna try it!")); + return result; + } +} +``` -// ... +When you view the spans in a trace visualization tool, `rollOnce` childActivity +will be tracked as a nested operation under `rollTheDice` activity. -myActivity?.AddEvent(new("Did it!")); +### Get the current Activity + +Sometimes it’s helpful to do something with the current/active Activity/Span at +a particular point in program execution. + +```csharp +var activity = Activity.Current; ``` -Events can also be created with a timestamp and a collection of Tags. +### Activity Tags + +Tags (the equivalent of [Attributes](/docs/concepts/signals/traces/#attributes)) +let you attach key/value pairs to an +[`Activity`](/docs/concepts/signals/traces/#spans) so it carries more +information about the current operation that it's tracking. ```csharp -using var myActivity = MyActivitySource.StartActivity("SayHello"); +private int rollOnce() +{ + using (var childActivity = activitySource.StartActivity("rollOnce")) + { + int result; + + result = Random.Shared.Next(min, max + 1); + childActivity?.SetTag("dicelib.rolled", result); -// ... + return result; + } +} +``` -myActivity?.AddEvent(new("Gonna try it!", DateTimeOffset.Now)); +### Add Events to Activities -// ... +[Spans](/docs/concepts/signals/traces/#spans) can be annotated with named events +(called [Span Events](/docs/concepts/signals/traces/#span-events)) that can +carry zero or more [Span Attributes](#activity-tags), each of which itself is a +key:value map paired automatically with a timestamp. -var eventTags = new Dictionary +```csharp +myActivity?.AddEvent(new("Init")); +... +myActivity?.AddEvent(new("End")); +``` + +```csharp +var eventTags = new ActivityTagsCollection { - { "foo", 1 }, - { "bar", "Hello, World!" }, - { "baz", new int[] { 1, 2, 3 } } + { "operation", "calculate-pi" }, + { "result", 3.14159 } }; -myActivity?.AddEvent(new("Gonna try it!", DateTimeOffset.Now, new(eventTags))); +activity?.AddEvent(new("End Computation", DateTimeOffset.Now, eventTags)); ``` -### Adding links +### Create Activities with links -An `Activity` can be created with zero or more -[`ActivityLink`s](/docs/concepts/signals/traces/#span-links) that are causally -related. +A [Span](/docs/concepts/signals/traces/#spans) may be linked to zero or more +other Spans that are causally related via a +[Span Link](/docs/concepts/signals/traces/#span-links). Links can be used to +represent batched operations where a Span was initiated by multiple initiating +Spans, each representing a single incoming item being processed in the batch. ```csharp -// Get a context from somewhere, perhaps it's passed in as a parameter -var activityContext = Activity.Current!.Context; - var links = new List { - new ActivityLink(activityContext) + new ActivityLink(activityContext1), + new ActivityLink(activityContext2), + new ActivityLink(activityContext3) }; -using var anotherActivity = - MyActivitySource.StartActivity( - ActivityKind.Internal, - name: "anotherActivity", - links: links); - -// do some work +var activity = MyActivitySource.StartActivity( + ActivityKind.Internal, + name: "activity-with-links", + links: links); ``` ### Set Activity status {{% docs/languages/span-status-preamble %}} -```csharp -using var myActivity = MyActivitySource.StartActivity("SayHello"); +A [status](/docs/concepts/signals/traces/#span-status) can be set on a +[span](/docs/concepts/signals/traces/#spans), typically used to specify that a +span has not completed successfully - `SpanStatus.Error`. -try -{ - // do something -} -catch (Exception ex) +By default, all spans are `Unset`, which means a span completed without error. +The `Ok` status is reserved for when you need to explicitly mark a span as +successful rather than stick with the default of Unset (i.e., “without error”). + +The status can be set at any time before the span is finished. + +It can be a good idea to record exceptions when they happen. It's recommended to +do this in conjunction with +[setting span status](/docs/specs/otel/trace/api/#set-status). + +```csharp +private int rollOnce() { - myActivity.SetStatus(ActivityStatusCode.Error, "Something bad happened!"); + using (var childActivity = activitySource.StartActivity("rollOnce")) + { + int result; + + try + { + result = Random.Shared.Next(min, max + 1); + childActivity?.SetTag("dicelib.rolled", result); + } + catch (Exception ex) + { + childActivity?.SetStatus(ActivityStatusCode.Error, "Something bad happened!"); + childActivity?.RecordException(ex); + throw; + } + + return result; + } } ``` diff --git a/content/en/docs/languages/net/netframework.md b/content/en/docs/languages/net/netframework.md index 1b8a590cc695..29c90a7b3d3f 100644 --- a/content/en/docs/languages/net/netframework.md +++ b/content/en/docs/languages/net/netframework.md @@ -131,8 +131,8 @@ this.tracerProvider = Sdk.CreateTracerProviderBuilder() .Build(); ``` -See [Add tags to an Activity](../instrumentation/#add-tags-to-an-activity) for -annotating trace data more generally. +See [Add tags to an Activity](../instrumentation/#activity-tags) for annotating +trace data more generally. ### RecordException diff --git a/static/refcache.json b/static/refcache.json index 1925ba6091e2..60c272e12fa3 100644 --- a/static/refcache.json +++ b/static/refcache.json @@ -2967,6 +2967,10 @@ "StatusCode": 200, "LastSeen": "2024-01-30T05:18:13.185301-05:00" }, + "https://github.com/open-telemetry/community/": { + "StatusCode": 200, + "LastSeen": "2024-05-10T13:05:22.581509+02:00" + }, "https://github.com/open-telemetry/community/discussions/1203": { "StatusCode": 200, "LastSeen": "2024-01-18T20:05:24.761684-05:00"