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 fromView on GitHub (pinned to bbd4ecbd87)
Solutions
- Read the wrapped cause for each path: register missing sink schemes via zap.RegisterSink or fix the path (create directory, fix permissions).
- Ensure output directories exist before building the logger (os.MkdirAll on the file's directory).
- Check file permissions / run the process as a user that can write to the configured log path.
- 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
- Create log directories at startup before building the logger.
- Register all custom sink schemes before cfg.Build/Open is called.
- Prefer stderr/stdout in containerized environments instead of file paths.
- Check that the process user has write permission on configured log paths.
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
- can't register a sink factory for empty string
- must start with a letter
- %q is not a valid scheme: %v
- sink factory already registered for scheme %q
- can't parse %q as a URL: %v
AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31).
Data as JSON: /api/errors/38cfea7b1b562132.
Report an issue: GitHub.