From 85db70887dc4d128011af84384a74ebb16266c4b Mon Sep 17 00:00:00 2001 From: febux Date: Sun, 6 Sep 2026 19:41:04 +0300 Subject: [PATCH] Fix Vector config and OpenObserve auth for live E2E verification Ticket 10 verification surfaced runtime issues that static validation missed; all verified against running Docker stack (Vector 0.43 + OO 0.92.2): - Rewrite VRL for Vector 0.43: no coalesce/default funcs, merge!/replace! for guarded fallible calls, no two-value parse_json destructuring - Quote env-interpolated sink credentials (empty env left a bare YAML null) - Fix logs sink URI: OO needs /api/{org}/{stream}/_json (stream segment was missing, causing 404); logs now land in contract_check stream - Replace OPENOBSERVE_AUTH_TOKEN with root email/password Basic auth: prometheus_remote_write ignores request.headers, so remote-write got 401 - Update .env.example, DEPLOY.md, ARCHITECTURE.md, ticket 05 accordingly Verified live: logs with service + correlation_id searchable in OpenObserve; contract_check_* metrics queryable via its Prometheus API; no outbound port 4318 connections from app containers. --- .env.example | 6 +-- deploy/observability/vector-config.yaml | 64 +++++++++++-------------- docker-compose.yml | 8 ++-- docs/ARCHITECTURE.md | 9 ++-- docs/DEPLOY.md | 7 +-- 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/.env.example b/.env.example index 9129cf7..673532d 100644 --- a/.env.example +++ b/.env.example @@ -48,10 +48,6 @@ REFUND_FULL_USAGE_THRESHOLD=0.20 # ≤20% consumed + within window ⇒ full ref # --- Observability (leave empty to disable) --- SENTRY_DSN= -# Auth header for OpenObserve. Used by Vector in the `observer` profile to forward -# logs and metrics. Generate with: echo -n 'user:pass' | base64 -# Default value is for root@example.com:Complexpass#123. -OPENOBSERVE_AUTH_TOKEN=cm9vdEBleGFtcGxlLmNvbTpDb21wbGV4cGFzcyMxMjM= # OTEL_EXPORTER_OTLP_ENDPOINT is no longer used. The application does not push # OTLP; Vector/OpenObserve collects logs/metrics passively from stdout and # /metrics endpoints. Remove this line from existing .env files. @@ -59,6 +55,8 @@ OPENOBSERVE_AUTH_TOKEN=cm9vdEBleGFtcGxlLmNvbTpDb21wbGV4cGFzcyMxMjM= # OTEL_EXPORTER_OTLP_HEADERS= # --- OpenObserve (profile: observer) --- +# Vector (profile `observer`) authenticates to OpenObserve with these +# credentials, so keep them in sync with the OpenObserve root user below. OPENOBSERVE_PORT=5080 OPENOBSERVE_ROOT_USER_EMAIL=root@example.com OPENOBSERVE_ROOT_USER_PASSWORD=Complexpass#123 diff --git a/deploy/observability/vector-config.yaml b/deploy/observability/vector-config.yaml index 2f69f6d..5a4b72d 100644 --- a/deploy/observability/vector-config.yaml +++ b/deploy/observability/vector-config.yaml @@ -26,7 +26,10 @@ sources: - http://worker-billing:9105/metrics - http://worker-notify:9103/metrics scrape_interval_secs: 15 - # Auth header is injected via a remap transform when METRICS_BEARER_TOKEN is set. + # The token is optional: an empty METRICS_BEARER_TOKEN leaves /metrics open. + authorization: + strategy: bearer + token: "${METRICS_BEARER_TOKEN-}" # ----------------------------------------------------------------------------- # Transforms @@ -38,43 +41,32 @@ transforms: inputs: - docker_logs source: | - # Keep the raw message and container name; derive a short service label. - .container = .container_name ?? .container_id ?? "unknown" - .service = .container_name ?? "unknown" + # Derive a short service label from the container metadata + # (docker_logs always provides container_name). + .container = .container_name + .service = .container_name # If the log line is JSON from our structured logger, merge its fields # without overwriting Vector/container metadata. - raw = .message ?? "" + raw = .message if is_string(raw) { - parsed, err = parse_json(raw) - if err == null && is_object(parsed) { - for_each(parsed) -> |key, value| { - if !exists(.) || !exists(.[key]) { - .[key] = value - } + parsed = parse_json(raw) ?? null + if is_object(parsed) { + . = merge!(parsed, .) + service = parsed.service + if service != null { + .service = service } - # Ensure OpenObserve can filter by the canonical service label even when - # the JSON payload carries its own "service" field. - if exists(parsed.service) { - .service = parsed.service - } - if exists(parsed.correlation_id) { - .correlation_id = parsed.correlation_id + correlation_id = parsed.correlation_id + if correlation_id != null { + .correlation_id = correlation_id } } } - # Trim container runtime prefixes for a cleaner service label. - .service = replace(.service, "^contract_check-", "") - - add_metrics_auth: - type: remap - inputs: - - service_metrics - source: | - token = get_env_var("METRICS_BEARER_TOKEN") ?? "" - if token != "" { - .headers = {"Authorization": "Bearer " + token} + # Trim the compose project prefix for a cleaner service label. + if is_string(.service) { + .service = replace!(.service, r'^contract_check-', "") } # ----------------------------------------------------------------------------- @@ -86,12 +78,12 @@ sinks: type: http inputs: - enrich_logs - uri: http://openobserve:5080/openobserve/api/default/_json + uri: http://openobserve:5080/openobserve/api/default/contract_check/_json method: post auth: strategy: basic - user: "" - password: ${OPENOBSERVE_AUTH_TOKEN} + user: "${OPENOBSERVE_ROOT_USER_EMAIL-}" + password: "${OPENOBSERVE_ROOT_USER_PASSWORD-}" encoding: codec: json batch: @@ -104,12 +96,14 @@ sinks: openobserve_metrics: type: prometheus_remote_write inputs: - - add_metrics_auth + - service_metrics + # OpenObserve answers Vector's healthcheck probe with 405; this is + # harmless — actual remote-write POSTs succeed (200). endpoint: http://openobserve:5080/openobserve/api/default/prometheus/api/v1/write auth: strategy: basic - user: "" - password: ${OPENOBSERVE_AUTH_TOKEN} + user: "${OPENOBSERVE_ROOT_USER_EMAIL-}" + password: "${OPENOBSERVE_ROOT_USER_PASSWORD-}" batch: max_events: 100 timeout_secs: 1 diff --git a/docker-compose.yml b/docker-compose.yml index 80c160a..24932a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -390,8 +390,9 @@ services: # profile: obs → Grafana + Prometheus + Loki (mature, heavier). # Use for production-grade visibility. # - # For the `observer` profile, set OPENOBSERVE_AUTH_TOKEN so Vector can write - # to OpenObserve. No OTLP endpoint configuration is needed in the app. + # Vector (profile `observer`) authenticates to OpenObserve with + # OPENOBSERVE_ROOT_USER_EMAIL / OPENOBSERVE_ROOT_USER_PASSWORD. No OTLP + # endpoint configuration is needed in the app. # ── LIGHTWEIGHT OBSERVABILITY (profile: observer) ─────────────────────────── openobserve: @@ -428,7 +429,8 @@ services: container_name: contract_check-vector restart: unless-stopped environment: - OPENOBSERVE_AUTH_TOKEN: ${OPENOBSERVE_AUTH_TOKEN} + OPENOBSERVE_ROOT_USER_EMAIL: ${OPENOBSERVE_ROOT_USER_EMAIL:-root@example.com} + OPENOBSERVE_ROOT_USER_PASSWORD: ${OPENOBSERVE_ROOT_USER_PASSWORD:-Complexpass#123} METRICS_BEARER_TOKEN: ${METRICS_BEARER_TOKEN:-} volumes: - /var/run/docker.sock:/var/run/docker.sock:ro diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2d5c706..d6ad153 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1087,7 +1087,8 @@ None of 1–5 requires touching `core/` application code — only compose/infra. | `REFUND_WINDOW_DAYS` | `14` | full-refund window (§8a) | | `REFUND_FULL_USAGE_THRESHOLD` | `0.20` | usage ratio for full refund | | `SENTRY_DSN` | (empty) | if set, init sentry | -| `OPENOBSERVE_AUTH_TOKEN` | default creds base64 | Basic auth token Vector uses to forward logs/metrics to OpenObserve | +| `OPENOBSERVE_ROOT_USER_EMAIL` | `root@example.com` | OpenObserve root user; Vector uses it for Basic auth | +| `OPENOBSERVE_ROOT_USER_PASSWORD` | `Complexpass#123` | OpenObserve root password; Vector uses it for Basic auth | | `OTEL_EXPORTER_OTLP_ENDPOINT` | (removed) | no longer used; the application does not push OTLP | | `OTEL_EXPORTER_OTLP_HEADERS` | (removed) | no longer used | | `OTEL_SERVICE_NAME` | (removed) | service name is set by `configure_logging()` and compose labels | @@ -1400,9 +1401,11 @@ enforcing the adapter boundary even in dependency ordering. compose network (`api:8000/metrics`, workers on `:9101`/`:9102`/`:9104`/`:9105`/`:9103`). - Vector enriches logs with `service` (container name) and preserves `correlation_id` from JSON log lines. -- Logs are sent to `http://openobserve:5080/openobserve/api/default/_json` and +- Logs are sent to + `http://openobserve:5080/openobserve/api/default/contract_check/_json` and metrics to `http://openobserve:5080/openobserve/api/default/prometheus/api/v1/write`, - both authenticated with `OPENOBSERVE_AUTH_TOKEN`. + both authenticated with the OpenObserve root credentials + (`OPENOBSERVE_ROOT_USER_EMAIL` / `OPENOBSERVE_ROOT_USER_PASSWORD`). - OpenObserve UI is exposed under `/openobserve/` via nginx. - Worker metrics ports are no longer published on the Docker host; they are only reachable inside the compose network for scraping. diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index f44d783..106c67f 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -144,9 +144,10 @@ PRICE_PER_DOC_KOPECKS=19900 # 199 ₽ за документ без подпис # --- Observability (опционально) --- SENTRY_DSN=https://...@sentry.io/... -# OpenObserve auth token used by Vector in the `observer` profile. -# Generate with: echo -n 'user:pass' | base64 -OPENOBSERVE_AUTH_TOKEN=cm9vdEBleGFtcGxlLmNvbTpDb21wbGV4cGFzcyMxMjM= +# Vector (профиль `observer`) авторизуется в OpenObserve с этими кредами +# (совпадают с root-пользователем OpenObserve). +OPENOBSERVE_ROOT_USER_EMAIL=root@example.com +OPENOBSERVE_ROOT_USER_PASSWORD=Complexpass#123 # OTEL_EXPORTER_OTLP_ENDPOINT больше не используется: приложение не шлёт OTLP. ```