Ir al contenido

Logs & Telemetry

Esta página aún no está disponible en tu idioma.

Every Greentic runtime emits observability data in two layers:

  • Local file logs — always on. gtc start installs a tracing subscriber that writes structured logs to files under the bundle’s logs/ directory. No configuration required.
  • OpenTelemetry (OTLP) export — opt-in. When a bundle’s telemetry: block or the TELEMETRY_EXPORT / OTLP_ENDPOINT environment variables ask for it, the same event stream is also exported as OTLP traces, metrics, and logs to a collector of your choice.

This page covers both layers, plus a one-command docker stack (OpenTelemetry Collector + Prometheus + Loki + Grafana) for inspecting whatever a bundle emits. Everything here applies to any bundle — substitute your own bundle directory and service_name throughout.

At startup gtc start resolves a log directory (the bundle’s logs/ folder by default, overridable with --log-dir) and writes two files:

FileContents
logs/system.logThe runtime’s tracing event stream — startup, HTTP ingress, flow engine, provider invocations.
logs/flow.logFlow execution log — a focused view of each flow run.

The base filter for system.log (and, when enabled, the OTLP exporter) is resolved in this order:

  1. RUST_LOG environment variable — when set, it always wins.
  2. The bundle’s telemetry.log_level — an EnvFilter directive carried in bundle.yaml.
  3. info — the default.
Terminal window
# One-off verbose run
RUST_LOG=debug gtc start ./my-bundle
# Target a single subsystem
RUST_LOG="info,greentic.fast2flow=trace" gtc start ./my-bundle

Because the same filter gates the OTLP exporter, raising telemetry.log_level to debug or trace is what lets sub-info events reach Loki without having to export RUST_LOG on every run.

There are two independent telemetry decisions, and it helps to keep them apart:

DecisionWhere it livesWhat it controls
Capabilitymay this component emit telemetry, and how broadly?the component manifest (host.telemetry.scope), declared in the packgrants the WASM guest the right to emit, and the maximum scope of what it emits
Transportwhere do the signals go, over which protocol?the bundle telemetry: block or env varsthe exporter (otlp-grpc / otlp-http), endpoint, and service name

The first is a static capability grant: a component cannot emit telemetry at all unless its pack declares it. Add it to the host capabilities of each component’s component.manifest.json:

"capabilities": {
"host": {
"telemetry": {
"scope": "node"
}
}
}

scope is the maximum granularity the host will attribute to that component’s telemetry:

ScopeTelemetry is attributed to…
tenantthe tenant only
packthe pack
nodethe individual flow node invocation (finest-grained)

The transport — the gRPC-or-HTTP export choice — is configured either declaratively in the bundle or via environment variables. Environment variables take precedence, so an operator can flip exporters without re-running setup.

telemetry:
enabled: true
exporter: otlp-grpc # otlp-grpc | otlp-http
endpoint: http://localhost:4317
service_name: my-bundle
log_level: info # EnvFilter directive; gates files + OTLP
sampling: 1.0 # 1.0 = export every span
VariablePurposeDefault
TELEMETRY_EXPORTotlp-grpc or otlp-http (also accepts otlp)off
OTLP_ENDPOINTCollector endpoint (OTEL_EXPORTER_OTLP_ENDPOINT also honored)http://localhost:4317 (gRPC) / :4318 (HTTP)
OTEL_SERVICE_NAMEService name attached to all signalsbundle’s telemetry.service_name, else the bundle directory name
Terminal window
export TELEMETRY_EXPORT=otlp-grpc
export OTLP_ENDPOINT=http://localhost:4317
gtc start ./my-bundle

A reusable local stack lives in the docker/ directory of the greentic-demo repository. It brings up four services already wired to each other with a single docker compose up:

ServiceImageRole
OTel Collectorotel/opentelemetry-collector-contribReceives OTLP, fans signals out to the backends below.
Prometheusprom/prometheusScrapes the collector’s metrics endpoint; 1h retention.
Lokigrafana/lokiStores exported logs.
Grafanagrafana/grafanaDashboards; Prometheus + Loki pre-wired, anonymous admin.
Terminal window
docker compose --project-directory ./docker -f ./docker/docker-compose.yml up -d

Tear it down with the same command and down.

