deadlog

package module
v0.0.0-...-e05726b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2026 License: MIT Imports: 9 Imported by: 0

README

deadlog

Build Badge

A Go library for debugging mutex deadlocks with logged wrappers and analysis tools.

Installation

go get github.com/stevenctl/deadlog

Usage

Replace sync.Mutex or sync.RWMutex with deadlog.Mutex:

import "github.com/stevenctl/deadlog"

// Before
var mu sync.RWMutex

// After
var mu = deadlog.New(deadlog.WithName("my-service"))

The API is compatible with both sync.Mutex and sync.RWMutex:

// Write lock (sync.Mutex compatible)
mu.Lock()
defer mu.Unlock()

// Read lock (sync.RWMutex compatible)
mu.RLock()
defer mu.RUnlock()
Tracking unreleased locks

Use LockFunc() or RLockFunc() to get correlated RELEASED events:

unlock := mu.LockFunc()
defer unlock()

This logs START, ACQUIRED, and RELEASED events with the same correlation ID, making it easy to identify which lock was never released.

Stack traces

Enable stack traces to see where locks are being acquired:

mu := deadlog.New(
    deadlog.WithName("my-mutex"),
    deadlog.WithTrace(5), // 5 frames deep
)
Custom logging

By default, events are written as JSON to stdout. Use a custom logger:

mu := deadlog.New(
    deadlog.WithLogger(func(e deadlog.Event) {
        log.Printf("[DEADLOG] %s %s %s id=%d", e.Type, e.State, e.Name, e.ID)
    }),
)

Or write to a specific writer:

f, _ := os.Create("locks.jsonl")
mu := deadlog.New(deadlog.WithLogger(deadlog.WriterLogger(f)))

Analysis

CLI

Install the CLI:

go install github.com/stevenctl/deadlog/cmd/deadlog@latest

Analyze a log file:

deadlog analyze app.log

Or pipe from your application:

go run ./myapp 2>&1 | deadlog analyze -

Example output:

===============================================
  LOCK CONTENTION ANALYSIS
===============================================

=== STUCK: Started but never acquired (waiting for lock) ===
  WLOCK | worker-pool          | ID: 7339384
  RWLOCK | cache               | ID: 6593621

=== HELD: Acquired but never released (holding lock) ===
  RLOCK | database             | ID: 5377378

=== SUMMARY ===
  Stuck waiting: 2
  Held:          1
Library

Use the analysis library programmatically:

import "github.com/stevenctl/deadlog/analyze"

result, err := analyze.AnalyzeFile("app.log")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Stuck: %d, Held: %d\n", len(result.Stuck), len(result.Held))

// Print formatted report
analyze.PrintReport(os.Stdout, result)

Log Format

Events are logged as JSON:

{"type":"LOCK","state":"START","name":"my-mutex","id":1234567,"ts":1704067200000000000}
{"type":"LOCK","state":"ACQUIRED","name":"my-mutex","id":1234567,"ts":1704067200000001000}
{"type":"LOCK","state":"RELEASED","name":"my-mutex","id":1234567,"ts":1704067200000002000}

Fields:

  • type: lock type (see below)
  • state: START, ACQUIRED, or RELEASED
  • name: mutex name from WithName()
  • id: correlation ID (random, same for START/ACQUIRED/RELEASED of one lock operation)
  • ts: unix nanoseconds
  • trace: stack trace (if enabled with WithTrace())
Lock Types
Method Type Tracked Description
LockFunc() LOCK Yes Write lock with RELEASED tracking
RLockFunc() RLOCK Yes Read lock with RELEASED tracking
Lock() WLOCK No Write lock, no RELEASED event
RLock() RWLOCK No Read lock, no RELEASED event

Tracked types (LOCK, RLOCK) emit RELEASED events via the unlock function, so the analyzer can detect held locks. Untracked types (WLOCK, RWLOCK) are drop-in compatible with sync.Mutex/sync.RWMutex but won't be reported as "held" since there's no RELEASED event to correlate.

Use untracked methods (Lock()/RLock()) initially to detect contention, then switch to tracked methods (LockFunc()/RLockFunc()) where you need to identify which locks are being held.

How It Works

  1. START: Logged before attempting to acquire the lock
  2. ACQUIRED: Logged after the lock is acquired
  3. RELEASED: Logged when the unlock function is called (only with LockFunc()/RLockFunc())

The analyzer detects:

  • Stuck: START without ACQUIRED (goroutine waiting for a lock) - all types
  • Held: ACQUIRED without RELEASED (lock not released) - tracked types only (LOCK, RLOCK)

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultLogger

func DefaultLogger(e Event)

DefaultLogger writes JSON events to stdout.

Types

type Event

type Event struct {
	Type  string `json:"type"`            // "LOCK" or "RLOCK"
	State string `json:"state"`           // "START", "ACQUIRED", or "RELEASED"
	Name  string `json:"name"`            // mutex name
	ID    int    `json:"id"`              // correlation ID
	Trace string `json:"trace,omitempty"` // optional stack trace
	Ts    int64  `json:"ts"`              // unix nanoseconds
}

Event represents a lock operation for logging.

type LogFunc

type LogFunc func(Event)

LogFunc is a function that handles lock events.

func WriterLogger

func WriterLogger(w io.Writer) LogFunc

WriterLogger returns a LogFunc that writes JSON events to the given writer.

type Mutex

type Mutex struct {
	// contains filtered or unexported fields
}

Mutex is a logged wrapper around sync.RWMutex. It can be used as a drop-in replacement for both sync.Mutex and sync.RWMutex.

func New

func New(opts ...Option) *Mutex

New creates a new logged Mutex with the given options.

func (*Mutex) Lock

func (m *Mutex) Lock()

Lock acquires the write lock. Uses type "WLOCK" which does not track RELEASED (use LockFunc for that).

func (*Mutex) LockFunc

func (m *Mutex) LockFunc() func()

LockFunc acquires the write lock and returns an unlock function that logs the RELEASED event with a correlated ID. Uses type "LOCK" which tracks the full lifecycle.

func (*Mutex) RLock

func (m *Mutex) RLock()

RLock acquires the read lock. Uses type "RWLOCK" which does not track RELEASED (use RLockFunc for that).

func (*Mutex) RLockFunc

func (m *Mutex) RLockFunc() func()

RLockFunc acquires the read lock and returns an unlock function that logs the RELEASED event with a correlated ID. Uses type "RLOCK" which tracks the full lifecycle.

func (*Mutex) RUnlock

func (m *Mutex) RUnlock()

RUnlock releases the read lock.

func (*Mutex) Unlock

func (m *Mutex) Unlock()

Unlock releases the write lock.

type Option

type Option func(*Mutex)

Option configures a Mutex.

func WithLogger

func WithLogger(fn LogFunc) Option

WithLogger sets a custom logging function. Default is DefaultLogger which writes JSON to stdout.

func WithName

func WithName(name string) Option

WithName sets an identifier for this mutex instance.

func WithTrace

func WithTrace(depth int) Option

WithTrace enables stack trace logging with the specified depth. A depth of 0 disables stack traces (default).

Directories

Path Synopsis
Package analyze provides tools for analyzing deadlog output to detect deadlocks.
Package analyze provides tools for analyzing deadlog output to detect deadlocks.
cmd
deadlog command
Command deadlog analyzes lock logs for deadlock detection.
Command deadlog analyzes lock logs for deadlock detection.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL