diff --git a/README.md b/README.md index 3a2ecdf93a5a..71989abbdf00 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ This repository contains what you need to trace C# applications. Some quick note - **Datadog C# APM is currently in Alpha** - It supports .Net Framework version above 4.5 and .Net Core 2.0. - It does not support out of process propagation. -- It does not provide automatic framework instrumentation, all instrumentation is [manual](#manual-instrumentation). - Multiple AppDomains are not supported. -- Our tracer is based on the current OpenTracing standard, however we do not yet support the following features: `FollowsFrom` references, `Baggage` or `Log`. ## The Components @@ -56,6 +54,26 @@ And check that the status is "running". The logs are available at the path you configured in `trace.config` `log_file` above. +### Automatic Instrumentation + +#### ASP.NET Core + +To instrument you ASP.NET Core application install the +`Datadog.Trace.AspNetCore` NuGet package and the following line to your +`ConfigureServices` method: + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + services + .AddDatadogTrace() +} +``` + +Once your application is configured this way all the requests to your +application will be traced and the active span will automatically be set to the +currently executing request. + ### Manual Instrumentation #### Introduction @@ -64,167 +82,111 @@ Before instrumenting your application, have a look at the [Datadog APM Terminolo #### Setup -In order to instrument you code you need to add the `Datadog.Trace` NuGet package to your project. +In order to instrument your code you need to add the `Datadog.Trace` NuGet +package to your project. + +Your tracing adventure starts with the `Tracer` class that will be used to +instrument your code and should be accessed exclusively through the +`Tracer.Instance` singleton. `Trace.Instance` is statically initialized with a +`Tracer` created with the default settings but you may instantiate a new one +with customized values with the `Tracer.Create` method. -Your tracing adventure starts with the `ITracer` object, you should typically instantiate only one `ITracer` for the lifetime of your app and use it in all places of your code where you want to add tracing. Instantiating the `ITracer` is done with the `TracerFactory.GetTracer` method. +`Tracer.Create` takes a number of optional parameters that can be used to +customize the returned `Tracer`: -To get a tracer with default parameters (i.e. the agent endpoint set to `http://localhost:8126`, and the default service name set to the name of the AppDomain): +- agentEndpoint: the agent endpoint where the traces will be sent (default is http://localhost:8126) +- defaultServiceName: default name of the service (default is the name of the executing assembly) +- isDebugEnabled: turns on all debug logging, this may have an impact on application performance (default is false) + +For example to set a custom service name: ```csharp -ITracer tracer = TracerFactory.GetTracer(); +Tracer.Instance = Tracer.Create(defaultServiceName: "YourServiceName") ``` -Customize your tracer object by adding optional parameters to the `TracerFactory.GetTracer` call: +#### In process propagation + +We want to keep track of the dependencies between spans created inside a +process. This is done automatically by the tracer when using the `StartActive` +method that returns a Scope representing the scope in which the created Span is +considered active. All Spans created without `ignoreActiveScope = true` are +automatically parented to the current active Span and become themselves active +(unless the `StartSpan` method is used). -By default the service name is set to the name of the AppDomain, choose a custom name with the defaultServiceName parameter: +If not created with `finishOnClose = false` closing a Scope will also close the +Span it is enclosing. + +Examples: ```csharp -ITracer tracer = TracerFactory.GetTracer(defaultServiceName: "YourServiceName") +// The second span will be a child of the first one. +using (Scope scope = Tracer.Instance.StartActive("Parent")){ + using(Scope scope = Tracer.Instance.StartActive("Child")){ + } +} ``` -By default, the trace endpoint is set to http://localhost:8126, send traces to a different endpoint with the agentEndpoint parameter: - ```csharp -ITracer tracer = TracerFactory.GetTracer(agentEndpoint: new Url("http://myendpoint:port")); +// Since it is created with the StartSpan method the first span is not made +// active and the second span will not be parented to it. +using (Span span = Tracer.Instance.StartSpan("Span1")){ + using(Scope scope = Tracer.Instance.StartActive("Span2")){ + } +} ``` -#### Examples +#### Code instrumentation -Use the shared `ITracer` object you created to create spans, instrument any section of your code, and get detailed metrics on it. +Use the shared `Tracer` object you created to create spans, instrument any +section of your code, and get detailed metrics on it. -Set the ServiceName to recognize which service this trace belongs to; if you don't, the parent span's service name or in case of a root span the defaultServiceName stated above is used. +Set the ServiceName to recognize which service this trace belongs to; if you +don't, the parent span's service name or in case of a root span the +defaultServiceName stated above is used. Set the ResourceName to scope this trace to a specific endpoint or SQL Query; For instance: - "GET /users/:id" - "SELECT * FROM ..." if you don't the OperationName will be used. -A minimal examples is: +A minimal example is: ```csharp -using (ISpan span = tracer.BuildSpan("OperationName").WithTag(DDTags.ServiceName, "ServiceName").Start()) +using (Scope scope = Tracer.Instance.StartActive("OperationName", serviceName: "ServiceName")) { - span.SetTag(DDTags.ResourceName, "ResourceName"); + scope.Span.ResourceName = "ResourceName"; // Instrumented code Thread.Sleep(1000); } ``` -You may also choose, not to use the `using` construct and close the `ISpan` object explictly: +You may also choose, not to use the `using` construct and close the `Scope` object explictly: ```csharp -ISpan span = tracer.BuildSpan("OperationName").WithTag(DDTags.ServiceName, "ServiceName").Start(); -span.SetTag(DDTags.ResourceName, "ResourceName"); +Scope scope = Tracer.Instance.StartActive("OperationName", serviceName: "ServiceName"); +scope.Span = "ResourceName"; // Instrumented code Thread.Sleep(1000); -// Finish sets the span duration and sends it to the agent (if you don't call finish the data will never be sent to Datadog) -span.Finish(); -``` - -You may add custom tags by calling `ISpan.SetTag`: - -```csharp -ISpan span = tracer.BuildSpan("SqlQuery").Start(); -span.SetTag("db.rows", 10); +// Close closes the underlying span, this sets its duration and sends it to the agent (if you don't call Close the data will never be sent to Datadog) +scope.Close(); ``` -You should not have to explicitly declare parent/children relationship between your spans, but to override the default behavior - a new span is considered a child of the innermost open span in its logical context- use: +You may add custom tags by calling `Span.SetTag`: ```csharp -ISpan parent = tracer.BuildSpan("Parent").Start(); -ISpan child = tracer.BuildSpan("Child").AsChildOf(parent).Start(); +Scope scope = Tracer.Instance.StartActive("SqlQuery"); +scope.Span.SetTag("db.rows", 10); ``` -#### Cross-process tracing - -Cross-process tracing is supported by propagating context through HTTP headers. This is done with the `ITracer.Inject` and `ITracer.Extract` methods as documented in the [opentracing documentation](http://opentracing.io/documentation/pages/api/cross-process-tracing.html) - -##### Injection - -Example code to send a HTTP request with the right headers: +You should not have to explicitly declare parent/children relationship between your spans, but to override the default behavior - a new span is considered a child of the active Span - use: ```csharp -using (var span = tracer.BuildSpan("Operation").Start()) -{ - var client = new HttpClient(); - var request = new HttpRequestMessage(HttpMethod.Get, "https://example.com"); - var carrier = new HttpHeadersCarrier(_request.Headers); - tracer.Inject(span.Context, Formats.HttpHeaders, carrier); - await client.SendAsync(request); -} -``` - -##### Extraction - -In order to extract context from a http request, you need to write a wrapper -around the header container used by your web framework to make it implement the -`ITextMap` interface. - -For example such a wrapper for the `IHeaderDictionary` used by Asp.Net Core could be: - -```csharp -using System.Collections.Generic; -using Microsoft.AspNetCore.Http; -using OpenTracing.Propagation; - -public class AspNetHeadersTextMap : ITextMap -{ - private readonly IHeaderDictionary _headers; - - public AspNetHeadersTextMap(IHeaderDictionary headers) - { - _headers = headers; - } - - public string Get(string key) - { - return _headers[key]; - } - - public IEnumerable> GetEntries() - { - return (IEnumerable>)_headers; - } - - public void Set(string key, string value) - { - _headers[key] = value; - } -} -``` - -You can then leverage the extract method to extract the cross-process -correlation context from a request's headers: - -```csharp -var tracer = TracerFactory.GetTracer(); -var headersTextMap = new AspNetHeadersTextMap(HttpContext.Request.Headers); -var spanContext = tracer.Extract(Formats.HttpHeaders, headersTextMap); -using (var span = tracer.BuildSpan("Operation").AsChildOf(spanContext).Start()) -{ - // Your instrumented code -} -``` - -#### Advanced Usage - -When creating a tracer, add some metadata to your services to customize how they will appear in your Datadog application: - -```csharp -var serviceInfoList = new List -{ - new ServiceInfo - { - App = "MyAppName", - AppType = "web", - ServiceName = "MyServiceName" - } -}; -ITracer tracer = TracerFactory.GetTracer(serviceInfoList: serviceInfoList); +Span parent = tracer.StartSpan("Parent"); +Span child = tracer.StartSpan("Child", childOf: parent.Context); ``` ## Development