Skip to content

Colored Migration Guide

RAprogramm edited this page Oct 20, 2025 · 1 revision

Migration Guide

Upgrading to colored terminal output

This guide helps you migrate existing masterror applications to use the new colored terminal output feature.

Table of Contents

Overview

The colored feature was introduced in masterror 0.24 as an optional, zero-cost feature. Migration is straightforward and non-breaking.

Key facts:

  • Opt-in: Colors are disabled by default
  • Zero-cost: No overhead when feature is disabled
  • Backward compatible: Existing code continues to work
  • Same API: No code changes required
  • Automatic detection: Colors only appear when appropriate

Breaking Changes

None. The colored feature is fully backward compatible.

Existing applications continue to work exactly as before. Colors are only enabled when:

  1. You opt into the colored feature
  2. stderr is a TTY
  3. Environment variables allow it (NO_COLOR not set, TERM not dumb)

Migration Steps

Step 1: Update Cargo.toml

Add the colored feature to your dependency:

Before:

[dependencies]
masterror = "0.24"

After:

[dependencies]
masterror = { version = "0.24", features = ["colored"] }

Or using cargo add:

cargo add masterror --features colored

Step 2: Test Locally

Run your application in a terminal:

cargo run

Colored errors should now appear automatically.

Verify colors work:

# Should show colors
cargo run --features colored

# Should NOT show colors (piped)
cargo run --features colored | cat

Step 3: Update CI/CD

Ensure your CI doesn't show ANSI codes in logs:

GitHub Actions .github/workflows/ci.yml:

- name: Run tests
  run: cargo test --features colored
  env:
    NO_COLOR: "1"  # Disable colors in CI logs

GitLab CI .gitlab-ci.yml:

test:
  script:
    - cargo test --features colored
  variables:
    NO_COLOR: "1"  # Or omit to see colors in GitLab

Step 4: Update Tests

If you have tests that assert on error strings, update them:

Before (exact match):

#[test]
fn test_error_display() {
    let err = AppError::internal("test");
    assert_eq!(err.to_string(), "Internal server error");
}

After (contains match):

#[test]
fn test_error_display() {
    let err = AppError::internal("test");
    let output = err.to_string();

    // Works with or without colors
    assert!(output.contains("Internal server error"));
}

Or disable colors in tests:

#[test]
fn test_error_display() {
    std::env::set_var("NO_COLOR", "1");

    let err = AppError::internal("test");
    assert_eq!(err.to_string(), "Error: Internal server error\nCode: INTERNAL");
}

Step 5: Deploy to Production

Option A: Gradual Rollout with Colors Disabled

Deploy with colors disabled initially:

export NO_COLOR=1
./my-app

Monitor for issues, then remove NO_COLOR to enable colors.

Option B: Deploy with Colors Enabled

If you log to files, ensure colors are disabled for file output:

use tracing_subscriber::fmt;

fn init_logging() {
    let file_layer = fmt::layer()
        .with_writer(get_file_writer())
        .with_ansi(false);  // Disable for files

    let stdout_layer = fmt::layer()
        .with_ansi(true);   // Enable for terminal

    tracing_subscriber::registry()
        .with(file_layer)
        .with(stdout_layer)
        .init();
}

Backward Compatibility

No Feature Flag: Works Identically to Before

[dependencies]
masterror = "0.24"  # No colored feature

Result: Zero overhead, plain text errors (same as 0.23).

With Feature Flag: Automatic Colors

[dependencies]
masterror = { version = "0.24", features = ["colored"] }

Result: Colored errors in terminals, plain text when piped.

Cargo Feature Resolution

If your dependency tree has mixed feature flags:

# Your app
[dependencies]
masterror = { version = "0.24", features = ["colored"] }

# Your library dependency
[dependencies]
masterror = "0.24"  # No colored feature

Result: Cargo's feature unification enables colored for the entire build. Your library will also have colored errors.

To prevent this, libraries should avoid enabling colored by default. Only end-user applications should opt into colors.

Testing Your Migration

Manual Testing

# 1. Interactive terminal - should show colors
cargo run --features colored

# 2. Piped output - should NOT show colors
cargo run --features colored | cat

# 3. NO_COLOR set - should NOT show colors
NO_COLOR=1 cargo run --features colored

# 4. TERM=dumb - should NOT show colors
TERM=dumb cargo run --features colored

# 5. Stderr redirect - should NOT show colors
cargo run --features colored 2>error.log
cat error.log  # Check for ANSI codes

Automated Testing

#[cfg(test)]
mod tests {
    use masterror::AppError;

    #[test]
    fn error_output_compatible() {
        std::env::set_var("NO_COLOR", "1");

        let err = AppError::internal("test");
        let output = err.to_string();

        // Core content is preserved
        assert!(output.contains("Internal server error"));

        // No ANSI codes
        assert!(!output.contains("\x1b["));
    }

    #[test]
    fn error_with_context_compatible() {
        std::env::set_var("NO_COLOR", "1");

        let root = std::io::Error::other("root cause");
        let err = AppError::internal("test").with_context(root);
        let output = err.to_string();

        assert!(output.contains("Internal server error"));
        assert!(output.contains("root cause"));
    }

