Skip to content
Merged

450 #457

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 37 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -628,12 +628,15 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED");
<summary><b>Environment detection with DisplayMode</b></summary>

`DisplayMode` detects the deployment environment (`Prod`, `Local` or
`Staging`) so your code can branch on it. `DisplayMode::current()` resolves
the mode in this order and caches the result on first access:
`Staging`) and drives the `Display` output of `AppError`.
`DisplayMode::current()` resolves the mode in this order and caches the
result on first access (the environment is read once per process):

1. `MASTERROR_ENV` environment variable (`prod`, `local`, or `staging`)
1. `MASTERROR_ENV` environment variable (`prod`/`production`,
`local`/`dev`/`development`, or `staging`/`stage`)
2. `KUBERNETES_SERVICE_HOST` presence (selects `Prod`)
3. Build configuration (`debug_assertions` → `Local`, release → `Prod`)
3. Build configuration (`debug_assertions` → `Local`, release → `Prod`;
the only detection step available without the `std` feature)

~~~rust
use masterror::DisplayMode;
Expand All @@ -647,28 +650,24 @@ match mode {
}
~~~

Note: `Display` for `AppError` does not consult `DisplayMode` yet — the
output is identical in all modes. The only formatting branch today is the
`colored` feature described below; mode-aware formatting is not wired up.
`Display` for `AppError` dispatches on the detected mode:

**Colored Terminal Output:**

Enable the `colored` feature for enhanced terminal output. It applies
whenever the feature is enabled, regardless of the detected mode:
| Mode | Layout |
|------|--------|
| `Local` | Multi-line human-readable report: kind, code, message, source chain, metadata |
| `Prod` | Compact single-line JSON: `kind`, `code`, optional `message`, metadata |
| `Staging` | Same JSON as `Prod` plus a `source_chain` array |

~~~toml
[dependencies]
masterror = { version = "0.28.0", features = ["colored"] }
~~~
All layouts apply per-field redaction policies: `Redact` renders the
`[REDACTED]` placeholder, `Hash` renders a SHA-256 hex digest, `Last4`
keeps only the trailing characters (fields that cannot be masked are
omitted). Set `MASTERROR_ENV=local` on any host to force the
human-readable layout, e.g. when debugging inside Kubernetes.

Without the `colored` feature, errors display their `AppErrorKind` label:
~~~
NotFound
~~~
In `Local` mode the output looks like:

With `colored`, a full multi-line format with context:
~~~
Error: NotFound
Error: Not found
Code: NOT_FOUND
Message: User not found

Expand All @@ -677,6 +676,23 @@ Context:
request_id: abc-def
~~~

In `Prod` mode the same error renders as:

~~~
{"kind":"NotFound","code":"NOT_FOUND","message":"User not found","metadata":{"request_id":"abc-def","user_id":12345}}
~~~

**Colored Terminal Output:**

Enable the `colored` feature for ANSI styling with automatic TTY
detection. It affects only the `Local` layout; `Prod` and `Staging` JSON
never contains escape sequences:

~~~toml
[dependencies]
masterror = { version = "0.28.0", features = ["colored"] }
~~~

</details>

<div align="right">
Expand Down
58 changes: 37 additions & 21 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,12 +623,15 @@ assert_eq!(problem.grpc.expect("grpc").name, "UNAUTHENTICATED");
<summary><b>Environment detection with DisplayMode</b></summary>

`DisplayMode` detects the deployment environment (`Prod`, `Local` or
`Staging`) so your code can branch on it. `DisplayMode::current()` resolves
the mode in this order and caches the result on first access:
`Staging`) and drives the `Display` output of `AppError`.
`DisplayMode::current()` resolves the mode in this order and caches the
result on first access (the environment is read once per process):

1. `MASTERROR_ENV` environment variable (`prod`, `local`, or `staging`)
1. `MASTERROR_ENV` environment variable (`prod`/`production`,
`local`/`dev`/`development`, or `staging`/`stage`)
2. `KUBERNETES_SERVICE_HOST` presence (selects `Prod`)
3. Build configuration (`debug_assertions` → `Local`, release → `Prod`)
3. Build configuration (`debug_assertions` → `Local`, release → `Prod`;
the only detection step available without the `std` feature)

~~~rust
use masterror::DisplayMode;
Expand All @@ -642,28 +645,24 @@ match mode {
}
~~~

