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.
This commit is contained in:
febux 2026-09-06 19:41:04 +03:00
parent b279d6c61a
commit 85db70887d
5 changed files with 46 additions and 48 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1087,7 +1087,8 @@ None of 15 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.

View file

@ -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.
```