span.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package telemetry
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "fmt"
  7. "runtime"
  8. "github.com/google/uuid"
  9. "go.opentelemetry.io/otel"
  10. "go.opentelemetry.io/otel/attribute"
  11. "go.opentelemetry.io/otel/codes"
  12. "go.opentelemetry.io/otel/trace"
  13. )
  14. // AttributeKey helps enforce consistent naming conventions for attribute key
  15. type AttributeKey string
  16. const (
  17. // AttributeKeyUser refers to a Porter user
  18. AttributeKeyUser AttributeKey = "user"
  19. // AttributeKeyProject refers to a Porter project
  20. AttributeKeyProject AttributeKey = "project"
  21. // AttributeKeyCluster refers to a Porter cluster
  22. AttributeKeyCluster AttributeKey = "cluster"
  23. )
  24. // NewSpan is a convenience function for creating a new span, with a Porter-namespaced name to avoid conflicts.
  25. // Any commonly used variables in the context, will be added to the span such as clusterID, projectID.
  26. // When using this function, make sure to call `defer span.End()` immediately after, to avoid lost spans.
  27. func NewSpan(ctx context.Context, name string) (context.Context, trace.Span) {
  28. ctx, span := otel.Tracer("").Start(ctx, prefixSpanKey(name))
  29. // if user, ok := UserFromContext(ctx); ok {
  30. // WithAttributes(span, AttributeKV{Key: AttributeKeyUser, Value: user.ID})
  31. // }
  32. // WithAttributes(
  33. // span,
  34. // // TODO: find out where these context keys are actually stored. I believe that these are scopes, not context keys
  35. // AttributeKV{Key: AttributeKeyCluster, Value: stringFromContext(ctx, types.ClusterScope)},
  36. // AttributeKV{Key: AttributeKeyProject, Value: stringFromContext(ctx, types.ProjectScope)},
  37. // )
  38. return ctx, span
  39. }
  40. // AttributeKV is a wrapper for otel attributes KV
  41. type AttributeKV struct {
  42. Key AttributeKey
  43. Value any
  44. }
  45. // WithAttributes is a convenience function for adding attributes to a given span
  46. // This will also add the namespaced prefix to the keys
  47. func WithAttributes(span trace.Span, attrs ...AttributeKV) {
  48. for _, attr := range attrs {
  49. if attr.Key != "" {
  50. switch val := attr.Value.(type) {
  51. case uuid.UUID:
  52. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String()))
  53. case string:
  54. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val))
  55. case sql.NullString:
  56. if val.Valid {
  57. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String))
  58. } else {
  59. span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), "NULL"))
  60. }
  61. case int:
  62. span.SetAttributes(attribute.Int(prefixSpanKey(string(attr.Key)), val))
  63. case int64:
  64. span.SetAttributes(attribute.Int64(prefixSpanKey(string(attr.Key)), val))
  65. case int32:
  66. span.SetAttributes(attribute.Int64(prefixSpanKey(string(attr.Key)), int64(val)))
  67. case uint:
  68. span.SetAttributes(attribute.Int(prefixSpanKey(string(attr.Key)), int(val)))
  69. case float64:
  70. span.SetAttributes(attribute.Float64(prefixSpanKey(string(attr.Key)), val))
  71. case bool:
  72. span.SetAttributes(attribute.Bool(prefixSpanKey(string(attr.Key)), val))
  73. }
  74. }
  75. }
  76. }
  77. // Error adds the given error message and related context to a span
  78. // If message is empty, the span status_message will be set to the error
  79. // It is advised to let the raw error be set at err, and message to be a human
  80. // readable string
  81. func Error(_ context.Context, span trace.Span, err error, message string) error {
  82. if message != "" {
  83. WithAttributes(span, AttributeKV{Key: "message", Value: message})
  84. }
  85. if err == nil {
  86. err = errors.New(message)
  87. }
  88. 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/
  89. _, fn, line, _ := runtime.Caller(1)
  90. stackTraceLocation := fmt.Sprintf("%s:%d", fn, line)
  91. WithAttributes(span, AttributeKV{Key: "stack-trace-location", Value: stackTraceLocation})
  92. span.SetStatus(codes.Error, err.Error())
  93. span.RecordError(err)
  94. return err
  95. }
  96. func prefixSpanKey(name string) string {
  97. return fmt.Sprintf("porter.run/%s", name)
  98. }