uber-go/zap · error

can't parse %q as a URL: %v

Error message

can't parse %q as a URL: %v

What it means

newSink parses the log destination string as a URL after checking for absolute paths; if url.Parse fails, this error wraps the parse error. It means the value passed to zap.Open / zap.Config.Build (the log sink URL) is not a parseable URL string.

Source

Thrown at sink.go:104

	if _, ok := sr.factories[normalized]; ok {
		return fmt.Errorf("sink factory already registered for scheme %q", normalized)
	}
	sr.factories[normalized] = factory
	return nil
}

func (sr *sinkRegistry) newSink(rawURL string) (Sink, error) {
	// URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to
	// the drive, and path is unset unless `c:/log.txt` is used.
	// To avoid Windows-specific URL handling, we instead check IsAbs to open as a file.
	// filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows.
	if filepath.IsAbs(rawURL) {
		return sr.newFileSinkFromPath(rawURL)
	}

	u, err := url.Parse(rawURL)
	if err != nil {
		return nil, fmt.Errorf("can't parse %q as a URL: %v", rawURL, err)
	}
	if u.Scheme == "" {
		u.Scheme = schemeFile
	}

	sr.mu.Lock()
	factory, ok := sr.factories[u.Scheme]
	sr.mu.Unlock()
	if !ok {
		return nil, &errSinkNotFound{u.Scheme}
	}
	return factory(u)
}

// RegisterSink registers a user-supplied factory for all sinks with a
// particular scheme.
//
// All schemes must be ASCII, valid under section 0.1 of RFC 3986

View on GitHub (pinned to bbd4ecbd87)

Solutions

  1. Fix the URL syntax in your config (quote/escape spaces and special characters, balance IPv6 brackets).
  2. Use plain absolute file paths (e.g. /var/log/app.log) instead of file:// URLs when logging to files.
  3. Validate the destination with url.Parse in your own code and report a clear config error before constructing the logger.

Example fix

// before
logger, _ := zap.Open("file://my logs/app.log") // space breaks parsing
// after
logger, _ := zap.Open("/var/log/myapp/app.log")
Defensive patterns

Strategy: validation

Validate before calling

func validSinkURL(raw string) error {
    if filepath.IsAbs(raw) {
        return nil
    }
    if _, err := url.Parse(raw); err != nil {
        return fmt.Errorf("invalid sink URL %q: %w", raw, err)
    }
    return nil
}

Try / catch

logger, err := cfg.Build()
if err != nil {
    if strings.Contains(err.Error(), "can't parse") {
        return fmt.Errorf("check log output URLs in config: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling zap.Open("http://[bad::url") or Build with an Outputs entry containing unescaped characters (spaces, stray '%', unbalanced brackets), or control characters in a path derived from config/env.

Common situations: Windows-style paths with backslashes or drive letters fed as URLs; URLs with raw spaces copied from documentation; config interpolations producing 'file://%VAR%' with malformed values.

Related errors


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