2 Deployment

2.1 Getting Started

The Switchgear binary runs all services as well as the CLI admin interface.

2.1.1 Install

2.1.1.1 Host

cargo install switchgear-server

2.1.1.2 Docker

The docker image is multi-platform for:

docker pull bitshock/switchgear

2.1.2 Starting Switchgear Services

swgr service --config {path/to/config/file} {service enabledment list}

The configuration file is in YAML format, and controls settings for all services.

The service enablement list can be any of:

If left empty, all services will be enabled (same as all).

2.1.2.1 Docker

To run the Docker image:

docker run bitshock/switchgear

The image is configured with a default configuration file path of /etc/swgr/config.yaml . Mount a volume on top of /etc/swgr to provide your own configuration file.

To build the Docker image:

docker buildx build --platform linux/arm64,linux/amd64 -t swgr .

Set the build arg WEBPKI_ROOTS=true if you need Mozilla's web PKI roots bundle installed on the image:

docker buildx build --platform linux/arm64,linux/amd64 --build-arg WEBPKI_ROOTS=true -t swgr .

2.1.3 Administration

Switchgear can be configured by both the REST API and the CLI.

2.1.3.1 REST Administration

Administration Service endpoints:

https://{host}/discovery
https://{host}/offers

See the Manage Lightning Node Backends with Discovery Service and Manage LNURLs with Offer Service sections for complete REST API.

2.1.3.2 CLI Administration

# Manage Lightning Node Backends
swgr discovery
#  Manage LNURLs
swgr offer

See the Manage Lightning Node Backends with Discovery Service and Manage LNURLs with Offer Service sections for complete CLI manual.

2.1.3.3 Docker

To run the CLI administration from Docker:

docker run bitshock/switchgear {cli-options}

2.2 Feature Reference

2.2.1 Configuring Switchgear Services

All service configuration is controlled by a yaml file passed to the server at startup.

See server/config directory for more configuration examples.

Each service has a root entry the configuration file:

# LNURL Service Configuration
lnurl-service:

# Discovery Service Configuration
discovery-service:

# Offer Service Configuration  
offer-service:

# Persistence Settings for Discovery and Offer data  
store:

See the service entries below for complete configuration manual.

2.2.1.1 Env Var Shell Expansion

Shell-style env var expansion is supported anywhere in the yaml configuration file.

Example config.yaml:

lnurl-service:
  address: "${MY_LNURL_SERVICE_ADDRESS:-127.0.0.1:8080}"

Run with:

MY_LNURL_SERVICE_ADDRESS=192.168.1.100:8080 swgr service --config ./config.yaml

The configuration would be parsed as:

lnurl-service:
  address: "192.168.1.100:8080"

If the env var is unset:

swgr service --config ./config.yaml

The configuration would be parsed as:

lnurl-service:
  address: "127.0.0.1:8080"

2.2.1.2 Secrets Expansion

Secrets are loaded from files and used to replace tokens in specific configuration fields. Each secret is stored in its own file. The file contents (with a trailing newline stripped) become the secret value.

Example secret files:

# /etc/secrets/offer-mysql-username
root
# /etc/secrets/offer-mysql-password
mysql

Secrets are declared inline within the configuration section that consumes them. Each secrets: block has a ttl ( cache time-to-live in seconds) and a map of named secrets, each pointing to a file path:

    secrets:
      ttl: 300.0
      secrets:
        MYSQL_USERNAME:
          path: "/etc/secrets/offer-mysql-username"
        MYSQL_PASSWORD:
          path: "/etc/secrets/offer-mysql-password"

Once declared, secrets are referenced with the {{SECRET_NAME}} template syntax in fields that support expansion:

    database-uri: "postgres://{{POSTGRES_USERNAME}}:{{POSTGRES_PASSWORD}}@localhost:5432/discovery"

Unlike env var expansion, secret expansion is selective. Only specific configuration fields have access to secrets, and each secrets: block is scoped to the section that declares it. Secret file contents are cached in memory for ttl seconds and re-read on expiry, allowing rotation without a service restart.

2.2.2 LNURL Service Configuration

See server/config directory for more configuration examples.

