# Observability Standard: Metrics

## Overview
Custom metrics with .NET Meters API and Azure Monitor integration.

## Metrics Setup

### Packages
```xml
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.*" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="0.*" />
```

### Meter Definition (per feature/service)
```csharp
// Define meters as static singletons
public static class AppMetrics
{
    private static readonly Meter Meter = new("MyApp.Business", "1.0.0");

    // Counters (monotonically increasing)
    public static readonly Counter<long> OrdersCreated =
        Meter.CreateCounter<long>("orders.created", "orders", "Total orders created");

    public static readonly Counter<long> PaymentsFailed =
        Meter.CreateCounter<long>("payments.failed", "failures", "Total payment failures");

    // Histograms (distributions)
    public static readonly Histogram<double> OrderProcessingTime =
        Meter.CreateHistogram<double>("orders.processing_time", "ms", "Order processing duration");

    // Gauges (current value)
    public static readonly ObservableGauge<int> ActiveConnections =
        Meter.CreateObservableGauge<int>("connections.active", GetActiveConnections, "connections");

    private static int GetActiveConnections() => ConnectionPool.ActiveCount;
}
```

### Recording Metrics
```csharp
// Counter increment
AppMetrics.OrdersCreated.Add(1, new TagList
{
    { "status", "success" },
    { "payment_method", order.PaymentMethod }
});

// Histogram measurement
var sw = Stopwatch.StartNew();
await ProcessOrderAsync(order);
AppMetrics.OrderProcessingTime.Record(sw.ElapsedMilliseconds, new TagList
{
    { "order_type", order.Type },
    { "region", order.Region }
});
```

### Register with OpenTelemetry
```csharp
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics
            .AddAspNetCoreInstrumentation()
            .AddHttpClientInstrumentation()
            .AddRuntimeInstrumentation()
            .AddMeter("MyApp.Business")
            .AddAzureMonitorMetricExporter(opts =>
            {
                opts.ConnectionString = config["ApplicationInsights:ConnectionString"];
            });
    });
```

## Required Business Metrics

| Metric | Type | Tags | Why |
|--------|------|------|-----|
| `requests.total` | Counter | route, method, status | Request volume |
| `requests.duration` | Histogram | route, status | Latency distribution |
| `errors.total` | Counter | type, severity | Error rate |
| `{domain}.operations` | Counter | operation, result | Business KPIs |
| `db.query_duration` | Histogram | table, operation | DB performance |
| `queue.depth` | Gauge | queue_name | Backlog monitoring |
| `cache.hit_rate` | Gauge | cache_name | Cache effectiveness |

## KQL Metric Queries

### Request Volume by Endpoint
```kql
customMetrics
| where name == "requests.total" and timestamp > ago(1h)
| summarize total = sum(value) by bin(timestamp, 5m), tostring(customDimensions["route"])
| render timechart
```

### P50/P95/P99 Latency
```kql
customMetrics
| where name == "requests.duration" and timestamp > ago(1h)
| summarize
    p50 = percentile(value, 50),
    p95 = percentile(value, 95),
    p99 = percentile(value, 99)
  by bin(timestamp, 5m)
| render timechart
```

## Naming Conventions

| Pattern | Example | Description |
|---------|---------|-------------|
| `{domain}.{noun}.{verb}` | `orders.items.created` | Domain event count |
| `{resource}.{metric}` | `db.query_duration` | Resource measurement |
| `{service}.{noun}.active` | `connections.sockets.active` | Current state |

Rules:
- Lowercase with dots as separators
- Use consistent units: `ms` for duration, `bytes` for size, `count` for dimensionless
- Include relevant tags (avoid high-cardinality tags like userId)
