Коротко. Журналы RX это JSON по строке в каталоге LOGS_PATH. На рабочем месте их удобно читать в Notepad++ с JSTool, LogExpert или переводить в CSV утилитой вендора DirectumLogConverter. На сервере фильтровать через jq -R 'fromjson?', чтобы битые строки не обрывали разбор, или смотреть в lnav одной лентой по всем сервисам. Порядок поиска: время и пользователь, ошибка в журнале веб-сервера, трасса tr через все сервисы, тип исключения.

Пользователь пишет, что в 10:15 не сохранился документ. Администратор открывает папку с логами, видит два десятка файлов по несколько сотен мегабайт и открывает самый большой в блокноте. Через десять минут он прокручивает стену из фигурных скобок, а ошибки так и не нашёл. Журналы RX рассчитаны на разбор программами, и глазами их читать тяжело. Ниже разберём, чем их открывать на рабочем месте и на сервере, и порядок поиска, который приводит к причине за несколько минут.

Симптом

  • Файлы большие, в одной строке сотни символов, перенос строк превращает запись в абзац.
  • Поиск по слову Error находит тысячи совпадений, в том числе в тексте сообщений и именах методов.
  • Ошибка в одном сервисе, а причина в другом, и непонятно, как связать записи двух файлов.
  • Часть записей не открывается форматировщиком JSON, и инструмент падает на середине файла.

Почему так

Сервисы Directum RX пишут журналы в структурированном виде: одна запись на строку, каждая запись это объект JSON. Такой формат удобен для автоматического разбора и сбора в хранилище, но без подсветки и фильтров читать его тяжело.

Где лежат файлы. На Linux каталог задаётся параметром LOGS_PATH в config.yml Directum Launcher, по умолчанию это <папка с данными>/logs. Имя файла собрано из имени компьютера, контейнера, сервиса и даты:

rx01-web.WebServer.2026-10-22.log
rx01-worker.Worker.2026-10-22.log
rx02-preview.PreviewService.2026-10-22.log

Журналы веб-клиента и веб-агента лежат в подпапке remote, по файлу на компьютер пользователя и учётную запись. Журналы самого Directum Launcher хранятся отдельно, в папке DirectumLauncher/log: там ищут ошибки установки и обновления.

Что в записи. Самые нужные поля:

Поле Что в нём
t время с часовым поясом: 2026-10-22 10:15:03.412+03:00
l уровень: Fatal, Error, Warning, Info, Debug, Trace
lg логгер, то есть компонент, который написал запись
mt текст сообщения, иногда объект
ex исключение: тип type, текст m, стек stack
span операция и её длительность в durationMs
un учётная запись пользователя
tr трасса: связывает записи одного действия во всех сервисах

Полный список полей и разбор каждого есть в статье про сбор журналов через Vector.

Трасса. Поле tr выглядит как cl-4e326a73-7815ae. Первая часть общая для всего действия: пользователь нажал кнопку в веб-клиенте (префикс cl), веб-сервер обратился к сервису хранилищ, тот к сервису предпросмотра, и у всех этих записей начало трассы одинаковое. Префикс показывает, где действие началось: cl веб-клиент, ws веб-сервер, js планировщик, do отложенные операции, wk сервис асинхронных событий. По трассе можно собрать полную историю одного действия из файлов разных сервисов.

Инструменты на рабочем месте

Вендор в документации называет несколько программ. Все они работают на Windows, файлы забирают с сервера.

  • Notepad++ с плагином JSTool. Подсвечивает и форматирует строку JSON по Ctrl+Alt+M, ищет по всем открытым файлам сразу. Ищите "Error" в кавычках: так находятся записи уровня ошибки, а не каждое слово Error в тексте.
  • Visual Studio Code. То же самое плюс многострочный поиск по регулярным выражениям в целой папке.
  • LogExpert и LogViewPlus. Специальные просмотрщики журналов: держат открытыми файлы на гигабайты, обновляют их по мере записи, умеют фильтры и подсветку по условию. Вендор рекомендует их для журналов мобильных сервисов, но для серверных они подходят так же.
  • DirectumLogConverter. Утилита вендора, лежит на GitHub. Переводит журнал в простой текст (dlc файл.log) или в CSV (dlc -c файл.log), после чего файл открывается в Excel с фильтрами по колонкам. Для разовых разборов и для передачи коллегам, которые не работают с консолью, это самый быстрый путь.

