rust
44 lines · 9 steps
Redacting sensitive data from logs in Rust
Compile regexes once and validate credit card matches with the Luhn checksum before masking them out of log text.
Explained by
highlit
1use once_cell::sync::Lazy;
2use regex::Regex;
3
4static CREDIT_CARD: Lazy<Regex> = Lazy::new(|| {
5 Regex::new(r"\b(?:\d[ -]*?){13,16}\b").unwrap()
6});
7
8static SSN: Lazy<Regex> = Lazy::new(|| {
9 Regex::new(r"\b\d{3}-\d{2}-\d{4}\b").unwrap()
10});
11
12fn luhn_valid(candidate: &str) -> bool {
13 let digits: Vec<u32> = candidate.chars().filter_map(|c| c.to_digit(10)).collect();
14 if !(13..=16).contains(&digits.len()) {
15 return false;
16 }
17 let sum: u32 = digits
18 .iter()
19 .rev()
20 .enumerate()
21 .map(|(i, &d)| {
22 if i % 2 == 1 {
23 let doubled = d * 2;
24 if doubled > 9 { doubled - 9 } else { doubled }
25 } else {
26 d
27 }
28 })
29 .sum();
30 sum % 10 == 0
31}
32
33pub fn redact(log: &str) -> String {
34 let cc_masked = CREDIT_CARD.replace_all(log, |caps: ®ex::Captures| {
35 let matched = &caps[0];
36 if luhn_valid(matched) {
37 "[REDACTED-CC]".to_string()
38 } else {
39 matched.to_string()
40 }
41 });
42
43 SSN.replace_all(&cc_masked, "[REDACTED-SSN]").into_owned()
44}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Lazy statics let you compile expensive resources like regexes exactly once and share them safely.
- 2A structural regex match isn't proof of a real value — pairing it with a checksum like Luhn cuts false positives.
- 3Passing a closure to replace_all lets each match be inspected and conditionally transformed.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/redacting-sensitive-data-from-logs-in-rust-explained-rust-c11b/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.