Вообще, эта статья — по сути краткий обзор современных инструментов логирования, которые часто используются в.NET. Но из‑за того, что настройка ELK — относительно трудоемкий процесс, весь фокус по итогу сместился в сторону использования сего стека. Я просто смирился с этим. Надеюсь, мой опыт сэкономит время и силы читателю, желающему ознакомиться с ELK.

Что такое и зачем нужно?
ELK — это довольно популярный стек для сбора логов, основанный на базе данных Elasticsearch. Приблизительная схема работы ELK такова: есть три сервиса (сам Elasticsearch, Logstash и Kibana) и есть ваше приложение.
Ваше приложение отправляет свои логи в Logstash
Logstash их сохраняет в Elasticsearch
Через Kibana пользователь может посмотреть сохраненные логи
Почему так сложно, и не проще ли grep'ать данные из файликов? Да, проще. Но не когда у тебя куча сервисов, ещё и от разных команд. Тут понадобится мощный инструмент для хранения и поиска (он же — Elasticsearch) и желательно удобный интерфейс (привет, Kibana).

Мониторинг в ELK
На самом деле Elastic предоставляет множество различных решений для сбора данных, которые так или иначе могут помочь в troubleshooting'е. Одно из них — сервис Application Performance Monitoring (APM). Он осуществляет сбор телеметрии — метрик, логов и трассировок. Это позволяет разработчикам не только отловить нежелательное поведение, но и полностью проследить «путь» поломавшегося запроса, даже если он проходит через брокер сообщений типа RabbitMQ. В общем, APM — инструмент впечатляющий и безумно полезный в SOA‑подходах.
APM использует протокол OpenTelemetry для получения данных из приложения. Это значит, что ваш код, написанный для отправки телеметрии в APM, также сгодится и для Prometheus или.NET Aspire. По крайней мере в теории, ибо на практике приходется скачивать дополнительные Nuget‑пакеты для совместимости.

Немного об аналогах
Loki-стек
Раз речь зашла не только о ELK, хотелось бы кратенько затронуть тему его конкурентов. Наверное, один из самых известных аналогов — Loki‑стек. Он представляет собой связку Promtail + Loki + Grafana. К сожалению, у меня нет опыта его использования. Но мне доводилось пользоваться связкой Grafana + Prometheus. Это стек для сбора телеметрии. Из плюсов могу отметить, что он потребляет значительно меньше ресурсов, чем решение от Elastic.
.NET Aspire
Говоря об инструментах логирования и мониторинга в.NET, нельзя не упомянуть.NET Aspire (а вернее отдельный его компонент — Dashboard). Он является dev‑time решением, которое осуществляет сбор логов и телеметрии. Его очень легко развернуть (достаточно запустить докер‑образ) и он не поедает много ресурсов. В общем, очень рекомендую к ознакомлению.

Написание кода
Подключить отправку логов в ELK в C# очень легко. Достаточно добавить nuget-пакет Elastic.Extensions.Logging
, после чего будет доступен специальный метод расширения для отправки данных в Logstash:
// По-умолчанию присоединится к http://localhost:5044
builder.Logging.AddElasticsearch();
Для мониторинга нужно установить пакеты Elastic.OpenTelemetry
и OpenTelemetry.Instrumentation.AspNetCore
builder.Services
.AddOpenTelemetry()
.WithTracing(opt =>
opt
.AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))
.WithElasticDefaults()
.AddSource("MassTransit") // если используете MassTransit
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.ConfigureResource(resource => resource.AddService("MicroFun.A"))
).WithLogging(opt =>
opt
.AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))
.ConfigureResource(resource => resource.AddService("MicroFun.A"))
.WithElasticDefaults()
).WithMetrics(opt => opt
.AddOtlpExporter(x => x.Endpoint = new Uri("http://localhost:8200"))
.ConfigureResource(resource => resource.AddService("MicroFun.A"))
.WithElasticDefaults()
);
Этого в принципе достаточно, чтобы собирать телеметрию. Если нужно собрать метрики по специфичному участку кода, то можно добавить в трейсинг новый источник и обернуть этот участок в Activity
builder.Services.AddOpenTelemetry().WithTracing(opt => opt.AddSource("MicroFun.B")
// ...
);
// ... в некотором участке кода:
using var source = new System.Diagnostics.ActivitySource("MicroFun.B");
using var activity = source.StartActivity("CreatingProduct");
// ...
Установка ELK
Установка ELK может занять некоторые время, т.к. потребуется сконфигурировать каждый сервис. В примере я буду использовать docker-compose
Elasticsearch
В первую очередь запускаем сам Elasticsearch
services:
my-elasticsearch:
image: elasticsearch:9.0.2
environment:
ES_JAVA_OPTS: -Xms2g -Xmx2g # Elasticsearch очень любит поедать оперативку
cluster.name: "docker-cluster"
network.host: 0.0.0.0
discovery.type: single-node
node.name: my-elasticsearch
xpack.security.enabled: false
xpack.security.audit.enabled: false
ports:
- 9200:9200
и генерим токен для Кибаны
docker exec -it dd6add1b6b82 sh
./bin/elasticsearch-service-tokens create elastic/kibana my-token
# SERVICE_TOKEN elastic/kibana/my-token = AAEAAWVsYXN0aWMva2liE... <--- это наш токен
Такая сложность только с Elasticsearch. Остальные сервисы можно будет спокойно запустить с уже готовым docker-compose
Logstash
Теперь можно сконфигурировать остальные сервисы. Для Logtash пишем простой конфиг, который собирает логи по TCP и передает их в Elasticsearch
input {
tcp {
port => 5044
type => "logs"
}
}
output {
elasticsearch {
hosts => ["my-elasticsearch:9200"]
index => "%{[@metadata][beat]}-%{+YYYY.MM.dd}"
}
}
services:
my-elasticsearch:
// ...
my-logstash:
image: logstash:9.0.2
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
ports:
- 5044:5044
depends_on:
- my-elasticsearch
Kibana
Для Kibana не забываем добавить сгенерированный токен в конфиг (ей богу, не знаю, как это автоматизировать)
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://my-elasticsearch:9200" ]
elasticsearch.serviceAccountToken: AAEAAWVsYXN0aWMva2liYW5h...
services:
my-elasticsearch:
// ...
my-logstash:
// ...
my-kibana:
image: kibana:9.0.2
volumes:
- ./kibana.yml:/usr/share/kibana/config/kibana.yml
ports:
- 5601:5601
depends_on:
- my-elasticsearch
APM
Для APM никаких хитростей благо нет. Так же пишем конфиг и добавляем в docker-compose секцию с сервисом
apm-server:
host: "0.0.0.0:8200"
shutdown_timeout: 15s
output.elasticsearch:
hosts: ['my-elasticsearch:9200']
setup.dashboards.enabled: true
setup.kibana:
host: "my-kibana:5601"
services:
my-logstash:
// ...
my-elasticsearch:
// ...
my-kibana:
// ...
my-apm:
image: elastic/apm-server:9.0.2
volumes:
- ./apm-server.yml:/usr/share/apm-server/apm-server.yml
ports:
- "8200:8200"
depends_on:
- my-elasticsearch
Теперь можем запускать наше приложение и смотреть в Kibana, как Elasticsearch наполняется драгоценными логами. Для этого переходим в Observability > Logs > Discover
и наблюдаем записи

