# .NET

> Instrument your .NET application with OpenTelemetry to send traces, metrics, and logs to Last9

Source: https://last9.io/docs/integrations/languages/dotnet/

Use OpenTelemetry to automatically instrument your .NET application and send telemetry data to Last9 without modifying your application code.

## Prerequisites

- .NET SDK 8.0 or later

## Installation

1. **Install OpenTelemetry .NET Auto-Instrumentation**

    Download and install the auto-instrumentation agent:

**Linux/macOS**

    ```bash
    # Download the installation script
    curl -sSfL https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.sh -O

    # Make it executable and run
    chmod +x otel-dotnet-auto-install.sh
    ./otel-dotnet-auto-install.sh
    ```

**Windows**

    ```powershell
    # Download and run the installation script
    Invoke-WebRequest -Uri "https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest/download/otel-dotnet-auto-install.ps1" -OutFile "otel-dotnet-auto-install.ps1"
    .\otel-dotnet-auto-install.ps1
    ```

2. **Verify Installation**

   Check that the installation was successful:

**Linux/macOS**

   ```bash
   # Verify installation directory exists
   ls -la $HOME/.otel-dotnet-auto

   # Check for key files
   ls -la $HOME/.otel-dotnet-auto/instrument.sh
   ls -la $HOME/.otel-dotnet-auto/net/
   ```

**Windows**

   ```powershell
   # Verify installation directory exists
   Get-ChildItem "$env:USERPROFILE\.otel-dotnet-auto"

   # Check for key files
   Get-ChildItem "$env:USERPROFILE\.otel-dotnet-auto\instrument.cmd"
   ```

## Setup Auto-Instrumentation

### Environment Variables

Set the required environment variables for Last9. Replace the placeholder values with your actual Last9 configuration:

**Linux/macOS**

```bash
export OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED=true
export OTEL_SERVICE_NAME="<your_service_name>"
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT="{{ .Logs.WriteURL }}"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization={{ .Logs.AuthValue }}"
export OTEL_TRACES_SAMPLER="always_on"
export OTEL_LOG_LEVEL=error
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=local"
```

**Windows**

```powershell
$env:OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED="true"
$env:OTEL_SERVICE_NAME="<your_service_name>"
$env:OTEL_TRACES_EXPORTER="otlp"
$env:OTEL_EXPORTER_OTLP_ENDPOINT="{{ .Logs.WriteURL }}"
$env:OTEL_EXPORTER_OTLP_HEADERS="Authorization={{ .Logs.AuthValue }}"
$env:OTEL_TRACES_SAMPLER="always_on"
$env:OTEL_LOG_LEVEL="error"
$env:OTEL_RESOURCE_ATTRIBUTES="deployment.environment=local"
```

### Source the Instrumentation Script

Before running your application, source the instrumentation script:

**Linux/macOS**

    ```bash . $HOME/.otel-dotnet-auto/instrument.sh ```
  </TabItem>
  <TabItem label="Windows">
    ```cmd call "%USERPROFILE%\.otel-dotnet-auto\instrument.cmd" ```

## Application Code

### Minimal Application Setup

With auto-instrumentation, your application code requires **no OpenTelemetry-specific packages or configuration**. Keep your `Program.cs` clean.

### Project File (.csproj)

Your project file should be minimal - **no OpenTelemetry packages needed**:

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>MyDotNetApi</RootNamespace>
  </PropertyGroup>
</Project>
```

## Running Your Application

### Complete Startup Script

Create a startup script that sets up everything:

**Linux/macOS**

Create `start.sh`:
```bash
#!/bin/bash

# Set OpenTelemetry environment variables

export OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED=true
export OTEL_SERVICE_NAME="<your_service_name>"
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT="{{ .Logs.WriteURL }}"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization={{ .Logs.AuthValue }}"
export OTEL_TRACES_SAMPLER="always_on"
export OTEL_LOG_LEVEL=error
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=local"

echo "Starting .NET application with OpenTelemetry auto-instrumentation..."
echo "Service Name: $OTEL_SERVICE_NAME"
echo "OTLP Endpoint: $OTEL_EXPORTER_OTLP_ENDPOINT"

# Source the auto-instrumentation

. $HOME/.otel-dotnet-auto/instrument.sh

# Build and run the application

dotnet build --configuration Release
dotnet run --configuration Release

````

**Windows**

