HardТеория3 min

Мониторинг и Application Insights

Application Insights, кастомная телеметрия, KQL-запросы, Azure Monitor, Redis-кеширование

Мониторинг -- критическая часть эксплуатации облачных приложений. Application Insights автоматически собирает телеметрию, а KQL позволяет анализировать данные.

Application Insights

Подключение к ASP.NET Core

// Program.cs
builder.Services.AddApplicationInsightsTelemetry();
// appsettings.json
{
  "ApplicationInsights": {
    "ConnectionString": "InstrumentationKey=xxx;IngestionEndpoint=..."
  }
}

Автоматическая телеметрия

Application Insights автоматически собирает:

  • Requests -- HTTP-запросы (URL, время ответа, код, длительность)
  • Dependencies -- вызовы к внешним сервисам (SQL, HTTP, Redis)
  • Exceptions -- необработанные исключения
  • Performance Counters -- CPU, память, GC
  • Page Views -- посещения страниц (для веб-приложений)

Кастомная телеметрия

public class OrderService
{
    private readonly TelemetryClient _telemetry;

    public OrderService(TelemetryClient telemetry) => _telemetry = telemetry;

    public async Task<Order> CreateOrder(CreateOrderCommand command)
    {
        // Custom Event -- бизнес-событие
        _telemetry.TrackEvent("OrderCreated", new Dictionary<string, string>
        {
            { "CustomerId", command.CustomerId },
            { "ItemCount", command.Items.Count.ToString() }
        }, new Dictionary<string, double>
        {
            { "TotalAmount", (double)command.Total }
        });

        // Custom Metric -- числовой показатель
        _telemetry.TrackMetric("OrderTotal", (double)command.Total);

        // Trace -- информационное сообщение
        _telemetry.TrackTrace("Processing order", SeverityLevel.Information);

        try
        {
            // Business logic...
        }
        catch (Exception ex)
        {
            // Exception tracking с контекстом
            _telemetry.TrackException(ex, new Dictionary<string, string>
            {
                { "OrderId", command.OrderId.ToString() },
                { "Operation", "CreateOrder" }
            });
            throw;
        }
    }
}

KQL -- Kusto Query Language

KQL используется для запросов к логам в Application Insights и Log Analytics.

Базовые запросы

// Ошибки за последние 24 часа
exceptions
| where timestamp > ago(24h)
| summarize count() by type
| order by count_ desc

// Медленные запросы (> 2 секунды)
requests
| where duration > 2000
| project timestamp, name, duration, resultCode
| order by duration desc

// Зависимости -- время отклика SQL и Redis
dependencies
| where timestamp > ago(1h)
| summarize avg(duration), percentile(duration, 95), count() by target, type
| order by avg_duration desc

Запросы для бизнес-метрик

// Заказы в час (через custom events)
customEvents
| where name == "OrderCreated" and timestamp > ago(24h)
| summarize orderCount = count() by bin(timestamp, 1h)
| render timechart

// Кастомные события по клиентам
customEvents
| where name == "OrderCreated"
| extend customerId = tostring(customDimensions.CustomerId)
| summarize orderCount = count() by customerId
| top 10 by orderCount

// End-to-end трассировка конкретного запроса
let operationId = "abc123";
union requests, dependencies, exceptions, traces
| where operation_Id == operationId
| order by timestamp asc
| project timestamp, itemType, name, duration, success, message

Запросы для мониторинга ошибок

// Процент ошибок по эндпоинтам
requests
| where timestamp > ago(1h)
| summarize
    total = count(),
    failed = countif(success == false)
    by name
| extend errorRate = round(100.0 * failed / total, 2)
| order by errorRate desc

// Топ-10 самых медленных эндпоинтов (P95)
requests
| where timestamp > ago(24h)
| summarize
    avgDuration = avg(duration),
    p95Duration = percentile(duration, 95),
    requestCount = count()
  by name
| order by p95Duration desc
| take 10

Azure Monitor

Алерты

# Алерт при > 10 ошибок 5xx за 5 минут
az monitor metrics alert create \
  --name "HighErrorRate" \
  --resource-group rg-orderapp \
  --scopes "/subscriptions/{sub}/resourceGroups/rg-orderapp/providers/Microsoft.Web/sites/app-orderapp" \
  --condition "count requests/failed > 10" \
  --window-size 5m \
  --evaluation-frequency 1m \
  --severity 1

# Алерт при среднем времени ответа > 3 секунды
az monitor metrics alert create \
  --name "HighLatency" \
  --resource-group rg-orderapp \
  --scopes "..." \
  --condition "avg requests/duration > 3000" \
  --window-size 5m

Diagnostic Settings

Направление логов в различные хранилища:

  • Log Analytics Workspace -- для KQL-запросов и анализа
  • Storage Account -- для долгосрочного хранения
  • Event Hub -- для стриминга во внешние системы (Splunk, ELK)

Structured Logging с Serilog

builder.Host.UseSerilog((context, configuration) =>
{
    configuration
        .ReadFrom.Configuration(context.Configuration)
        .WriteTo.Console()
        .WriteTo.ApplicationInsights(
            TelemetryConfiguration.Active,
            TelemetryConverter.Traces)
        .Enrich.FromLogContext()
        .Enrich.WithProperty("Application", "OrderManagement");
});

// Использование -- параметры, а не строковая конкатенация!
_logger.LogInformation(
    "Creating order for customer {CustomerId} with {ItemCount} items",
    command.CustomerId,
    command.Items.Count);

Кеширование с Azure Cache for Redis

Redis используется для снижения нагрузки на базу данных через паттерн Cache-Aside.

// Регистрация в DI
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = configuration.GetConnectionString("Redis");
    options.InstanceName = "OrderApp:";
});

// Паттерн Cache-Aside
public class CachedOrderService
{
    private readonly IDistributedCache _cache;
    private readonly IOrderRepository _repository;

    public async Task<OrderDto?> GetOrderAsync(string orderId)
    {
        var cacheKey = $"order:{orderId}";

        // 1. Проверить кеш
        var cached = await _cache.GetStringAsync(cacheKey);
        if (cached != null)
            return JsonSerializer.Deserialize<OrderDto>(cached);

        // 2. Загрузить из БД
        var order = await _repository.GetByIdAsync(orderId);
        if (order == null) return null;

        // 3. Положить в кеш
        await _cache.SetStringAsync(cacheKey,
            JsonSerializer.Serialize(order.ToDto()),
            new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
            });

        return order.ToDto();
    }
}

Health Checks

builder.Services.AddHealthChecks()
    .AddSqlServer(connectionString, name: "sql")
    .AddAzureBlobStorage(blobConnectionString, name: "blob")
    .AddRedis(redisConnectionString, name: "redis");

app.MapHealthChecks("/health");

Проверь себя

Какие данные Application Insights собирает автоматически без дополнительного кода?

Какой KQL-оператор используется для подсчета количества записей по группам?

В паттерне Cache-Aside, что происходит, если данные не найдены в кеше?

Почему в Serilog используют параметры {CustomerId} вместо строковой конкатенации?