Пара слов о best-practises
Во‑первых, хотел бы упомянуть, что вообще в ELK‑стек чуть ли не по‑умолчанию входит Filebeat. Это сервис, который собирает логи из файлов и отправляет их в Logstash. Мне он показался не настолько интересным, чтобы перегружать им статью и без того полную всяких конфигов. Тем не менее это не отменяет его полезности и даже необходимости, поскольку как‑минимум third‑party приложения скорее всего не смогут самостоятельно слать логи в Logstash.
Во‑вторых, хорошей практикой в C# является использование Serilog. Если кто‑то вдруг с ним не знаком, то в двух словах Serilog — это такой провайдер логов «на стероидах» (хотя в основном все его используют просто для сохранения логов в файлы, т.к. в.NET нет такого провайдера по‑умолчанию). И скорее всего в реальном приложении вам потребуется пакет Elastic.Serilog.Sinks
https://www.elastic.co/docs/reference/ecs/logging/dotnet/serilog‑data‑shipper
Вместо заключения
Несмотря на то, что современные инструменты логирования обладают высоким порогом вхождения (и я имею в виду в первую очередь ресурсоемкость сбора и обработки логов), они созданы для того, чтобы наоборот упростить нам жизнь. И в целом с этой задачей справляются они неплохо, хоть и сисадмины скорее всего облегчения особо не чувствуют.
Для интеграции ELK в.NET приложение разработчик Elastic предоставляет очень удобный Nuget‑пакет, с помощью которого сия задача решается в одну строчку кода. Примерно так же обстоят дела с мониторингом, что не может не радовать.
Из минусов можно отметить то, что ELK довольно громоздкий. По этой причине в будущем хочу копнуть в сторону Loki‑стека. Впрочем, для облачных приложений это не проблема. Популярные облачные платформы поддерживают интеграцию с ELK, а в development среде всегда можно развернуть Prometheus + Grafana или воспользоваться.NET Aspire, который существенно упрощает жизнь.
Комментарии (2)
NightBlade74
28.06.2025 17:54Во-первых, лучше называть не ELK-стеком, а Elastic-стеком. Beats, который тут не упомянут напрямую (только один Filebeat), является четвертым компонентом стека Elastic.
Кроме Serilog, есть еще NLog и log4net, которые тоже прекрасно работают с Elastic'ом.
Пробрасывать логи через файлы - это такое себе, все вышеуказанные библиотеки логирования позволяют указывать несколько приемников логов, поэтому лучше отправлять данные во все приемники параллельно, это добавит простоты, гибкости и надежности, сохраняя при этом возможность хорошо структурировать логи - проще говоря, разбивать по полям, что позволит потом осуществлять поиск и визуализацию в непринужденной манере. А штуки типа Filebeat хороши для сбора логов от third-party систем, которые логи уже пишут в файлы и перенастроить их нельзя.
dyadyaSerezha
Вопрос. Почему не лучше всегда пользоваться Filebeat? Тогда все логи идут в локальные файлы и в деве или поддержке можно быстро глянуть локальный файл. А уж если надо что-то обозреть, сделать сложные фильтры с условиями и расчётами, то можно и в ELK зайти. К тому же, в этом случае приложение ничего не знает про ELK и это вроде бы как хорошо. Нет?