Create `start.cmd`:
```cmd
@echo off

REM Set OpenTelemetry environment variables
set OTEL_DOTNET_AUTO_INSTRUMENTATION_ENABLED=true
set OTEL_SERVICE_NAME="<your_service_name>"
set OTEL_TRACES_EXPORTER=otlp
set OTEL_EXPORTER_OTLP_ENDPOINT="{{ .Logs.WriteURL }}"
set OTEL_EXPORTER_OTLP_HEADERS="Authorization={{ .Logs.AuthValue }}"
set OTEL_TRACES_SAMPLER=always_on

echo Starting .NET application with OpenTelemetry auto-instrumentation...
echo Service Name: %OTEL_SERVICE_NAME%
echo OTLP Endpoint: %OTEL_EXPORTER_OTLP_ENDPOINT%

REM Source the auto-instrumentation
call "%USERPROFILE%\.otel-dotnet-auto\instrument.cmd"

REM Build and run the application
dotnet build --configuration Release
dotnet run --configuration Release
````

### Verify Telemetry

You should see debug output in your console showing:

- OpenTelemetry initialization
- Span creation for HTTP requests
- Trace export attempts to Last9

## What Gets Automatically Instrumented

The auto-instrumentation automatically captures:

- **HTTP requests** (ASP.NET Core)
- **Database calls** (Entity Framework, SQL Client, MongoDB, etc.)
- **HTTP client calls** (HttpClient)
- **Message queues** (RabbitMQ, Azure Service Bus, etc.)
- **Custom logs** via `ILogger`
- **gRPC calls**
- **Redis operations**

## Advanced Configuration

### Custom Resource Attributes

Add additional resource attributes to identify your service better:

```bash
export OTEL_RESOURCE_ATTRIBUTES="service.name=my-dotnet-api,deployment.environment=production,service.version=1.2.3"
```

### Disable Specific Instrumentations

Disable instrumentations you don't need:

```bash
export OTEL_DOTNET_AUTO_INSTRUMENTATION_ASPNETCORE_ENABLED=true
export OTEL_DOTNET_AUTO_INSTRUMENTATION_HTTPCLIENT_ENABLED=true
export OTEL_DOTNET_AUTO_INSTRUMENTATION_SQLCLIENT_ENABLED=false
```

Once you run your application with the proper environment setup, it will automatically start sending telemetry data to Last9.

## Capture HTTP Request/Response Bodies

:::caution
Capturing HTTP bodies increases trace data volume and may expose sensitive data (PII/PHI). Use the configuration options below to control what gets captured, and always pair with PII redaction at the collector level.
:::

Add HTTP request and response body capture to your traces with the `Last9.OpenTelemetry.BodyCapture` package. Bodies appear as `http.request.body` and `http.response.body` span attributes.

1. **Install the package**

   ```bash
   dotnet add package Last9.OpenTelemetry.BodyCapture
   ```

2. **Add one line to `Program.cs`**

   ```csharp
   var builder = WebApplication.CreateBuilder(args);

   builder.Services.AddHttpBodyCapture(builder.Configuration); // Add this line

   var app = builder.Build();
   ```

   The middleware auto-registers via `IStartupFilter` — no `app.UseMiddleware<...>()` needed.

3. **Configure in `appsettings.json`**

   ```json
   {
     "BodyCapture": {
       "Enabled": true,
       "CaptureRequestBody": true,
       "CaptureResponseBody": true,
       "MaxBodySizeBytes": 8192,
       "CaptureOnErrorOnly": false,
       "ContentTypes": ["application/json", "application/xml", "text/plain"],
       "IncludePaths": [],
       "ExcludePaths": ["/health", "/ready", "/metrics"]
     }
   }
   ```

### Configuration Options

| Setting               | Default                             | Description                            |
| --------------------- | ----------------------------------- | -------------------------------------- |
| `Enabled`             | `true`                              | Master switch for body capture         |
| `CaptureRequestBody`  | `true`                              | Capture incoming request bodies        |
| `CaptureResponseBody` | `true`                              | Capture outgoing response bodies       |
| `MaxBodySizeBytes`    | `8192`                              | Max body size before truncation        |
| `CaptureOnErrorOnly`  | `false`                             | Only capture on 4xx/5xx responses      |
| `ContentTypes`        | `["application/json", ...]`         | Content types to capture               |
| `IncludePaths`        | `[]`                                | Path prefixes to include (empty = all) |
| `ExcludePaths`        | `["/health", "/ready", "/metrics"]` | Path prefixes to skip                  |

:::tip[Production recommendation]
Set `CaptureOnErrorOnly` to `true` in production. This captures bodies only on failed requests — dramatically reducing data volume while preserving debugging value.
:::

### PII/PHI Redaction at the Collector

Body capture should be paired with PII redaction at the OTel Collector to prevent sensitive data from reaching your observability backend. Use the `transform` processor to mask patterns before export:

```yaml
processors:
  transform/redact-pii:
    trace_statements:
      - context: span
        statements:
          # SSN
          - replace_pattern(attributes["http.request.body"], "\\b\\d{3}-\\d{2}-\\d{4}\\b", "***-**-****")
          - replace_pattern(attributes["http.response.body"], "\\b\\d{3}-\\d{2}-\\d{4}\\b", "***-**-****")
          # Email
          - replace_pattern(attributes["http.request.body"], "[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}", "****@****.***")
          - replace_pattern(attributes["http.response.body"], "[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}", "****@****.***")
          # Phone
          - replace_pattern(attributes["http.request.body"], "\\b\\d{3}[\\-.]\\d{3}[\\-.]\\d{4}\\b", "***-***-****")
          - replace_pattern(attributes["http.response.body"], "\\b\\d{3}[\\-.]\\d{3}[\\-.]\\d{4}\\b", "***-***-****")

service:
  pipelines:
    traces:
      processors: [transform/redact-pii, batch]
```

Add this processor to your gateway collector. Redaction rules are centralized — one config change applies to all apps.

For a complete working example with Docker Compose, see the [HTTP body capture example](https://github.com/last9/opentelemetry-examples/tree/main/dotnet/http-body-capture).

---

## Troubleshooting

Please get in touch with us on [Discord](https://discord.com/invite/Q3p2EEucx9) or [Email](mailto:support@last9.io) if you have any questions.
