# Logging

> Structured logging helps you understand your application

Encore.rs logs through the standard [`log`](https://docs.rs/log) crate. Install Encore's logger once at startup, and every `log` record is printed as a structured line, with its key-value pairs as fields and the IDs of the current trace and span, so it's linked to the [trace](/docs/rs/observability/tracing) it was written in.

## Setup

Add the `log` crate with its `kv` feature, for key-value pairs:

```toml
-- Cargo.toml --
[dependencies]
encore-dev = { path = "/path/to/encore-dev" }
log = { version = "0.4", features = ["kv"] }
```

Call `encore::log::init()` at the start of `main`:

```rust
-- src/main.rs --
#[tokio::main]
async fn main() {
    encore::initialize();
    encore::log::init();

    // ...
}
```

`init()` installs Encore's logger as the `log` crate's global logger. It's safe to call more than once, but it panics if another global logger is already installed, such as `env_logger`.

## Usage

Use the `log` macros: `error!`, `warn!`, `info!`, `debug!`, and `trace!`. Key-value pairs go before the message, separated from it by a semicolon:

```rust
log::info!(
    order_id = order.id.as_str(),
    customer_id = customer.id.as_str(),
    total = order.total;
    "order created"
);
```

Each pair becomes a field of the log record. Pass variable data as fields rather than interpolating it into the message:

```rust
// Prefer
log::info!(order_id = order_id.as_str(); "order created");

// Avoid
log::info!("order {order_id} created");
```

Interpolated values have to be parsed back out before they can be queried, and they make every message unique, so equivalent events can no longer be grouped or counted.

### Logging errors

Log an error as a field. The `kv` feature captures values that implement `Display` with the `%` sigil:

```rust
if let Err(err) = charge_customer(&customer_id, amount).await {
    log::error!(
        err:% = err,
        customer_id = customer_id.as_str(),
        amount = amount;
        "payment authorization failed"
    );
    return Err(err);
}
```

## What's in a record

Each record is printed to stderr in Encore's structured log format, where `encore run` and Encore's log collection pick it up. A record has:

- The level, message, and time.
- The key-value pairs as fields.
- The record's target as its logger name: by default the module path, such as `my_app::orders`.
- The source file and line, as `code.file.path` and `code.line.number`.
- The trace ID and span ID of the current OpenTelemetry context, if there is one.

The span IDs come from `encore::opentelemetry::Context::current()`. Inside a Pub/Sub subscription handler, Encore sets that context for you. In an HTTP handler, attach your request span's context to the handler's future, as [Tracing](/docs/rs/observability/tracing#parenting-on-your-request-span) shows, and records written while handling the request are linked to its trace.

## Levels

By default, records at `info`, `warn`, and `error` are printed, and `debug` and `trace` records are dropped.

The SDK's own log lines (from the `encore` crate and the runtime) are logged under the `encore.runtime` logger. Their level is controlled with the `RUST_LOG` environment variable, using the same syntax as `env_logger`.

## Using your own logger

If your app installs its own global logger, compose Encore's into it instead of calling `init()`. `encore::log::logger()` returns Encore's logger as a `&'static impl log::Log`, which you can forward records to.

## `log` and `tracing`

Encore's logger reads records from the `log` crate. Events from the [`tracing`](https://docs.rs/tracing) crate's macros aren't log records, so they aren't printed by it. Use `tracing` for spans (see [Tracing](/docs/rs/observability/tracing)) and `log` for log lines, or enable the `tracing` crate's `log-always` feature, which also emits its events as `log` records when a `tracing` subscriber is installed.

## What to log

- A business state transition, such as a subscription activating or an order shipping.
- A decision the application made, together with the inputs that drove it.
- A fallback or retry that allowed an operation to complete.
- A failure, at the boundary that owns the recovery decision.
- A security-relevant or administrative action.

Encore already traces database queries, Redis commands, object storage operations, and Pub/Sub messages. Logs that narrate the same steps ("querying database", "publishing event") duplicate the trace. Use a [metric](/docs/rs/observability/metrics) rather than a log line per event when you only need to know how often something happens.

## Viewing logs

Logs are shown in `encore run`'s output. To read them afterwards, use `encore logs`, which reads the app's local telemetry store, so the app doesn't need to be running:

```shell
$ encore logs                  # the latest session
$ encore logs --errors         # only entries in which something failed
$ encore logs --svc orders --since 10m
```