PortServiceNotes
4317OTel Collector — OTLP gRPCgtc start default for otlp-grpc
4318OTel Collector — OTLP HTTPdefault for otlp-http
8889OTel Collector — Prometheus exporterscraped by Prometheus
9090Prometheus UIquery browser
3100Lokilog ingest + query
3000Grafana UIanonymous admin, datasources pre-wired
SignalPathHow to look at it
Traces (host spans + provider invocations)Collector → debug exporterdocker logs -f greentic-demo-otelcol
Metrics (counters, histograms, gauges)Collector → Prometheus scrape on :8889 → Prometheus :9090http://localhost:9090 or Grafana
LogsCollector → Loki :3100Grafana Explore → Loki, or curl http://localhost:3100/loki/api/v1/query_range
DashboardsGrafana, both datasources pre-wiredhttp://localhost:3000
  1. Start the telemetry stack in one terminal:

    Terminal window
    docker compose --project-directory ./docker -f ./docker/docker-compose.yml up -d
  2. Start your bundle with OTLP export in another:

    Terminal window
    export TELEMETRY_EXPORT=otlp-grpc
    export OTLP_ENDPOINT=http://localhost:4317
    gtc start ./my-bundle
  3. Drive some traffic through the bundle (WebChat or any wired messaging provider), then confirm the collector is receiving data:

    Terminal window
    docker logs --tail 20 greentic-demo-otelcol | grep -E "ResourceSpans|ResourceMetrics|ResourceLogs"
    curl -s http://localhost:8889/metrics | head -40
  4. Open Grafana at http://localhost:3000, go to Explore, and run the queries below.

Open Grafana → Explore → Prometheus, or use the Prometheus UI at http://localhost:9090.

# Throughput — HTTP requests/sec per route
sum by (route) (rate(greentic_greentic_http_requests_total[1m]))
# Throughput — flow executions/sec, by flow
sum by (flow_id) (rate(greentic_greentic_flow_executions_total[1m]))
# Throughput — new conversations per minute
60 * rate(greentic_greentic_session_starts_total[5m])
# Concurrency — active conversations, per tenant
sum by (tenant) (greentic_greentic_conversations_active)
# Latency — p95 HTTP latency, by route
histogram_quantile(0.95, sum by (le, route) (rate(greentic_greentic_http_request_duration_ms_milliseconds_bucket[5m])))
# Errors — HTTP 5xx rate, by route
sum by (route) (rate(greentic_greentic_http_requests_total{status_code=~"5.."}[5m]))
# Errors — failed flow ratio
sum(rate(greentic_greentic_flow_executions_total{status="err"}[5m])) / sum(rate(greentic_greentic_flow_executions_total[5m]))
MetricWhere it’s emittedLabels
greentic.http.requestsHTTP ingressmethod, route, status_code
greentic.http.request_duration_msHTTP ingress (histogram)same
greentic.session.startsWebSocket session servetenant, provider
greentic.conversations.activesession connect/drop (UpDownCounter)tenant, provider
greentic.flow.executionsflow engine executetenant, flow_id, status
greentic.flow.duration_msflow engine execute (histogram)tenant, flow_id, status
greentic.provider.invocationsprovider invoketenant, provider, op, status
greentic.provider.op_duration_msprovider invoke (histogram)same

Same Grafana Explore screen — switch the datasource to Loki. Filter by the bundle’s service_name (defaults to the bundle directory name unless telemetry.service_name or OTEL_SERVICE_NAME overrides it). Substitute your own value for my-bundle below.

# All logs from this bundle
{service_name="my-bundle"}
# Only the flow engine crate
{service_name="my-bundle", scope_name="greentic_runner_host::runner::engine"}
# Only errors
{service_name="my-bundle", severity_text="ERROR"}
# Anything mentioning flow.execute
{service_name="my-bundle"} |= "flow.execute"
# Log volume per scope (rate)
sum by (scope_name) (rate({service_name="my-bundle"}[1m]))

In Explore, use Add → Add to dashboard to clone panels. Three rows map cleanly onto the metrics above:

  1. Throughput — HTTP-rps, flow-rps, and new-conversations-per-minute in one time-series panel.
  2. Latency — p50/p95/p99 of HTTP duration via histogram_quantile, plus flow duration.
  3. Health — a gauge for greentic_greentic_conversations_active, a stat for failed-flow ratio, and a Loki logs panel filtered to severity_text="ERROR".

To make a dashboard come up automatically, export its JSON, commit it under docker/grafana-dashboards/, and provision it via Grafana’s dashboard provisioning.

The docker stack is a local convenience. Any OTLP-compatible collector or SaaS backend works the same way — point OTLP_ENDPOINT at it and supply credentials with OTLP_HEADERS:

Terminal window
export TELEMETRY_EXPORT=otlp-http
export OTLP_ENDPOINT=https://otlp.example-observability.com
export OTLP_HEADERS="x-api-key=…"
gtc start ./my-bundle

This sends the same traces, metrics, and logs to managed backends such as Grafana Cloud, Honeycomb, or any vendor that speaks OTLP — no change to the bundle required.

  • No persistence. The docker stack has no named volumes — restarting it wipes the Prometheus TSDB and Grafana state. Add volumes to docker-compose.yml if you need durable data.
  • Short retention. Prometheus retention is set to 1h to keep local disk usage tiny. Bump --storage.tsdb.retention.time for longer windows.
  • Trace sampling in the collector. The debug exporter samples (sampling_initial: 5, sampling_thereafter: 50) so traces don’t flood the terminal. Drop the sampling block in otel-collector-config.yaml to see every span.