apache/beam · error

Logger cannot be nil

Error message

Logger cannot be nil

What it means

SetLogger in the Beam Go log package panics if called with a nil Logger. The global logger must always have a valid implementation; installing nil would cause nil dereferences in every later log call, so it is rejected up front. It is intended for initialization-time use only.

Solutions

  1. Construct a real Logger implementation before calling SetLogger (e.g. log.NewLogger(...) or a slog/zap adapter).
  2. Guard with an interface-nil check plus reflect-based typed-nil check if the logger comes from an external factory.
  3. Handle the factory error path so a failed logger construction aborts initialization instead of returning nil.

Example fix

// before
var myLog *MyLogger
log.SetLogger(myLog) // typed nil, panics
// after
if myLog == nil { myLog = NewMyLogger() }
log.SetLogger(myLog)
Defensive patterns

Strategy: type-guard

Validate before calling

if logger == nil {
    return errors.New("cannot install nil logger")
}

Type guard

func isNilLogger(l log.Logger) bool {
    if l == nil { return true }
    v := reflect.ValueOf(l)
    switch v.Kind() {
    case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface:
        return v.IsNil()
    }
    return false
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("SetLogger rejected the logger: %v", r)
    }
}()

Prevention

When it happens

Trigger: Calling log.SetLogger(nil), or passing a typed-nil (e.g. a nil *MyLogger wrapped in the Logger interface) which is non-nil as an interface but nil as a pointer.

Common situations: Initializing logging from a config factory that returned nil on failure; forgetting to construct the logger before registering it; the classic typed-nil-interface pitfall in Go.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/40d1f6173c246e26. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/log/log.go:75

}

var logger atomic.Value

// concreteLogger works around atomic.Value's requirement that the type
// be identical for all callers.
type concreteLogger struct {
	Logger
}

func init() {
	logger.Store(&concreteLogger{&Structural{}})
}

// SetLogger sets the global Logger. Intended to be called during initialization
// only.
func SetLogger(l Logger) {
	if l == nil {
		panic("Logger cannot be nil")
	}
	logger.Store(&concreteLogger{l})
}

// Output logs the given message to the global logger. Calldepth is the count
// of the number of frames to skip when computing the file name and line number.
func Output(ctx context.Context, sev Severity, calldepth int, msg string) {
	logger.Load().(Logger).Log(ctx, sev, calldepth+1, msg) // +1 for this frame
}

// User-facing logging functions.

// Debug writes the fmt.Sprint-formatted arguments to the global logger with
// debug severity.
func Debug(ctx context.Context, v ...any) {
	Output(ctx, SevDebug, 1, fmt.Sprint(v...))
}

View on GitHub (pinned to 12126d8942)