@@ -5,9 +5,14 @@ package command
55
66import (
77 "context"
8+ "fmt"
9+ "io/fs"
810 "net/url"
911 "os"
1012 "path"
13+ "path/filepath"
14+ "strings"
15+ "unicode"
1116
1217 "github.com/pkg/errors"
1318 "go.opentelemetry.io/otel"
@@ -77,14 +82,7 @@ func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
7782
7883 switch u .Scheme {
7984 case "unix" :
80- // Unix sockets are a bit weird. OTEL seems to imply they
81- // can be used as an environment variable and are handled properly,
82- // but they don't seem to be as the behavior of the environment variable
83- // is to strip the scheme from the endpoint, but the underlying implementation
84- // needs the scheme to use the correct resolver.
85- //
86- // We'll just handle this in a special way and add the unix:// back to the endpoint.
87- endpoint = "unix://" + path .Join (u .Host , u .Path )
85+ endpoint = unixSocketEndpoint (u )
8886 case "https" :
8987 secure = true
9088 fallthrough
@@ -135,3 +133,109 @@ func dockerMetricExporter(ctx context.Context, cli Cli) []sdkmetric.Option {
135133 }
136134 return []sdkmetric.Option {sdkmetric .WithReader (newCLIReader (exp ))}
137135}
136+
137+ // unixSocketEndpoint converts the unix scheme from URL to
138+ // an OTEL endpoint that can be used with the OTLP exporter.
139+ //
140+ // The OTLP exporter handles unix sockets in a strange way.
141+ // It seems to imply they can be used as an environment variable
142+ // and are handled properly, but they don't seem to be as the behavior
143+ // of the environment variable is to strip the scheme from the endpoint
144+ // while the underlying implementation needs the scheme to use the
145+ // correct resolver.
146+ func unixSocketEndpoint (u * url.URL ) string {
147+ // GRPC does not allow host to be used.
148+ socketPath := u .Path
149+
150+ // If we are on windows and we have an absolute path
151+ // that references a letter drive, check to see if the
152+ // WSL equivalent path exists and we should use that instead.
153+ if isWsl () {
154+ if p := wslSocketPath (socketPath , os .DirFS ("/" )); p != "" {
155+ socketPath = p
156+ }
157+ }
158+ // Enforce that we are using forward slashes.
159+ return "unix://" + filepath .ToSlash (socketPath )
160+ }
161+
162+ // wslSocketPath will convert the referenced URL to a WSL-compatible
163+ // path and check if that path exists. If the path exists, it will
164+ // be returned.
165+ func wslSocketPath (s string , f fs.FS ) string {
166+ if p := toWslPath (s ); p != "" {
167+ if _ , err := stat (p , f ); err == nil {
168+ return "/" + p
169+ }
170+ }
171+ return ""
172+ }
173+
174+ // toWslPath converts the referenced URL to a WSL-compatible
175+ // path if this looks like a Windows absolute path.
176+ //
177+ // If no drive is in the URL, defaults to the C drive.
178+ func toWslPath (s string ) string {
179+ drive , p , ok := parseUNCPath (s )
180+ if ! ok {
181+ return ""
182+ }
183+ return fmt .Sprintf ("mnt/%s%s" , drive , p )
184+ }
185+
186+ func parseUNCPath (s string ) (drive , p string , ok bool ) {
187+ // UNC paths use backslashes but we're using forward slashes
188+ // so also enforce that here.
189+ //
190+ // In reality, this should have been enforced much earlier
191+ // than here since backslashes aren't allowed in URLs, but
192+ // we're going to code defensively here.
193+ s = filepath .ToSlash (s )
194+
195+ const uncPrefix = "//./"
196+ if ! strings .HasPrefix (s , uncPrefix ) {
197+ // Not a UNC path.
198+ return "" , "" , false
199+ }
200+ s = s [len (uncPrefix ):]
201+
202+ parts := strings .SplitN (s , "/" , 2 )
203+ if len (parts ) != 2 {
204+ // Not enough components.
205+ return "" , "" , false
206+ }
207+
208+ drive , ok = splitWindowsDrive (parts [0 ])
209+ if ! ok {
210+ // Not a windows drive.
211+ return "" , "" , false
212+ }
213+ return drive , "/" + parts [1 ], true
214+ }
215+
216+ // splitWindowsDrive checks if the string references a windows
217+ // drive (such as c:) and returns the drive letter if it is.
218+ func splitWindowsDrive (s string ) (string , bool ) {
219+ if b := []rune (s ); len (b ) == 2 && unicode .IsLetter (b [0 ]) && b [1 ] == ':' {
220+ return string (b [0 ]), true
221+ }
222+ return "" , false
223+ }
224+
225+ func stat (p string , f fs.FS ) (fs.FileInfo , error ) {
226+ if f , ok := f .(fs.StatFS ); ok {
227+ return f .Stat (p )
228+ }
229+
230+ file , err := f .Open (p )
231+ if err != nil {
232+ return nil , err
233+ }
234+
235+ defer file .Close ()
236+ return file .Stat ()
237+ }
238+
239+ func isWsl () bool {
240+ return os .Getenv ("WSL_DISTRO_NAME" ) != ""
241+ }
0 commit comments