Мониторинг -- критическая часть эксплуатации облачных приложений. 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");