A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
81
fork

Configure Feed

Select the types of activity you want to include in your feed.

Standardize Appview Logging #4

open opened by evan.jarrett.net

Standardize Backend Logging with slog#

Goal#

Replace inconsistent fmt.Printf and log.Printf calls with structured logging using Go's built-in log/slog package.

Current State#

The codebase uses multiple logging approaches inconsistently:

fmt.Printf patterns:

fmt.Printf("WARNING: Failed to increment pull count: %v\n", err)
fmt.Printf("DEBUG [oauth/server]: Starting OAuth flow for handle=%s\n", handle)
fmt.Printf("ERROR [oauth/server]: Failed to start auth flow: %v\n", err)

log.Printf patterns:

log.Printf("ERROR: Failed to check hold public flag: %v", err)
log.Printf("✓ Hold service already registered in PDS")
log.Printf("Checking registration status for DID: %s", did)

Problems:

  • No structured logging (hard to parse/query)
  • Inconsistent log levels (WARNING vs Warning vs ERROR)
  • Mixed prefixes (some have brackets, some don't)
  • No context propagation
  • Can't configure log levels dynamically

Use Go's built-in log/slog package for structured, leveled logging.

Benefits:

  • Structured key-value logging
  • Standard log levels (Debug, Info, Warn, Error)
  • Context-aware logging
  • Easy to switch output format (JSON, text)
  • Built-in, no external dependencies

Tasks#

1. Create Logger Initialization (pkg/logging/logger.go)#

Create a new package with centralized logger setup:

package logging

import (
    "log/slog"
    "os"
)

var Logger *slog.Logger

func init() {
    // Default to JSON handler for production
    handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
        Level: getLogLevel(),
    })
    Logger = slog.New(handler)
}

func getLogLevel() slog.Level {
    level := os.Getenv("LOG_LEVEL")
    switch level {
    case "DEBUG":
        return slog.LevelDebug
    case "INFO":
        return slog.LevelInfo
    case "WARN":
        return slog.LevelWarn
    case "ERROR":
        return slog.LevelError
    default:
        return slog.LevelInfo
    }
}

2. Replace fmt.Printf/log.Printf Calls#

Pattern transformations:

// Before
fmt.Printf("WARNING: Failed to increment pull count for %s/%s: %v\n", did, repo, err)

// After
logging.Logger.Warn("failed to increment pull count",
    "did", did,
    "repository", repo,
    "error", err)
// Before
log.Printf("DEBUG [oauth/server]: Starting OAuth flow for handle=%s\n", handle)

// After
logging.Logger.Debug("starting oauth flow",
    "component", "oauth/server",
    "handle", handle)
// Before
fmt.Printf("ERROR [oauth/server]: Failed to start auth flow: %v\n", err)

// After
logging.Logger.Error("failed to start auth flow",
    "component", "oauth/server",
    "error", err)

3. Update Key Files#

Priority files (highest logging volume):

  • pkg/auth/oauth/server.go - OAuth flows
  • pkg/hold/registration.go - Hold registration
  • pkg/appview/middleware/registry.go - Request routing
  • pkg/appview/storage/proxy_blob_store.go - Blob operations
  • pkg/hold/authorization.go - Authorization checks
  • cmd/appview/serve.go - Server startup
  • cmd/hold/main.go - Hold service startup

4. Keep Emojis in Terminal Output#

Terminal-facing output can keep emojis (registration success, etc.):

// This is fine - user-facing CLI output
fmt.Println("✓ Hold service registered successfully!")

// But log it structured too
logging.Logger.Info("hold service registered", "url", publicURL)

5. Add Component/Module Tags#

Use consistent component tags for filtering:

logging.Logger.Info("manifest stored",
    "component", "manifest_store",
    "did", did,
    "repository", repo)

Common components:

  • oauth/server, oauth/client
  • manifest_store, blob_store
  • hold/registration, hold/authorization
  • middleware/registry, middleware/auth
  • jetstream/worker, jetstream/backfill

Testing#

  • Set LOG_LEVEL=DEBUG and verify debug logs appear
  • Set LOG_LEVEL=ERROR and verify only errors appear
  • Check logs are valid JSON (if using JSON handler)
  • Verify all error conditions still log appropriately
  • Ensure no fmt.Printf debugging statements remain

Configuration#

Add to .env.appview.example and .env.hold.example:

# Logging configuration
LOG_LEVEL=INFO  # DEBUG, INFO, WARN, ERROR
LOG_FORMAT=json # json or text

Migration Strategy#

  1. Start with one package (e.g., pkg/auth/oauth/server.go)
  2. Convert all logs in that file
  3. Test thoroughly
  4. Move to next package
  5. Create PR when a logical chunk is complete (don't need to do everything at once)

Files to Create#

  • pkg/logging/logger.go - Logger initialization

Files to Modify#

All .go files with fmt.Printf or log.Printf (30 files, but prioritize high-traffic paths first)

Notes#

  • Keep terminal user output separate from logs (use fmt.Println for CLI messages)
  • Structured logs make debugging production issues much easier
  • JSON logs integrate well with log aggregation tools (CloudWatch, Datadog, etc.)
  • Consider adding request IDs for tracing in future work
sign up or login to add to the discussion
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:pddp4xt5lgnv2qsegbzzs4xg/sh.tangled.repo.issue/3m35zqw4xvz22