diff --git a/telemetry/profiler.go b/telemetry/profiler.go index dbd524a..1a3e95d 100644 --- a/telemetry/profiler.go +++ b/telemetry/profiler.go @@ -10,6 +10,7 @@ import ( otelpyroscope "github.com/grafana/otel-profiling-go" "github.com/grafana/pyroscope-go" "go.opentelemetry.io/otel" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) func NewProfiler(ctx context.Context) (*pyroscope.Profiler, error) { @@ -18,14 +19,6 @@ func NewProfiler(ctx context.Context) (*pyroscope.Profiler, error) { tags["pod"] = v } - if v := os.Getenv("OTEL_SERVICE_GIT_REF"); v != "" { - tags["service_git_ref"] = v - } - - if v := os.Getenv("OTEL_SERVICE_REPOSITORY"); v != "" { - tags["service_repository"] = v - } - if v := os.Getenv("OTEL_SERVICE_ROOT_PATH"); v != "" { tags["service_root_path"] = v } @@ -68,12 +61,21 @@ func NewProfiler(ctx context.Context) (*pyroscope.Profiler, error) { return nil, err } - for _, value := range resource.Attributes() { - if value.Key == "service.name" { + for _, attr := range resource.Attributes() { + var ( + key string + ) + switch attr.Key { + case "service.name": continue + case semconv.VCSRefHeadRevisionKey: + key = "service_git_ref" + case semconv.VCSRepositoryURLFullKey: + key = "service_repository" + default: + key = strings.ReplaceAll(string(attr.Key), ".", "_") } - - tags[strings.ReplaceAll(string(value.Key), ".", "_")] = value.Value.Emit() + tags[key] = attr.Value.Emit() } p, err := pyroscope.Start(pyroscope.Config{ diff --git a/telemetry/resource.go b/telemetry/resource.go index d39cc3f..6590b80 100644 --- a/telemetry/resource.go +++ b/telemetry/resource.go @@ -2,15 +2,39 @@ package telemetry import ( "context" + "os" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" ) // NewResource creates and returns a default resource for telemetry data, using the provided context. func NewResource(ctx context.Context) (*resource.Resource, error) { + var attrs []attribute.KeyValue + + envs := map[attribute.Key][]string{ + semconv.VCSRepositoryNameKey: {"REPO_NAME", "GIT_REPOSITORY_NAME", "GITHUB_REPOSITORY_NAME", "GIT_OTEL_VCS_REPOSITORY_NAME"}, + semconv.VCSRepositoryURLFullKey: {"REPO_URL", "GIT_REPOSITORY_URL", "GITHUB_REPOSITORY", "OTEL_VCS_REPOSITORY_URL_FULL"}, + semconv.VCSRefBaseNameKey: {"OTEL_VCS_BASE_NAME"}, + semconv.VCSRefBaseRevisionKey: {"OTEL_VCS_BASE_REVSION"}, + semconv.VCSRefBaseTypeKey: {"OTEL_VCS_BASE_TYPE"}, + semconv.VCSRefHeadNameKey: {"GIT_BRANCH", "OTEL_VCS_HEAD_NAME"}, + semconv.VCSRefHeadRevisionKey: {"GIT_COMMIT_HASH", "OTEL_VCS_HEAD_REVSION"}, + semconv.VCSRefHeadTypeKey: {"OTEL_VCS_HEAD_TYPE"}, + } + for k, keys := range envs { + for _, key := range keys { + if v := os.Getenv(key); v != "" { + attrs = append(attrs, k.String(v)) + break + } + } + } + return resource.New(ctx, resource.WithFromEnv(), resource.WithSchemaURL(semconv.SchemaURL), + resource.WithAttributes(attrs...), ) } diff --git a/telemetry/span.go b/telemetry/span.go index 7b5a39e..b30abb1 100644 --- a/telemetry/span.go +++ b/telemetry/span.go @@ -2,46 +2,53 @@ package telemetry import ( "context" + "errors" + "net/http" "runtime" + "strings" - "github.com/grafana/pyroscope-go" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) +// Deprecated: use StartFunc instead. func Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { return Tracer().Start(ctx, spanName, opts...) //nolint:spancheck } -func Span(ctx context.Context, handler func(ctx context.Context, span trace.Span) error) (err error) { //nolint:nonamedreturns - name := "unknown" - - pc, _, _, ok := runtime.Caller(1) - if ok { - details := runtime.FuncForPC(pc) - if details != nil { - name = details.Name() +func StartFunc(ctx context.Context) (context.Context, func(errs ...error)) { + name := "FUNC" + var attrs []attribute.KeyValue + if pc, file, line, ok := runtime.Caller(1); ok { + attrs = append(attrs, + semconv.CodeLineNumber(line), + semconv.CodeFilePath(file), + ) + if details := runtime.FuncForPC(pc); details != nil { + funcName := details.Name() + attrs = append(attrs, semconv.CodeFunctionName(funcName)) + lastSlash := strings.LastIndexByte(funcName, '/') + if lastSlash < 0 { + lastSlash = 0 + } + lastDot := strings.LastIndexByte(funcName[lastSlash:], '.') + lastSlash + name += " " + funcName[lastDot+1:] } } - pyroscope.TagWrapper(ctx, pyroscope.Labels("span_name", name), func(c context.Context) { - ctx, span := Tracer().Start(ctx, name) - - defer func() { - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - } - - span.End() - }() - - err = handler(ctx, span) - }) - - return + ctx, span := Tracer().Start(ctx, name, trace.WithAttributes(attrs...)) + span.SetStatus(codes.Ok, "") + return ctx, end(span) } +func StartFuncRequest(r *http.Request) (*http.Request, func(errs ...error)) { + ctx, end := StartFunc(r.Context()) + return r.WithContext(ctx), end +} + +// Deprecated: use StartFunc instead. func End(sp trace.Span, err error) { sp.SetStatus(codes.Ok, "") @@ -52,3 +59,14 @@ func End(sp trace.Span, err error) { sp.End() } + +func end(span trace.Span) func(errs ...error) { + return func(errs ...error) { + if len(errs) > 0 { + err := errors.Join(errs...) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + span.End() + } +}