Перейти к содержимому
27 дек. 2024 г.·7 мин чтения

Какие логи хранить для аудита без лишнего шума и затрат

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

Какие логи хранить для аудита без лишнего шума и затрат

Почему аудит тонет в логах

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

Полезное событие для аудита выглядит скучно, и это нормально. В нем есть время, субъект действия, само действие, объект, результат, request_id и немного контекста, чтобы восстановить цепочку. Шум выглядит богаче, но пользы от него мало: полный payload, все headers, сырой ответ модели, служебные флаги SDK, промежуточные тайминги, куски промпта и поля, которые никто не открывает при разборе.

Разница видна сразу. Для аудита достаточно записи вроде пользователь X отправил запрос к модели Y и получил отказ по policy Z. Шум - это весь запрос целиком, все токены и системные поля клиента. Для аудита полезно увидеть, что оператор изменил маршрут модели для tenant A. Шумом становятся десятки внутренних параметров прокси, которые нужны только разработчику.

Лишние поля бьют не только по объему. Они замедляют поиск, утяжеляют индекс и мешают фильтрам. Вместо одного спорного действия инженер листает экран с мусором. Если в каждом событии лежат полный prompt, тело ответа и случайные технические атрибуты, даже поиск по request_id или пользователю перестает быть быстрым.

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

В LLM-сервисах это видно особенно хорошо. Если писать в аудит все промпты и ответы без разбора, система быстро превращается в склад чувствительных данных. Для российских команд это уже не вопрос удобства, а вопрос 152-ФЗ, маскирования PII и понятного журнала аудита.

Рабочая схема обычно простая: держать три потока отдельно. Аудит отвечает за действия и ответственность. Отладочные логи помогают чинить сбои и живут меньше. Метрики показывают объем, задержку и ошибки в агрегированном виде. Когда эти слои не смешаны, аудит остается коротким, поиск идет быстрее, а хранение не раздувается без пользы.

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

С каких вопросов начинать разбор инцидента

Удобнее идти не от списка полей, а от живого разбора. Возьмите любой спорный случай и проверьте: сможете ли вы за 10 минут восстановить цепочку событий без догадок. Если нет, дело обычно не в объеме логов, а в том, что в них нет ответа на несколько базовых вопросов.

Первый вопрос - кто запустил действие. Это может быть пользователь, сервисный аккаунт, администратор, джоб или внешний интегратор. Одного user_id часто мало. Нужно понимать, кто именно сделал запрос, от чьего имени он шел и было ли делегирование прав.

Второй вопрос - что именно изменилось. Не вызван метод update, а конкретный факт: сменили модель, подняли лимит, отключили маскирование PII, удалили запись, пересчитали ответ. Если лог хранит только имя операции, команда начнет читать код вместо журнала аудита.

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

Четвертый вопрос - где это произошло. В каком сервисе, окружении, регионе, проекте, tenant, с каким request_id или trace_id. Для LLM-систем это особенно важно. Один запрос может пройти через шлюз, маршрутизацию модели, биллинг и внутренние проверки. Если вы не видите маршрут, вы не поймете, где появился сбой.

Пятый вопрос - чем закончилось действие. Успех, отказ, частичный успех, retry, ручная отмена, блокировка политикой. Итог должен быть явным. Лог, в котором есть только факт вызова без результата, показывает движение, но не показывает последствия.

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

Какие поля оставлять в каждом событии

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

Начните со времени. Нужен точный timestamp с часовым поясом, а не просто 10:14:03. Когда инцидент тянется через несколько сервисов, разница в часовом поясе или округление до секунд ломают картину. Для аудита обычно хватает миллисекунд.

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

Почти всегда нужны request_id и trace_id. Первый удобен для поиска одного запроса. Второй помогает собрать всю цепочку, если запрос прошел через API-шлюз, приложение и воркер. В LLM-сценариях это особенно заметно: один пользовательский запрос может задеть gateway, маршрутизацию модели и биллинг. Без этих двух полей вы увидите несколько наборов логов, но не поймете, что они относятся к одному случаю.