Ограничение у всех этих способов одно: файлы надо скопировать к себе, а за инцидент их набирается много.

Инструменты на сервере

На сервере с Linux удобнее не копировать гигабайты, а фильтровать на месте.

jq: отфильтровать и посчитать

jq есть в репозиториях всех дистрибутивов. Журнал нужно читать построчно через -R и разбирать каждую строку с fromjson?. Если открыть файл как обычный JSON, jq остановится на первой битой строке и молча потеряет всё, что после неё. Битые строки бывают: веб-агент пишет пути Windows с одиночным обратным слешем, и такая строка не является корректным JSON.

Ошибки за конкретные пять минут во всех журналах сервера:

cd /srv/logs
jq -rR 'fromjson? | select(.l == "Error" and .t >= "2026-10-22 10:15" and .t < "2026-10-22 10:20")
  | [.t, .lg, (.ex.type // "-"), (.mt | tostring)] | @tsv' *.log | sort

Сравнение времени строками работает, потому что время записано в одном формате и в одном часовом поясе сервера.

Какие ошибки случаются чаще всего за день:

jq -rR 'fromjson? | select(.l == "Error") | "\(.ex.type // "-")  \(.ex.m // (.mt | tostring))"' \
  *.2026-10-22.log | sort | uniq -c | sort -rn | head -20

Всё, что делал пользователь:

jq -rR 'fromjson? | select(.un == "ivanova") | [.t, .l, .lg, (.span.name // (.mt | tostring))] | @tsv' \
  *.2026-10-22.log | sort

Одно действие по трассе через все сервисы. Сначала находим ошибку, берём из неё начало поля tr, затем:

grep -h '"tr":"cl-4e326a73' *.2026-10-22.log \
  | jq -rR 'fromjson? | [.t, .lg, .l, (.span.name // (.mt | tostring))] | @tsv' | sort

Медленные операции, дольше трёх секунд:

jq -rR 'fromjson? | select(.span.durationMs? > 3000) | [.t, .span.name, .span.durationMs] | @tsv' \
  *.2026-10-22.log | sort -t$'\t' -k3 -rn | head

Стек исключения целиком, с переносами строк:

jq -rR 'fromjson? | select(.ex.type == "Npgsql.PostgresException") | .t, .ex.stack, ""' *.log | head -40

lnav это консольный просмотрщик журналов. Он склеивает файлы разных сервисов в одну ленту по времени, подсвечивает уровни, фильтрует и даже выполняет SQL по полям. Формат RX ему надо описать один раз: файл ~/.lnav/formats/installed/directum_rx.json.

{
  "$schema": "https://lnav.org/schemas/format-v1.schema.json",
  "directum_rx": {
    "title": "Directum RX",
    "json": true,
    "file-pattern": "\\.log$",
    "timestamp-field": "t",
    "timestamp-format": ["%Y-%m-%d %H:%M:%S.%L%z"],
    "level-field": "l",
    "level": {
      "fatal": "Fatal", "error": "Error", "warning": "Warning",
      "info": "Info", "debug": "Debug", "trace": "Trace"
    },
    "body-field": "mt",
    "opid-field": "tr",
    "line-format": [
      { "field": "__timestamp__" }, " ",
      { "field": "__level__", "text-transform": "uppercase", "min-width": 7 }, " ",
      { "field": "lg", "max-width": 40, "overflow": "dot-dot" }, " ",
      { "field": "un", "default-value": "-" }, " ",
      { "field": "mt", "default-value": "" },
      { "prefix": " ", "field": "ex/type", "default-value": "" },
      { "prefix": " ", "field": "ex/m", "default-value": "" },
      { "prefix": " span=", "field": "span/name", "default-value": "" },
      { "prefix": " ms=", "field": "span/durationMs", "default-value": "" }
    ],
    "value": {
      "lg": { "kind": "string", "identifier": true },
      "un": { "kind": "string", "identifier": true },
      "tr": { "kind": "string", "identifier": true },
      "mt": { "kind": "string" },
      "ex": { "kind": "json" },
      "ex/type": { "kind": "string", "identifier": true },
      "ex/m": { "kind": "string" },
      "span": { "kind": "json" },
      "span/name": { "kind": "string", "identifier": true },
      "span/durationMs": { "kind": "integer" },
      "args": { "kind": "json", "hidden": true },
      "cust": { "kind": "json", "hidden": true },
      "pid": { "kind": "string", "hidden": true },
      "v": { "kind": "string", "hidden": true }
    }
  }
}

После этого lnav /srv/logs/*.2026-10-22.log показывает все сервисы одной лентой:

2026-10-22 10:15:03.412+03:00 ERROR   Sungero.Domain.Shar..oteFunctionExecutor petrov Error while executing remote function {name} Npgsql.PostgresException 57014: canceling statement due to statement timeout
  tr: cl-9a1b2c3d-81fa02
2026-10-22 10:15:03.900+03:00 ERROR   RabbitMqConnectionsProvider - RabbitMQ connection '{connectionName}' recovery failed. System.AggregateException One or more errors occurred. (Connection failed)

Полезные приёмы внутри lnav: e и E прыгают к следующей и предыдущей ошибке, :filter-in petrov оставляет записи пользователя, :filter-out TraceSpan убирает служебные замеры. Сводка по типам ошибок одним запросом:

;SELECT log_level, "ex/type", count(*) AS n FROM directum_rx GROUP BY 1, 2 ORDER BY n DESC

Формат мы проверили на lnav 0.14 на журналах в формате RX. Битые строки веб-агента lnav, как и jq, пропускает без предупреждения: если ищете проблему веб-агента, проверьте файл ещё и обычным grep.

Windows и PowerShell

Если сервисы RX работают на Windows, тот же приём делается средствами PowerShell:

Get-Content .\rx01.WebServer.2026-10-22.log |
  ForEach-Object { try { $_ | ConvertFrom-Json } catch {} } |
  Where-Object { $_.l -eq 'Error' } |
  Select-Object t, lg, @{n='ex'; e={$_.ex.type}}, mt

try вокруг разбора нужен по той же причине, что fromjson? в jq: одна битая строка не должна останавливать обработку.

Как найти ошибку за пять минут

  1. Узнать время и пользователя. «Утром не сохранилось» не годится: попросите время с точностью до минут, учётную запись и что именно делали. Если пользователь не помнит, ищите по учётной записи за весь день.
  2. Найти запись в журнале веб-сервера. Большинство действий пользователя начинается там. Фильтр: уровень Error, окно в пять минут вокруг времени, при возможности учётная запись.
  3. Взять трассу и пройти по всем сервисам. Начало поля tr из найденной записи ищется во всех файлах за день. Так видно, в каком сервисе действие сломалось на самом деле: веб-сервер часто сообщает только то, что ему не ответили.
  4. Прочитать исключение снизу вверх. Тип исключения и первые строки стека показывают слой: Npgsql это база данных, RabbitMQ.Client брокер, System.Net сеть или прокси. Текст вроде 57014: canceling statement due to statement timeout уже готовое направление для поиска: что он значит, разобрано в справочнике ошибок.
  5. Проверить, единичный ли это случай. Тот же тип ошибки за день, сгруппированный по часам. Если ошибка повторяется весь день, искать стоит в инфраструктуре, а не в действиях пользователя.

Когда серверов больше одного

Все приёмы выше работают на одном сервере. Если сервисы разнесены по нескольким машинам, трасса уходит в файлы на других серверах, и разбор превращается в обход по SSH. Здесь помогает только сбор журналов в одно место: как это устроить и во что упирается хранение, разобрано в статье «Логи Directum RX: что собирать». Если отдельного хранилища нет, лёгкий вариант на одном сервере без интернета мы собрали в мониторинг в коробке: журналы RX там разбираются на поля, и поиск по трассе или пользователю делается в веб-интерфейсе.

Чего не делать

  • Не открывать журнал на сотни мегабайт в «Блокноте» на сервере. Редактор загрузит файл целиком в память рабочего сервера и может зависнуть вместе с сеансом.
  • Не искать слово Error без кавычек. Найдутся имена методов и тексты сообщений, а записи уровня Warning с настоящей причиной останутся незамеченными. Ищите "l":"Error" или фильтруйте по полю.
  • Не разбирать журнал обычным jq .. Первая битая строка остановит разбор, и остаток файла вы не увидите, не получив внятной ошибки.
  • Не удалять журналы вручную, чтобы освободить место. У Directum Launcher для этого есть команда ./do.sh clean_logs --skip_last_modified_days=7, её ставят в расписание. Сами журналы лучше держать на отдельном диске: если он переполнится, RX перестанет писать журналы, но системный диск и сама ОС не пострадают.
  • Не отправлять вендору «все логи за месяц». Поддержке нужны время ошибки и журналы за этот день со всех серверов. Архив на десятки гигабайт задержит ответ.