Управление логированием без перезапуска

Управление логированием без перезапуска особенно важно для production-сервисов. Подробные debug-логи очень нужны именно тогда, когда что-то пошло не так, но держать их включенными постоянно обычно нельзя. Они создают шум, увеличивают объем файлов, могут замедлять горячие участки кода и иногда раскрывают детали, которые не должны постоянно попадать в лог.

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

Поэтому в библиотеке логирования logme есть отдельная идея: логированием можно управлять во время работы приложения.

Runtime control

В logme управление логированием вынесено в control API. Это не отдельная “вторая конфигурация”, а командный интерфейс поверх уже существующих каналов, уровней, backend-ов, trace points и других объектов библиотеки.

Например, можно включить подробный уровень для конкретного канала:

level --channel raw debug

Можно включить trace points:

trace enable Raw:*:*

Можно временно добавить backend, поменять flags, включить или выключить канал. То есть control API работает не с абстрактным “глобальным уровнем логирования”, а с реальной моделью logme: channels, subsystems, backends, trace points.

Это важно. В большом приложении редко бывает полезно включить вообще все debug-логи. Обычно нужно включить диагностику только для конкретной части системы: сетевого протокола, SSL, storage, конкретного subsystem-а или конкретного trace point-а. Именно такая гранулярность и нужна в production.

Control API может использоваться программно, через control server, через logmectl или через logmeweb. В интерактивном сценарии это выглядит так: приложение уже работает, мы подключаемся к нему control-клиентом и меняем поведение логирования без перезапуска процесса.

Почему это лучше обычного reload config

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

Control-команда, наоборот, хорошо подходит именно для временного действия:

level --channel raw debug

Это легко применить и легко отменить:

level --channel raw info

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

Startup control через environment

Есть еще один частый сценарий. Иногда нужно изменить логирование не в уже запущенном процессе, а прямо на старте. Например, в тесте, в CI, в Docker-контейнере, в systemd unit или в Windows service wrapper.

Для этого в logme добавлена поддержка environment control. Приложение может явно разрешить чтение control-команд из переменных окружения:

Logme::EnvironmentControlOptions options; 
options.Policy = Logme::ControlPolicy::Safe(); 
options.ErrorMode = Logme::ENV_CONTROL_CONTINUE_ON_ERROR; 
Logme::Instance->ApplyEnvironmentControl(options);

После этого можно запустить приложение так:

LOGME_CONTROL="level --channel raw debug; trace enable Raw:*:*" ./service

Одна переменная может содержать несколько команд, разделенных ;. Это удобно, потому что часто нужно не одно действие, а небольшой сценарий: включить канал, поменять уровень, включить trace point.

LOGME_CONTROL="channel --enable raw; level --channel raw debug; trace enable Raw:*:*"

Также можно использовать несколько переменных:

LOGME_CONTROL_1="channel --enable raw"
LOGME_CONTROL_2="level --channel raw debug; trace enable Raw:*:*"

Важно, что logme не читает environment автоматически. Если приложение не вызвало ApplyEnvironmentControl(), переменные LOGME_CONTROL и LOGME_CONTROL_N не имеют никакого эффекта.

Это принципиальный момент безопасности. Environment — внешний источник данных. В некоторых сценариях его может контролировать не тот человек, который должен управлять диагностикой приложения. Поэтому environment control в logme является opt-in механизмом. Приложение само решает, доверяет ли оно окружению в данном запуске.

Зачем нужны control policies

Если дать control API полный доступ, через него можно сделать очень многое. Например, можно добавить file backend, изменить routing, прочитать логи, вызвать extension-команду. Для локального доверенного API это нормально, но для environment или сетевого control server-а иногда слишком широко.

Поэтому в logme есть control policies. Политика определяет, какие control-команды разрешены в конкретном сценарии.

Например, безопасная политика подходит для обычных startup overrides:

options.Policy = Logme::ControlPolicy::Safe();

