Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 79 additions & 117 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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<KeyValuePair<string, string>> GetEntries()
{
return (IEnumerable<KeyValuePair<string, string>>)_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<ServiceInfo>
{
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
Expand Down