mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
122 lines
3.6 KiB
Go
122 lines
3.6 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package tracing
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/codes"
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
|
|
"go.opentelemetry.io/otel/trace"
|
|
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
)
|
|
|
|
func Tracer() trace.Tracer {
|
|
if !isTracingEnabled {
|
|
return otel.Tracer("")
|
|
}
|
|
|
|
pc, _, _, ok := runtime.Caller(1)
|
|
if !ok || runtime.FuncForPC(pc) == nil {
|
|
return otel.Tracer("")
|
|
}
|
|
|
|
// We use the import path of the caller function as the tracer name.
|
|
return otel.GetTracerProvider().Tracer(extractImportPath(runtime.FuncForPC(pc).Name()))
|
|
}
|
|
|
|
// SetSpanError sets the error or diagnostic information on the span.
|
|
// It accepts an error, a string, or a diagnostics object.
|
|
// It also sets the span status to Error and records the error or message.
|
|
func SetSpanError(span trace.Span, input any) {
|
|
if span == nil || input == nil {
|
|
return
|
|
}
|
|
|
|
switch v := input.(type) {
|
|
case error:
|
|
if v != nil {
|
|
span.SetStatus(codes.Error, v.Error())
|
|
span.RecordError(v)
|
|
}
|
|
case string:
|
|
if v != "" {
|
|
span.SetStatus(codes.Error, v)
|
|
span.RecordError(errors.New(v))
|
|
}
|
|
case tfdiags.Diagnostics: // Assuming Diagnostics is a custom type you have defined elsewhere
|
|
if v.HasErrors() { // Assuming IsEmpty() checks if the diagnostics object has content
|
|
span.SetStatus(codes.Error, v.Err().Error())
|
|
span.RecordError(v.Err())
|
|
}
|
|
default:
|
|
// Handle unsupported types gracefully
|
|
// TODO: Discuss if this should panic?
|
|
span.SetStatus(codes.Error, "ERROR: unsupported input type for SetSpanError.")
|
|
span.AddEvent("ERROR: unsupported input type for SetSpanError")
|
|
}
|
|
}
|
|
|
|
// ForceFlush ensures that all spans are exported to the collector before
|
|
// the application terminates. This is particularly important for CLI
|
|
// applications where the process exits immediately after the operation.
|
|
//
|
|
// This should be called before the application terminates to ensure
|
|
// all spans are exported properly.
|
|
func ForceFlush(timeout time.Duration) {
|
|
if !isTracingEnabled {
|
|
return
|
|
}
|
|
|
|
provider, ok := otel.GetTracerProvider().(*sdktrace.TracerProvider)
|
|
if !ok {
|
|
log.Printf("[TRACE] OpenTelemetry: tracer provider is not an SDK provider, can't force flush")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
|
|
log.Printf("[TRACE] OpenTelemetry: flushing spans")
|
|
if err := provider.ForceFlush(ctx); err != nil {
|
|
log.Printf("[WARN] OpenTelemetry: error flushing spans: %v", err)
|
|
}
|
|
}
|
|
|
|
// extractImportPath extracts the import path from a full function name.
|
|
// the function names returned by runtime.FuncForPC(pc).Name() can be in the following formats
|
|
//
|
|
// main.(*MyType).MyMethod
|
|
// github.com/you/pkg.(*SomeType).Method-fm
|
|
// github.com/you/pkg.functionName
|
|
func extractImportPath(fullName string) string {
|
|
lastSlash := strings.LastIndex(fullName, "/")
|
|
if lastSlash == -1 {
|
|
// When there is no slash, then use everything before the first dot
|
|
if dot := strings.Index(fullName, "."); dot != -1 {
|
|
return fullName[:dot]
|
|
}
|
|
log.Printf("[WARN] unable to extract import path from function name: %q. Tracing may be incomplete. This is a bug in OpenTofu, please report it.", fullName)
|
|
return "unknown"
|
|
}
|
|
|
|
dotAfterSlash := strings.Index(fullName[lastSlash:], ".")
|
|
if dotAfterSlash == -1 {
|
|
log.Printf("[WARN] unable to extract import path from function name: %q. Tracing may be incomplete. This is a bug in OpenTofu, please report it.", fullName)
|
|
return "unknown"
|
|
}
|
|
|
|
return fullName[:lastSlash+dotAfterSlash]
|
|
}
|