uber-go/zap · error

open sink %q: %w

Error message

open sink %q: %w

What it means

When building a logger, zap Open()s each configured output path (via Build option Open or Config.Build). If any sink fails to open, the error is wrapped per-path as "open sink %q: %w" and collected with multierr, so one report enumerates every failing output path and its underlying cause (e.g. permission denied, bad scheme, no registered sink).

Source

Thrown at writer.go:73

	writer := CombineWriteSyncers(writers...)
	return writer, closeAll, nil
}

func open(paths []string) ([]zapcore.WriteSyncer, func(), error) {
	writers := make([]zapcore.WriteSyncer, 0, len(paths))
	closers := make([]io.Closer, 0, len(paths))
	closeAll := func() {
		for _, c := range closers {
			_ = c.Close()
		}
	}

	var openErr error
	for _, path := range paths {
		sink, err := _sinkRegistry.newSink(path)
		if err != nil {
			openErr = multierr.Append(openErr, fmt.Errorf("open sink %q: %w", path, err))
			continue
		}
		writers = append(writers, sink)
		closers = append(closers, sink)
	}
	if openErr != nil {
		closeAll()
		return nil, nil, openErr
	}

	return writers, closeAll, nil
}

// CombineWriteSyncers is a utility that combines multiple WriteSyncers into a
// single, locked WriteSyncer. If no inputs are supplied, it returns a no-op
// WriteSyncer.
//
// It's provided purely as a convenience; the result is no different from

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Read the wrapped cause for each path: register missing sink schemes via zap.RegisterSink or fix the path (create directory, fix permissions).
  2. Ensure output directories exist before building the logger (os.MkdirAll on the file's directory).
  3. Check file permissions / run the process as a user that can write to the configured log path.
  4. If the path should be stderr/stdout, use "stderr"/"stdout" instead of a custom scheme.

Example fix

// before
cfg.OutputPaths = []string{"/var/log/app.log"} // dir missing
// after
os.MkdirAll("/var/log", 0o755)
cfg.OutputPaths = []string{"/var/log/app.log"}
Defensive patterns

Strategy: validation

Validate before calling

for _, p := range cfg.OutputPaths {
    if p != "stderr" && p != "stdout" && !strings.Contains(p, "://") {
        if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
            return err
        }
    }
}
logger, err := cfg.Build()

Try / catch

if err != nil {
    var me multierr.Error
    if errors.As(err, &me) {
        for _, e := range me.Errors() {
            log.Printf("sink failure: %v", e)
        }
    }
    return fmt.Errorf("logger init: %w", err)
}

Prevention

When it happens

Trigger: zap.Open(paths...) or cfg.Build() with an output path whose scheme has no registered sink factory, a file that cannot be created (permissions, missing directory), or an invalid URL.

Common situations: Log file paths in config pointing to read-only or nonexistent directories; typo'd or unregistered sink scheme like "kafka://..." without RegisterSink; container/user without write permission to /var/log; running as a different user in Docker.

Related errors


AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31). Data as JSON: /api/errors/38cfea7b1b562132. Report an issue: GitHub.