span.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. "go.opentelemetry.io/otel"
  12. "go.opentelemetry.io/otel/attribute"
  13. "go.opentelemetry.io/otel/codes"
  14. "go.opentelemetry.io/otel/trace"
  15. )
  16. // AttributeKey helps enforce consistent naming conventions for attribute key
  17. type AttributeKey string
  18. const (
  19. // AttributeKeyUser refers to a Porter user
  20. AttributeKeyUser AttributeKey = "user"
  21. // AttributeKeyProject refers to a Porter project
  22. AttributeKeyProject AttributeKey = "project"
  23. // AttributeKeyCluster refers to a Porter cluster
  24. AttributeKeyCluster AttributeKey = "cluster"
  25. )
  26. // NewSpan is a convenience function for creating a new span, with a Porter-namespaced name to avoid conflicts.
  27. // Any commonly used variables in the context, will be added to the span such as clusterID, projectID.
  28. // When using this function, make sure to call `defer span.End()` immediately after, to avoid lost spans.
  29. func NewSpan(ctx context.Context, name string) (context.Context, trace.Span) {
  30. ctx, span := otel.Tracer("").Start(ctx, prefixSpanKey(name))
  31. // if user, ok := UserFromContext(ctx); ok {
  32. // WithAttributes(span, AttributeKV{Key: AttributeKeyUser, Value: user.ID})
  33. // }
  34. // WithAttributes(
  35. // span,
  36. // // TODO: find out where these context keys are actually stored. I believe that these are scopes, not context keys
  37. // AttributeKV{Key: AttributeKeyCluster, Value: stringFromContext(ctx, types.ClusterScope)},
  38. // AttributeKV{Key: AttributeKeyProject, Value: stringFromContext(ctx, types.ProjectScope)},
  39. // )
  40. return ctx, span
  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. }