Два окна TurboJedi: весь журнал и только предупреждения с ошибками, найденное подсвечено

journald умеет почти всё, что нужно для разбора проблемы на сервере: индексирует поля, фильтрует по юниту, процессу, пользователю и загрузке, ищет по времени и отдаёт записи по курсору. А смотрим мы его через journalctl, то есть одной командой за раз. Эта статья — о том, как оконный интерфейс из 1990 года оказался почти идеальной формой для этих возможностей, и о том, как устроен получившийся просмотрщик TurboJedi.

Откуда взялось

Мои демоны на C пишут журнал через ReportTools — небольшую библиотеку с подключаемыми обработчиками. Каждая запись уходит во все установленные обработчики: в консоль, в системный журнал, куда угодно ещё.

TurboReport начинался как ещё один такой обработчик. Он занимает место консольного и вместо потока строк поднимает внутри процесса экран на magiblot/tvision, современном порте Turbo Vision. Интерфейс живёт в собственном потоке, чтобы не мешать демону, а записи попадают к нему через тот же вызов, что и в консоль. Запускаешь демон с ключом — и получаешь окна с прокруткой, фильтром по уровням, паузой, выделением и копированием.

Когда этим начинаешь пользоваться, становится понятно, что ценность не в конкретном обработчике. Несколько окон с разными фильтрами, диалог уровней, меню, мышь и строка статуса оказались настолько удобными, что захотелось того же для всего системного журнала. Демонов, которые пишут в journald, на сервере гораздо больше, чем тех, что используют ReportTools.

Так появился TurboJedi — Turbo Journald Interface. Отдельная программа, которая ничего не знает про ReportTools и просто разговаривает с journald.

Чем неудобен journalctl

Сам journalctl хорош: -u, -t, -p, --since, --grep, FIELD=VALUE, курсоры. Неудобство в другом — в том, как устроена работа с ним при разборе инцидента:

  • Один запрос — один вид. Поменять фильтр значит выйти из less и набрать команду заново, потеряв место, где ты был.

  • Корреляция — это tmux. Посмотреть, что делали nginx и база в ту же секунду, можно либо одним потоком (-u nginx -u postgresql, и тогда строки перемешаны), либо в нескольких терминалах, каждый со своей прокруткой.

  • Следить и листать одновременно неудобно. -f следит за хвостом, но в нём нет навигации по истории; в пейджере есть навигация, но слежение там приходится включать вручную.

  • Значения фильтров надо знать заранее. Как точно называется юнит, какой PID был у упавшего процесса, какой boot ID у позавчерашней загрузки — всё это отдельные команды.

Ни одна из этих проблем не требует нового хранилища или нового языка запросов. Не хватает интерфейса, который держит несколько запросов открытыми одновременно.

Почему Turbo Vision

Turbo Vision появился в 1990 году вместе с Turbo Pascal 6.0 и решал ту же задачу, что и сейчас: дать текстовому экрану полноценный оконный интерфейс. Окна с рамкой, номером и заголовком, диалоги с полями и галками, выпадающие меню с горячими клавишами, строка статуса с подсказками, мышь.

Для просмотра журнала это ложится один в один:

  • окно — один запрос к журналу со своим фильтром и своей прокруткой;

  • диалог — изменение фильтра без выхода из программы и повторного набора команды;

  • меню и строка статуса — все возможности видны, ничего не надо помнить;

  • плитка окон (F9) — несколько источников рядом, а не в разных терминалах.

magiblot/tvision приносит в эту модель всё, что нужно сегодня: UTF-8, 24-битный цвет, мышь в современных терминалах, буфер обмена через OSC 52. Последнее важно на сервере: Ctrl‑Ins копирует строки в буфер обмена рабочей машины прямо через SSH.

При этом библиотека маленькая, собирается за минуту и не тянет за собой ничего, кроме ncurses. Программа целиком — около 6000 строк C++: 22 модуля, каждый со своим заголовком.

Как это устроено

Каждое окно — свой клиент журнала

Главное решение: окно не фильтрует общий поток, а само открывает журнал (sd_journal_open) и задаёт фильтр запросом. Окно с ошибками nginx читает только ошибки nginx, сколько бы ни писали остальные.

Источник, уровни и PID превращаются в матчи journald (фрагмент упрощён):