lnurl-service:
  # List of partitions this service will handle
  # Partitions allow you to segment different Lightning node groups
  partitions: [ "default" ]

  # Network address and port for the LNURL service to bind to
  address: "127.0.0.1:8080"

  # Frequency in seconds for health checking Lightning node backends (float)
  health-check-frequency-secs: 1.0

  # Whether to perform health checks in parallel across all backends
  parallel-health-check: true

  # Number of consecutive successful health checks needed to mark a backend as healthy
  health-check-consecutive-success-to-healthy: 1

  # Number of consecutive failed health checks needed to mark a backend as unhealthy
  health-check-consecutive-failure-to-unhealthy: 1

  # Frequency in seconds for updating backend node information (float)
  backend-update-frequency-secs: 1.0

  # Invoice expiry time in seconds (integer)
  invoice-expiry-secs: 180

  # Timeout in seconds for Lightning node client connections (float)
  ln-client-timeout-secs: 2.0

  # Optional trusted roots pem bundle for all LN clients
  ln-trusted-roots: "/etc/ssl/certs/ln-ca.pem"

  # List of allowed host headers for incoming requests
  # Used for safely generating callback/invoice URLs.
  allowed-hosts: [ "lnurl.example.com" ]

  # Backoff configuration for retrying failed operations.
  # Backoff is used when Lightning Node invoice request fails.
  backoff:
    # Type of backoff strategy: "stop" or "exponential"
    type: "exponential"
    # Optional: Initial interval in seconds for exponential backoff (float)
    initial-interval-secs: 1.0
    # Optional: Randomization factor (0.0 to 1.0)
    randomization-factor: 0.5
    # Optional: Multiplier for each retry attempt
    multiplier: 2.0
    # Optional: Maximum interval between retries in seconds (float)
    max-interval-secs: 60.0
    # Optional: Maximum total elapsed time before giving up in seconds (float)
    max-elapsed-time-secs: 300.0

  # Backend selection strategy for load balancing
  # Options: "round-robin", "random", or "consistent"
  backend-selection: "round-robin"
    # For consistent hashing, specify max iterations (only used with "consistent")
    # backend-selection:
  #   type: "consistent"
  #   max-iterations: 10000

  # Optional: Bias factor for capacity-influenced selection
  # Negative values are restrictive:
  # prefer nodes with capacity higher than requested amount
  # Positive values are lenient:
  # refer nodes with capacity less than requested amount  
  selection-capacity-bias: -0.2

  # Optional: Allow &comment query param in LNURL invoice request, sized in char len
  # Used for Consistent backend selection
  comment_allowed: 64,

  # Optional: TLS configuration for HTTPS support
  tls:
    # Path to TLS certificate file
    cert-path: "/etc/ssl/certs/lnurl-cert.pem"
    # Path to TLS private key file
    key-path: "/etc/ssl/certs/lnurl-key.pem"

  # QR module width x height
  bech32-qr-scale: 8
  # QR light gray level
  bech32-qr-light: 255
  # QR dark gray level
  bech32-qr-dark: 0

  # Optional: OTLP telemetry export
  otlp:
    # Required. Shared transport for every signal below: a signal that declares
    # its own `export` block replaces this one in full (no field-level merge),
    # and signals that do not share one gRPC connection.
    export:
      # OTLP collector endpoint (gRPC)
      endpoint: "http://127.0.0.1:4317"
      # Name of the secret whose contents are sent as the bearer token
      auth-token: "OTEL_AUTH_TOKEN"
      # Optional: pem bundle of trusted CA certificate paths for TLS verification
      trusted-roots: "/etc/ssl/certs/otlp-ca.pem"
      # Optional: pinned collector address for the trusted-roots bundle
      trusted-root-address: "127.0.0.1:4317"
      # Optional: per-request timeout on the gRPC channel
      export-timeout-secs: 5.0
      # Optional: mTLS client identity, referencing secret names
      client-identity:
        cert-secret: "OTEL_CLIENT_CERT"
        key-secret: "OTEL_CLIENT_KEY"
      # Secrets consumed by this exporter (see Secrets Expansion)
      secrets:
        ttl: 300.0
        secrets:
          OTEL_AUTH_TOKEN:
            path: "/etc/ssl/certs/otlp-auth.token"
          OTEL_CLIENT_CERT:
            path: "/etc/ssl/certs/otlp-client.pem"
          OTEL_CLIENT_KEY:
            path: "/etc/ssl/certs/otlp-client-key.pem"
    # Present = span export is on. Omit the block to turn tracing off.
    tracing:
      # always-on | always-off | trace-id-ratio | parent-based-trace-id-ratio
      # `parent-based-trace-id-ratio` with `ratio: 1.0` is the OTel default.
      sampler:
        type: parent-based-trace-id-ratio
        ratio: 1.0
    # Present = metric export is on. Omit the block to turn metrics off.
    metrics:
      # cumulative | delta | low-memory
      temporality: cumulative
      # trace | debug | info | warn | error | off. Optional; defaults to info.
      level: info

2.2.3 Discovery Service Configuration

See server/config directory for more configuration examples.

discovery-service:
  # Network address and port for the Discovery service to bind to
  address: "127.0.0.1:8081"

  # Path to the authentication authority certificate/key file
  # This file contains the public key used to verify API access
  auth-authority: "/etc/ssl/certs/discovery-auth-authority.pem"

  # Optional: TLS configuration for HTTPS support
  tls:
    # Path to TLS certificate file
    cert-path: "/etc/ssl/certs/discovery-cert.pem"
    # Path to TLS private key file
    key-path: "/etc/ssl/certs/discovery-key.pem"

  # Optional: OTLP telemetry export
  otlp:
    # Required. Shared transport for every signal below: a signal that declares
    # its own `export` block replaces this one in full (no field-level merge),
    # and signals that do not share one gRPC connection.
    export:
      # OTLP collector endpoint (gRPC)
      endpoint: "http://127.0.0.1:4317"
      # Name of the secret whose contents are sent as the bearer token
      auth-token: "OTEL_AUTH_TOKEN"
      # Optional: pem bundle of trusted CA certificate paths for TLS verification
      trusted-roots: "/etc/ssl/certs/otlp-ca.pem"
      # Optional: pinned collector address for the trusted-roots bundle
      trusted-root-address: "127.0.0.1:4317"
      # Optional: per-request timeout on the gRPC channel
      export-timeout-secs: 5.0
      # Optional: mTLS client identity, referencing secret names
      client-identity:
        cert-secret: "OTEL_CLIENT_CERT"
        key-secret: "OTEL_CLIENT_KEY"
      # Secrets consumed by this exporter (see Secrets Expansion)
      secrets:
        ttl: 300.0
        secrets:
          OTEL_AUTH_TOKEN:
            path: "/etc/ssl/certs/otlp-auth.token"
          OTEL_CLIENT_CERT:
            path: "/etc/ssl/certs/otlp-client.pem"
          OTEL_CLIENT_KEY:
            path: "/etc/ssl/certs/otlp-client-key.pem"
    # Present = span export is on. Omit the block to turn tracing off.
    tracing:
      # always-on | always-off | trace-id-ratio | parent-based-trace-id-ratio
      # `parent-based-trace-id-ratio` with `ratio: 1.0` is the OTel default.
      sampler:
        type: parent-based-trace-id-ratio
        ratio: 1.0
    # Present = metric export is on. Omit the block to turn metrics off.
    metrics:
      # cumulative | delta | low-memory
      temporality: cumulative
      # trace | debug | info | warn | error | off. Optional; defaults to info.
      level: info

2.2.3.1 Authentication Setup

Generate key pairs and tokens for Discovery service authentication:

# Generate a new key pair for token signing
swgr discovery token key --public discovery-public.pem --private discovery-private.pem

# The public key (discovery-public.pem) should be used as the auth-authority in the configuration
# The private key (discovery-private.pem) is used to mint authentication tokens

# Create a token (default 3600 seconds)
swgr discovery token mint --key discovery-private.pem --output discovery.token

2.2.4 Offer Service Configuration

See server/config directory for more configuration examples.

offer-service:
  # Network address and port for the Offers service to bind to
  address: "127.0.0.1:8082"

  # Path to the authentication authority certificate/key file
  # This file contains the public key used to verify API access
  auth-authority: "/etc/ssl/certs/offer-auth-authority.pem"

  # Optional: TLS configuration for HTTPS support
  tls:
    # Path to TLS certificate file
    cert-path: "/etc/ssl/certs/offer-cert.pem"
    # Path to TLS private key file
    key-path: "/etc/ssl/certs/offer-key.pem"

  # max page size for get all queries
  max-page-size: 100

  # Optional: OTLP telemetry export
  otlp:
    # Required. Shared transport for every signal below: a signal that declares
    # its own `export` block replaces this one in full (no field-level merge),
    # and signals that do not share one gRPC connection.
    export:
      # OTLP collector endpoint (gRPC)
      endpoint: "http://127.0.0.1:4317"
      # Name of the secret whose contents are sent as the bearer token
      auth-token: "OTEL_AUTH_TOKEN"
      # Optional: pem bundle of trusted CA certificate paths for TLS verification
      trusted-roots: "/etc/ssl/certs/otlp-ca.pem"
      # Optional: pinned collector address for the trusted-roots bundle
      trusted-root-address: "127.0.0.1:4317"
      # Optional: per-request timeout on the gRPC channel
      export-timeout-secs: 5.0
      # Optional: mTLS client identity, referencing secret names
      client-identity:
        cert-secret: "OTEL_CLIENT_CERT"
        key-secret: "OTEL_CLIENT_KEY"
      # Secrets consumed by this exporter (see Secrets Expansion)
      secrets:
        ttl: 300.0
        secrets:
          OTEL_AUTH_TOKEN:
            path: "/etc/ssl/certs/otlp-auth.token"
          OTEL_CLIENT_CERT:
            path: "/etc/ssl/certs/otlp-client.pem"
          OTEL_CLIENT_KEY:
            path: "/etc/ssl/certs/otlp-client-key.pem"
    # Present = span export is on. Omit the block to turn tracing off.
    tracing:
      # always-on | always-off | trace-id-ratio | parent-based-trace-id-ratio
      # `parent-based-trace-id-ratio` with `ratio: 1.0` is the OTel default.
      sampler:
        type: parent-based-trace-id-ratio
        ratio: 1.0
    # Present = metric export is on. Omit the block to turn metrics off.
    metrics:
      # cumulative | delta | low-memory
      temporality: cumulative
      # trace | debug | info | warn | error | off. Optional; defaults to info.
      level: info

2.2.4.1 Authentication Setup

Generate key pairs and tokens for Offer service authentication:

# Generate a new key pair for token signing
swgr offer token key --public offer-public.pem --private offer-private.pem

# The public key (offer-public.pem) should be used as the auth-authority in the configuration
# The private key (offer-private.pem) is used to mint authentication tokens

# Create a token (default 3600 seconds)
swgr offer token mint --key offer-private.pem --output offer.token

2.2.5 Persistence

Both Discovery and Offer services support multiple storage backends. Configure persistence in the store section of your configuration file.

2.2.5.1 Common Storage Types

Both Discovery and Offer stores support these storage backends:

2.2.5.1.1 Database Storage (SQLite/MySQL/PostgreSQL)
store:
  discover: # or 'offer'
    type: "database"
    # Database connection URI (SQLite/MySQL/PostgreSQL)
    # Supports secrets expansion
    database-uri: "connection-url"
    # Maximum number of concurrent database connections
    max-connections: 5
    # Timeout in seconds for establishing a new database connection
    connect-timeout-secs: 5.0
    # Timeout in seconds for acquiring a connection from the pool
    acquire-timeout-secs: 10.0
    # Optional: inline secrets consumed by database-uri (see Secrets Expansion)
    secrets:
      ttl: 300.0
      secrets:
        POSTGRES_USERNAME:
          path: "/etc/secrets/discovery-postgres-username"
        POSTGRES_PASSWORD:
          path: "/etc/secrets/discovery-postgres-password"

For database-uri formats, see Database Connection URLs.

2.2.5.1.2 HTTP Storage (Remote Service)

Both Discovery and Offer can use a remote http store, making custom integrations straightforward. The store clients connect to the same REST API used for remote administration, making it possible for Switchgear to run headless as well, serving only as a database for other Switchgear instances.

store:
  discover: # or 'offer'
    type: "http"
    # Base URL of the remote service
    base-url: "https://service.example.com"
    # Timeout in seconds for establishing connection
    connect-timeout-secs: 2.0
    # Total timeout in seconds for complete request/response
    total-timeout-secs: 5.0
    # Optional pem bundle of trusted CA certificate paths for TLS verification
    trusted-roots: "/etc/ssl/certs/ca.pem"
    # Name of the secret whose contents are sent as the bearer token
    authorization: "DISCOVERY_STORE_HTTP_BEARER"
    # Inline secrets consumed by authorization (see Secrets Expansion)
    secrets:
      ttl: 300.0
      secrets:
        DISCOVERY_STORE_HTTP_BEARER:
          path: "/etc/ssl/certs/discovery-authorization.token"