Еще несколько полей стоит писать почти всегда: event_name, итоговый status, имя сервиса, версия сервиса и окружение. Этого уже хватает, чтобы сузить круг поиска. Если ошибка появилась только в prod после версии 1.24.3, вы сразу понимаете, куда смотреть.

Полный текст исключения не нужно тащить в каждое событие. Намного полезнее короткий код ошибки, например AUTH_401, MODEL_TIMEOUT или PII_MASK_FAIL. Код легко агрегировать, по нему видны частота и всплески. Длинный дамп лучше хранить отдельно и писать только там, где он реально нужен.

Хорошая схема логирования скучная. Зато по ней можно быстро ответить, кто отправил запрос, через какой сервис он прошел, какая версия его обработала и почему система вернула именно такой статус.

Что писать только при ошибке или спорном действии

Обычное событие не должно тащить за собой полтела запроса и полный ответ модели. Для аудита это почти всегда шум. Расширенную запись стоит включать только там, где есть отказ, жалоба клиента, ручная эскалация или действие, которое меняет деньги, доступ или юридически значимый статус.

Когда спор уже начался, команде нужны не все данные подряд, а несколько точных следов. Сохраните входные параметры, которые помогают повторить ситуацию, но вырежьте секреты и сырые персональные данные. Токены, cookie, поля Authorization, номера документов, телефоны и полные тексты с PII лучше маскировать сразу, а не надеяться на ручную чистку потом.

Отдельно запишите причину отказа в понятном виде. Не ошибка политики, а что именно сработало, кто вернул решение и на каком этапе. Для LLM-сервиса это может быть модерация, внутренний policy check, лимит по тарифу, блокировка по 152-ФЗ или отказ провайдера модели. Когда в журнале аудита есть код правила и короткое объяснение, поиск обычно занимает минуты.

Нужен и идентификатор объекта, которого коснулся запрос: conversation_id, order_id, document_id, внутренний ID пользователя или другой стабильный идентификатор. Без него вы увидите сам сбой, но не поймете, что именно сломалось и кого это затронуло.

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

Полный stack trace и сырой payload лучше держать под флагом. Пусть приложение пишет их только в debug-режиме, при явной эскалации или для небольшой доли запросов по sampling. Такой подход особенно удобен в OpenAI-совместимых шлюзах: основной аудит остается компактным, а тяжелые детали включаются только там, где без них не разобрать маршрут запроса, ответ провайдера или сбой на своей стороне.

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

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

Как собрать схему логирования по шагам

Смените только base_url
Переведите вызовы на RU LLM и сохраните SDK, код и промпты без правок.

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

Сначала выпишите 5-7 частых инцидентов. Не абстрактные ошибки, а живые случаи: чужой доступ к данным, спорное удаление, внезапный рост расходов, таймаут у провайдера, странный ответ модели, который ушел клиенту. Такой список быстро отрезает лишнее.

Потом для каждого инцидента запишите, что нужно понять в первые 10 минут: кто отправил запрос, когда это случилось, через какой сервис он прошел, что вернуло правило доступа, какой провайдер или модель ответили, чем закончился запрос. На этом шаге быстро проявляются обязательные поля: request_id, actor_id, tenant_id, timestamp, event_name, result, status_code, policy_decision, model, provider.

Дальше сведите события к одному словарю имен. Если в одном сервисе событие называется request_done, в другом response_complete, а в третьем просто ok, поиск снова становится ручной работой. Общая схема с одинаковыми обязательными полями экономит много времени, особенно когда журнал аудита собирается из нескольких систем.

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

И только потом задавайте сроки хранения. Не одной цифрой на все, а по типу события. Решения по доступу, смена настроек, действия администратора и финансовые операции хранят дольше. Рутинные запросы и служебные метаданные - меньше. Отдельно проверьте маскирование PII: персональные данные почти никогда не помогают искать причину так сильно, как request_id и понятный контекст запроса.

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

