Наблюдаемость и аудит
Логи и трассировка вызовов используются для анализа ошибок запросов, задержек и расхода моделей; журнал аудита фиксирует изменения ресурсов; панель очереди задач служит для проверки задержек в фоне, сбоев и повторов.
| Цель диагностики | Точка входа |
|---|---|
| Что искал некоторый вопрос-ответ, сколько раз вызывались модели, сколько потрачено token | После подключения Langfuse смотрите полную цепочку вызовов в Langfuse |
| Изменения баз знаний, участников или системных настроек | «Активность» в настройках базы знаний, а также «Настройки → Журнал аудита» |
| Накапливаются ли фоновые задачи разбора, аннотирования и Wiki или падают ли они | «Настройки → Очереди среды выполнения» |
| Жив ли сервис | GET /health |
| Связать логи сервиса по одному запросу | Ищите в логах по X-Request-ID из заголовка ответа |
При диагностике проблем с запросом сначала получите X-Request-ID из заголовка ответа, затем свяжите логи и записи трассировки.
Быстрая справка по эксплуатации
| Хочу узнать… | Куда идти |
|---|---|
| Что произошло на всём пути некоторого запроса | grep по логам приложения по X-Request-ID из заголовка ответа; после включения LLM_DEBUG_LOG смотрите llm_debug/<request_id>.log |
| Дерево вызовов LLM и расход token для одного чата/разбора | Langfuse UI (имя trace POST /api/v1/agent-chat или asynq.document:process) |
| Кто, когда и что изменил | Аудит пространства /tenants/:id/audit-log; активность KB /knowledge-bases/:id/activity; аудит платформы /system/admin/audit-log |
| Почему некоторый документ постоянно падает | Таблица task_dead_letters (scope=knowledge/knowledge_base) + last_error задач archived на панели среды выполнения |
| Жив ли сервис | GET /health (200 {"status":"ok"}) |
| Загрузилась ли конфигурация как ожидалось | Баннер [startup-env] в логе запуска (internal/runtime/startup.go, секретные значения показываются только длиной) |
Проверка здоровья
internal/router/router.go регистрирует пробу здоровья без аутентификации (белый список публичных путей в internal/middleware/auth.go включает /health):
// internal/router/router.go
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})Это чистая проба живости (liveness, не проверяет зависимости DB/Redis), подходит в качестве цели проверки здоровья контейнера / LB. langfuse.shouldTrace и выборка для журнала запросов также исключают её, чтобы избежать шума от проб. Uptime процесса предоставляется панели эксплуатации через MarkServerStarted/ServerUptime из internal/runtime/server.go.
Справочник по логам, трассировке и аудиту
Система логирования (internal/logger)
Формат и уровни
- В основе — приватный экземпляр logrus (
appLogger, чтобы внешние зависимости не перезаписывали глобальный logrus и не теряли логи), с пользовательскимCustomFormatter. - Формат по умолчанию в одну строку:
LEVEL[метка времени] [поле request_id...] caller | message, где caller —файл:строка[имя функции](addCaller). - Через переменную окружения
LOG_FORMATможно задать шаблон; заполнители:%d=время,%level=уровень,%thread=ID goroutine (берётся, только если на него ссылается шаблон, чтобы не запускатьruntime.Stackна каждой строке),%logger=caller,%traceId=request_id,%msg=сообщение+структурированные поля. Однопроходная заменаstrings.NewReplacerисключает повторную подстановку. - Уровень управляется
LOG_LEVEL(debug/info/warn/error/fatal; если не задан или недопустим, по умолчанию debug). - Цвета: ANSI-цвета включаются, когда stdout — терминал; при не-терминале (сбор в Docker) отключаются; при записи в файл
ansiStripWriterудаляет ANSI-последовательности, сохраняя чистый текст. - API структурированных полей:
logger.WithField(ctx, k, v)/WithFieldsсохраняют entry с полями в context (types.LoggerContextKey), а последующиеlogger.Infof(ctx, ...)подхватывают их автоматически;WarnWithFieldsпредназначен для событий, связанных с аудитом (кросс-тенантные пробы, нарушение инвариантов), что упрощает индексацию агрегаторами логов по tenant/ресурсу. CloneContextпри порождении фоновой goroutine копирует ключевые ключи context (tenant/user/request_id/роль/язык и т. д.) и одновременно сохраняет дескриптор*TraceLangfuse и активный OTel span, чтобы дочерний span не стал осиротевшей trace.
Вывод и ротация
ConfigureFromEnv() (выполняется при init, может вызываться повторно после загрузки .env в main): всегда пишет в stdout; при непустом LOG_PATH (или при работе упакованного macOS .app автоматически попадает в ~/Library/Logs/<App>/<App>.log) дополнительно пишет на диск через lumberjack:
// internal/logger/logger.go openLogFile()
return &lumberjack.Logger{
Filename: logPath,
MaxSize: 50, // megabytes
MaxBackups: 3,
MaxAge: 28, // days
Compress: true,
}, nilОтладочный журнал LLM (internal/logger/llm_logger.go)
При включении LLM_DEBUG_LOG=true|1|<каталог> каждый вызов модели (Chat / Chat Stream / Embedding / Rerank / VLM) записывает полные входные сообщения, вызовы инструментов, вывод и ошибки в каталог llm_debug/, причём все вызовы с одним request_id дописываются в один файл (<request_id>.log), что упрощает восстановление всех взаимодействий с моделью в одной сессии. Файлы старше 7 дней в каталоге очищаются в фоне при запуске (cleanupOldDebugFiles).
Промежуточное ПО журнала запросов (internal/middleware/logger.go)
RequestID(): читает или генерируетX-Request-ID, записывает его обратно в заголовок ответа и вместе с logger с полями помещает request_id в gin context и contexthttp.Request— так сквозные логи (включая прокинутые со стороны asynq worker метки сессии) связываются по request_id.Logger(): записывает method, path (query черезsanitizeQueryвычищает чувствительные к OAuth параметрыtoken/code/stateи т. п.), status_code, latency, client_ip, size, а также тело запроса/ответа размером до 10KB. Тело запроса/ответа обезличивается черезsensitiveFieldRegex(значения полей password/token/api_key/secret/private_key и т. п. заменяются на"***", поддерживаются snake_case/camelCase); тело ответа SSE записывается как[потоковый ответ SSE, пропущено]; пути/assets/и опроса wiki stats пропускаются полностью.- Доверенные прокси:
r.SetTrustedProxies(...)(WEKNORA_TRUSTED_PROXIES) предотвращает подделкуX-Forwarded-Forдля обхода ограничения частоты на основеClientIP.
Трассировка Langfuse (internal/tracing/langfuse)
Распределённая трассировка использует OpenTelemetry Go SDK, записывает атрибуты span по семантическим соглашениям Langfuse (langfuse.observation.*) и экспортирует через OTLP/HTTP на POST <host>/api/public/otel/v1/traces, совместимо с Langfuse v3+ и LiteFuse. Трассировка по умолчанию выключена; пока не включена, соответствующие точки входа не выполняют экспорт.
Конфигурация (переменные окружения, config.go)
| Переменная окружения | Значение по умолчанию | Описание |
|---|---|---|
LANGFUSE_ENABLED | автовключение при наличии публичного и секретного ключей | Общий переключатель (согласуется с конвенцией Python SDK) |
LANGFUSE_HOST | https://cloud.langfuse.com | Базовый адрес Langfuse/LiteFuse (можно развернуть самостоятельно) |
LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY | — | Учётные данные проекта Basic Auth |
LANGFUSE_RELEASE / LANGFUSE_ENVIRONMENT | — | Добавляются к каждой trace для фильтрации в UI |
LANGFUSE_FLUSH_AT | 15 | Размер пакета экспорта (BatchSpanProcessor MaxExportBatchSize) |
LANGFUSE_FLUSH_INTERVAL | 3s | Максимальный интервал пакетного экспорта (BatchTimeout) |
LANGFUSE_QUEUE_SIZE | 2048 | Верхняя граница буфера в памяти (защита от неограниченного роста при недоступности эндпоинта) |
LANGFUSE_REQUEST_TIMEOUT | 10s | Таймаут одного HTTP ingestion |
LANGFUSE_SAMPLE_RATE | 1.0 | Частота выборки ParentBased(TraceIDRatioBased), 0..1 |
LANGFUSE_DEBUG | false | Подробный лог ошибок пакетной отправки |
Экспортёр (exporter.go)
Экспортёр OTLP/HTTP, Authorization: Basic base64(public:secret); этот экспортёр использует собственный HTTP-клиент, не проходит через защиту SSRF и не подпадает под белый список даже при включённом SSRF_DNS_WHITELIST_ONLY; x-langfuse-ingestion-version: 4 — обязательный пороговый заголовок для пути прямой записи OTel в Langfuse v3/LiteFuse (при его отсутствии возвращается 400), а x-langfuse-sdk-name/version — маркеры совместимости. Manager (manager.go) держит отдельный TracerProvider (resource service.name=weknora) и не вызывает глобальных изменений OTel вроде otel.SetTextMapPropagator, чтобы не влиять на другие точки инструментирования OTel в процессе; W3C TraceContext propagator — приватное значение уровня пакета.
Модель наблюдений и точки инструментирования
Три вида дескрипторов (tracer.go): Trace (корень, один запрос), Span (логическая единица работы, не LLM), Generation (один вызов модели, включая статистику token TokenUsage и потоковый time-to-first-token MarkCompletionStart). Родительско-дочерние связи устанавливаются автоматически через OTel span context; при отсутствии trace автоматически открывается auto-trace, чтобы не было осиротевших span.
Основные точки инструментирования:
| Точка | Исходный код | Результат |
|---|---|---|
| Вход HTTP | middleware.go GinMiddleware | На пути белого списка shouldTrace (knowledge-chat / agent-chat / knowledge-search / различные ingestion POST/PUT / импорт FAQ / wiki auto-fix / evaluation / определение initialization и т. д.) открывает корневую Trace с именем METHOD /path, metadata содержит http.method/path/query/request_id, вывод — status и response.size; извлекает вышестоящий заголовок W3C traceparent и наследует trace id внешнего вызывающего |
| asynq worker | asynq.go AsynqMiddleware | Восстанавливает traceparent из payload и продолжает HTTP trace, иначе открывает новую trace asynq.<task_type>; оборачивает в SPAN, metadata содержит task_id/queue/retry/max_retry/payload_bytes; payload показывается только первые 1KB |
| Инъекция на стороне постановки в очередь | asynq.go InjectTracing + internal/types/tracing.go TracingContext | Встраивает traceparent и метки user/session в payload задачи как JSON-поля lf_* для передачи между процессами |
| Вызов модели | internal/models/{chat,embedding,rerank,vlm,asr}/langfuse_wrapper.go | Один Generation на вызов (имя модели, вход, параметры, выход, token usage, ошибка) |
| Сводка поиска/реранжирования | retrieval_obs.go | SummarizeRetrieveOutput / SummarizeSearchResults и т. п. сжимают результаты поиска в предпросмотр top-25 (rank/chunk_id/score/preview на 160 символов), чтобы в trace не попадал полный текст |
| Выполнение Agent | internal/agent/engine.go, act.go | SPAN'ы agent.execute и т. п., через logger.CloneContext сохраняются в одном дереве с корневой HTTP trace |
Отправляемое содержимое (атрибуты span, events.go): langfuse.observation.type/input/output/metadata/model.name/model.parameters/usage_details/completion_start_time, langfuse.trace.name/input/output/metadata/tags, user.id (явный user или tenant:<id>), session.id, langfuse.environment/release.
Вызовы поиска и диагностические записи
embedding.embed, retrieve и rerank фиксируют время вычисления вектора запроса, фактического этапа поиска и вызова модели переранжирования соответственно. Узлы с префиксом diagnostics. записывают только статистику уже полученных результатов и решения о фильтрации, дополнительных запросов к поиску или модели они не выполняют и не должны считаться отдельными вызовами.
| Диагностический узел | Значение |
|---|---|
diagnostics.retrieval.vector.result_pool.initial | Пул результатов, впервые возвращённый векторным поиском |
diagnostics.retrieval.keywords.result_pool.initial | Пул результатов, впервые возвращённый поиском по ключевым словам |
diagnostics.retrieval.<vector/keywords>.result_pool.refill | Пул результатов, возвращённый дополнительным поиском (появляется только при реально выполненном дополнительном поиске) |
diagnostics.retrieval.<vector/keywords>.filter_decision | Пул результатов до применения фильтров и решение о сохранении/отсеве |
diagnostics.retrieval.fusion.candidate_selection | Решение о обрезке по лимиту числа кандидатов после слияния |
diagnostics.rerank.selection | Решение о сохранении/отсеве после переранжирования |
Эти пулы результатов содержат текст, выдержки, описания изображений, OCR и векторы оригинальных изображений — все записи в пределах области запроса, а не только изображения. Общий подсчёт охватывает всех кандидатов, image_samples — только выборка попаданий по векторам оригинальных изображений; в metadata назначение помечается через observation_kind=diagnostic и sample_scope=image_vectors. Если дополнительного поиска не было, сэмплы result_pool.initial и filter_decision могут совпадать — это одни и те же данные, наблюдавшиеся на двух этапах.
Изменение имён затрагивает только новые трейсы; в исторических записях сохраняются прежние имена retrieval.image_* и rerank.selection. Дашборды и запросы, фильтрующиеся по именам узлов, следует соответствующим образом обновить.
Расход за раунд, назначение вызовов и кэш
Каждый вызов модели по-прежнему создаёт отдельный Generation; usage сообщения хранит агрегированный расход token за текущий раунд, событие завершения Agent возвращает его через turn_usage, и при повторном открытии исторических сообщений его всё ещё можно прочитать. prompt_tokens, completion_tokens, total_tokens — это итоговые значения; cached_tokens — совместимый псевдоним cache_read_tokens, а cache_read/write/miss — детализация входных token, которую нельзя повторно прибавлять к prompt_tokens.
cache_reported и cache_status различают hit, miss, unreported, unsupported; отсутствие отчёта нельзя считать промахом кэша. Metadata Generation фиксирует call_purpose и отпечаток префикса промпта, чтобы различать назначения (ответ, перезапись, аннотирование и т. п.) и объяснять изменения кэша.
При сборке промпта стабильные инструкции размещаются в начале, а динамическое содержимое сессии — в конце; поддерживаемые провайдеры используют cache marker. Попадание по-прежнему зависит от протокола провайдера, модели и префикса запроса, и наличие кэша для каждого запроса не гарантируется. При диагностике сначала сравните назначение вызова, отпечаток префикса и модель, а затем смотрите состояние отчётности.
Вызовы песочницы тоже имеют span Langfuse, что позволяет связывать время операций с экземпляром и выполнения инструментов с текущей trace и различать ожидание песочницы и ожидание модели. Фоновое сопровождение песочницы (например, сборка сессий) записывает дочерние span только при наличии родительской trace и не создаёт trace самостоятельно; задачи установки, удаления и сопровождения навыков открывают trace сами. Пока переключатель трассировки не включён, экспорт в Langfuse не выполняется.
Журнал аудита
Модель данных (internal/types/audit_log.go)
Таблица audit_logs работает append-only (без UpdatedAt, без мягкого удаления); монотонный id служит одновременно первичным ключом и курсором:
| Поле | Тип | Описание |
|---|---|---|
id | uint64 автоинкремент | Первичный ключ + курсор постраничной навигации (WHERE id < after_id ORDER BY id DESC) |
tenant_id | uint64 | Пространство; 0 = событие системного уровня (system-scope) |
actor_user_id / actor_role | varchar | Автор операции и его тогдашняя роль (пусто при системном срабатывании) |
action | varchar(64) | Именование через точку <area>.<event> (см. 4.2) |
scope_type / scope_id | varchar | Область ресурса (например, knowledge_base + kbID, управляет страницей активности KB) |
target_type / target_id / target_user_id | varchar | Конкретный целевой ресурс / пользователь |
request_path / request_method | varchar | Шаблон маршрута (не исходный URL, чтобы курсор не разрастался; исходный URL хранится в Details.raw_path) |
outcome | varchar(16) | success / accepted (асинхронно принято, но не в конечном состоянии) / denied / failed / partial / canceled |
details | jsonb | Нагрузка, зависящая от действия; значения секретов никогда не попадают в БД (например, для vector_store записываются только имена изменённых полей). Активность базы знаний, инициированная API Key, дополнительно записывает api_key_id / api_key_name (снимок имени, без открытого Key) |
created_at | timestamp | Основа для очистки по политике хранения |
Список действий аудита
| Группа | Действия |
|---|---|
| RBAC / участники | rbac.member_added, rbac.member_removed, rbac.member_role_changed, rbac.member_left, rbac.access_denied, rbac.invitation_sent, rbac.invitation_accepted, rbac.invitation_declined, rbac.invitation_revoked, rbac.invitation_expired |
| Векторное хранилище | vector_store.created, vector_store.updated, vector_store.deleted |
| Производные ресурсы OpenSearch | opensearch.index_created, opensearch.index_deleted, opensearch.reindex_executed |
| Системное управление (tenant_id=0) | system.setting_changed, system.admin_promoted, system.admin_revoked, system.user_created, system.user_password_reset, system.api_key_created, system.api_key_revoked |
| Операции с очередями среды выполнения (tenant_id=0) | system.queue_task_retried, system.queue_task_deleted, system.queue_task_run_now, system.queue_task_cancelled, system.queue_archived_purged |
| Базы знаний | kb.created, kb.updated, kb.deleted, kb.duplicated, kb.clone_started, kb.clone_completed, kb.clone_failed, kb.share_added, kb.share_permission_changed, kb.share_removed |
| Знания | knowledge.created, knowledge.updated, knowledge.deleted, knowledge.batch_deleted, knowledge.reparse_started, knowledge.parse_canceled, knowledge.move_started, knowledge.move_completed, knowledge.move_failed |
| Теги / источники данных | tag.created, tag.updated, tag.deleted, datasource.created, datasource.updated, datasource.deleted, datasource.sync_started, datasource.sync_completed, datasource.sync_failed, datasource.paused, datasource.resumed |
| Wiki / FAQ | wiki.content_changed, faq.import_started, faq.import_completed, faq.import_failed |
Путь записи (service + middleware)
auditLogService.Log(internal/application/service/audit_log.go) — каноническая точка записи: по умолчаниюoutcome=success, заполняетсяCreatedAt; при сбое записи только пишется лог ERROR, ошибка не распространяется вверх — сбой аудита ни в коем случае не должен прерывать бизнес-операцию.LogDeniedфиксирует отказы промежуточного ПО RBAC: выполняет дедупликацию в скользящем окне 1 минуты по ключу(tenant_id, actor, action=rbac.access_denied, шаблон route)(denyDedupWindow,repo.CountSinceForDedup), чтобы клиенты-пробники не заполнили таблицу (100 RPS по одному эндпоинту дают лишь 1 строку в минуту); в качестве ключа дедупликации используется шаблон route, а не исходный URL, чтобы перебор UUID не обходил окно. Лог[rbac] role insufficientв stderr не подвержен дедупликации и пишется при каждом отказе.AuditServiceProviderизmiddleware/audit_provider.goвнедряет service в gin context (ключweknora.audit_service); промежуточное ПО RBAC получает его черезAuditServiceFromContext, безопасно при nil (в режиме Lite аудит можно не настраивать).
API запросов (internal/handler/audit_log.go)
| Маршрут | Права | Описание |
|---|---|---|
GET /api/v1/tenants/:id/audit-log | PathTenantMatch + Admin | Поток аудита пространства; возвращает только строки уровня пространства с scope_type='' (UnscopedOnly) |
GET /api/v1/knowledge-bases/:id/activity | Создатель KB или Admin пространства, и обязательно пространство-владелец (потребители общего доступа организации читать не могут) | Проекция активности KB с scope_type=knowledge_base + scope_id=kbID |
GET /api/v1/system/admin/audit-log | SystemAdmin (+ Platform API Key system.audit_read) | События уровня платформы с tenant_id=0 (настройки / повышение / операции с очередями и т. д.) |
Единые параметры запроса: after_id (курсор, возвращает строки с меньшим id), limit (1–100, по умолчанию 50, жёсткий предел auditLogListLimitMax=100), точные фильтры action / outcome / actor. Ответ содержит next_cursor (минимальный id на странице, 0 означает достижение конца).
Политика хранения (internal/application/service/audit_log_retention.go)
- Конфигурация:
audit.retention_days(YAML) /WEKNORA_AUDIT_RETENTION_DAYS(переопределение через env); при отсутствии секцииaudit:по умолчанию 90 дней; явный 0 отключает очистку (архивирование вне БД для сценариев соответствия), отрицательное значение вызывает ошибку при проверке config. AuditLogRetentionRunner: фоновая goroutine на чистомtime.Ticker(без зависимостей от cron / asynq), задержка запуска 10 минут (чтобы избежать пересечения с миграциями и стартовой нагрузкой), затем каждые 24 ч выполняетсяPurge→DeleteOlderThan(now - retention_days)(одиночный с индексированным DELETE, таймаут 30s). Число удалённых записей логируется на уровне INFO, сбой — WARN (повтор в следующем раунде). Собирается вinternal/container/container.goи регистрируетResourceCleanerдля корректной остановки (Stopидемпотентен, при отсутствии Start возвращается сразу).
Ограничение частоты (internal/ratelimit и промежуточное ПО)
Универсальный ограничитель со скользящим окном (internal/ratelimit/limiter.go)
- Приоритет Redis: Lua-скрипт атомарно выполняет «удаление устаревших членов ZSET → подсчёт
ZCARD→ при непревышенииZADD+PEXPIRE», бюджет совместно используется несколькими экземплярами; member вида<instanceID>:<ms>гарантирует уникальность. - Когда Redis недоступен (ошибка или Lite без Redis), происходит автоматическая деградация до внутрипроцессного
localLimiter(sync.Map+ массив меток времени на ключ);StartCleanupпериодически вычищает пустые ключи. maxпередаётся при каждом вызовеAllow, поэтому один limiter может использовать разные бюджеты для разных ключей (например, своя квота у каждого канала embed).- Потребители: публичные интерфейсы Web embed (два limiter'а — на минуту + на 24 ч, по channel+ClientIP,
internal/middleware/embed_auth.go), сервис IM (internal/im/service.go).
Ограничение по IP на публичных эндпоинтах аутентификации (internal/middleware/auth_public_ratelimit.go)
PublicAuthRateLimit() защищает неаутентифицированные эндпоинты ссылок-приглашений (/auth/invitations/lookup, /auth/register-by-invite): внутрипроцессное скользящее окно, 30 раз в минуту на IP (общий бакет на оба эндпоинта); при превышении возвращается 429 (ErrTooManyRequests). Реализация чисто локальная (низконагруженные эндпоинты); в комментариях указано, что при горизонтальном масштабировании следует перейти на Redis-версию из internal/ratelimit.
Статистика ссылок на модели (internal/application/repository/model_usage.go)
Этот файл предоставляет запрос ссылок на модель (usage-by-reference), то есть отвечает на вопрос «какие ресурсы используют данную модель», и служит защитой зависимостей перед удалением модели, а не тарификацией по расходу token:
scopeKnowledgeBasesByModelID: сопоставляет любое поле привязки модели вknowledge_bases—embedding_model_id,summary_model_id,image_processing_config.model_id,vlm_config.model_id,asr_config.model_id,wiki_config.synthesis_model_id,auto_tag_config.model_id(в Postgres используется оператор->>JSON, в SQLite —json_extract; оба диалекта эквивалентны).scopeCustomAgentsByModelID: сопоставляетmodel_id,rerank_model_id,vlm_model_id,asr_model_id,query_understand_model_id,question_suggestions.follow_ups.model_idвcustom_agents.config.- Конфигурация долговременной памяти пространства:
extract_model_idиembedding_model_idизmemory_configтакже считаются ссылками и появляются вlong_term_memory.bindings. - Потребитель:
ListModelUsagesиз репозиториевknowledgebase.go/custom_agent.goпереиспользует указанные scope и возвращает минимальную проекцию активных объектов тенанта (id,name, объединённыеbindings) с верхним пределомModelUsageListLimit(50). Защита от удаления вinternal/application/service/model.goиспользуетknowledge_base_total/agent_totalи привязки долговременной памяти как основание для блокировки и в HTTP 400 вerror.detailsодновременно возвращает усечённый список, общее число иlong_term_memory.
Расход моделей на уровне token передаётся через usage_details Generation Langfuse (TokenUsage: input/output/total/cache_*) и просматривается в Langfuse UI с агрегацией по модели / пользователю (tenant:<id>) / сессии.
Обзор потоков данных наблюдаемости
Справочник по реализации
Все пути ниже указаны относительно корня репозитория:
| Возможность | Путь к исходному коду |
|---|---|
| Логи приложения | internal/logger/logger.go |
| Отладочный журнал вызовов LLM | internal/logger/llm_logger.go |
| Журнал запросов / промежуточное ПО RequestID | internal/middleware/logger.go |
| Трассировка Langfuse (OTel SDK) | internal/tracing/langfuse/ (config.go, manager.go, exporter.go, tracer.go, middleware.go, asynq.go, events.go, retrieval_obs.go, context.go) |
| Носитель trace между процессами | internal/types/tracing.go |
| Журнал аудита handler / service / repo | internal/handler/audit_log.go, internal/application/service/audit_log.go, internal/application/repository/audit_log.go |
| Политика хранения аудита | internal/application/service/audit_log_retention.go, internal/config/config.go (applyAuditDefaults) |
| Действия / модель аудита | internal/types/audit_log.go |
| Ограничение частоты | internal/ratelimit/limiter.go, internal/middleware/auth_public_ratelimit.go |
| Проверка здоровья | internal/router/router.go (GET /health) |
| Статистика ссылок на модели | internal/application/repository/model_usage.go |