2.2.5.1.3 In-memory Storage

Volatile storage, data is lost on restart:

store:
  discover: # or 'offer'
    type: "memory"

2.2.5.2 Configuration Examples

2.2.5.2.1 Using Same Database for Both Stores
store:
  # Discovery backend storage
  discover:
    type: "database"
    database-uri: "postgres://{{POSTGRES_USERNAME}}:{{POSTGRES_PASSWORD}}@localhost:5432/switchgear"
    max-connections: 5
    connect-timeout-secs: 5.0
    acquire-timeout-secs: 10.0
    secrets:
      ttl: 300.0
      secrets:
        POSTGRES_USERNAME:
          path: "/etc/secrets/postgres-username"
        POSTGRES_PASSWORD:
          path: "/etc/secrets/postgres-password"

  # Offer storage (sharing same database)
  offer:
    type: "database"
    database-uri: "postgres://{{POSTGRES_USERNAME}}:{{POSTGRES_PASSWORD}}@localhost:5432/switchgear"
    max-connections: 10
    connect-timeout-secs: 5.0
    acquire-timeout-secs: 10.0
    secrets:
      ttl: 300.0
      secrets:
        POSTGRES_USERNAME:
          path: "/etc/secrets/postgres-username"
        POSTGRES_PASSWORD:
          path: "/etc/secrets/postgres-password"
2.2.5.2.2 Mixed Storage Types
store:
  # Memory storage for Discovery
  discover:
    type: "memory"

  # Database storage for Offers
  offer:
    type: "database"
    database-uri: "sqlite:///var/lib/switchgear/offers.db?mode=rwc"
    max-connections: 5
    connect-timeout-secs: 5.0
    acquire-timeout-secs: 10.0
2.2.5.2.3 Remote Service Configuration
store:
  # Connect to remote Discovery service
  discover:
    type: "http"
    base-url: "https://discovery.internal:8081"
    connect-timeout-secs: 2.0
    total-timeout-secs: 5.0
    trusted-roots: "/etc/ssl/certs/internal-ca.pem"
    authorization: "DISCOVERY_STORE_HTTP_BEARER"
    secrets:
      ttl: 300.0
      secrets:
        DISCOVERY_STORE_HTTP_BEARER:
          path: "/etc/ssl/certs/discovery.token"

  # Local database for Offers
  offer:
    type: "database"
    database-uri: "sqlite:///data/offers.db?mode=rwc"
    max-connections: 10
    connect-timeout-secs: 5.0
    acquire-timeout-secs: 10.0

2.2.5.3 Database Connection URLs

Both Discovery and Offer data stores have a database-uri field to configure the database.

2.2.5.3.1 Sqlite
sqlite:///path/to/file.db?{options}

See https://www.sqlite.org/uri.html for all connection URL options.

2.2.5.3.2 MySQL
mysql://[host][/database][?properties]

Properties:

ParameterDefaultDescription
ssl-modePREFERREDDetermines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [MySqlSslMode].
ssl-caNoneSets the name of a file containing a list of trusted SSL Certificate Authorities.
statement-cache-capacity100The maximum number of prepared statements stored in the cache. Set to 0 to disable.
socketNonePath to the unix domain socket, which will be used instead of TCP if set.
2.2.5.3.3 Postgres
postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...]

See https://www.postgresql.org/docs/current/libpq-connect.html for all connection URL options.

2.2.6 Observability

2.2.6.1 Logging

Switchgear service processes emit structured logs to stderr in ECS logging format. Logging output is newline-delimited JSON (ND-JSON) that conforms to the Elastic Common Schema. Every record can be ingested directly by Elasticsearch, Filebeat, Vector, Fluent Bit, or any pipeline that understands ECS.

Each line begins with the four MVP keys defined by the ecs-logging specification, in this order:

  1. @timestamp: ISO-8601 UTC timestamp
  2. log.level: TRACE, DEBUG, INFO, WARN, or ERROR
  3. message: human-readable text
  4. ecs.version: ECS schema version the record conforms to