В таком режиме можно менять уровни, flags, включать trace points, управлять состоянием каналов и subsystem-ов. Но опасные действия вроде добавления произвольного file backend-а будут запрещены.

Это позволяет использовать environment control в production более спокойно. Можно разрешить инженеру включить debug для нужного канала:

LOGME_CONTROL="level --channel raw debug"

но не позволить через environment перенаправить логи в произвольный файл или выполнить расширенную команду приложения.

Для более сильной диагностики можно использовать diagnostic policy. Она может разрешать временные memory/backend-сценарии, например RingBufferBackend, но по-прежнему запрещать действия, которые считаются опасными для конкретного deployment-а.

Если же control-команды приходят из полностью доверенного источника, можно использовать full policy. Это поведение совместимо со старым control API.

Политики применимы не только к environment

Environment control — только один из источников команд. Та же идея применима и к control server-у.

Например, приложение может запустить control server с ограниченной политикой:

Logme::ControlConfig config;

config.Enable = true;
config.Port = 3131;
config.Interface = Logme::CONTROL_INTERFACE_LOOPBACK;

Logme::Instance->StartControlServer(
  config
  , Logme::ControlPolicy::Safe()
);

Такой control server по-прежнему позволяет управлять логированием во время работы процесса, но не обязательно дает полный доступ ко всем control-командам. Это полезно для долгоживущих сервисов, где control endpoint должен существовать, но его возможности должны быть ограничены.

Позже policy можно изменить программно:

Logme::Instance->SetControlServerPolicy( 
  Logme::ControlPolicy::Diagnostic() 
);

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

Что происходит при ошибках

Environment control выполняется на старте, и там важно решить, что делать, если одна из команд ошибочна. Например, команда может ссылаться на несуществующий канал или быть запрещена policy.

Для этого у EnvironmentControlOptions есть режим обработки ошибок.

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

В более строгом режиме обработка останавливается на первой ошибке. Это полезно для тестов и CI, где неправильная diagnostic-конфигурация должна быть сразу заметна.

Все ошибки environment control логируются обычным внутренним логированием logme через CHINT. Это важно: проблемы с применением команд не исчезают молча. Если команда была запрещена policy или не смогла выполниться, это должно быть видно в логах.

Почему это не просто переменная LOG_LEVEL

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

LOG_LEVEL=debug

В маленьком приложении этого достаточно. В большом сервисе — почти никогда.

Проблема в том, что “debug везде” слишком груб. Нужна диагностика конкретной части системы. Сегодня это raw, завтра http2, потом конкретный subsystem, потом trace point вокруг подозрительного участка кода.

Поэтому logme не вводит отдельный язык environment-настроек. Environment control использует те же команды, что и основной control API:

LOGME_CONTROL="level --channel raw debug; trace enable Raw:*:*"

Это делает механизм намного мощнее, но при этом не создает вторую параллельную систему конфигурации. Все управление проходит через один control layer и может быть ограничено одной policy-моделью.

Практический смысл

Управление логированием без перезапуска хорошо работает в нескольких типичных ситуациях.

В production можно держать обычный спокойный уровень логирования, но иметь возможность быстро включить подробную диагностику для конкретного канала без перезапуска. В тестах можно запускать один и тот же binary с разными diagnostic-сценариями, не меняя конфиг-файлы. В контейнерной среде можно передавать временные logging overrides через environment. В сервисах можно держать control server включенным, но ограниченным safe или diagnostic policy.

Главная идея в том, что логирование становится управляемым. Не просто “пишем файл” и не просто “включили debug”. Приложение получает отдельный operational interface для диагностики, а разработчик может заранее решить, какие действия через этот interface разрешены.

Это особенно важно для долгоживущих C/C++ сервисов. В них стоимость перезапуска, стоимость постоянного debug-лога и стоимость потерянной диагностической информации часто гораздо выше, чем кажется на этапе разработки.

Control API, environment control и control policies в logme решают эту задачу вместе: дают гибкость, но не заставляют жертвовать безопасностью и предсказуемостью.

Добавить комментарий