{"record":{"id":"ab342397238ee440","repo":"uber-go/zap","slug":"file-urls-must-leave-host-empty-or-use-localhost","errorCode":null,"errorMessage":"file URLs must leave host empty or use localhost: got %v","messagePattern":"file URLs must leave host empty or use localhost: got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sink.go","lineNumber":145,"sourceCode":"\treturn _sinkRegistry.RegisterSink(scheme, factory)\n}\n\nfunc (sr *sinkRegistry) newFileSinkFromURL(u *url.URL) (Sink, error) {\n\tif u.User != nil {\n\t\treturn nil, fmt.Errorf(\"user and password not allowed with file URLs: got %v\", u)\n\t}\n\tif u.Fragment != \"\" {\n\t\treturn nil, fmt.Errorf(\"fragments not allowed with file URLs: got %v\", u)\n\t}\n\tif u.RawQuery != \"\" {\n\t\treturn nil, fmt.Errorf(\"query parameters not allowed with file URLs: got %v\", u)\n\t}\n\t// Error messages are better if we check hostname and port separately.\n\tif u.Port() != \"\" {\n\t\treturn nil, fmt.Errorf(\"ports not allowed with file URLs: got %v\", u)\n\t}\n\tif hn := u.Hostname(); hn != \"\" && hn != \"localhost\" {\n\t\treturn nil, fmt.Errorf(\"file URLs must leave host empty or use localhost: got %v\", u)\n\t}\n\n\treturn sr.newFileSinkFromPath(u.Path)\n}\n\nfunc (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) {\n\tswitch path {\n\tcase \"stdout\":\n\t\treturn nopCloserSink{os.Stdout}, nil\n\tcase \"stderr\":\n\t\treturn nopCloserSink{os.Stderr}, nil\n\t}\n\treturn sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)\n}\n\nfunc normalizeScheme(s string) (string, error) {\n\t// https://tools.ietf.org/html/rfc3986#section-3.1\n\ts = strings.ToLower(s)","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/uber-go/zap/blob/bbd4ecbd8760678fdbac94c93d9aca8069c83b2f/sink.go#L127-L163","documentation":"newFileSinkFromURL requires a file:// URL's host to be empty or exactly 'localhost'; any other hostname is rejected because file URLs address the local filesystem only. This is the final host validation before the path is opened.","triggerScenarios":"Passing file://remotehost/var/log/app.log or file://nas.example.com/logs/app.log to zap.Open expecting zap to log to a remote machine's file.","commonSituations":"Assuming file:// works like a network file share; copying destination strings from other machines; intending remote logging but choosing the wrong scheme (should be syslog, a custom sink, or a network protocol).","solutions":["Use file:///path (empty host) or file://localhost/path for local files.","For remote logging, use a network sink scheme (e.g. a registered custom sink, syslog, or ship the file with a log shipper like Fluentd/Vector).","Validate the configured sink URL scheme before building the logger."],"exampleFix":"// before\nzap.Open(\"file://logserver/var/log/app.log\")\n// after\nzap.Open(\"file:///var/log/app.log\") // ship remotely with a log shipper if needed","handlingStrategy":"validation","validationCode":"u, err := url.Parse(dest)\nif err == nil && u.Scheme == \"file\" {\n    hn := u.Hostname()\n    if hn != \"\" && hn != \"localhost\" {\n        return fmt.Errorf(\"file URL host must be empty or localhost, got %q\", hn)\n    }\n}","typeGuard":null,"tryCatchPattern":"if _, err := zap.Open(dest); err != nil {\n    if strings.Contains(err.Error(), \"must leave host empty or use localhost\") {\n        return fmt.Errorf(\"file URLs are local-only; use a network sink for %q\", dest)\n    }\n    return err\n}","preventionTips":["Remember file:// only addresses the local filesystem","For remote logging use syslog, a custom sink, or a log shipper","Validate the host component of sink URLs at config load time"],"tags":["zap","file-sink","url","validation"],"backgroundTag":"invalid-file-url","analyzedSha":"bbd4ecbd8760678fdbac94c93d9aca8069c83b2f","analyzedAt":"2026-08-31T13:01:40.228Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}