diff --git a/pkg/tracing/provider.go b/pkg/tracing/provider.go index 0bbd4ed..cbede69 100644 --- a/pkg/tracing/provider.go +++ b/pkg/tracing/provider.go @@ -26,7 +26,21 @@ func NewProviderFromEnv(ctx context.Context, resourceOptions ...resource.Option) resourceOptions, // Allow environment variables to override constant attributes provided by the caller. resource.WithFromEnv(), - resource.WithProcess(), + // We need to replace the default implementation of WithProcessOwner, as it can fail + // if there is no passwd file (for example in Docker containers without /etc/passwd + // but with CGO enabled). However, there is no straight-forward way to produce a new + // With... grouping, due to package visibility rules. (And there is no way, also due + // to visibility, to replace the default process owner provider.) So we have to enumerate + // _all_ the calls WithProcess is equivalent to, so we can change the process owner + // implementation. + resource.WithProcessPID(), + resource.WithProcessExecutableName(), + resource.WithProcessExecutablePath(), + resource.WithProcessCommandArgs(), + WithSafeProcessOwner(), + resource.WithProcessRuntimeName(), + resource.WithProcessRuntimeVersion(), + resource.WithProcessRuntimeDescription(), )..., ) if err != nil { diff --git a/pkg/tracing/resource.go b/pkg/tracing/resource.go new file mode 100644 index 0000000..b008012 --- /dev/null +++ b/pkg/tracing/resource.go @@ -0,0 +1,46 @@ +package tracing + +import ( + "context" + "fmt" + "os" + "os/user" + + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.12.0" +) + +type safeProcessOwnerProvider struct{} + +var _ resource.Detector = safeProcessOwnerProvider{} + +func (safeProcessOwnerProvider) Detect(context.Context) (*resource.Resource, error) { + // Re-implement the username logic of + // https://cs.opensource.google/go/go/+/refs/tags/go1.19.5:src/os/user/lookup_stubs.go + // so we can use the same rules in CGO mode as well. + username := os.Getenv("USER") + if u, err := user.Current(); err == nil { + username = u.Username + } + + // Instead of returning an empty user, just convert the ID number to a string + // and use that -- so we always provide some sort of user. (Like the 'id' program + // would do.) This will allow us tostill provide some form of owner attribute + // regardless of any mismatch amongst the Docker USER parameter, the + // /etc/passwd file, the Kubernetes runAsUser setting... + if username == "" { + // ...but on Windows id will be -1: ignore all negatives. + if id := os.Getuid(); id >= 0 { + username = fmt.Sprintf("%d", id) + } else { + // Just give up. + return resource.Empty(), nil + } + } + + return resource.NewWithAttributes(semconv.SchemaURL, semconv.ProcessOwnerKey.String(username)), nil +} + +func WithSafeProcessOwner() resource.Option { + return resource.WithDetectors(safeProcessOwnerProvider{}) +}