Example access-log line:

{
  "@timestamp": "2026-08-24T21:07:11.482Z",
  "log.level": "INFO",
  "message": "handled request",
  "ecs.version": "8.11.0",
  "service.name": "swgr.lnurl",
  "service.version": "0.5.0",
  "http.request.method": "GET",
  "http.response.status_code": 200,
  "http.version": "1.1",
  "url.path": "/offers/default/6a38ebdd-83ef-4b94-b843-3b18cd90a833/invoice",
  "url.query": "amount=100000",
  "client.ip": "10.0.0.42",
  "event.dataset": "swgr.lnurl.access",
  "event.duration": 18342119,
  "trace.id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span.id": "00f067aa0ba902b7",
  "log.origin.file.name": "request_logger.rs",
  "log.origin.file.line": 73
}

Example error record:

{
  "@timestamp": "2026-08-24T21:07:12.104Z",
  "log.level": "ERROR",
  "message": "upstream offer store returned 502",
  "ecs.version": "8.11.0",
  "service.name": "swgr.lnurl",
  "service.version": "0.5.0",
  "error.type": "switchgear_service::axum::crud::error::CrudError",
  "error.message": "connect error",
  "error.stack_trace": "...",
  "event.kind": "event",
  "event.category": [
    "web"
  ],
  "event.type": [
    "error"
  ],
  "event.outcome": "failure",
  "http.response.status_code": 502,
  "trace.id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span.id": "00f067aa0ba902b7"
}
2.2.6.1.1 Service Identification

Each enabled service emits records under its own service.name:

Serviceservice.name
LNURLswgr.lnurl
Discoveryswgr.discovery
Offerswgr.offer
CLI / bootstrapswgr

service.version carries the running Switchgear build version.

2.2.6.1.2 Access Logs

Every completed HTTP request produces an INFO record with ECS HTTP and ECS URL fields:

2.2.6.1.3 Error Logs

WARN (4xx) and ERROR (5xx) records add ECS error and ECS event fields:

2.2.6.1.4 Log-to-Trace Correlation

When OTLP tracing is enabled and a request is executing inside a span, log records carry ECS tracing fields trace.id and span.id. These correlate to the OTLP spans exported to the collector, so you can pivot from a log line in Kibana/Elastic APM directly to its distributed trace.

See OTLP Tracing below for exporter configuration.

2.2.6.1.5 Level Filtering

swgr service reads its log level from the standard RUST_LOG environment variable and defaults to info. It accepts the full tracing-subscriber env-filter grammar:

RUST_LOG=info,switchgear_service=debug swgr service --config ./config.yaml

The -l / --log-level global flag is ignored for swgr service. Use RUST_LOG instead.

2.2.6.1.6 CLI Output

CLI subcommands (swgr discovery ..., swgr offer ...) output human-readable text on stderr intended for interactive use; their level is controlled by the -l / --log-level global flag (defaults to info):

swgr --log-level debug discovery ls

2.2.6.2 OTLP Tracing

Each service can export OpenTelemetry spans to an OTLP-compatible collector (Jaeger, Tempo, Elastic APM, Grafana Cloud, etc.). Presence of the otlp.tracing block turns span export on; the transport comes from otlp.export unless otlp.tracing.export overrides it. See the LNURL, Discovery, and Offer service configuration samples above.

2.2.6.3 OTLP Metrics

Each service can also export OpenTelemetry metrics over the same OTLP pipeline. Presence of the otlp.metrics block turns metric export on; the transport comes from otlp.export unless otlp.metrics.export overrides it.

  otlp:
    export:
      endpoint: "http://127.0.0.1:4317"
      auth-token: "OTEL_AUTH_TOKEN"
      # ... shared transport, as in the samples above
    metrics:
      temporality: cumulative
2.2.6.3.1 Enabling And Disabling

Metric export is on when the service's otlp.metrics block is present. Setting the OTel-standard OTEL_METRICS_EXPORTER environment variable to none forces it off regardless of configuration:

OTEL_METRICS_EXPORTER=none swgr service --config ./config.yaml

none is the only value that changes anything: any other setting, and an unset variable, leave metrics on. Log records and spans are unaffected either way.

2.2.6.3.2 Metric Level

otlp.metrics.level sets how much metric detail is recorded:

  otlp:
    metrics:
      temporality: cumulative
      level: debug
