Skip to content

Instantly share code, notes, and snippets.

@quad
Last active September 13, 2024 06:40
Show Gist options
  • Save quad/56c924d655362b2f5c1c70e1c52d805e to your computer and use it in GitHub Desktop.
Save quad/56c924d655362b2f5c1c70e1c52d805e to your computer and use it in GitHub Desktop.
Don't use log levels, use structured logging [DRAFT]

Don't use log levels, use structured logging

I read an argument for only two log levels: INFO and ERROR. I responded essentially saying:

  1. I lightly agree that we could get away with two log levels.
  2. I strongly disagree that we only need two ways to log.

Like the blog post's author, most of systems that I've worked on have had poor logging practices. But unlike the blog post's author, I strongly suspect that's because most logging APIs offer poor affordances. That is to say, their design makes it easy to do the wrong thing.

What follows will be a thought experiment for an alternative logging API design that, in an imaginary world, would be harder to misuse.

But, first, let's review what I mean by "most logging APIs."

Most Logging APIs

log4j (Java)

https://logging.apache.org/log4j/2.x/javadoc/log4j-api/org/apache/logging/log4j/Logger.html

logging (Python)

https://docs.python.org/3/howto/logging.html

tracing (Rust)

https://docs.rs/tracing/latest/tracing/index.html

log (Go)

https://pkg.go.dev/log https://pkg.go.dev/log/slog

Flaws of most logging APIs

  • Too many levels, no guidance on when to use them.
  • Limit support for structured logging (contexts, spans, events)
    • Logs aren't events
  • Hard (or impossible) to compose the conditions for when to log
    • log4j and friends do this in global filters; IMHO policy is easier to maintain and less magic when located close to the mechanism

What do The People want?

  • Raw logs of what's on the wire (debug)
  • Deprecation (warning)
  • is an error recoverable or not? (error / fatal)
  • stack traces (debug or trace if it's available)
  • eisenhower priority (actionable: urgent = error, not urgent = warning, not unactionabe: urgent = info, not urgent = debug)
  • what module is logging
  • excessive logging (sampling)
  • sensitive information (redaction)
  • optional logging depending on success (request-scoped circular buffer)

An imaginary alternative logging API

# Structured logging

logger = Logger.new()
  .record(key=value)          # Custom keys
  .record(log.tags.TAG=true)  # Well-known keys

# Good affordances

def Logger.important(self):
  return self.record(log.tags.IMPORTANT=True)

def Logger.sampled(self):
  return self.record(
    log.tags.SAMPLE_KEY=key(prefix=caller),
    log.tags.SAMPLE_RATE=0.1,
  )

def Logger.error(self, error=None):
  return self.record(
    log.tags.ERROR=True,
    log.tags.ERROR_DETAIL=error,
  )

# Example uses

logger = Logger.new()

logger.log("A normal log")
logger.important().log("way too much detail for normal purposes")
logger.important().error(e).log("handled an error")

with logger.sampled() as logger:
  logger.log("got a request on a high QPS endpoint")
  
  try:
    something()
  except e:
    logger.error(e).log("could not handle this error")
    raise e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment