Redacting Sensitive Data in Go's slog: A Practical Guide…
    Neura MarketNeura Market/DeepSeek
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeek
    CoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    View All Directories
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityTrending
    DeepSeekBlogRedacting Sensitive Data in Go's slog: A Practical Guide with masq
    Back to Blog
    Redacting Sensitive Data in Go's slog: A Practical Guide with masq
    go

    Redacting Sensitive Data in Go's slog: A Practical Guide with masq

    Masayoshi Mizutani January 18, 2026
    0 views

    Logging is essential for debugging, monitoring, and auditing. But here's the catch—logs can...


    title: "Redacting Sensitive Data in Go's slog: A Practical Guide with masq" tags: go,logging,security,slog published: true

    Logging is essential for debugging, monitoring, and auditing. But here's the catch—logs can accidentally expose sensitive data like passwords, API tokens, or personal information. Once sensitive data lands in your logs, removing it becomes a nightmare, especially when logs are immutable by design for compliance reasons.

    In this post, I'll show you how to automatically redact sensitive values from your structured logs using Go's standard log/slog package and my open-source library masq.

    The Problem: Sensitive Data Leaks in Logs

    Consider a typical scenario: you're logging a user struct for debugging purposes.

    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)
    }
    

    Output:

    level=INFO msg="user logged in" user="{ID:u123 Email:alice@example.com APIToken:sk-secret-token-12345}"
    

    Oops. The API token is now in your logs, potentially accessible to anyone with log access, stored for months or years, and nearly impossible to delete.

    slog's Built-in Solution: LogValuer

    Go's slog package (part of the standard library since Go 1.21) provides LogValuer interface for customizing how values appear in logs:

    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)
    }
    

    Output:

    level=INFO msg="token received" token=[REDACTED]
    

    This works for direct values. However, LogValuer doesn't work for struct fields:

    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)
    }
    

    Output (token is exposed!):

    level=INFO msg=credentials creds="{UserID:u123 Token:sk-secret-token-12345}"
    

    When you log a struct, slog uses reflection and bypasses the LogValue() method on nested fields. This is a significant limitation for real-world applications.

    Enter masq: Automatic Deep Redaction

    I built masq to solve this problem. It hooks into slog's ReplaceAttr option and recursively inspects all logged values—including nested structs—to redact sensitive data.

    Basic Usage

    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)
    }
    

    Output:

    {
      "time": "2026-01-18T12:00:00.000Z",
      "level": "INFO",
      "msg": "user registered",
      "user": {
        "ID": "u123",
        "Email": "[FILTERED]"
      }
    }
    

    The email is automatically redacted, even though it's nested inside the struct.

    Redaction Strategies

    masq offers multiple ways to identify sensitive data:

    1. By Custom Type

    Define sensitive data as distinct types and redact them:

    type Password string
    type CreditCard string
    
    masq.New(
        masq.WithType[Password](),
        masq.WithType[CreditCard](),
    )
    

    2. By Struct Tag

    Mark sensitive fields with a struct tag:

    type User struct {
        ID       string
        Password string `masq:"secret"`
        SSN      string `masq:"secret"`
    }
    
    masq.New(masq.WithTag("secret"))
    

    3. By Field Name

    Target specific field names:

    masq.New(
        masq.WithFieldName("Password"),
        masq.WithFieldName("APIKey"),
    )
    

    4. By Field Prefix

    Redact all fields starting with a prefix:

    type Config struct {
        SecretKey      string  // redacted
        SecretToken    string  // redacted
        PublicEndpoint string  // not redacted
    }
    
    masq.New(masq.WithFieldPrefix("Secret"))
    

    5. By Regex Pattern

    Match values against patterns (useful for credit cards, phone numbers, etc.):

    import "regexp"
    
    // Redact potential credit card numbers (16 digits)
    cardPattern := regexp.MustCompile(`\b\d{16}\b`)
    
    masq.New(masq.WithRegex(cardPattern))
    

    6. By String Content

    Redact any value containing a specific string:

    masq.New(masq.WithContain("Bearer "))
    

    Combining Multiple Strategies

    You can combine multiple 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 struct tag
                masq.WithTag("secret"),
    
                // Redact fields with "Secret" prefix
                masq.WithFieldPrefix("Secret"),
    
                // Redact phone numbers
                masq.WithRegex(regexp.MustCompile(`^\+[1-9]\d{10,14}$`)),
            ),
        }),
    )
    

    Real-World Example

    Here's a complete example showing masq in a typical web application context:

    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
        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)
    }
    

    Output:

    {"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]"}}
    

    All sensitive data is automatically redacted while preserving the structure and non-sensitive fields.

    Best Practices

    1. Define custom types for sensitive data: Instead of using string for passwords or tokens, create distinct types like type Password string. This makes redaction explicit and catches issues at compile time.

    2. Use struct tags for external data: When working with third-party structs or database models, use the masq:"secret" tag to mark sensitive fields.

    3. Apply regex patterns carefully: Regex matching runs on every string value, so use specific patterns to avoid performance issues.

    4. Test your redaction: Write tests that verify sensitive data doesn't appear in log output.

    5. Default to redaction: When in doubt, redact. It's easier to remove redaction than to clean up leaked data.

    Limitations

    • Private map fields: masq cannot reliably clone embedded private map types—they become nil. Use struct fields for sensitive data.
    • Performance: Deep inspection has some overhead. For high-throughput systems, consider sampling or async logging.

    Conclusion

    Preventing sensitive data leaks in logs is crucial for security and compliance. While slog's LogValuer helps with direct values, masq extends this protection to nested structs and provides flexible redaction strategies.

    Give masq a try and let me know what you think!


    If you found this helpful, feel free to star the repo on GitHub or share your feedback in the comments below.

    Tags

    gologgingsecurityslog

    Comments

    More Blog

    View all
    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught megemma

    Five Gemma-4 models, one accelerator: what porting E2B 31B to AWS Inferentia2 taught me

    I ported the whole Gemma-4 family — E2B, E4B, 12B, 31B, and the 26B-A4B MoE — to run on...

    X
    xbill
    Hey DEV, I'm Tobore. Let's actually connect.community

    Hey DEV, I'm Tobore. Let's actually connect.

    Hey DEV, I'm Tobore. Let's actually connect. I've been on here for a while now, mostly writing and...

    L
    Laurina Ayarah
    I burned through thousands of AI tokens. Then a friend did it for freeai

    I burned through thousands of AI tokens. Then a friend did it for free

    (yep, kinda clickbait, just for the funsies 😊) At the beginning of the year, I relaunched my...

    P
    Paulo Henrique
    Claude might be saturating your machineai

    Claude might be saturating your machine

    My laptop was sitting idle with the fan at full tilt. Nothing was running that I knew of. The culprit...

    S
    Sidhant Panda
    Automated GitHub Code Reviews Using Google Geminigithubactions

    Automated GitHub Code Reviews Using Google Gemini

    I Built a Thing! TL;DR — Google Gemini-based Pull Request reviews and Issue Triaging for...

    D
    Darren "Dazbo" Lester
    What is an "agentic harness," actually?ai

    What is an "agentic harness," actually?

    I've been hearing the word "harness" thrown around a lot lately. I assumed it just meant "the IDE" or...

    T
    Tilde A. Thurium

    Stay up to date

    Get the latest DeepSeek prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for DeepSeek and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Guides

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions for your business.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this DeepSeek resource

    • Automate Amazon Price Monitoring with Bright Data and Google Sheetsn8n · $9.99 · Related topic
    • Automate Local Event Monitoring and Analysis with Bright Data and OpenAIn8n · $14.99 · Related topic
    • Automate Competitor Product Launch Monitoring with Bright Data and OpenAIn8n · $14.99 · Related topic
    • Automate Competitor Pricing Monitoring with Bright Data and OpenAI in n8nn8n · $14.99 · Related topic
    Browse all workflows