    #[test]
    fn error_with_metadata_compatible() {
        use masterror::field;

        std::env::set_var("NO_COLOR", "1");

        let err = AppError::internal("test")
            .with_field(field::str("key", "value"));
        let output = err.to_string();

        assert!(output.contains("Internal server error"));
        assert!(output.contains("key"));
        assert!(output.contains("value"));
    }
}

Integration Testing

# Create test script
cat > test_colors.sh << 'EOF'
#!/bin/bash
set -e

echo "Testing colored output..."

# Test 1: TTY detection
OUTPUT=$(cargo run --example colored_cli --features colored 2>&1)
if echo "$OUTPUT" | grep -q "Error:"; then
    echo "✓ Basic output works"
else
    echo "✗ Output missing"
    exit 1
fi

# Test 2: NO_COLOR respected
OUTPUT=$(NO_COLOR=1 cargo run --example colored_cli --features colored 2>&1)
if echo "$OUTPUT" | grep -qv $'\\x1b\\['; then
    echo "✓ NO_COLOR respected"
else
    echo "✗ Colors present with NO_COLOR"
    exit 1
fi

# Test 3: Piped output has no colors
OUTPUT=$(cargo run --example colored_cli --features colored 2>&1 | cat)
if echo "$OUTPUT" | grep -qv $'\\x1b\\['; then
    echo "✓ Piped output has no colors"
else
    echo "✗ Colors present in piped output"
    exit 1
fi

echo "All tests passed!"
EOF

chmod +x test_colors.sh
./test_colors.sh

Rollback Strategy

If you encounter issues after migration, you can easily rollback.

Rollback Option 1: Disable Colors via Environment

export NO_COLOR=1
./my-app

Pros: Instant, no code changes, no redeployment Cons: Colors disabled for all users

Rollback Option 2: Remove Feature Flag

[dependencies]
masterror = "0.24"  # Remove colored feature
cargo build --release
./deploy.sh

Pros: Complete rollback to pre-colored behavior Cons: Requires rebuild and redeployment

Rollback Option 3: Downgrade Version

[dependencies]
masterror = "0.23"  # Previous version

Pros: 100% identical to previous deployment Cons: Lose any other 0.24 improvements

Common Migration Scenarios

Scenario 1: CLI Application

Before:

[dependencies]
masterror = "0.23"

After:

[dependencies]
masterror = { version = "0.24", features = ["colored"] }

Impact: Users see colored errors in terminal, improving UX.

Scenario 2: Web Server

Before:

[dependencies]
masterror = { version = "0.23", features = ["axum"] }

After:

[dependencies]
masterror = { version = "0.24", features = ["axum", "colored"] }

Configuration:

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        // Log colored errors to terminal
        error!("Request error: {}", self);

        // Return clean JSON to HTTP clients
        let body = Json(json!({
            "error": {
                "code": self.code().to_string(),
                "message": self.message().unwrap_or("Error"),
            }
        }));

        (self.kind().status_code(), body).into_response()
    }
}

Impact: Server logs are colored, HTTP responses remain clean JSON.

Scenario 3: Library

Recommendation: Do NOT enable colored feature in libraries.

# Library Cargo.toml
[dependencies]
masterror = "0.24"  # No colored feature

Rationale: Let end-user applications decide whether to enable colors.

Documentation for library users:

## Optional Colored Output

To enable colored error output in your application:

\`\`\`toml
[dependencies]
my-library = "1.0"
masterror = { version = "0.24", features = ["colored"] }
\`\`\`

Scenario 4: Docker Application

Dockerfile:

FROM rust:1.75 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release --features colored

FROM debian:bookworm-slim
COPY --from=builder /app/target/release/myapp /usr/local/bin/

# Enable colors when running interactively
CMD ["myapp"]

docker-compose.yml:

services:
  app:
    image: myapp
    tty: true        # Enable TTY for colors
    stdin_open: true
    environment:
      - RUST_LOG=info

Run:

# Interactive mode - colors enabled
docker run -it myapp

# Daemon mode - colors disabled automatically
docker run -d myapp

Before-and-After Comparison

Before (0.23 or 0.24 without colored)

Error: Internal server error
Code: INTERNAL
Message: Database connection failed

  Caused by: Connection timeout

After (0.24 with colored)

In terminal:

Error: Internal server error    ← Red
Code: INTERNAL                  ← Cyan
Message: Database connection failed  ← Bright white

  Caused by: Connection timeout  ← Dimmed

When piped (identical to before):

Error: Internal server error
Code: INTERNAL
Message: Database connection failed

  Caused by: Connection timeout

Migration Checklist

  • Update Cargo.toml to add colored feature
  • Run cargo test to ensure tests pass
  • Update tests that assert on error strings
  • Add NO_COLOR=1 to CI workflows
  • Test locally: interactive, piped, NO_COLOR, TERM=dumb
  • Review logging configuration (separate file and stdout)
  • Test in staging environment
  • Monitor logs after deployment
  • Document colored output in user-facing docs

Need Help?

If you encounter issues during migration:

  1. Check Troubleshooting Guide
  2. Review Integration Guide
  3. Open an issue: https://github.com/RAprogramm/masterror/issues

Related Pages:


Previous: Troubleshooting | Back to: Colored Terminal Output

Clone this wiki locally