
Всем привет, с вами Юрий Ковальчук, backend разработчик в ВебРайз. В этой статье разберем процесс вывода логов из приложения c автотестами на .NET в ELK с последующей визуализаций в Kibana.
ELK представляет из себя достаточно массивный инструмент для сбора, хранения, обработки и анализа логов, организации мониторингов. С наскоку разобраться с ним вряд ли получится, поэтому подготовили небольшую инструкцию с примерами - на базе простого теста прокинуть результаты до Kibana.
Отправка логов из .NET-теста
Ниже — обезличенный пример e2e-/UI-теста на .NET (xUnit + Playwright), который:
Запускает браузер
Выполняет действия на странице
Формирует объект с результатом теста
Пишет лог в Serilog, откуда его потом заберёт Filebeat
[Fact] public async Task SubmitPaymentForm_EmptyForm_ShouldShowValidationErrors() { string baseUrl = $"{_baseUrl}payment"; var testName = "SubmitPaymentForm_EmptyForm_ShouldShowValidationErrors"; var context = await _browser.NewContextAsync(new() { IgnoreHTTPSErrors = true }); var page = await context.NewPageAsync(); var result = new TestResult { Test = testName, Url = baseUrl, Timestamp = DateTime.UtcNow, ErrorText = "", ErrorStyle = "", Success = false }; try { if (string.IsNullOrEmpty(baseUrl)) throw new Exception("BaseUrl not configured"); await WaitForSiteReady(page, baseUrl, 60); await page.GotoAsync(baseUrl); await page.ClickAsync("button.nf-button--primary"); await page.ClickAsync("button.js-pay-button-submit"); var error = await page.WaitForSelectorAsync(".cell__error-message", new() { Timeout = 30000 }); var text = await error.InnerTextAsync(); var style = await error.EvaluateAsync<string>("el => el.getAttribute('style')"); Assert.Contains("Заполните", text); Assert.Contains("display: block", style); result.Success = true; } catch (Exception ex) { Log.Error(ex, $"Test {testName} failed"); result.ErrorText = ex.Message; result.ErrorStyle = "danger"; } finally { await context.CloseAsync(); LogTestResult(result); } }
Пояснения к ключевым строкам:
baseUrl — конечная точка тестируемой страницы
testName — удобное имя теста, которое попадёт в логи
result — объект, который вы будете сериализовать/логировать (его потом легко разобрать в Filebeat)
try/catch/finally — в catch пишем ошибку, в finally — пишем структурированный результат
Log.Error(...) — классический Serilog-вызов, который попадёт в файл
Filebeat: сбор логов тестов и отправка в Logstash
Пример filebeat.yml:
filebeat.inputs: - type: log enabled: true paths: - /var/www/project/tests/bin/Debug/net6.0/Logs/e2e*.log fields: type: e2e_tests fields_under_root: true scan_frequency: 5s processors: - decode_json_fields: fields: ["message"] process_array: false max_depth: 3 target: "" overwrite_keys: true - rename: fields: - from: "Properties.TestResult.Timestamp" to: "test_timestamp" - from: "Properties.TestResult.Test" to: "test_name" - from: "Properties.TestResult.Success" to: "test_success" ignore_missing: true - drop_fields: fields: ["Properties", "MessageTemplate", "Level"] output.logstash: hosts: ["111.18.100.38:5044"]
Что важно:
paths — путь до логов приложения/тестов
decode_json_fields — разбираем Serilog JSON, чтобы получить плоские поля
rename — переименовываем вложенные поля Serilog в удобные (test_name, test_success и т.д.)
drop_fields — удаляем лишний технический шум
output.logstash — указываем, куда отправлять (Logstash)
Logstash: приём и отправка в Elasticsearch
Минимальный pipeline Logstash (пример logstash.conf):
input { beats { port => 5044 } } filter { if [type] == "e2e_tests" { mutate { add_field => { "[@metadata][index]" => "e2e-tests-%{+YYYY.MM.dd}" } } } } output { elasticsearch { hosts => ["http://localhost:9200"] index => "%{[@metadata][index]}" } stdout { codec => rubydebug } }
input.beats — то, что получает от Filebeat
filter — можно добавлять/нормализовывать поля
output.elasticsearch — конечная точка, индекс называется e2e-tests-YYYY.MM.dd
Поиск логов в Kibana/Dev Tools
Стартуем с простого запроса:
GET _cat/indices?v
Так проверяем, что индекс e2e-tests-* вообще есть. Дальше — простой поиск:
GET e2e-tests-*/_search { "size": 10 }
Чтобы находить именно тестовые логи Serilog со вложенным TestResult, используем match_phrase по message:
GET e2e-tests-*/_search { "size": 10, "query": { "match_phrase": { "message": "\"Properties\":{\"TestResult\"" } }, "sort": [ { "@timestamp": "desc" } ] }
size — сколько документов вернуть
match_phrase — ищем конкретный фрагмент JSON, характерный для наших тестовых логов
sort — сортируем по времени прихода документа
Визуализация в Kibana
Ниже показано, как создать визуализации в Kibana для отображения результатов e2e-тестов .NET. Будем использовать Filebeat и Logstash для отправки логов в Elasticsearch, а Kibana — для построения графиков.
1. Список визуализаций

2. Создание новой визуализации

Нажимаем «Create visualization». Для простоты используем тип Lens — он подходит для построения базовых графиков.
3. Настройка Lens визуализации

В окне Lens выбираем индекс e2e-tests*. На оси X указываем @timestamp, на оси Y — уникальное количество test_success. Сверху можно добавить фильтр test_name, чтобы отображать результаты только конкретного теста.
4. Сохранение визуализации

После настройки графика сохраняем его с понятным названием, чтобы использовать при создании дашборда.
5. Открытие Dashboard

В меню Kibana переходим в раздел Dashboard — здесь создаются панели мониторинга, состоящие из нескольких визуализаций.
6. Создание нового Dashboard

Можно создать новый дашборд («Create dashboard») или открыть существующий. В примере используется дашборд «Тесты_Site.ru».
7. Добавление визуализаций

Чтобы добавить визуализацию, нажмите Add → Lens Visualization и выберите нужный график из списка.
8. Итоговый Dashboard

После добавления всех графиков на панель вы получите наглядный дашборд, отображающий состояние e2e-тестов в реальном времени.
9. Пример визуализации теста

Этот график показывает результаты теста во времени: успешные и неуспешные прогоны.
Теперь дашборд можно использовать для контроля состояния и стабильности тестов, а также для анализа проблем при падении отдельных сценариев.
По вопросам, телеграм @webrise1
