2
0

span.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package telemetry
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "runtime"
  8. "strings"
  9. "time"
  10. "github.com/google/uuid"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/models"
  13. "go.opentelemetry.io/otel"
  14. "go.opentelemetry.io/otel/attribute"
  15. "go.opentelemetry.io/otel/codes"
  16. "go.opentelemetry.io/otel/trace"
  17. )
  18. // AttributeKey helps enforce consistent naming conventions for attribute key
  19. type AttributeKey string
  20. // NewSpan is a convenience function for creating a new span, with a Porter-namespaced name to avoid conflicts.
  21. // Any commonly used variables in the context, will be added to the span such as clusterID, projectID.
  22. // When using this function, make sure to call `defer span.End()` immediately after, to avoid lost spans.
  23. func NewSpan(ctx context.Context, name string) (context.Context, trace.Span) {
  24. ctx, span := otel.Tracer("").Start(ctx, prefixSpanKey(name))
  25. AddKnownContextVariablesToSpan(ctx, span)
  26. return ctx, span
  27. }
  28. // AddKnownContextVariablesToSpan adds known commonly read context variables to a span
  29. func AddKnownContextVariablesToSpan(ctx context.Context, span trace.Span) {
  30. user, ok := ctx.Value(types.UserScope).(*models.User)
  31. if ok {
  32. WithAttributes(span, AttributeKV{Key: "user-id", Value: user.ID})
  33. }
  34. cluster, ok := ctx.Value(types.ClusterScope).(*models.Cluster)
  35. if ok {
  36. WithAttributes(span, AttributeKV{Key: "cluster-id", Value: cluster.ID})
  37. }
  38. if project, ok := ctx.Value(types.ProjectScope).(*models.Project); ok {
  39. WithAttributes(span, AttributeKV{Key: "project-id", Value: project.ID})
  40. }
  41. }
  42. // AttributeKV is a wrapper for otel attributes KV
  43. type AttributeKV struct {
  44. Key AttributeKey
  45. Value any
  46. }
  47. // WithAttributes is a convenience function for adding attributes to a given span
  48. // This will also add the namespaced prefix to the keys
  49. func WithAttributes(span trace.Span, attrs ...AttributeKV) {
  50. for _, attr := range attrs {
  51. if attr.Key != "" {
  52. switch val := attr.Value.(type) {
  53. case uuid.UUID:
  54. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String()))
  55. case string:
  56. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val))
  57. case []string:
  58. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), strings.Join(val, ", ")))
  59. case sql.NullString:
  60. if val.Valid {
  61. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String))
  62. } else {
  63. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), "NULL"))
  64. }
  65. case int:
  66. span.SetAttributes(attribute.Int(prefixSpanKey(string(attr.Key)), val))
  67. case int64:
  68. span.SetAttributes(attribute.Int64(prefixSpanKey(string(attr.Key)), val))
  69. case int32:
  70. span.SetAttributes(attribute.Int64(prefixSpanKey(string(attr.Key)), int64(val)))
  71. case uint:
  72. span.SetAttributes(attribute.Int(prefixSpanKey(string(attr.Key)), int(val)))
  73. case float64:
  74. span.SetAttributes(attribute.Float64(prefixSpanKey(string(attr.Key)), val))
  75. case bool:
  76. span.SetAttributes(attribute.Bool(prefixSpanKey(string(attr.Key)), val))
  77. case time.Time:
  78. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String()))
  79. zone, offset := val.Zone()
  80. span.SetAttributes(attribute.String(prefixSpanKey(fmt.Sprintf("%s-timezone", string(attr.Key))), zone))
  81. span.SetAttributes(attribute.Int(prefixSpanKey(fmt.Sprintf("%s-offset", string(attr.Key))), offset))
  82. }
  83. }
  84. }
  85. }
  86. // Error adds the given error message and related context to a span
  87. // If message is empty, the span status_message will be set to the error
  88. // It is advised to let the raw error be set at err, and message to be a human
  89. // readable string
  90. func Error(_ context.Context, span trace.Span, err error, message string) error {
  91. if message != "" {
  92. WithAttributes(span, AttributeKV{Key: "message", Value: message})
  93. }
  94. if err == nil {
  95. err = errors.New(message)
  96. }
  97. WithAttributes(span, AttributeKV{Key: "error-message", Value: err.Error()}) // exact same as `status_message` but gives a more consolidated view when searching by porter.run/
  98. _, fn, line, _ := runtime.Caller(1)
  99. stackTraceLocation := fmt.Sprintf("%s:%d", fn, line)
  100. WithAttributes(span, AttributeKV{Key: "stack-trace-location", Value: stackTraceLocation})
  101. span.SetStatus(codes.Error, err.Error())
  102. span.RecordError(err)
  103. return err
  104. }
  105. func prefixSpanKey(name string) string {
  106. return fmt.Sprintf("porter.run/%s", name)
  107. }