1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
---
title: Filtering sensitive data
layout: gem-single
name: dry-logger
---
When logging in production environments, you often need to filter sensitive information like passwords, API keys, credit card numbers, and personal data from your logs. dry-logger provides a flexible filtering mechanism to redact sensitive data.
## Basic filtering
Use the `filters` option to specify which keys should be filtered from log payloads:
```ruby
logger = Dry.Logger(:my_app,
filters: [:password, :api_key, :ssn]
)
logger.info("User signup",
email: "user@example.com",
password: "secret123",
api_key: "sk_live_1234567890"
)
# email="user@example.com" password="[FILTERED]" api_key="[FILTERED]"
```
Filtered values are replaced with the string `"[FILTERED]"`.
## How filters match
Filters match **exact key names** or **dot-separated paths** for nested data. They do NOT match partial key names:
```ruby
logger = Dry.Logger(:my_app, filters: [:password])
logger.info(password: "secret") # password="[FILTERED]"
logger.info(user_password: "secret") # user_password="secret" (NOT filtered)
logger.info(password_hash: "abc123") # password_hash="abc123" (NOT filtered)
```
To filter keys like `user_password`, you must explicitly list them:
```ruby
logger = Dry.Logger(:my_app, filters: [:password, :user_password, :password_hash])
```
## Nested data filtering
Filters work with nested hashes using dot notation:
```ruby
logger = Dry.Logger(:my_app,
filters: ["user.password", "credit_card.number"]
)
logger.info("Payment processed",
user: {
email: "user@example.com",
password: "secret"
},
credit_card: {
number: "4111111111111111",
expiry: "12/25"
}
)
# user={"email"=>"user@example.com", "password"=>"[FILTERED]"}
# credit_card={"number"=>"[FILTERED]", "expiry"=>"12/25"}
```
## Per-backend filtering
Different backends can have different filters:
```ruby
logger = Dry.Logger(:my_app) do |setup|
# Development logs: minimal filtering
setup.add_backend(
stream: $stdout,
filters: [:password, :api_key]
)
# Production logs: aggressive filtering
setup.add_backend(
stream: "logs/production.log",
formatter: :json,
filters: [
:password, :api_key, :token, :secret,
:ssn, :email, :phone, :address,
:credit_card, :card_number, :cvv
]
)
end
```
## Limitations
### Filters work on structured logs only
Filters operate on structures logs only. This means that when logging an exception, its backtrace and message are **not** filtered. Avoid including sensitive data in exception messages:
```ruby
# DON'T do this - password will appear in logs
raise "Failed to authenticate with password: #{password}"
# DO this instead - password is filtered from payload
logger.error("Failed to authenticate", password: password, error: "auth_failed")
```
### No filtering within arrays
Filters work on hash structures but do not traverse arrays. If your payload contains arrays of hashes, the values inside those arrays won't be filtered:
```ruby
logger = Dry.Logger(:my_app, filters: [:password])
logger.info(
password: "filtered", # Will be filtered
users: [
{name: "Alice", password: "secret"} # password inside array NOT filtered
]
)
```
|