Пример: спорный ответ в LLM-сервисе

Жалоба обычно звучит расплывчато: модель ответила странно, слишком уверенно или нарушила внутреннее правило. Если в логах есть только текст ответа и статус 200, разбор быстро вязнет. Команда читает десятки похожих запросов и гадает, где именно началась проблема.

Представим простой случай. В 14:07 клиент банка получил ответ, который противоречит внутренней policy. У команды есть request_id из обращения, поэтому поиск начинается не с полного текста, а с одного события и соседних записей по тому же trace. За пару минут находят время, модель, провайдера и итоговый статус запроса.

Дальше важен не сырой промпт, а его версия после маскирования PII. Это помогает понять, что именно увидела модель, не вытаскивая в расследование персональные данные. Если в логах сохранились masked_prompt, response_status, policy_version и AI-Law метки, уже видно, где проблема: в пользовательском вводе, в маршрутизации или в измененной policy.

Следующий шаг - посмотреть маршрут. Поля вроде model, provider, router_decision, retry_count и fallback_target быстро показывают, не ушел ли запрос в другую модель после таймаута или отказа основного провайдера. Без них ситуация выглядит так, будто модель внезапно начала отвечать хуже, хотя реальная причина банальнее: сработал retry, и ответ пришел от другого провайдера.

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

Если запросы идут через RU LLM, логично опираться на встроенные аудит-трейлы, маскирование PII и AI-Law метки, а сверху добавлять только свои бизнес-поля. Так журнал не разрастается без пользы, а маршрут запроса и решение по политике остаются видны.

Ошибки, из-за которых поиск тянется часами

Сведите модели к одному API
Маршрутизируйте запросы к 500+ моделям без отдельной обвязки под каждого провайдера.

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

Одна из самых частых ошибок - писать полный payload в каждый запрос. Для LLM-сервиса это особенно дорого: в лог летят промпт, история диалога, служебные параметры и куски ответа. При разборе почти всегда нужны request_id, actor_id, модель, время, статус, код ошибки и пара полей маршрутизации. Остальное стоит писать выборочно: при сбое, спорной операции или по отдельному trace.

Та же история с IP и user agent. Если событие связано с внутренним сервисом, batch-задачей или серверным вызовом через единый шлюз, эти поля часто не отвечают ни на один вопрос и только утяжеляют индекс. Хуже, когда одна команда хранит client_ip, другая ip_address, а третья remoteAddr. Поиск сразу превращается в ручную археологию.

Еще одна частая ошибка - смешивать аудит с debug-логом приложения. Аудит отвечает на вопрос, кто, что, когда и с каким результатом сделал. Debug нужен разработчику, чтобы понять поведение кода. Если держать это в одном потоке, рядом окажутся изменение прав доступа, SQL-трассировка, retry, stack trace и случайный вывод библиотеки. Даже хорошие фильтры тут помогают слабо.

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

Отдельная дыра - ручные правки. Администратор поменял промпт, лимит, правило маршрутизации или тег AI-Law через панель, а запись в журнал не попала. Через час сервис отвечает иначе, но в логах тишина. Команда ищет баг в модели, хотя причина была в одном ручном действии.

Нормальная схема тут довольно приземленная: одинаковые имена полей во всех сервисах, отдельный поток для аудита и отдельный для debug, полный payload только по явному флагу или при ошибке, PII маскируется до записи, ручные действия пишутся так же строго, как API-вызовы. Если этого нет, поиск инцидента почти всегда длится дольше самого инцидента.

Быстрая проверка перед запуском

Сравнивайте провайдеров на одном API
Пробуйте разные модели через один API и не собирайте новую интеграцию под каждого.

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

Хороший тест выглядит так. В каждом событии есть actor_id или service_id, тип действия, точное время, источник запроса, объект действия и итоговый статус. Одной записи должно хватать, чтобы понять суть без догадок.