ValueRecorded
off, error, warnNothing
infoThe standard metric set. This is the default when the field is omitted
debugThe standard set, plus finer-grained per-operation detail
traceEverything

Values are case-insensitive.

Leave the level at info for normal running. Raise it to debug when you are investigating something and need per-operation detail; expect more datapoints and higher tag cardinality at your metrics sink, so lower it again afterward.

Setting off stops metrics being recorded but leaves the exporter connected. To shut the pipeline down entirely, omit the otlp.metrics block or set OTEL_METRICS_EXPORTER=none (see Enabling And Disabling above).

RUST_LOG has no effect on metrics — it governs log records only. otlp.metrics.level is the only setting that changes metric verbosity.

2.2.6.3.3 Temporality

otlp.metrics.temporality selects how cumulative state is reported:

ValueBehaviour
cumulativeEach export carries the running total since process start
deltaEach export carries only what changed since the previous one
low-memoryDelta for counters and histograms, cumulative for up-down counters and gauges

2.2.6.4 Emitted Metrics

MetricEmitted when
db.client.operation.durationA discovery or offer store is backed by a database
http.client.request.durationA discovery or offer store is backed by a remote service, see Persistence
rpc.client.call.durationThe LNURL service calls a CLN or LND node

Metric and attribute names follow the OpenTelemetry semantic conventions, so a collector, dashboard or alert built against those conventions understands them without mapping. Three things hold across all three:

2.2.6.4.1 db.client.operation.duration

How long a database call took.

Source: Database client metrics — histogram, stable.

AttributePresentValue
db.system.nameAlwayssqlite, mysql or postgresql
db.namespace | db.collection.nameAlwaysThe database and the table. For SQLite the database is the file name without its extension, or :memory:
server.address | server.portExcept SQLiteHost and port of the database server. Never any credential from the connection URL
error.typeOn failure onlyunique_constraint, foreign_key_constraint, constraint, connection_acquire, connection, statement, conversion, or _OTHER. The same set whichever database you run
db.response.status_codeOn failure onlyThe database's own error code, which differs between SQLite, MySQL and Postgres
swgr.operationAlwaysThe store operation, see below

swgr.operation is finer-grained than the table alone. The pairings are fixed:

db.collection.nameswgr.operation
offer_recordget_offer, get_offers, post_offer, put_offer_upsert, put_offer_fetch, delete_offer
offer_metadataget_metadata, get_all_metadata, post_metadata, put_metadata_upsert, put_metadata_fetch, delete_metadata
discovery_backendget, get_all_backends, post, put, patch, delete
discovery_backend_etagget_all_etag
2.2.6.4.2 http.client.request.duration

How long a request to a remote discovery or offer service took.

Source: HTTP client metrics — histogram, stable.

AttributePresentValue
http.request.method | url.templateAlwaysThe method, and the route pattern rather than the requested path, so no identifier reaches your metrics sink
server.address | server.portAlwaysHost and port of the remote store
http.response.status_codeWhen a response arrivedThe status, including a 4xx or 5xx
error.typeOn failure onlyThe status, when one was returned, otherwise timeout, connect, decode or request

The route patterns are fixed:

Storeurl.templatehttp.request.method
Offer/offers, /offers/{partition}, /offers/{partition}/{id}GET, POST, PUT, DELETE
Offer/metadata, /metadata/{partition}, /metadata/{partition}/{id}GET, POST, PUT, DELETE
Discovery/discovery, /discovery/{public_key}GET, POST, PUT, PATCH, DELETE
2.2.6.4.3 rpc.client.call.duration

How long a gRPC call to a Lightning node took.

Source: RPC client metrics — histogram, release candidate. Older tooling may know this metric by its previous name, rpc.client.duration.

AttributePresentValue
rpc.system.nameAlwaysgrpc
rpc.methodAlwaysThe node call, see below
server.address | server.portAlwaysHost and port of the node
rpc.response.status_codeAlwaysThe gRPC status, including OK on success
error.typeOn failure onlyThe gRPC status, set only when the call did not return OK

The methods are fixed:

Noderpc.method
CLNcln.Node/Invoice, cln.Node/ListPeerChannels
LNDlnrpc.Lightning/AddInvoice, lnrpc.Lightning/ChannelBalance