Go의 slog에서 민감한 데이터 가리기: masq와 함께하는 실용 가이드
I’m happy to translate the article for you, but I need the actual text you’d like translated. Could you please paste the content (or the portion you want translated) here? Once I have the text, I’ll provide a Korean translation while preserving the original formatting, markdown syntax, and any code blocks or URLs.
문제: 로그에서 민감한 데이터 유출
일반적인 상황을 생각해 보세요: 디버깅을 위해 User 구조체를 로그에 남기고 있습니다.
type User struct {
ID string
Email string
APIToken string
}
func main() {
user := User{
ID: "u123",
Email: "alice@example.com",
APIToken: "sk-secret-token-12345",
}
slog.Info("user logged in", "user", user)
}
출력
level=INFO msg="user logged in" user="{ID:u123 Email:alice@example.com APIToken:sk-secret-token-12345}"
앗. 이제 API 토큰이 로그에 남아 로그 접근 권한이 있는 누구든지 볼 수 있게 되었으며, 수개월·수년 동안 보관될 수 있고 삭제하기가 거의 불가능합니다.
slog의 내장 솔루션: LogValuer
Go의 slog 패키지(Go 1.21부터 표준 라이브러리)에서는 값이 로그에 어떻게 표시될지를 커스터마이징할 수 있도록 LogValuer 인터페이스를 제공합니다:
type APIToken string
func (APIToken) LogValue() slog.Value {
return slog.StringValue("[REDACTED]")
}
func main() {
token := APIToken("sk-secret-token-12345")
slog.Info("token received", "token", token)
}
출력
level=INFO msg="token received" token=[REDACTED]
이는 직접 값에 대해서는 정상적으로 동작합니다. 하지만 LogValuer는 구조체 필드에 대해서는 동작하지 않습니다:
type APIToken string
func (APIToken) LogValue() slog.Value {
return slog.StringValue("[REDACTED]")
}
type Credentials struct {
UserID string
Token APIToken
}
func main() {
creds := Credentials{
UserID: "u123",
Token: "sk-secret-token-12345",
}
slog.Info("credentials", "creds", creds)
}
출력 (토큰이 노출됨!)
level=INFO msg=credentials creds="{UserID:u123 Token:sk-secret-token-12345}"
구조체를 로그에 기록할 때 slog는 리플렉션을 사용하고, 중첩된 필드에 정의된 LogValue() 메서드를 우회합니다. 이 제한 때문에 실제 애플리케이션에서 민감한 데이터를 보호하기가 어렵습니다.
masq: 자동 깊은 마스킹
masq는 slog의 ReplaceAttr 옵션에 연결되어 중첩 구조체를 포함한 모든 로그 값들을 재귀적으로 검사하여 민감한 데이터를 마스킹합니다.
기본 사용법
package main
import (
"log/slog"
"os"
"github.com/m-mizutani/masq"
)
type EmailAddr string
type User struct {
ID string
Email EmailAddr
}
func main() {
// Create a logger with masq redaction
logger := slog.New(
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: masq.New(masq.WithType[EmailAddr]()),
}),
)
user := User{
ID: "u123",
Email: "alice@example.com",
}
logger.Info("user registered", "user", user)
}
예시 출력
{
"time": "2026-01-18T12:00:00.000Z",
"level": "INFO",
"msg": "user registered",
"user": {
"ID": "u123",
"Email": "[FILTERED]"
}
}
이메일 주소는 중첩 구조체에 포함되어 있더라도 자동으로 마스킹됩니다.
Source: …
마스킹 전략
masq는 민감한 데이터를 식별하고 마스킹하는 여러 방법을 제공합니다.
1. 사용자 정의 타입별
민감한 데이터를 별개의 타입으로 정의하고 마스킹합니다:
type Password string
type CreditCard string
masq.New(
masq.WithType[Password](),
masq.WithType[CreditCard](),
)
2. 구조체 태그별
구조체 태그를 사용해 민감한 필드를 표시합니다:
type User struct {
ID string
Password string `masq:"secret"`
SSN string `masq:"secret"`
}
masq.New(masq.WithTag("secret"))
3. 필드 이름별
특정 필드 이름을 대상으로 합니다:
masq.New(
masq.WithFieldName("Password"),
masq.WithFieldName("APIKey"),
)
4. 필드 접두사별
주어진 접두사로 시작하는 모든 필드를 마스킹합니다:
type Config struct {
SecretKey string // redacted
SecretToken string // redacted
PublicEndpoint string // not redacted
}
masq.New(masq.WithFieldPrefix("Secret"))
5. 정규식 패턴별
정규식에 맞는 값을 찾아 마스킹합니다(신용카드 번호, 전화번호 등에 유용합니다):
import "regexp"
// 잠재적인 신용카드 번호(16자리)를 마스킹
cardPattern := regexp.MustCompile(`\b\d{16}\b`)
masq.New(masq.WithRegex(cardPattern))
6. 문자열 내용별
특정 문자열을 포함하는 모든 값을 마스킹합니다:
masq.New(masq.WithContain("Bearer "))
여러 전략 결합
You can combine any number of redaction rules:
logger := slog.New(
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: masq.New(
// Redact by type
masq.WithType[Password](),
masq.WithType[APIToken](),
// Redact by tag
masq.WithTag("secret"),
// Redact by field name
masq.WithFieldName("SSN"),
// Redact by prefix
masq.WithFieldPrefix("Secret"),
// Redact by regex
masq.WithRegex(regexp.MustCompile(`\b\d{16}\b`)),
// Redact by string content
masq.WithContain("Bearer "),
),
}),
)
이제 모든 로그 항목이 구성된 모든 필터를 통과하므로, 얼마나 깊게 중첩되었든 민감한 정보가 누출되지 않도록 보장합니다.
TL;DR
- Problem:
slog의LogValuer는 중첩된 필드를 마스킹할 수 없습니다. - Solution: masq를 사용하여 로그 값들을 재귀적으로 검사하고 필터링합니다.
- How:
masq.New(...)를slog.HandlerOptions.ReplaceAttr에 연결하고, 하나 이상의 식별 전략(타입, 태그, 필드 이름, 접두사, 정규식, 문자열 내용)을 선택합니다.
한번 시도해 보세요, 그리고 로그를 깔끔하고 규정에 맞게 유지하세요!
실제 예시
다음은 일반 웹 애플리케이션 컨텍스트에서 masq를 보여주는 완전한 예시입니다:
package main
import (
"log/slog"
"os"
"regexp"
"github.com/m-mizutani/masq"
)
type (
Password string
AuthToken string
)
type LoginRequest struct {
Username string
Password Password
}
type UserSession struct {
UserID string `masq:"pii"`
AuthToken AuthToken
Email string `masq:"pii"`
PhoneNumber string
}
func main() {
// Configure comprehensive redaction
logger := slog.New(
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: masq.New(
masq.WithType[Password](),
masq.WithType[AuthToken](),
masq.WithTag("pii"),
masq.WithRegex(regexp.MustCompile(`^\+[1-9]\d{10,14}$`)), // phone numbers
),
}),
)
slog.SetDefault(logger)
// Simulate login flow
req := LoginRequest{
Username: "alice",
Password: "super-secret-password",
}
slog.Info("login attempt", "request", req)
session := UserSession{
UserID: "u123",
AuthToken: "tok_abc123xyz",
Email: "alice@example.com",
PhoneNumber: "+14155551234",
}
slog.Info("session created", "session", session)
}
출력
{"time":"2026-01-18T12:00:00.000Z","level":"INFO","msg":"login attempt","request":{"Username":"alice","Password":"[FILTERED]"}}
{"time":"2026-01-18T12:00:00.000Z","level":"INFO","msg":"session created","session":{"UserID":"u123","AuthToken":"[FILTERED]","Email":"[FILTERED]","PhoneNumber":"[FILTERED]"}}
모든 민감한 데이터는 자동으로 마스킹되며 구조와 비민감 필드는 그대로 유지됩니다.
모범 사례
- 민감한 데이터에 대한 사용자 정의 타입 정의 – 비밀번호나 토큰에 일반
string을 사용하는 대신, 별도의 타입(e.g.,type Password string)을 만들세요. 이렇게 하면 마스킹이 명시적이며 컴파일 시점에 문제를 잡을 수 있습니다. - 외부 데이터에 struct 태그 사용 – 서드파티 struct나 데이터베이스 모델을 사용할 때, 민감한 필드를 표시하기 위해
masq:"secret"태그를 추가하세요. - 정규식 패턴을 신중히 적용 – 정규식 매칭은 모든 문자열 값에 대해 실행되므로, 성능 문제를 피하기 위해 구체적인 패턴을 사용하세요.
- 마스킹을 테스트 – 민감한 데이터가 로그 출력에 나타나지 않음을 검증하는 테스트를 작성하세요.
- 기본적으로 마스킹 적용 – 확신이 서지 않을 때는 마스킹하세요. 마스킹을 제거하는 것이 유출된 데이터를 정리하는 것보다 쉽습니다.
제한 사항
-
프라이빗 맵 필드 –
masq는 내장된 프라이빗 맵 타입을 신뢰성 있게 복제하지 못하며,nil이 됩니다.
민감한 데이터는 구조체 필드를 사용하세요. -
성능 – 깊은 검사는 약간의 오버헤드를 추가합니다.
고처리량 시스템의 경우 샘플링이나 비동기 로깅을 고려하세요.
결론
로그에서 민감한 데이터 유출을 방지하는 것은 보안 및 규정 준수에 필수적입니다. slog의 LogValuer가 직접 값에 도움이 되는 반면, masq는 중첩 구조체까지 보호를 확장하고 유연한 마스킹 전략을 제공합니다.
masq 를 사용해 보고 의견을 알려 주세요!
이 내용이 도움이 되었다면, GitHub에서 레포지토리에 ⭐를 눌러 주시거나 아래 댓글에 피드백을 공유해 주세요.