Note: `Display` for `AppError` does not consult `DisplayMode` yet — the
output is identical in all modes. The only formatting branch today is the
`colored` feature described below; mode-aware formatting is not wired up.
`Display` for `AppError` dispatches on the detected mode:

**Colored Terminal Output:**

Enable the `colored` feature for enhanced terminal output. It applies
whenever the feature is enabled, regardless of the detected mode:
| Mode | Layout |
|------|--------|
| `Local` | Multi-line human-readable report: kind, code, message, source chain, metadata |
| `Prod` | Compact single-line JSON: `kind`, `code`, optional `message`, metadata |
| `Staging` | Same JSON as `Prod` plus a `source_chain` array |

~~~toml
[dependencies]
masterror = { version = "{{CRATE_VERSION}}", features = ["colored"] }
~~~
All layouts apply per-field redaction policies: `Redact` renders the
`[REDACTED]` placeholder, `Hash` renders a SHA-256 hex digest, `Last4`
keeps only the trailing characters (fields that cannot be masked are
omitted). Set `MASTERROR_ENV=local` on any host to force the
human-readable layout, e.g. when debugging inside Kubernetes.

Without the `colored` feature, errors display their `AppErrorKind` label:
~~~
NotFound
~~~
In `Local` mode the output looks like:

With `colored`, a full multi-line format with context:
~~~
Error: NotFound
Error: Not found
Code: NOT_FOUND
Message: User not found

Expand All @@ -672,6 +671,23 @@ Context:
request_id: abc-def
~~~

In `Prod` mode the same error renders as:

~~~
{"kind":"NotFound","code":"NOT_FOUND","message":"User not found","metadata":{"request_id":"abc-def","user_id":12345}}
~~~

**Colored Terminal Output:**

Enable the `colored` feature for ANSI styling with automatic TTY
detection. It affects only the `Local` layout; `Prod` and `Staging` JSON
never contains escape sequences:

~~~toml
[dependencies]
masterror = { version = "{{CRATE_VERSION}}", features = ["colored"] }
~~~

</details>

<div align="right">
Expand Down
9 changes: 5 additions & 4 deletions src/app_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
//!
//! [`AppError`] is a thin, framework-agnostic wrapper around a canonical
//! error taxonomy [`AppErrorKind`] plus an optional public-facing message.
//! Without the `colored` feature, the `Display` for `AppError` prints only
//! the kind, not the message, to keep logs and errors concise by default.
//! With `colored` enabled, `Display` renders a multi-line report (kind, code,
//! message, source chain, metadata).
//! The `Display` implementation dispatches on [`DisplayMode::current`]:
//! `Local` renders a multi-line human-readable report (kind, code, message,
//! source chain, metadata), while `Prod` and `Staging` render compact JSON.
//! The `colored` feature adds ANSI styling to the `Local` layout only.
//!
//! ## Design
//!
Expand Down Expand Up @@ -75,6 +75,7 @@ mod context;
mod core;
mod inline_vec;
mod metadata;
pub(crate) mod redaction;

pub use core::{AppError, AppResult, DisplayMode, Error, ErrorChain, MessageEditPolicy};
#[cfg(all(test, feature = "backtrace"))]
Expand Down
14 changes: 9 additions & 5 deletions src/app_error/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ pub mod telemetry;
/// - `CapturedBacktrace` type alias
pub mod types;

/// Environment detection and alternative formatting helpers.
/// Environment detection and mode-aware `Display` layouts.
///
/// Provides [`DisplayMode`], which detects the deployment environment from
/// `MASTERROR_ENV`, `KUBERNETES_SERVICE_HOST` or build configuration and
/// caches the result. The `Display` implementation for `Error` does not
/// consult this mode; it is a detection API for callers to choose their own
/// formatting.
/// caches the result once per process. The `Display` implementation for
/// `Error` dispatches on this mode: `Local` renders a multi-line
/// human-readable report, while `Prod` and `Staging` render compact JSON
/// with redaction-aware metadata.
pub mod display;

#[cfg(all(test, feature = "backtrace"))]
Expand Down Expand Up @@ -183,9 +184,12 @@ mod tests {

#[test]
fn error_display_shows_kind() {
let _guard = display::force_display_mode(DisplayMode::Local);
let err = Error::new(AppErrorKind::Internal, "test");
let display = format!("{}", err);
assert!(!display.is_empty());
assert!(display.contains("Error: Internal server error"));
assert!(display.contains("Code: INTERNAL"));
assert!(display.contains("Message: test"));
}

#[cfg(feature = "std")]
Expand Down
Loading
Loading