Дальше проверьте цепочку. Один request_id должен проходить через весь путь запроса: gateway, приложение, вызов модели, проверку правил и ответ клиенту. Цепочка должна собираться одним поиском, а не ручной склейкой по времени.

Потом посмотрите на чувствительные данные. Логи не должны писать пароли, токены, API-ключи, cookie и сырые персональные данные. Email, телефон и другие чувствительные поля лучше маскировать или хешировать до записи.

Отдельно проверьте ошибки. Полезна не только строка с текстом, но и код, короткая причина и имя правила, которое сработало. Иначе через месяц у вас останется только расплывчатое something went wrong.

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

Для систем с LLM это особенно заметно. В журнале аудита полезно видеть маршрут, модель, провайдера, policy_id и результат проверки, но не полный сырой ввод пользователя без явной причины. Для команд, которые работают в российском контексте и учитывают 152-ФЗ, это обычная гигиена, а не формальность.

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

Раз в квартал полезно чистить мертвые поля. Они почти всегда остаются после экспериментов, временных флагов и срочных правок. Если поле никто не использовал ни в поиске, ни в расследовании, ни в отчетах, его лучше удалить. Иначе хранение растет, поиск тормозит, а пользы нет.

Что сделать дальше

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

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

На практике дальше хватает короткой проверки: PII маскируется до записи в лог, журнал собирает всю цепочку действий, сроки хранения привязаны к пользе события, обязательные поля зафиксированы отдельно от полей для спорных случаев, а поиск по request_id, user_id и trace_id работает без ручной склейки.

Последний шаг скучный, но очень полезный. Соберите одну внутреннюю памятку на 1-2 страницы: название каждого поля, формат, когда оно обязательно, кто отвечает за запись, что маскируется и сколько хранится. Через месяц это сэкономит часы. Команда не будет гадать, почему в одном месте user_id строка, а в другом число.

Если после этих изменений хотя бы один недавний инцидент разбирается быстрее, схема уже себя оправдала.

Часто задаваемые вопросы

Чем аудит отличается от debug-логов и метрик?

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

Какие поля нужны почти в каждом событии аудита?

Обычно хватает timestamp, actor_id или service_account, tenant_id, event_name, request_id, trace_id, имени сервиса, версии сервиса и итогового status. Если есть отказ, добавьте понятный error_code или policy_decision. Такой набор уже позволяет собрать цепочку без чтения сырого payload.

Нужно ли хранить полный prompt и ответ модели?

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

Зачем нужны и request_id, и trace_id?

request_id помогает найти один конкретный запрос. trace_id связывает все шаги одного пути через gateway, приложение, воркер, биллинг и проверку policy. В LLM-сервисах без этих двух полей вы увидите отдельные куски истории, но не поймете, что они относятся к одному случаю.

Как хранить персональные данные в аудит-логах?

PII лучше маскировать или хешировать до записи в лог. Не пишите в аудит пароли, токены, API-ключи, cookie, номера документов, телефоны и сырой текст с персональными данными без явной причины. Для разбора инцидента почти всегда полезнее request_id, actor_id и код решения, чем полный набор личных данных.

Когда стоит включать расширенные логи?

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

Нужно ли отдельно логировать ручные действия администратора?

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

Как выбрать срок хранения для разных логов?

Не ставьте один срок на все события. Изменения прав, решения по доступу, финансовые операции и действия администратора обычно держат дольше. Рутинные технические записи и debug-шум можно удалять раньше, если они не помогают в расследовании.

Как быстро проверить, что схема логирования работает?

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

Какие поля обычно раздувают аудит без пользы?

Чаще всего шумят полный payload в каждом событии, сырые headers, служебные поля SDK, длинные stack trace без нужды, дублирующие имена полей и случайные атрибуты вроде client_ip там, где они ничего не объясняют. Поле стоит оставлять только тогда, когда оно помогает ответить на вопрос по инциденту.