Skip to content

Graphic preview

100%
Page type: how-to

Trace migration runs

Build ptah with the observability build tag, send the spans of migrations up, down and status to an OTLP receiver, and read what each run records.

You run ptah migrations up, down and status in a pipeline, and you want each run as a trace in the backend that already collects your services’ traces. ptah exports migration spans over OTLP/HTTP, but only when it is built with the observability build tag. The release archives, the Homebrew formulas, the container image and the installer are built without it, so they cannot export traces.

For logs and metrics you need no special build: --log-format and --log-level shape the run log, and --metrics-addr serves Prometheus metrics. See Apply migrations.

  • A Go toolchain.
  • A receiver that takes OTLP over HTTP, such as an OpenTelemetry Collector with the HTTP protocol of its otlp receiver enabled. Its default port is 4318. ptah has no gRPC exporter.
  • A migration directory and a database. The commands below use the SQLite example from Apply migrations.

Install a release with the tag:

Terminal window
go install -tags observability ptah.run/cmd/ptah@vX.Y.Z

Or build from a checkout of the repository:

Terminal window
go build -tags observability -o bin/ptah ./cmd/ptah

The binary records its build tags. Check that the tag is there before you rely on it:

Terminal window
go version -m bin/ptah | grep -- -tags

Expected output on standard output:

build -tags=observability

A binary built without the tag prints nothing here, and it ignores every variable on this page.

Point OTEL_EXPORTER_OTLP_ENDPOINT at the receiver and run the command as usual:

Terminal window
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
ptah migrations up --db-url "sqlite://app.db" --migrations-dir ./migrations

ptah sends the spans to $OTEL_EXPORTER_OTLP_ENDPOINT/v1/traces. It collects them during the run and sends them when the command ends, so a run appears in the backend after the command exits.

If your receiver takes traces at another path, set OTEL_EXPORTER_OTLP_TRACES_ENDPOINT to the full URL instead. ptah uses that URL as written, and either variable on its own starts the export.

Without either variable, ptah exports nothing, even when it was built with the tag. A variable that is empty or holds only spaces counts as unset.

Each command reports as its own service, and every span carries the instrumentation scope ptah.run:

Command service.name
ptah migrations up ptah.migrations.up
ptah migrations down ptah.migrations.down
ptah migrations status ptah.migrations.status

OTEL_RESOURCE_ATTRIBUTES adds resource attributes, for example deployment.environment=staging. OTEL_SERVICE_NAME has no effect, because ptah sets service.name itself.

A run of migrations up that applies one migration sends these spans:

ptah.migrate.status the state before the run
ptah.migrate.up
├── ptah.lock.acquire
└── ptah.migrate.apply one span per migration
ptah.migrate.status the state after the run

migrations down has the same shape, with ptah.migrate.down and one ptah.migrate.rollback per migration. migrations status sends one ptah.migrate.status span. When there is nothing to apply or roll back, ptah.migrate.up or ptah.migrate.down is sent alone, with migration.pending_count set to 0.

Span Attributes
ptah.migrate.up db.system, migration.direction, migration.current_version, migration.target_version, migration.pending_count, lock.wait_ms
ptah.migrate.down the same as ptah.migrate.up, and migration.requested_target_version
ptah.migrate.apply, ptah.migrate.rollback db.system, migration.direction, migration.version, migration.description
ptah.lock.acquire db.system, migration.operation, lock.name, lock.timeout_ms, lock.wait_ms
ptah.migrate.status db.system, migration.current_version, migration.pending_count, migration.total_count, migration.out_of_order_count

A span whose operation failed has an error status that carries the error message, and an exception event. A failed migration marks both its ptah.migrate.apply span and the ptah.migrate.up span above it.

ptah reads the standard OpenTelemetry variables for the OTLP/HTTP exporter. Each OTEL_EXPORTER_OTLP_* variable below also has a traces-only form, such as OTEL_EXPORTER_OTLP_TRACES_HEADERS, which takes precedence.

Variable Effect
OTEL_EXPORTER_OTLP_ENDPOINT The receiver’s base URL; spans go to its /v1/traces path. http:// sends in plain text and https:// uses TLS.
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT The full URL for traces, used as written. It starts the export on its own, and it wins when both endpoint variables are set.
OTEL_EXPORTER_OTLP_HEADERS Headers for every export request, as comma-separated key=value pairs, for example an authorization token.
OTEL_EXPORTER_OTLP_PROTOCOL http/protobuf, the default, or http/json.
OTEL_EXPORTER_OTLP_COMPRESSION gzip, or none, the default.
OTEL_EXPORTER_OTLP_TIMEOUT The time limit for one export request, in milliseconds. The default is 10000.
OTEL_EXPORTER_OTLP_CERTIFICATE A PEM file with the certificate authority to trust for TLS.
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE, OTEL_EXPORTER_OTLP_CLIENT_KEY A PEM client certificate and its key, for a receiver that asks for one.
OTEL_RESOURCE_ATTRIBUTES Extra resource attributes, as comma-separated key=value pairs.

A tracing failure never fails the migration: the command’s exit status is the same with or without a receiver.

  • The binary was built without the tag. go version -m shows no -tags=observability. Rebuild it as shown above.
  • The receiver refuses the connection or rejects the spans. ptah logs OpenTelemetry tracing failed at the warn level, with the exporter’s error: the refused connection, or the HTTP status the receiver answered with. The record stays visible at --log-level warn.
  • The receiver does not answer. ptah waits up to five seconds after the run for the export to finish. Then it logs failed to shut down observability at the warn level, and the spans are lost.
  • The receiver speaks only gRPC. Enable the HTTP protocol on it, or put a Collector in front of it.