Логирование LLM-запросов без утечки персональных данных

Логи нужны для отладки и учёта расходов, но в запросах к модели часто есть личные данные. Показываем, что логировать, как маскировать и как хранить.

4 мин · · разработка · ошибки

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

Что логировать, а что нет

Основа безопасного лога — метаданные, а не содержимое.

ЛогироватьНе логировать
ID моделиполный текст промпта
входные и выходные токеныполный ответ модели
стоимость запросаemail, телефоны, номера карт и документов в открытом виде
время ответаимена и адреса
ID ответаAPI-ключи и токены авторизации
HTTP-статус и тип ошибкизаголовки запроса целиком
версия промпта, название функции в кодевсё, что позволяет узнать человека

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

Маскирование личных данных на Python

Регулярные выражения не найдут всё, но закрывают самые частые шаблоны. Порядок важен: сначала ключи и email, затем номера карт, потом телефоны — иначе шаблон телефона может «съесть» часть номера карты.

bash
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

Проверка:

bash
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.

bash
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 не потратит бюджет продакшена.

bash
import os

ENV = os.getenv("APP_ENV", "dev")
API_KEY = os.environ[f"ZUKKO_KEY_{ENV.upper()}"]
MASK_LOGS = True

Ключи создаются в разделе API-ключи; полный ключ показывается один раз. Подробнее о лимитах — в документации.

Учёт расходов по логам

Из JSON-строк легко собрать расходы по моделям и версиям промпта:

bash
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-ФЗ

Это не юридическая консультация, а общие принципы, о которых стоит помнить:

  1. Запись в лог — тоже обработка персональных данных, поэтому на неё распространяются те же правила, что и на основную базу.
  2. Минимизация: не собирайте в логи то, без чего можно обойтись.
  3. Ограничение доступа и срока хранения.
  4. Отправка текста с персональными данными во внешний API — тоже обработка. Если задача позволяет, маскируйте данные ещё до отправки в модель.

Конкретные требования к вашему проекту уточните у юриста.

Частые вопросы

Нужно ли логировать каждый запрос?

Метаданные (токены, стоимость, статус) — да: они короткие и нужны для учёта. Текстовые фрагменты можно писать выборочно, например для 1% запросов или только при ошибках.

Что если маскирование пропустит данные?

Такое возможно — регулярные выражения не идеальны. Поэтому основная защита — не писать текст в лог вовсе, а маскирование — запасная мера. Регулярно проверяйте выборку логов на пропуски.

Что делать, если в лог уже попали чувствительные данные?

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

Можно ли хранить логи в облачном сервисе?

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

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

Логирование запросов к LLM: маскирование данных и 152-ФЗ · Zukko