서비스 기초 연습 문서의 문제 3 풀이 코드다.
//! 서비스 기초 연습 p3: 주문 처리 함수에 span을 달되 카드 번호는 빼고, 처리 결과(status)는 나중에 span에 기록한다.
//! JSON 로그를 붙잡아 한 주문의 로그만 골라내고, 카드 번호가 어디에도 없는지 확인한다.
//! 변형: fields(status = Empty)를 빼고 record하면 아무 오류 없이 status가 빠진다.
//! 의존성: tracing = "0.1", tracing-subscriber = { version = "0.3", features = ["json"] }, serde_json = "1"
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/45_service-basics-practice_p3.rs"
use std::io;
use std::sync::{Arc, Mutex};
use serde_json::Value;
use tracing::field::Empty;
use tracing::{Span, info, instrument, warn};
#[derive(Clone, Default)]
struct Buf(Arc<Mutex<Vec<u8>>>);
impl io::Write for Buf {
fn write(&mut self, b: &[u8]) -> io::Result<usize> {
self.0.lock().expect("lock").extend_from_slice(b);
Ok(b.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// card는 skip으로 span 필드에서 뺀다. status는 지금은 비워 두고(Empty) 나중에 채운다.
#[instrument(skip(card), fields(status = Empty))]
fn handle_order(order_id: u64, amount: u64, card: &str) -> bool {
info!(last4 = &card[card.len() - 4..], "charging");
let ok = amount <= 1_000;
Span::current().record("status", if ok { "paid" } else { "declined" });
if ok {
info!("order done");
} else {
warn!(amount, "limit exceeded");
}
ok
}
/// 변형: status를 미리 선언하지 않았다. record는 오류 없이 아무 일도 하지 않는다.
#[instrument(skip(card))]
fn handle_order_undeclared(order_id: u64, amount: u64, card: &str) -> bool {
let ok = amount <= 1_000 && !card.is_empty();
Span::current().record("status", if ok { "paid" } else { "declined" });
info!("order done");
ok
}
fn main() {
let buf = Buf::default();
let w = buf.clone();
let sub = tracing_subscriber::fmt()
.json()
.with_writer(move || w.clone())
.finish();
tracing::subscriber::with_default(sub, || {
handle_order(1, 500, "4111111111111111");
handle_order(2, 5_000, "5500000000000004");
handle_order_undeclared(3, 500, "4111111111111111");
});
let text = String::from_utf8(buf.0.lock().expect("lock").clone()).expect("utf8");
let lines: Vec<Value> = text
.lines()
.map(|l| serde_json::from_str(l).expect("json"))
.collect();
let order2: Vec<&Value> = lines
.iter()
.filter(|l| l["span"]["order_id"] == 2)
.collect();
for l in &order2 {
println!("{}", serde_json::to_string(l).expect("json"));
}
let order3: Vec<&Value> = lines
.iter()
.filter(|l| l["span"]["order_id"] == 3)
.collect();
println!("변형(선언 안 함): {}", order3[0]["span"]);
assert_eq!(order3.len(), 1);
assert!(order3[0]["span"].get("status").is_none());
assert_eq!(lines.len(), 5);
assert_eq!(order2.len(), 2);
assert!(
order2[0]["span"].get("status").is_none(),
"기록 전 이벤트에는 status가 없다"
);
assert_eq!(order2[1]["span"]["status"], "declined");
assert_eq!(order2[1]["fields"]["amount"], 5000);
assert_eq!(order2[0]["fields"]["last4"], "0004");
assert!(!text.contains("4111111111111111") && !text.contains("5500000000000004"));
println!("ALL CHECKS PASSED");
}