Skip to content
/ owl Public

Production-grade Go observability library with structured errors, logging, metrics, tracing, and health checks

License

Notifications You must be signed in to change notification settings

ahadiihsan/owl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Owl πŸ¦‰

Production-Grade Observability Toolkit for Go

owl is a unified observability library designed to simplify error handling, logging, metrics, and distributed tracing in Go applications. It provides a cohesive set of types and middleware to ensure your services are production-ready by default.

πŸš€ Features

  • Unified Error Handling: owl.Error separates internal debug messages (for logs) from public-safe messages (for API responses).
  • Structured Logging: Built-in slog adapter with support for context propagation and sanitization.
  • OpenTelemetry Metrics: Decoupled OTelAdapter that works with any OpenTelemetry MeterProvider (Prometheus, OTLP, etc.).
  • Middleware: robust HTTP and gRPC interceptors for:
    • Request Logging (latency, status, method)
    • Panic Recovery
    • Distributed Trace Injection/Extraction (W3C TraceContext)
    • Automatic Error Hydration
  • Production Ready: Handles numeric status codes, safe defaults, and nil-safe interfaces.

πŸ“¦ Installation

go get github.com/myuser/owl

πŸ›  Usage Guide

1. Error Handling

Stop returning simple strings. Use owl.Problem to return semantic errors with context.

package main

import (
    "github.com/myuser/owl"
)

func GetUser(id string) error {
    if id == "" {
        // Simple 400 Bad Request
        return owl.Problem(owl.Invalid, owl.WithMsg("user id cannot be empty"))
    }

    // ... database logic fails ...
    err := db.Find(id)
    if err != nil {
        // Return 500 Internal Error
        // - Msg: "db connection failed" (Logged Internally)
        // - SafeMsg: "Something went wrong" (Returned to User)
        // - Err: Wrapped original error
        return owl.Problem(owl.Internal, 
            owl.WithMsg("db connection failed"),
            owl.WithSafeMsg("Something went wrong"),
            owl.WithErr(err),
            owl.WithOp("User.Get"),
        )
    }
    return nil
}

2. Logging

Use the standard owl.Logger interface. The default implementation uses log/slog.

import "github.com/myuser/owl/logs"

func main() {
    logger := logs.NewSlogAdapter(nil) // Defaults to JSON handler on stdout
    
    ctx := context.Background()
    logger.Info(ctx, "service started", "port", 8080)
}

3. Metrics (OpenTelemetry)

owl stays out of your way regarding OTel provider configuration. You set up the Exporter (Prometheus, OTLP, stdout), and just pass the Meter to owl.

import (
    "github.com/myuser/owl/metrics"
    "go.opentelemetry.io/otel"
)

// ... configure your OTel provider ...
meter := otel.Meter("my-service")
monitor := metrics.NewOTelAdapter(meter)

// Use it
counter := monitor.Counter("requests_total")
counter.Inc(ctx, owl.Attr("type", "api"))

4. HTTP Middleware

Wrap your handlers to automatically log requests, record metrics, and handle errors.

import "github.com/myuser/owl/middleware"

factory := middleware.NewHTTPFactory(logger, monitor)

http.Handle("/", factory.Wrap(func(w http.ResponseWriter, r *http.Request) error {
    return owl.Problem(owl.NotFound) // Automatically returns 404 JSON response
}))

5. HTTP Client Middleware

Injects distributed tracing headers and handles error hydration from upstream services.

client := http.Client{
    Transport: middleware.NewHTTPClient(http.DefaultTransport, logger),
}

resp, err := client.Get("http://upstream-service/")
if err != nil {
    // If upstream returned a JSON error, 'err' is already hydrated as *owl.Error
    // containing the remote machine's code and message.
}

6. Safe Concurrency (owl.Go)

Spawn background goroutines safely. If they panic, the panic is recovered, logged (with stack trace), and the stack does not crash.

owl.Go(ctx, func(ctx context.Context) {
    // If this panics, it is logged to owl.Logger and "goroutine_panic_total" metric is incremented.
    processBackgroundJob()
})

7. Tracing Helper (owl.Start)

Reduce boilerplate when starting OTel spans.

func MyFunc(ctx context.Context) error {
    ctx, end := owl.Start(ctx, "MyFunc")
    var err error
    defer end(&err) // Automatically records error and sets status if err != nil

    return doWork()
}

8. Health Checks (health)

Standardized JSON health check handler.

import "github.com/myuser/owl/health"

func main() {
    checks := map[string]health.Checker{
        "db": dbChecker{},
        "redis": redisChecker{},
    }
    
    http.Handle("/health", health.Handler(checks))
}

9. Testing (owltest)

Easily test your code's observability side-effects without mocking OTel providers.

import "github.com/myuser/owl/owltest"

func TestMyLog logic(t *testing.T) {
    logger := owltest.NewLogger()
    owl.SetLogger(logger)
    
    MyFunction()
    
    if logger.LastEntry().Msg != "expected msg" {
        t.Fail()
    }
}

🧩 Architecture

  • root: Core types (Error, Code, interfaces).
  • logs: Logging adapters.
  • metrics: Metric adapters.
  • middleware: HTTP/gRPC interceptors.

🀝 Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

πŸ“„ License

MIT

About

Production-grade Go observability library with structured errors, logging, metrics, tracing, and health checks

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages