Skip to content

Observe it

Set the standard OpenTelemetry variables and tablewalk sends traces, metrics and logs over OTLP/HTTP. Leave them unset and nothing is loaded.

Terminal window
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 npx tablewalk --app ./my-app

Three setups, each only environment variables

Section titled “Three setups, each only environment variables”

OpenTelemetry Collector (or anything that takes OTLP/HTTP):

Terminal window
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
export OTEL_SERVICE_NAME=loans
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production

Datadog, through the Datadog Agent’s OTLP intake (enable otlp_config.receiver.protocols.http on the Agent):

Terminal window
export OTEL_EXPORTER_OTLP_ENDPOINT=http://datadog-agent:4318
export OTEL_SERVICE_NAME=loans
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production,service.version=1.4.0

Dynatrace, straight to the environment’s OTLP API with an access token (scopes openTelemetryTrace.ingest, metrics.ingest, logs.ingest):

Terminal window
export OTEL_EXPORTER_OTLP_ENDPOINT=https://{your-environment-id}.live.dynatrace.com/api/v2/otlp
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Api-Token%20${DT_API_TOKEN}"
export OTEL_SERVICE_NAME=loans

A host running OneAgent can take the same export at its local OTLP endpoint instead. dd-trace and OneAgent auto-instrumentation keep working beside it: tablewalk’s server is not bundled, so the modules they hook are the ones it runs.

Also read: OTEL_EXPORTER_OTLP_PROTOCOL (http/protobuf, the default, or http/json; gRPC is not spoken, so point at the HTTP port), the per-signal OTEL_EXPORTER_OTLP_{TRACES,LOGS,METRICS}_ENDPOINT and _HEADERS, OTEL_{TRACES,LOGS,METRICS}_EXPORTER=none, OTEL_TRACES_SAMPLER and its _ARG, OTEL_EXPORTER_OTLP_COMPRESSION=gzip, OTEL_METRIC_EXPORT_INTERVAL and OTEL_SDK_DISABLED=true.

Signal What
Traces One per request, GET /api/query, named by route template; a span per statement (SELECT loan: its kind and table), command (id, version, outcome, refusal code), job attempt, event delivery, outbound call and email; the gate’s decision as an event. W3C traceparent is read on requests and MCP calls (_meta.traceparent) and sent on webhooks and HTTP sources.
Metrics http.server.request.duration, db.client.operation.duration, tablewalk.command.executions and .duration, tablewalk.job.attempts, tablewalk.outbound.calls.
Logs Each refusal, with the trace and span it happened in.

Nothing private is in any of them: no request or response body, row value, SQL text or parameter, email, token or tenant data. A test runs a command round trip seeded with secrets into a local OTLP receiver and fails if one arrives.

GET /healthz answers while the process runs; GET /readyz answers 503 while it starts, while a row policy restarts after a save, and from the moment it begins to shut down.

Terminal window
npx tablewalk --app ./my-app --dev

http://localhost:4111/_tablewalk/observe/ (under the --base-path, when the server has one) shows every request as a waterfall: its SQL with parameters, rows, transaction and EXPLAIN; N+1 and slow statements; outbound calls with their bodies; commands with input, output and the refusal’s cause; the log lines written inside each span; all logs, live, and what went straight to stderr; the page’s errors, layout shifts and web vitals (LCP, CLS, INP); and why a role can or cannot read a column. A refusal on the page carries an Open in observer link; a request the browser gave up on is shown as aborted, not as an error. A tenant App serves it the same way.

It runs on a loopback listener only, keeps the last few hundred requests in memory, and masks credentials and hidden columns. A start without --dev never loads it, and the address answers 404.

npx tablewalk observe prints its address; npx tablewalk observe --mcp gives a coding agent the same as tools, and check, which answers what tablewalk check --json prints and needs no running server: recent_errors, list_traces, get_trace, search_logs, slow_queries, n_plus_one, explain_statement, explain_access, page_vitals and tail.