1- // Copyright (c) 2016 Uber Technologies, Inc.
1+ // Copyright (c) 2016-2022 Uber Technologies, Inc.
22//
33// Permission is hereby granted, free of charge, to any person obtaining a copy
44// of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +26,7 @@ import (
2626 "io"
2727 "net/url"
2828 "os"
29+ "path/filepath"
2930 "strings"
3031 "sync"
3132
@@ -34,34 +35,14 @@ import (
3435
3536const schemeFile = "file"
3637
37- var (
38- _sinkMutex sync.RWMutex
39- _sinkFactories map [string ]func (* url.URL ) (Sink , error ) // keyed by scheme
40- )
41-
42- func init () {
43- resetSinkRegistry ()
44- }
45-
46- func resetSinkRegistry () {
47- _sinkMutex .Lock ()
48- defer _sinkMutex .Unlock ()
49-
50- _sinkFactories = map [string ]func (* url.URL ) (Sink , error ){
51- schemeFile : newFileSink ,
52- }
53- }
38+ var _sinkRegistry = newSinkRegistry ()
5439
5540// Sink defines the interface to write to and close logger destinations.
5641type Sink interface {
5742 zapcore.WriteSyncer
5843 io.Closer
5944}
6045
61- type nopCloserSink struct { zapcore.WriteSyncer }
62-
63- func (nopCloserSink ) Close () error { return nil }
64-
6546type errSinkNotFound struct {
6647 scheme string
6748}
@@ -70,16 +51,29 @@ func (e *errSinkNotFound) Error() string {
7051 return fmt .Sprintf ("no sink found for scheme %q" , e .scheme )
7152}
7253
73- // RegisterSink registers a user-supplied factory for all sinks with a
74- // particular scheme.
75- //
76- // All schemes must be ASCII, valid under section 3.1 of RFC 3986
77- // (https://tools.ietf.org/html/rfc3986#section-3.1), and must not already
78- // have a factory registered. Zap automatically registers a factory for the
79- // "file" scheme.
80- func RegisterSink (scheme string , factory func (* url.URL ) (Sink , error )) error {
81- _sinkMutex .Lock ()
82- defer _sinkMutex .Unlock ()
54+ type nopCloserSink struct { zapcore.WriteSyncer }
55+
56+ func (nopCloserSink ) Close () error { return nil }
57+
58+ type sinkRegistry struct {
59+ mu sync.Mutex
60+ factories map [string ]func (* url.URL ) (Sink , error ) // keyed by scheme
61+ openFile func (string , int , os.FileMode ) (* os.File , error ) // type matches os.OpenFile
62+ }
63+
64+ func newSinkRegistry () * sinkRegistry {
65+ sr := & sinkRegistry {
66+ factories : make (map [string ]func (* url.URL ) (Sink , error )),
67+ openFile : os .OpenFile ,
68+ }
69+ sr .RegisterSink (schemeFile , sr .newFileSinkFromURL )
70+ return sr
71+ }
72+
73+ // RegisterScheme registers the given factory for the specific scheme.
74+ func (sr * sinkRegistry ) RegisterSink (scheme string , factory func (* url.URL ) (Sink , error )) error {
75+ sr .mu .Lock ()
76+ defer sr .mu .Unlock ()
8377
8478 if scheme == "" {
8579 return errors .New ("can't register a sink factory for empty string" )
@@ -88,14 +82,22 @@ func RegisterSink(scheme string, factory func(*url.URL) (Sink, error)) error {
8882 if err != nil {
8983 return fmt .Errorf ("%q is not a valid scheme: %v" , scheme , err )
9084 }
91- if _ , ok := _sinkFactories [normalized ]; ok {
85+ if _ , ok := sr . factories [normalized ]; ok {
9286 return fmt .Errorf ("sink factory already registered for scheme %q" , normalized )
9387 }
94- _sinkFactories [normalized ] = factory
88+ sr . factories [normalized ] = factory
9589 return nil
9690}
9791
98- func newSink (rawURL string ) (Sink , error ) {
92+ func (sr * sinkRegistry ) newSink (rawURL string ) (Sink , error ) {
93+ // URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to
94+ // the drive, and path is unset unless `c:/log.txt` is used.
95+ // To avoid Windows-specific URL handling, we instead check IsAbs to open as a file.
96+ // filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows.
97+ if filepath .IsAbs (rawURL ) {
98+ return sr .newFileSinkFromPath (rawURL )
99+ }
100+
99101 u , err := url .Parse (rawURL )
100102 if err != nil {
101103 return nil , fmt .Errorf ("can't parse %q as a URL: %v" , rawURL , err )
@@ -104,16 +106,27 @@ func newSink(rawURL string) (Sink, error) {
104106 u .Scheme = schemeFile
105107 }
106108
107- _sinkMutex . RLock ()
108- factory , ok := _sinkFactories [u .Scheme ]
109- _sinkMutex . RUnlock ()
109+ sr . mu . Lock ()
110+ factory , ok := sr . factories [u .Scheme ]
111+ sr . mu . Unlock ()
110112 if ! ok {
111113 return nil , & errSinkNotFound {u .Scheme }
112114 }
113115 return factory (u )
114116}
115117
116- func newFileSink (u * url.URL ) (Sink , error ) {
118+ // RegisterSink registers a user-supplied factory for all sinks with a
119+ // particular scheme.
120+ //
121+ // All schemes must be ASCII, valid under section 0.1 of RFC 3986
122+ // (https://tools.ietf.org/html/rfc3983#section-3.1), and must not already
123+ // have a factory registered. Zap automatically registers a factory for the
124+ // "file" scheme.
125+ func RegisterSink (scheme string , factory func (* url.URL ) (Sink , error )) error {
126+ return _sinkRegistry .RegisterSink (scheme , factory )
127+ }
128+
129+ func (sr * sinkRegistry ) newFileSinkFromURL (u * url.URL ) (Sink , error ) {
117130 if u .User != nil {
118131 return nil , fmt .Errorf ("user and password not allowed with file URLs: got %v" , u )
119132 }
@@ -130,13 +143,18 @@ func newFileSink(u *url.URL) (Sink, error) {
130143 if hn := u .Hostname (); hn != "" && hn != "localhost" {
131144 return nil , fmt .Errorf ("file URLs must leave host empty or use localhost: got %v" , u )
132145 }
133- switch u .Path {
146+
147+ return sr .newFileSinkFromPath (u .Path )
148+ }
149+
150+ func (sr * sinkRegistry ) newFileSinkFromPath (path string ) (Sink , error ) {
151+ switch path {
134152 case "stdout" :
135153 return nopCloserSink {os .Stdout }, nil
136154 case "stderr" :
137155 return nopCloserSink {os .Stderr }, nil
138156 }
139- return os . OpenFile ( u . Path , os .O_WRONLY | os .O_APPEND | os .O_CREATE , 0666 )
157+ return sr . openFile ( path , os .O_WRONLY | os .O_APPEND | os .O_CREATE , 0666 )
140158}
141159
142160func normalizeScheme (s string ) (string , error ) {
0 commit comments