rk_syslog 0.2.0 copy "rk_syslog: ^0.2.0" to clipboard
rk_syslog: ^0.2.0 copied to clipboard

A syslog sink for Dart — RFC 5424 framing, RFC 5425 delivery over TLS, and a bounded on-disk spool so records survive a crash and never block the caller.

rk_syslog #

Приёмник журнала для Dart: обрамление по RFC 5424, доставка по RFC 5425 поверх TLS и ограниченный буфер на диске. Нативная часть на Rust, привязка через dart:ffi.

Чем этот пакет не является #

Он не убирает вызовы журналирования из кода. Решение о том, пишет ли класс в журнал, что именно он пишет и какие из его полей — секрет, принимается в точке вызова, внутри самого класса. Нативная библиотека лежит ниже контракта и этих классов не видит вовсе; никакая обёртка над ней, ни на одном языке, не может дотянуться вверх и удалить тот код. Маскирование тем более остаётся у вызывающего: пакет не знает, что строка — это ПИН, и не угадывает.

Если вы пришли сюда за тем, чтобы стало меньше однообразного кода в каждом классе, — этого приёмник дать не может, ни на одной стороне границы FFI. Что он даёт, стоит того само по себе:

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

Пример #

final config = RkSyslogConfig(spoolDirectory: '/var/lib/telepos/syslog')
  ..identify(hostName: 'till-01', appName: 'telepos', procId: '$pid')
  ..facility = RkFacility.local0
  ..set('collector_scheme', 'tls')
  ..set('collector_host', 'logs.shop.example')
  ..set('tls_server_fingerprint_sha256', 'a1b2…');

final opened = RkSyslogSink.open(config);
if (opened case RkSyslogFailure(:final status, :final detail)) {
  return report(status, detail);   // отказ — значение, а не исключение
}
final sink = opened.valueOrThrow();

sink.submit(
  severity: RkSeverity.informational,
  msgid: 'SALE',
  structuredData: RkStructuredData()
    ..element('sale@0').param('total', '1250.00'),
  message: 'чек закрыт',
);

sink.flush();   // блокирует: всё отправленное теперь на диске
sink.close();   // детерминированно

Что гарантируется #

Порядок записи уходят в порядке отправки, в том числе после перезапуска
Доставка не менее одного раза, не «ровно один»
Долговечность начинается с буфера на диске, не с момента submit
Потери никогда не молча: см. правило буфера ниже
Блокировка submit не ждёт сеть и диск; ждёт только flush, и только того, кто его позвал

Почему не «ровно один раз». Курсор буфера сдвигается после записи в сокет. Машина, умершая между записью и сохранением курсора, отправит запись повторно. Обратный порядок превратил бы тот же промежуток в потерю, а для журнала дубль — неудобство, дыра — дефект. Сделать это точным нечем: в RFC 5425 нет подтверждения на уровне приложения.

Правило буфера и его цена #

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

spool_policy Что происходит Цена
drop_oldest (по умолчанию) удаляется самый старый сегмент целиком записи потеряны навсегда, и гранулярность — сегмент, то есть до spool_segment_bytes за раз, а не одна запись. Остаётся счётчик spool_dropped_records, и на место удалённых пишется запись о том, сколько их было
reject не удаляется ничего отказ переезжает к вызывающему: submit возвращает spoolFull, и запись остаётся у него. Для аудита и безопасности, где выброшенная запись — дефект, а у вызывающего есть своё надёжное хранилище

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

Три журнала (раздел 15 архитектуры) #

Журнал facility spool_policy
Технический local0 drop_oldest
Аудит audit (13) reject
Безопасность authpriv (10) reject

Настройки #

Устанавливаются по имени. Ключ, которого библиотека не знает, — отказ (unknownConfigKey), а не молчаливое игнорирование: spool_max_byte остановит открытие приёмника, а не оставит границу по умолчанию и не даст диску заполниться через полгода.