// Matches of the same field are joined by OR, of different fields by AND
for (field = 0; field < JOURNAL_FIELD_COUNT; field ++)
  if (!filter.values[field].empty())
  {
    snprintf(match, sizeof(match), "%s=%s", JournalFieldNames[field], value.c_str());
    sd_journal_add_match(*journal, match, 0);
  }

if (filter.levels != 0xff)
{
  // No level at all leaves a match nothing can satisfy
  sd_journal_add_match(*journal, "PRIORITY=-", 0);

  for (level = 0; level < 8; level ++)
    if (filter.levels & (1 << level))
    {
      snprintf(match, sizeof(match), "PRIORITY=%d", level);
      sd_journal_add_match(*journal, match, 0);
    }
}

Совпадения одного поля journald объединяет через OR, разных полей — через AND. Это ровно та семантика, которая нужна диалогу источника: юнит и пользователь, а внутри уровней — ошибки или предупреждения.

Матчи libsystemd сравнивают поле целиком, поэтому текст сообщения в запрос не передать. journalctl --grep тоже не отправляет его в journald: он читает записи и проверяет MESSAGE у себя. TurboJedi делает то же самое. Текст, обычный или регулярное выражение PCRE2, на показ или на исключение, проверяется у прочитанных записей, которые уже прошли матчи.

Диалоги вместо ключей

Диалог источника: у каждого поля есть список значений из журнала

Диалог источника (F7) содержит юнит, идентификатор, команду, транспорт, загрузку, пользователя и PID. У каждого поля есть кнопка «List…», которая спрашивает у журнала уникальные значения (sd_journal_query_unique) и показывает их списком со строкой фильтра. Загрузки подписаны временем первой записи, пользователи — именем и UID. В списке процессов по умолчанию стоит галка «Running only»: процесс считается живым, только если его PID есть в /proc и процесс стартовал не позже последней записи. Иначе PID уже достался кому‑то другому.

Время (Ctrl‑T) принимает то же, что --since: 2026-10-23 19:00, yesterday, -1h. Для выбора есть календарь.

Хвост, история и буфер

Окно следит за хвостом через sd_journal_get_fd и sd_journal_process в цикле простоя приложения. Новые записи читаются пачками по 5000, чтобы длинный диапазон не замораживал экран.

Прокрутка выше первой строки подгружает историю. Делает это отдельный экземпляр журнала: он встаёт по курсору первой строки (sd_journal_seek_cursor) и идёт назад, а основной экземпляр в это время продолжает следить за хвостом. Строки нумеруются абсолютно: подгруженная история уменьшает номер первой строки, а вытесненные лимитом буфера строки его увеличивают. Поэтому выделение, поиск и возврат назад не ломаются ни от подгрузки, ни от обрезки.

Синхронизация окон по времени

Это функция, ради которой стоило всё затевать. Нашёл ошибку в одном окне — нажимаешь Alt‑G, и все остальные окна переходят к ближайшей по времени строке, сохраняя свои фильтры. Если строки в буфере нет, окно догружает историю, пока не дойдёт до нужного момента.

Реализация укладывается в несколько строк: окно рассылает широковещательное сообщение с временем выделенной строки, остальные ищут ближайшую строку по __REALTIME_TIMESTAMP. Ctrl‑G делает то же по введённому времени, а Alt‑← возвращает окно туда, где оно было до прыжка.

Как это выглядит в работе. Слева окно сервиса, справа окно ядра (-k или транспорт kernel в диалоге источника). В окне сервиса видна строка worker 3 exited unexpectedly with status 137. Код 137 означает, что процесс убит сигналом 9, но кем — из этой строки не понять. Выделяешь её, нажимаешь Alt‑G, и окно ядра встаёт на ту же секунду: если рядом окажется сообщение OOM killer о том же PID, причина найдена. Если нет — Alt‑← возвращает окно ядра туда, где оно было, и можно открыть следующий источник.

Карточка записи и окно по полю

Enter на строке открывает все поля записи: курсор позволяет прочитать её целиком, даже если окно показывает три колонки. Enter на поле в карточке открывает новое окно с фильтром текущего плюс этим полем. Видишь _SYSTEMD_UNIT= или _PID= у подозрительной строки — одно нажатие, и рядом окно только с этим источником.

Раскладки

Все окна с положением, фильтрами, колонками и правилами подсветки сохраняются в INI, по секции на окно:

[window]
bounds = 0 0 120 28
unit = nginx.service
text = timeout
regex = yes
levels = EACRW---
highlight = conf.log
columns = date,time,level,identifier,pid,millisecond

При выходе раскладка сохраняется автоматически и открывается при следующем запуске. Удобно держать файл под конкретный сервис: turbojedi -c web.jedi, и сразу открыты фронтенд, бэкенд и база.

Подсветка: готовый словарь вместо своего формата

Подсвечивать IP‑адреса, UUID, пути и слова вроде failed хотелось, а придумывать для этого свой формат правил — нет. Готовый словарь уже есть: grc, generic colouriser. Его правила — это regexp=, colours= для совпадения и групп и count=once|more|stop. В Debian пакет grc содержит десятки готовых наборов.

TurboJedi читает эти файлы как есть. Регулярные выражения grc написаны для Python re, поэтому вместо std::regex пришлось взять PCRE2: в ECMAScript‑диалекте std::regex нет lookbehind, а PCRE2 вдобавок компилирует выражения JIT‑ом. Проверка показала, что все 722 выражения из пакета grc компилируются в PCRE2 без изменений.

Встроенный набор записан в том же формате и разбирается тем же кодом. Выбор набора — на окно (Ctrl‑L): встроенный, выключено или любой conf.*.

Грабли

Несколько мест, где поведение оказалось не таким, как ждёшь.

seek_tail и next(). После sd_journal_seek_tail вызов sd_journal_next ничего не возвращает: курсор стоит после последней записи. Чтобы начать с последних N записей, нужен sd_journal_previous_skip(journal, N), и делать шаг назад нужно даже при N = 0.

Время жизни данных. Указатель из sd_journal_get_data действителен только до следующего обращения к журналу. Поэтому MESSAGE читается последним, после всех остальных полей.

Пользователи. journald хранит _UID, а не имя. Фильтр по имени приходится переводить в номер через getpwnam, а несуществующее имя — в матч, которому ничего не соответствует, иначе окно молча покажет всё.

Пустой набор уровней. Если снять все галки уровней, ни одного матча PRIORITY= не добавится, и окно покажет всё. Отсюда PRIORITY=- в коде выше: матч, которому не соответствует ни одна запись.

Окно, которого ещё нет. В Turbo Vision полоса прокрутки сообщает о новом положении широковещательным сообщением владельцу. Окно журнала читало первые записи прямо в конструкторе, ещё до вставки в окно, и сообщение до него не доходило. Полоса прокрутки показывала конец, а окно — начало. Лечится одним вызовом scrollDraw(), который забирает положение у полосы прокрутки.

Слишком быстрый ввод. tvision считает быстро пришедшие символы вставкой из буфера обмена. На живом человеке это не проявляется, а в автоматических тестах через pty клавиши пришлось отправлять с паузами.

Сборка и пакеты

git clone --recurse-submodules https://github.com/cyanide-burnout/TurboJedi.git
cd TurboJedi
make
sudo make install

Нужны libsystemd-dev, libncurses-dev, libpcre2-dev и cmake для tvision. make deb собирает deb‑пакет, а make deb DISTRIBUTION=arm64 — то же самое кросс‑компилятором. Версия берётся из git describe, а changelog пакета — из тегов и коммитов.

Читать весь журнал может root или участник группы systemd-journal. Остальные видят только свои записи.

Чего в TurboJedi нет

Каждая из этих функций обсуждалась и была отброшена:

  • Закладки. Журнал — поток, а не документ. Несколько окон, синхронизация по времени и возврат назад закрывают тот же сценарий естественнее.

  • Экспорт в JSON. Карточка записи копируется целиком, а для машинной обработки есть journalctl -o json с теми же фильтрами.

  • Свой язык запросов. Фильтры journald уже выражают всё нужное, и диалоги просто дают к ним доступ.

Мерило, которого я старался держаться: инструмент должен оставаться маленьким, а всё, что он умеет, должно быть видно из меню.

Итог

Самое интересное в этой истории — не ностальгия по DOS. Модель «окно — запрос, диалог — фильтр, меню — все возможности на виду» оказалась удачной для задачи, которой в 1990 году не существовало: держать перед глазами несколько срезов системного журнала и переключаться между ними, не теряя места.

