Examples

This page shows complete, realistic configurations for common use cases, combining multiple dry-logger features.

Development setup

Maximum readability with colorized output:

require "dry/logger"

# Register a custom colorized template
Dry::Logger.register_template(
  :dev,
  "<gray>%<time>s</gray> <cyan>[%<progname>s]</cyan> " \
  "<yellow>%<severity>s</yellow> %<message>s <blue>%<payload>s</blue>"
)

logger = Dry.Logger(:my_app,
  template: :dev,
  level: :debug
)

logger.info("Server ready", port: 3000, env: "development")
# (colorized output) 2023-10-15 14:40:00 [my_app] INFO Server ready port=3000 env="development"

Production setup

Structured JSON logging with error file and filters:

require "dry/logger"

PRODUCTION_FILTERS = [
  :password,
  :api_key,
  :secret_token,
  :access_token,
  :ssn,
  :credit_card_number
].freeze

logger = Dry.Logger(:my_app) do |setup|
  # Main JSON log file
  setup.add_backend(
    stream: "logs/production.json",
    formatter: :json,
    filters: PRODUCTION_FILTERS
  )

  # Separate error file
  setup.add_backend(
    stream: "logs/errors.json",
    formatter: :json,
    filters: PRODUCTION_FILTERS,
    log_if: :error?
  )
end

logger.info("Request processed",
  user_id: 123,
  action: "update_profile",
  duration_ms: 45,
  password: "secret"  # Filtered
)
# {"progname":"my_app","severity":"INFO","time":"2023-10-15T14:42:30Z","message":"Request processed","user_id":123,"action":"update_profile","duration_ms":45,"password":"[FILTERED]"}

Web applications

Rack/Rails application

Combine different formatters and filters for different log types:

require "dry/logger"

# Define filters for sensitive data
FILTER_PARAMS = [
  :password,
  :password_confirmation,
  :api_key,
  :secret_token,
  :access_token,
  :ssn,
  :credit_card_number
].freeze

logger = Dry.Logger(:rails_app) do |setup|
  # General application logs (string format)
  setup.add_backend(
    stream: "logs/application.log",
    formatter: :string,
    template: :details,
    filters: FILTER_PARAMS
  )

  # HTTP request logs (rack format)
  setup.add_backend(
    stream: "logs/requests.log",
    formatter: :rack,
    filters: FILTER_PARAMS,
    log_if: -> (entry) { entry.key?(:verb) && entry.key?(:path) }
  )

  # Error tracking in JSON
  setup.add_backend(
    stream: "logs/errors.json",
    formatter: :json,
    filters: FILTER_PARAMS,
    log_if: -> (entry) { entry.error? || entry.fatal? }
  )
end

# Application log
logger.info("User authenticated", user_id: 42)

# HTTP request log
logger.info(
  verb: "POST",
  path: "/api/users",
  status: 201,
  elapsed: "23ms",
  ip: "192.168.1.1",
  length: 512,
  params: {name: "John"}
)

# Error log
begin
  raise "Database timeout"
rescue => e
  logger.error(e)
end

# Use in Rails
Rails.logger = logger

API applications

API-specific logging with custom templates and comprehensive filtering:

require "dry/logger"

# API-specific filters
API_FILTERS = [
  # Auth headers
  :authorization,
  :api_key,
  "headers.authorization",
  "headers.x-api-key",

  # Request data
  :password,
  :secret,
  :token,

  # Response data
  "response.access_token",
  "response.refresh_token"
].freeze

# Custom template for API requests
Dry::Logger.register_template(
  :api,
  "%<time>s | %<verb>s %<path>s | Status: %<status>s | %<elapsed>s"
)

logger = Dry.Logger(:api) do |setup|
  # Console output for development
  setup.add_backend(
    stream: $stdout,
    formatter: :string,
    template: :api,
    filters: API_FILTERS
  )

  # JSON logs for aggregation
  setup.add_backend(
    stream: "logs/api.json",
    formatter: :json,
    filters: API_FILTERS
  )
end

logger.info(
  verb: "POST",
  path: "/api/orders",
  status: 201,
  elapsed: "120ms",
  authorization: "Bearer secret"  # Filtered
)
# Console: 2023-10-15 14:50:00 +0000 | POST /api/orders | Status: 201 | 120ms
# JSON: {"progname":"api",...,"authorization":"[FILTERED]"}

Payment processing

PCI-compliant logging with comprehensive filters:

require "dry/logger"

# PCI compliance filters
PAYMENT_FILTERS = [
  # Card data (PCI DSS requirement)
  :card_number,
  :cvv,
  :cvc,
  :expiry,
  :card_holder,
  "payment.card_number",
  "payment.cvv",

  # Billing data
  :billing_address,
  :account_number,
  :routing_number,

  # Customer PII
  :ssn,
  :tax_id,
  :email,
  :phone
].freeze

logger = Dry.Logger(:payment_processor,
  stream: "logs/payments.log",
  formatter: :json,
  filters: PAYMENT_FILTERS
)

logger.info("Payment processed",
  transaction_id: "txn_123",
  amount: 99.99,
  card_number: "4111111111111111",  # Will be filtered
  cvv: "123",                        # Will be filtered
  status: "success"
)
# {"transaction_id":"txn_123","amount":99.99,"card_number":"[FILTERED]","cvv":"[FILTERED]","status":"success"}

Hybrid setup

Different backends for different purposes:

require "dry/logger"

logger = Dry.Logger(:my_app) do |setup|
  # Colorized console for development
  setup.add_backend(
    stream: $stdout,
    formatter: :string,
    template: "<yellow>%<severity>s</yellow> %<message>s <blue>%<payload>s</blue>",
    log_if: -> (entry) { ENV["RACK_ENV"] == "development" }
  )

  # Detailed file logs
  setup.add_backend(
    stream: "logs/app.log",
    formatter: :string,
    template: :details
  )

  # JSON for analysis tools
  setup.add_backend(
    stream: "logs/app.json",
    formatter: :json
  )

  # Separate error file
  setup.add_backend(
    stream: "logs/errors.log",
    formatter: :string,
    template: :details,
    log_if: :error?
  )
end

logger.info("Application started", version: "1.2.3")
# Console: INFO Application started version="1.2.3" (colorized, development only)
# File: [my_app] [INFO] [2023-10-15 14:55:00 +0000] Application started version="1.2.3"
# JSON: {"progname":"my_app","severity":"INFO",...}

Multi-environment configuration

Configure logger based on environment:

require "dry/logger"

def setup_logger(env)
  case env
  when "development"
    Dry.Logger(:my_app,
      template: :dev,
      colorize: true,
      level: :debug
    )
  when "production"
    Dry.Logger(:my_app) do |setup|
      setup.add_backend(
        stream: "logs/production.json",
        formatter: :json,
        filters: production_filters
      )
      setup.add_backend(
        stream: "logs/errors.json",
        formatter: :json,
        filters: production_filters,
        log_if: :error?
      )
    end
  when "test"
    require "stringio"
    Dry.Logger(:my_app,
      stream: StringIO.new,
      level: :warn  # Suppress noise in tests
    )
  end
end

def production_filters
  [:password, :api_key, :secret_token, :ssn, :credit_card_number]
end

logger = setup_logger(ENV.fetch("RACK_ENV", "development"))

octocatEdit on GitHub