Ключ По умолчанию
spool_dir обязателен
host_name, app_name, proc_id - поля заголовка; проверяются один раз при открытии
facility local0 по имени
spool_max_bytes 67108864 64 МиБ
spool_segment_bytes 1048576 1 МиБ — он же гранулярность потери
spool_policy drop_oldest drop_oldest | reject
queue_max_records 4096 окно потери при падении процесса
queue_policy reject drop_oldest | reject
collector_scheme none none | tls | tcp
collector_host
collector_port 6514
tls_server_name = collector_host SNI и имя для проверки
tls_roots_pem путь к связке корней
tls_server_fingerprint_sha256 закрепление, с двоеточиями или без
tls_client_cert_pem, tls_client_key_pem взаимный TLS; только вместе
max_message_bytes 8192 RFC 5425 §4.2 просит поддерживать 8192
oversize truncate truncate | reject
msg_bom true метка порядка байтов перед MSG, RFC 5424 §6.4
connect_timeout_ms, write_timeout_ms 5000
retry_min_ms, retry_max_ms 500, 30000 удвоение до предела

Закрыто по умолчанию. collector_schemenone: новая установка ничего наружу не открывает. И у пакета нет встроенного хранилища корней: сборщик у клиента обычно подписан своим удостоверяющим центром, и приёмник, который молча доверял бы публичным корням, доверял бы не тому набору. Либо связка, либо отпечаток — иначе открытие не проходит.

tcp — это не RFC 5425: ни шифрования, ни проверки другой стороны. Он есть потому, что сборщик на той же машине — реальная установка, и потому, что обрамление должно быть проверяемым без сертификата. Всё, что уходит с машины, хочет tls.

Счётчики #

Читаются по имени; неизвестное имя даёт unknownStat, а не ноль — «не измеряется» и «измеряется, и ноль» это разные ответы.

submitted, framing_refused, truncated, queue_refused, queue_dropped, queue_depth, spooled, spool_refused, spool_dropped_records, spool_dropped_segments, spool_torn_records, spool_bytes, spool_records, spool_io_failures, sent, send_failures, connected.

Правила, которые надо знать #

  • Отказ — значение (И144). Ничто здесь не бросает исключение при отказе нативной части; см. RkSyslogResult. Паника за границей ловится и приходит как panicked.
  • Не на изоляте интерфейса (И145). Любой вызов сюда — вызов в чужой код.
  • close() обязателен (И146). Сборщик мусора Dart не знает ни о буфере, ни о рабочем потоке, ни о сокете.
  • Перечисления пересекают границу по имени (И147). RkSyslogSink.verifyNameTables() сверяет таблицы пакета с таблицами загруженной библиотеки, а не предполагает их совпадение.

Сборка нативной части #

cd rust && cargo build --release

Обычная cdylib/staticlib с C ABI, без сборочного скрипта. Привязка находит её по RK_SYSLOG_LIB или рядом с исполняемым файлом.

Пакет — FFI-плагин Flutter: flutter build сам вызывает cargo и кладёт библиотеку в приложение на Windows, Linux и Android. Механизм, три разных пути к cargo и порядок проверки на Mac — в doc/native-build.md. Каталога hook/ здесь нет и не будет: само его присутствие ломает dart run, dart test и flutter build.

panic = "abort" ставить нельзя: граница ловит паники, чтобы отказ дошёл значением, и прерывание процесса делает это обещание ложью.

Куда библиотека доезжает:

Цель Состояние Чем доказано
Windows доезжает rk_syslog.dll рядом с runner собранного приложения
Linux доезжает librk_syslog.so в bundle/lib/ приложения
Android доезжает найдена внутри распакованного APK на armeabi-v7a, arm64-v8a, x86_64
macOS, iOS написано, ни разу не собрано Mac на проекте нет; файлы существуют, чтобы пакет не оказался неверным в день, когда Mac появится, и ни одно утверждение про них не проверено

Лицензия #

MIT, автор Rob Kim. См. LICENSE.

0
likes
0
points
219
downloads

Publisher

verified publisherspherex.kz

Weekly Downloads

A syslog sink for Dart — RFC 5424 framing, RFC 5425 delivery over TLS, and a bounded on-disk spool so records survive a crash and never block the caller.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

ffi

More

Packages that depend on rk_syslog

Packages that implement rk_syslog