Оказалось, что для удобной работы с journald не нужно заново изобретать ни систему журналирования, ни оконный интерфейс. Обе уже существуют, оставалось только их соединить.

TurboJedi распространяется под MIT: github.com/cyanide‑burnout/TurboJedi.

Комментарии (39)


  1. crawlingroof
    24.10.2026 12:26

    Красивое.


  1. Apoheliy
    24.10.2026 12:26

    Вообще задумка хорошая, самого прыжки по журналу напрягают.

    Один из вопросов: как приложение по потреблению памяти? Не течёт?

    Почему такие вопросы? Ну код, как бы, намекает:

      // Window shortcuts live here, the menu bar handles them without a status line item
      return new TMenuBar(bounds,
        *new TSubMenu("~F~ile", kbAltF) +
          *new TMenuItem("~N~ew window",     cmJournalNew,    kbF2,     hcNoContext, "F2") +
          *new TMenuItem("~O~pen layout...", cmJournalOpen,   kbCtrlO,  hcNoContext, "Ctrl-O") +
          *new TMenuItem("~S~ave layout...", cmJournalSave,   kbCtrlS,  hcNoContext, "Ctrl-S") +
          newLine() +
          *new TMenuItem("~E~xport lines...", cmJournalExport, kbCtrlE, hcNoContext, "Ctrl-E") +
          newLine() +
          *new TMenuItem("E~x~it",           cmQuit,          kbAltX,   hcNoContext, "Alt-X") +
        *new TSubMenu("~J~ournal", kbAltJ) +
          *new TMenuItem("~L~evels...",     cmJournalLevels,       kbF3,      hcNoContext, "F3") +
          *new TMenuItem("~T~ext...",       cmJournalText,         kbF4,      hcNoContext, "F4") +
          *new TMenuItem("~S~ource...",     cmJournalSource,       kbF7,      hcNoContext, "F7") +
          *new TMenuItem("T~i~me...",       cmJournalTime,         kbCtrlT,   hcNoContext, "Ctrl-T") +
          *new TMenuItem("Col~u~mns...",    cmJournalColumns,      kbCtrlK,   hcNoContext, "Ctrl-K") +
          *new TMenuItem("~W~rap lines",    cmJournalWrap,         kbCtrlW,   hcNoContext, "Ctrl-W") +
          *new TMenuItem("~H~ighlight...",  cmJournalHighlight,    kbCtrlL,   hcNoContext, "Ctrl-L") +
          newLine() +
          *new TMenuItem("~F~ind...",       cmJournalFind,         kbCtrlF,   hcNoContext, "Ctrl-F") +
          *new TMenuItem("Find ~n~ext",     cmJournalFindNext,     kbCtrlN,   hcNoContext, "Ctrl-N") +
          *new TMenuItem("Find p~r~evious", cmJournalFindPrevious, kbCtrlP,   hcNoContext, "Ctrl-P") +
          *new TMenuItem("~D~etails...",    cmJournalDetails,      kbNoKey,   hcNoContext, "Enter") +
          newLine() +
          *new TMenuItem("~G~o to time...", cmJournalGoTo,         kbCtrlG,   hcNoContext, "Ctrl-G") +
          *new TMenuItem("S~y~nc windows",  cmJournalSync,         kbAltG,    hcNoContext, "Alt-G") +
          *new TMenuItem("~B~ack",          cmJournalBack,         kbAltLeft, hcNoContext, "Alt-Left") +
          newLine() +
          *new TMenuItem("~P~ause",         cmJournalPause,        kbNoKey,   hcNoContext, "Space") +
          *new TMenuItem("C~o~py",          cmJournalCopy,         kbCtrlIns, hcNoContext, "Ctrl-Ins") +
          *new TMenuItem("~C~lear",         cmJournalClear,        kbF8,      hcNoContext, "F8") +
        *new TSubMenu("~W~indow", kbAltW) +
          *new TMenuItem("~T~ile",        cmTile,          kbF9,      hcNoContext, "F9") +
          *new TMenuItem("C~a~scade",     cmCascade,       kbNoKey) +
          *new TMenuItem("~Z~oom",        cmZoom,          kbF5,      hcNoContext, "F5") +
          *new TMenuItem("~S~ize/Move",   cmResize,        kbCtrlF5,  hcNoContext, "Ctrl-F5") +
          *new TMenuItem("~N~ext",        cmNext,          kbF6,      hcNoContext, "F6") +
          *new TMenuItem("~P~revious",    cmPrev,          kbShiftF6, hcNoContext, "Shift-F6") +
          *new TMenuItem("~C~lose",       cmClose,         kbAltF3,   hcNoContext, "Alt-F3") +
        *new TSubMenu("~H~elp", kbAltH) +
          *new TMenuItem("~K~eys",        cmJournalHelp,   kbF1,      hcNoContext, "F1") +
          *new TMenuItem("~A~bout",       cmJournalAbout,  kbNoKey));

    Хотя, возможно, это всё с собственным отслеживанием памяти (например, как в Qt) - и всё работает.


    1. performer Автор
      24.10.2026 12:26

      Собрал с AddressSanitizer и прогнал все окна и диалоги. Одна утечка нашлась, только не в меню, а в моём диалоге выбора из списка: TListBox удаляет старую коллекцию только при замене, и последняя оставалась висеть, около 2 КБ на каждое открытие списка. Исправил, теперь LeakSanitizer чистый.

      По потреблению: пустое окно — около 8 МБ RSS на весь процесс, из них куча 1,6 МБ, остальное — отображённые файлы журнала, которые ядро отдаст при нехватке памяти. Строка в буфере стоит около 440 байт. Буфер окна ограничен 100 000 строк, то есть до ~45 МБ, если долго следить за болтливым журналом; стартует окно с 1000 последних записей.


      1. oookkdjjjdjdj
        24.10.2026 12:26

        Цифры адекватные, главное что mmap не раздувает резидентную память до небес


    1. tbl
      24.10.2026 12:26

      нет там собственного отслеживания памяти, деструктор вызывает delete:
      https://github.com/magiblot/tvision/blob/master/source/tvision/tmenubar.cpp#L45


    1. vesper-bot
      24.10.2026 12:26

      эта штука вызывается один раз ЕМНИП в TProgram.init(), сама структура - TVision’овский способ описать меню кодом, портированная с Паскаля. Так как в TVision TProgram - синглтон, god object и забыл что ещё, такое построение в принципе не может утечь, так как второй TProgram создан не может быть (нет пути в коде для создания ещё одного), а если этот захлопнется, процесс выйдет.


      1. tbl
        24.10.2026 12:26

        А все равно подход у либы а-ля "кишки наружу". В 90-е это было нормой. Сейчас я бы взял ftxui (react-стиль, но окошек нет, надо пилить самому), ну или для линукса (как в этом приложении для работы с журналом убунты) - final cut (там вообще qt-like подход)


        1. performer Автор
          24.10.2026 12:26

          FINAL CUT концептуально очень близок к Turbo Vision, а FTXUI больше ориентирован на компоновку компонентов, чем на полноценный оконный интерфейс.

          Мне нужны независимые перекрывающиеся окна, меню, диалоги, управление фокусом и клавиатурой. Всё это в Turbo Vision уже есть и работает.

          Так что особых преимуществ от перехода на другую библиотеку я не вижу. А внешний вид — дело вкуса. :)


      1. Apoheliy
        24.10.2026 12:26

        Вот и похоже, что они такой подход перетащили прямо из паскаля.

        Да вот только в паскале при ошибках выделения памяти идёт возврат nil. И результат других операций New можно сохранить через "суммирование".

        Если же смотреть на C++, то при ошибке выделения памяти все незапомненные результаты new (а они тут ВСЕ незапомненные) плавно перетекут в разряд утечек памяти. Потому что C++ выбросит исключение и, и, и, ... всёёё. Причём может быть выделение памяти ещё до всяких суммирований - как компилятор соптимизирует. Конкретно в этом случае при ошибках на TMenuItem создание TMenuBar не вызовется и вся накопленная мега-сумма может утечь.

        Пара примечаний:

        Ошибка памяти может возникнуть не потому что память закончится (тут уже был холивар, что линукс всегда выделяет память), а потому что будет ограничен её максимальный объём. И это на удалённых серверах (для чего и предназначена утилита) вполне может быть.

        Классы при чистом создании нигде не регистрируются в каком-либо глобальном списке экземпляров (в частности в TMenu ничего такого нет).

        Операция new на классе не переопределяется, т.е. если указатель потеряется (не сохранился), то это будет утечка.

        И да, при выбрасывании исключения программа скорее всего закроется (и кто-то скажет, что программы такого типа это нормально). Но почему-бы не сделать хорошо (т.к. "плохо само получится")?


        1. vesper-bot
          24.10.2026 12:26

          Если при стартовом выделении памяти у вас исключение OOM, то или ви таки слишком много кушать, или у вашего хоста проблемы посерьезнее. TVision “тонкий” по памяти, значит если при запуске он упадет потому, что памяти мало, вы скорее всего не сможете запустить там даже ls или ps, и то, что просмотр логов не запустился, будет только сигналом. И таки да, программа, падающая при провале выделения памяти при инициализации - нормально, ибо делать в таком случае банально нечего. Вот при работе после запуска - может и стоит что-то обрабатывать.


        1. Siemargl
          24.10.2026 12:26

          Да вот только в паскале при ошибках выделения памяти идёт возврат nil. И результат других операций New можно сохранить через "суммирование".

          Суммирование это как? Не понял


          1. Apoheliy
            24.10.2026 12:26

            Честно говоря, не понял Ваш вопрос / комментарий. Однако, постараюсь ответить:

            Суммирование в моём описании специально взято в кавычки. И вот почему: в версии библиотеки на C++ используется operator+, который как-бы складывает экземпляры объектов (не указатели).

            Подозреваю, в исходной версии из паскаля тоже использовался какой-то синтаксис, который к одному экземпляру "присоединяет" другой (возможно, знатоки паскаля и библиотеки подскажут?). И в таком присоединении можно сделать запоминание созданного объекта.

            В чём отличие C++ от паскаля? В случае ошибки до вызова суммирования/присоединения/назовите-как-хотите дело просто не дойдёт - и объект потеряется.


            1. Siemargl
              24.10.2026 12:26

              Блин, ну зачем теоретизировать, не зная кода.

              И там и там, вызывалась лестница из вложенных функций а(а(а(а))).

              То что в с++ функция может выглядеть как оператор+(), дела не меняет.

              Обработка будет разной, в паскале вернется ниль, а в с++ исключение, но отработают деструкторы уже собранных объектов.

              Оба случая надо обработать на верхнем уровне, но потерь памяти можно спокойно не допускать.


  1. performer Автор
    24.10.2026 12:26

    Это стандартный способ создания меню в Turbo Vision, унаследованный ещё от Borland. TSubMenu и TMenuItem образуют структуру, владение которой передаётся меню. Выглядит непривычно для современного C++, но само по себе утечкой не является.

    Что касается памяти: буфер ограничен 100 000 записей на окно, старые записи вытесняются. Поэтому бесконечного накопления журнала в памяти не предусмотрено.

    Полноценного длительного теста на утечки пока не проводил, так что утверждать, что их гарантированно нет, не буду. Проверю отдельно.


  1. vlad4kr7
    24.10.2026 12:26

    плюсанул!

    немного напрягает сборка, ну наверное норм для сисадмина


    1. performer Автор
      24.10.2026 12:26

      Спасибо! Собирать больше не обязательно: в релизе v0.2 готовые пакеты для Debian 13, Ubuntu 24.04/26.04, Fedora 44 и RHEL 9/10, amd64 и arm64 — https://github.com/cyanide-burnout/TurboJedi/releases/tag/v0.2


      1. konkere
        24.10.2026 12:26

        Под У22.04 придётся (%


      1. TIEugene
        24.10.2026 12:26

        rpm немного неправильно собирается (точнее - собирается правильно, но там лишние телодвижения, не канонично). install не нужны. В %license и %doc указываете просто имена файлов в сырцах - rpmbuild сам разместит их куда надо.


        1. performer Автор
          24.10.2026 12:26

          Спасибо, справедливо: поправил — %license и %doc теперь просто по именам. Одна оговорка: сборка идёт прямо в дереве (архив git не включает подмодуль tvision), поэтому в %prep три файла копируются в каталог сборки — то, что иначе сделал бы %setup.


    1. oookkdjjjdjdj
      24.10.2026 12:26

      Собирается она в один make без мудреных зависимостей за пару минут

      Но готовый deb-пакет в проде конечно безопаснее


  1. ergonavtor
    24.10.2026 12:26

    а я такое сделал https://github.com/kvv256512-ux/turbo-ai


  1. Siemargl
    24.10.2026 12:26

    Более 25 лет назад портировал TV 2.0 под Watcom dos4gw, чтобы не мучаться с ограничениями 16-бит.

    Помню, у меня была и виртуализация списков и работа с СУБД с кэшированием.

    Но сейчас, вспоминая архитектуру TV, наверное не выбрал бы. Взял бы что попроще. Она создавалась по подобию системы сообщений и окон Windows.


    1. performer Автор
      24.10.2026 12:26

      Согласен, Turbo Vision не самая простая библиотека, особенно если приходится дорабатывать её внутренности. Но в данном случае мне как раз нужна была полноценная оконная модель с меню, диалогами и стандартной навигацией.

      Меня давно раздражает тенденция современных TUI делать всё через горячие клавиши, которые сначала надо выучить, а потом ещё вспомнить, если пользуешься программой раз в несколько месяцев.

      В Turbo Vision функции можно обнаружить через меню и диалоги, не читая предварительно руководство. А горячие клавиши остаются способом ускорения работы, а не обязательным условием её выполнения.

      Я около двух лет присматривался к TV, но для других своих проектов применение получалось искусственным — веб-интерфейс там удобнее. А вот для интерактивного просмотра journald через SSH он оказался практически идеальным.


    1. oookkdjjjdjdj
      24.10.2026 12:26

      Для текстового режима нормальных готовых фреймворков с окнами и мышью раз-два и обчелся


    1. dbpatch
      24.10.2026 12:26

      Но сейчас, вспоминая архитектуру TV, наверное не выбрал бы. Взял бы что попроще. Она создавалась по подобию системы сообщений и окон Windows.

      Не совсем так. Вернее совсем не так. Там прародителем была библиотека-среда MacApp, от Apple. С нее потом списали TV, OWL и даже частично MFC. Ну и VCL само собой. И что самое интересное - Object Pascal это изначально тоже от Apple популяризация, а не от Borland, правда у них потом случился сдвиг в C++ и потом в Objective C, но это уже другая история.
      Гуглится это великолепие по "Programmer's Guide to MacApp".

      А Windows это WinAPI, все эти HANDLE, HWND - там нет ничего от TV.


  1. antonchev
    24.10.2026 12:26

    В качестве пожелания: в Source добавить Machine_ID.


    1. performer Автор
      24.10.2026 12:26

      Спасибо за идею, сделал в v0.4. В Source появилось поле Machine со списком машин, подписанных именами хостов. А чтобы было из чего выбирать, у каждого окна теперь свой набор журналов (Shift-F7): эта машина, все машины, как journalctl --merge, или каталог — например, /var/log/journal/remote от systemd-journal-remote. Для первого окна то же задаётся ключами -m и -D. Пакеты: https://github.com/cyanide-burnout/TurboJedi/releases/tag/v0.4


  1. oookkdjjjdjdj
    24.10.2026 12:26

    Отдельный плюс за готовые правила подсветки из grc. Не пришлось изобретать свой велосипед для регекспов)


  1. JBFW
    24.10.2026 12:26

    Отличная идея )

    А что касается логов как таковых - динозавры писали их в разные текстовые файлы, которые можно было не только смотреть в том же миднайте, например, но и грепить из них выборки для сопоставлений по разному, хоть по pid, хоть по time, не вспоминая кучу ключей.

    Но то динозавры...


  1. Beholder
    24.10.2026 12:26

    Надо надеяться, что лет через 20 такими темпами, публика перерастёт текстовый режим и осилит GUI - Graphical User Interface...


    1. JBFW
      24.10.2026 12:26

      Не дождетесь )

      Интерфейс командной строки решает задачу создания автоматически работающих скриптов, он удобен именно для этого.

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

      Какую новую задачу решает gui? Кроме изображений и разных показометров, графиков-диаграмм? И нужно ли это на сервере?


    1. salnicoff
      24.10.2026 12:26

      Ага, и тягать этот гуй через половину земного шарика с дикими тормозами. В консоли все и делается, чтобы не тормозило. А для любителей гуеао админить удаленные сераера есть Windows и RDP.


  1. opium
    24.10.2026 12:26

    Для поиска по длинным сообщениям стоит отдельно проверить запись с MESSAGE больше 64 КиБ и искомой строкой в самом конце. По документации sd_journal_get_data поле по умолчанию может вернуться обрезанным до 64 КиБ, поэтому фильтр по полученному MESSAGE рискует пропустить такое совпадение: https://www.freedesktop.org/software/systemd/man/latest/sd_journal_get_data.html . Для чтения поля целиком предусмотрен sd_journal_set_data_threshold(journal, 0). При этом ненулевой threshold — только подсказка библиотеке, а не строгий предел размера возвращаемых данных, поэтому ограничение памяти просмотрщику всё равно нужно задавать отдельно.


    1. performer Автор
      24.10.2026 12:26

      Спасибо за замечание! Исправил: теперь все journal handles открываются с sd_journal_set_data_threshold(journal, 0), поэтому поиск, фильтрация и просмотр полей работают с полным содержимым MESSAGE, включая сообщения больше 64 КиБ.

      Заодно добавил ограничение буфера каждого окна по объёму данных — 64 МиБ, в дополнение к ограничению по числу записей. При этом одна большая запись всегда сохраняется целиком.

      Изменение уже в master: https://github.com/cyanide-burnout/TurboJedi/commit/87a8cb7


  1. krvfjss
    24.10.2026 12:26

    Лично я пришёл к выводу, что все журналы лучше заливать в базу данных. В ClickHouse, например. А там уже анализировать их написанием SQL запросов. Например: "Сколько событий определённого типа произошло с 9 часов до 10 часов?", "Насколько это значение типично?", "Для этого отобрази количество событий с 9 до 10 часов за каждые сутки на протяжении предыдущих трёх месяцев?". Каждый из этих вопросов элементарно переводим на язык SQL, а результаты вставляем в электронную таблицу, где уже можно отобразить график, диаграмму, сделать какой-то статистический анализ и выявить аномалии.


    1. performer Автор
      24.10.2026 12:26

      Для долгосрочного хранения и статистического анализа журналов вполне разумный подход. Мы для таких задач используем ELK.

      Но для оперативной диагностики непосредственно на сервере база данных не всегда нужна. Часто достаточно открыть journald, посмотреть события нужной службы, отфильтровать сообщения и сопоставить их по времени.

      В общем, задачи разные. Для анализа статистики за несколько месяцев я бы тоже предпочёл специализированные инструменты.


  1. Vladekk
    24.10.2026 12:26

    В конце девяностых мой отец написал на паскале и турбовижн программу "Aizdevums" (Займ) для выдачи кредитов латвийским сбербанком. Довольно навороченная была штука, и турбовижн делал её использование явно понятнее. Базой данных были DBF файлы.


    Один раз была история, как потеряли важные данные по глупости. У него в одном месте два раза спрашивала подверждение прога, с дефолтом на "нет", и всё равно нажали "удалить". Чудом он нашёл бэкап - он всегда делал проги так, что везде оставались копии и "снэпшоты". Сейчас это назвали бы ужасом безопасности, но тогда это спасло чьи-то карьеры.
    С тех пор в программе в этом месте стало не два, а три диалога потверждения "удалить".

    Отец писал в окне турбо паскаля, и меня это всё завораживало, как он думает и пишет что-то такое магическое, и потом появляется программа с окнами и вычислениями. Бывало, я сидел и просто смотрел по 10 минут, как он работает в этих синих окнах (хотя он потом переделал их на цвет потемнее, чтоб глаза не выпали)

    Потом в 12-13 лет я сидел в универе, комнате с компами преподов, и он мне тупо дал код программы на паскале, чтоб я её набил, без объяснений. Программа решала квадратное уравнение. Но как? Задавая вопросы, разобрался в течении получаса.

    Стал писать что-то дальше, типа текстовой игры из учебника. Понял, что нужно повторять действие неизвестное кол-во раз. Спросил отца: а как же быть?
    Цикл! - ответил он.

    Мой отец и какая-то коричневая книжка по Паскалю, название которой я не помню, определили весь мой жизненный путь.

    Я стал программистом.


    1. vesper-bot
      24.10.2026 12:26

      Фаронов, может? Хотя мой бы вроде бы синим


      1. Vladekk
        24.10.2026 12:26

        Вроде как был в коричневой тоже Фаронов, но кажется, что у меня была книжка поменьше размером и в твердой обложке.

        Другой кандидат - Трифонов, фамилия отзывается чем-то. Там скорее красноватый цвет, правда.

        Еще есть Йенсен и Вирт, там коричневый, но мягкая обложка.