ask-observability
Prometheus metrics, OpenTelemetry bootstrap, and structured logging for the ask-rb ecosystem.
The ask-rb observability story composes four small gems:
| Gem | Job |
|---|---|
ask-instrumentation |
emits an event for every LLM operation |
ask-opentelemetry |
events → OpenTelemetry spans |
| ask-observability | events → Prometheus metrics, plus the OTel + logging bootstrap and /metrics |
ask-monitoring |
events → in-app dashboard + alerts |
ask-observability is the infra-observability twin of ask-monitoring:
where the dashboard answers "what is the app spending?" inside the product,
these metrics answer "is the service healthy?" in Prometheus/OpenObserve/
Grafana, with alerting.
Installation
gem "ask-observability"
gem "ask-instrumentation"
Quick start (plain Ruby)
require "ask/observability"
Ask::Observability.install
Every Ask::Instrumentation event now maintains:
ask_llm_calls_total{provider,model,kind}
ask_llm_tokens_total{provider,model,kind,direction}
ask_llm_duration_seconds{provider,model,kind}
ask_llm_errors_total{provider,kind}
Read the registry from anywhere:
Ask::Observability.registry
Quick start (Rails)
The railtie auto-installs: OpenTelemetry spans export to the OTLP collector,
logs become JSON on stdout, /metrics is mounted, and the subscriber runs.
Run the generator for a config file to tune it:
rails generate ask:observability:install
Ask::Observability.configure do |config|
config.service_name = "my-app"
config. = [:workspace_id] # low-cardinality ONLY
end
Configuration
| Option | Default | Purpose |
|---|---|---|
enabled |
true |
master switch (set false in tests/CI) |
service_name |
Rails app name / "ask-app" |
service.name for spans and logs |
otlp_endpoint |
ENV["OTEL_EXPORTER_OTLP_ENDPOINT"] or http://localhost:4318/v1/traces |
OTLP/HTTP span export |
json_logging |
true |
JSON logs on stdout via rails_semantic_logger (skipped if the app configured its own appenders) |
metrics_path |
"/metrics" |
where /metrics is mounted (nil disables the mount) |
label_metadata |
[] |
Ask::Instrumentation.with_metadata keys exposed as metric labels |
Cardinality warning: every distinct label value creates a new time series. Only configure low-cardinality metadata keys (
workspace_id,org,tenant). Never put request ids or call ids inlabel_metadata. Configure before the first event — prometheus-client fixes a metric's label keys at creation.
Correlating a unit of work everywhere
with_context pushes the same metadata into instrumentation events (and
thus spans + metrics) and into Rails log lines as tags:
Ask::Observability.with_context(call_id: "abc", workspace_id: 4) do
# events carry call_id/workspace_id; Rails logs are tagged with them
end
Serving metrics yourself
If the mounted path conflicts with your app, point metrics_path elsewhere
or nil and mount the Rack app yourself:
# config/routes.rb
mount Ask::Observability::MetricsApp, at: "/ops/metrics"
Disabling
Set config.enabled = false — the subscriber, the OTel bootstrap, and the
/metrics mount all no-op. For tests, OTEL_DISABLED=true (or the test
environment) additionally skips the OpenTelemetry SDK configuration.
Development
bin/setup # bundle install + git hooks
bundle exec rake test
License
MIT