Логирование LLM-запросов без утечки персональных данных
Логи нужны для отладки и учёта расходов, но в запросах к модели часто есть личные данные. Показываем, что логировать, как маскировать и как хранить.
4 мин · · разработка · ошибки
Логирование запросов к LLM нужно для отладки, учёта расходов и мониторинга. Но в запросах к модели часто оказываются личные данные: имена, email, телефоны, номера карт. Лог — плохое место для них: файлы копируются, уходят в системы мониторинга и читаются людьми, у которых нет доступа к исходным данным. Разберём, как логировать запросы к LLM без утечки данных: что записывать, как маскировать и как хранить.
Что логировать, а что нет
Основа безопасного лога — метаданные, а не содержимое.
| Логировать | Не логировать |
|---|---|
| ID модели | полный текст промпта |
| входные и выходные токены | полный ответ модели |
| стоимость запроса | email, телефоны, номера карт и документов в открытом виде |
| время ответа | имена и адреса |
| ID ответа | API-ключи и токены авторизации |
| HTTP-статус и тип ошибки | заголовки запроса целиком |
| версия промпта, название функции в коде | всё, что позволяет узнать человека |
Если для отладки нужен текст, логируйте его в замаскированном виде и с ограничением длины.
Маскирование личных данных на Python
Регулярные выражения не найдут всё, но закрывают самые частые шаблоны. Порядок важен: сначала ключи и email, затем номера карт, потом телефоны — иначе шаблон телефона может «съесть» часть номера карты.
import re
PATTERNS = [
("API_KEY", re.compile(r"\b(?:fetch|sk)[-_][A-Za-z0-9_\-]{8,}")),
("EMAIL", re.compile(r"[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}")),
("CARD", re.compile(r"(?<!\d)(?:\d{4}[ \-]?){3}\d{4}(?!\d)")),
("PHONE", re.compile(r"(?<!\w)(?:\+7|8)[ \-]?\(?\d{3}\)?[ \-]?\d{3}[ \-]?\d{2}[ \-]?\d{2}(?!\d)")),
("PASSPORT", re.compile(r"(?<!\d)\d{2} ?\d{2} \d{6}(?!\d)")),
]
def mask_personal_data(text: str) -> str:
for label, pattern in PATTERNS:
text = pattern.sub(f"[{label}]", text)
return textПроверка:
print(mask_personal_data("Мой email ivan.petrov@example.ru, телефон +7 (495) 123-45-67"))
print(mask_personal_data("Карта 2200 1234 5678 9012, паспорт 45 06 123456"))Результат:
Мой email [EMAIL], телефон [PHONE]
Карта [CARD], паспорт [PASSPORT]
Шаблон телефона рассчитан на российские номера (+7 или 8 и десять цифр). Если у вас есть другие форматы — добавьте их и обязательно проверьте на реальных примерах из своих данных. Имена и адреса регулярными выражениями надёжно не находятся; если они критичны, не логируйте текст вообще.
Пример: логирование запроса к модели
Логируем одну JSON-строку на запрос. Стоимость считаем по ценам Zukko: стоимость = (входные × цена_входа + выходные × цена_выхода) / 1 000 000.
import json
import logging
import time
from datetime import datetime, timezone
import anthropic
logger = logging.getLogger("llm")
logging.basicConfig(level=logging.INFO, format="%(message)s")
client = anthropic.Anthropic(
base_url="https://api.zukko.pro",
api_key="fetch_ваш_ключ",
)Что здесь сделано:
- в лог попадают только метаданные и короткий замаскированный фрагмент запроса (
preview); если он не нужен — уберите поле; - в лог ошибок пишется тип исключения и статус, а не
str(e): текст ошибки иногда содержит фрагмент запроса; - цены — в одном словаре; набор моделей меняется, так что сверяйте его с
GET https://api.zukko.pro/v1/modelsи страницей «Модели» в кабинете.
Не включайте отладочное логирование HTTP-клиента в продакшене: в таком режиме в лог могут попадать тела запросов и ответов.
Где и как хранить логи
Разработка. Локальный файл, маскирование включено с первого дня — со временем dev-логи прочитает кто-то ещё.
Staging. Центральный сборщик логов, маскирование обязательно, доступ — только у разработчиков проекта.
Продакшен.
- Логи только в централизованной системе с разграничением доступа.
- Срок хранения задан явно: храните не дольше, чем нужно для отладки и учёта.
- Маскирование — до отправки, а не на стороне хранилища.
- Шифрование при передаче и на диске.
Отдельные ключи для окружений
Удобно завести на каждое окружение свой ключ Zukko. У каждого ключа есть имя, дневной и месячный лимит в $ и список разрешённых моделей, поэтому ошибка в dev не потратит бюджет продакшена.
import os
ENV = os.getenv("APP_ENV", "dev")
API_KEY = os.environ[f"ZUKKO_KEY_{ENV.upper()}"]
MASK_LOGS = TrueКлючи создаются в разделе API-ключи; полный ключ показывается один раз. Подробнее о лимитах — в документации.
Учёт расходов по логам
Из JSON-строк легко собрать расходы по моделям и версиям промпта:
import json
from collections import defaultdict
def costs_by_model(log_file: str) -> dict:
totals = defaultdict(float)
with open(log_file, encoding="utf-8") as f:
for line in f:
try:
entry = json.loads(line)
except json.JSONDecodeError:
continue
if entry.get("status") == 200:
totals[entry["model"]] += entry.get("cost_usd", 0.0)
return dict(totals)Сверяйте результат с разделом «Использование» в кабинете: там видны фактические списания, а баланс и историю расходов можно получить и через API (см. Usage).
Про 152-ФЗ
Это не юридическая консультация, а общие принципы, о которых стоит помнить:
- Запись в лог — тоже обработка персональных данных, поэтому на неё распространяются те же правила, что и на основную базу.
- Минимизация: не собирайте в логи то, без чего можно обойтись.
- Ограничение доступа и срока хранения.
- Отправка текста с персональными данными во внешний API — тоже обработка. Если задача позволяет, маскируйте данные ещё до отправки в модель.
Конкретные требования к вашему проекту уточните у юриста.
Частые вопросы
Нужно ли логировать каждый запрос?
Метаданные (токены, стоимость, статус) — да: они короткие и нужны для учёта. Текстовые фрагменты можно писать выборочно, например для 1% запросов или только при ошибках.
Что если маскирование пропустит данные?
Такое возможно — регулярные выражения не идеальны. Поэтому основная защита — не писать текст в лог вовсе, а маскирование — запасная мера. Регулярно проверяйте выборку логов на пропуски.
Что делать, если в лог уже попали чувствительные данные?
Найдите и удалите или перезапишите затронутые записи во всех копиях, включая бэкапы и сборщик логов. Если в лог попал API-ключ, создайте новый ключ в кабинете, переключите приложение на него, а старый удалите.
Можно ли хранить логи в облачном сервисе?
Да, если есть шифрование при передаче и хранении, разграничение доступа и понятный срок хранения. Требования к размещению данных уточните у юриста.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Как обновить Claude Code, откатить версию или полностью удалитьКоманды для обновления, отката и удаления Claude Code по способу установки, и как не потерять настройки подключения к шлюзу при переустановке.
- Ключи OpenAI API в открытом доступе: почему ими нельзя пользоватьсяОткуда берутся «бесплатные» ключи на GitHub, почему пользоваться ими опасно и для вас, и для владельца, и как защитить собственный ключ от такой же утечки.
- Ошибки GLM API: почему не работает запрос и как это исправитьНеверный id модели, переполненный контекст 128K, ключ, баланс, лимиты: типичные причины ошибок GLM API и быстрый способ найти свою.
- This organization has been disabled в Claude API: что это и как продолжитьКлюч Anthropic внезапно отвечает «organization has been disabled». Объясняем, что это за ошибка, что проверить и как перенести SDK и Claude Code на другой провайдер.