
Всем привет, с вами Юрий Ковальчук, 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
Комментарии (3)

withkittens
13.11.2025 11:11var testName = "SubmitPaymentForm_EmptyForm_ShouldShowValidationErrors"; ... var result = new TestResult { Test = testName, Url = baseUrl, Timestamp = DateTime.UtcNow, ErrorText = "", ErrorStyle = "", Success = false }; ... LogTestResult(result);У меня есть стойкое ощущение, что все это - лишний шум, которого в тестах не должно быть. У вас xUnit - разве runner reporter (json или на худой конец - самописный) не решает проблему выгрузки результатов тестов в обрабатывабельном виде?
gotch
input type: log исключен много релизов назад.
Может эффективнее разобрать не через processors, а сразу в parsers?
Зачем переименовывать поля, если можно сразу в исходном JSON написать как надо?
Еще не понял, откуда берется @timestamp в документах Elastic. Было бы логично взять его из Properties.TestResult.Timestamp и не хранить два разных?
webrise Автор
Да, согласен.
У меня Filebeat уже использовался с type: log, поэтому добавлял блок для e2e-тестов в ту же конфигурацию и сделал всё однотипно.
Про filestream и timestamp — да, вариант с parsers.ndjson и подтягиванием времени из логов в @timestamp выглядит логичнее, особенно если логи уже в JSON.