1
- // Copyright (c) 2016 Uber Technologies, Inc.
1
+ // Copyright (c) 2016-2022 Uber Technologies, Inc.
2
2
//
3
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
// of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +26,7 @@ import (
26
26
"io"
27
27
"net/url"
28
28
"os"
29
+ "path/filepath"
29
30
"strings"
30
31
"sync"
31
32
@@ -34,34 +35,14 @@ import (
34
35
35
36
const schemeFile = "file"
36
37
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 ()
54
39
55
40
// Sink defines the interface to write to and close logger destinations.
56
41
type Sink interface {
57
42
zapcore.WriteSyncer
58
43
io.Closer
59
44
}
60
45
61
- type nopCloserSink struct { zapcore.WriteSyncer }
62
-
63
- func (nopCloserSink ) Close () error { return nil }
64
-
65
46
type errSinkNotFound struct {
66
47
scheme string
67
48
}
@@ -70,16 +51,29 @@ func (e *errSinkNotFound) Error() string {
70
51
return fmt .Sprintf ("no sink found for scheme %q" , e .scheme )
71
52
}
72
53
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 ()
83
77
84
78
if scheme == "" {
85
79
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 {
88
82
if err != nil {
89
83
return fmt .Errorf ("%q is not a valid scheme: %v" , scheme , err )
90
84
}
91
- if _ , ok := _sinkFactories [normalized ]; ok {
85
+ if _ , ok := sr . factories [normalized ]; ok {
92
86
return fmt .Errorf ("sink factory already registered for scheme %q" , normalized )
93
87
}
94
- _sinkFactories [normalized ] = factory
88
+ sr . factories [normalized ] = factory
95
89
return nil
96
90
}
97
91
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
+
99
101
u , err := url .Parse (rawURL )
100
102
if err != nil {
101
103
return nil , fmt .Errorf ("can't parse %q as a URL: %v" , rawURL , err )
@@ -104,16 +106,27 @@ func newSink(rawURL string) (Sink, error) {
104
106
u .Scheme = schemeFile
105
107
}
106
108
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 ()
110
112
if ! ok {
111
113
return nil , & errSinkNotFound {u .Scheme }
112
114
}
113
115
return factory (u )
114
116
}
115
117
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 ) {
117
130
if u .User != nil {
118
131
return nil , fmt .Errorf ("user and password not allowed with file URLs: got %v" , u )
119
132
}
@@ -130,13 +143,18 @@ func newFileSink(u *url.URL) (Sink, error) {
130
143
if hn := u .Hostname (); hn != "" && hn != "localhost" {
131
144
return nil , fmt .Errorf ("file URLs must leave host empty or use localhost: got %v" , u )
132
145
}
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 {
134
152
case "stdout" :
135
153
return nopCloserSink {os .Stdout }, nil
136
154
case "stderr" :
137
155
return nopCloserSink {os .Stderr }, nil
138
156
}
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 )
140
158
}
141
159
142
160
func normalizeScheme (s string ) (string , error ) {
0 commit comments