1
0

datadog.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. package datadog
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "net"
  7. "net/http"
  8. "reflect"
  9. "strconv"
  10. "time"
  11. "github.com/DataDog/datadog-go/v5/statsd"
  12. "github.com/DataDog/dd-trace-go/v2/ddtrace/ext"
  13. "github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
  14. "github.com/felixge/httpsnoop"
  15. "github.com/imgproxy/imgproxy/v3/monitoring/errformat"
  16. "github.com/imgproxy/imgproxy/v3/monitoring/stats"
  17. "github.com/imgproxy/imgproxy/v3/version"
  18. "github.com/imgproxy/imgproxy/v3/vips"
  19. )
  20. // spanCtxKey is the context key type for storing the root span in the request context
  21. type spanCtxKey struct{}
  22. // dataDogLogger is a custom logger for DataDog
  23. type dataDogLogger struct{}
  24. func (l dataDogLogger) Log(msg string) {
  25. slog.Info(msg)
  26. }
  27. // DataDog holds DataDog client and configuration
  28. type DataDog struct {
  29. stats *stats.Stats
  30. config *Config
  31. statsdClient *statsd.Client
  32. statsdClientStop chan struct{}
  33. }
  34. // New creates a new DataDog instance
  35. func New(config *Config, stats *stats.Stats) (*DataDog, error) {
  36. dd := &DataDog{
  37. stats: stats,
  38. config: config,
  39. }
  40. if !config.Enabled() {
  41. return dd, nil
  42. }
  43. tracer.Start(
  44. tracer.WithService(config.Service),
  45. tracer.WithServiceVersion(version.Version),
  46. tracer.WithLogger(dataDogLogger{}),
  47. tracer.WithLogStartup(config.TraceStartupLogs),
  48. tracer.WithAgentAddr(net.JoinHostPort(config.AgentHost, strconv.Itoa(config.TracePort))),
  49. )
  50. // If additional metrics collection is not enabled, return early
  51. if !config.EnableMetrics {
  52. return dd, nil
  53. }
  54. var err error
  55. dd.statsdClient, err = statsd.New(
  56. net.JoinHostPort(config.AgentHost, strconv.Itoa(config.StatsDPort)),
  57. statsd.WithTags([]string{
  58. "service:" + config.Service,
  59. "version:" + version.Version,
  60. }),
  61. )
  62. if err == nil {
  63. dd.statsdClientStop = make(chan struct{})
  64. go dd.runMetricsCollector()
  65. } else {
  66. slog.Warn(fmt.Sprintf("can't initialize DogStatsD client: %s", err))
  67. }
  68. return dd, nil
  69. }
  70. // Enabled returns true if DataDog is enabled
  71. func (dd *DataDog) Enabled() bool {
  72. return dd.config.Enabled()
  73. }
  74. // Stop stops the DataDog tracer and metrics collection
  75. func (dd *DataDog) Stop() {
  76. if !dd.Enabled() {
  77. return
  78. }
  79. tracer.Stop()
  80. if dd.statsdClient != nil {
  81. close(dd.statsdClientStop)
  82. dd.statsdClient.Close()
  83. }
  84. }
  85. func (dd *DataDog) StartRootSpan(ctx context.Context, rw http.ResponseWriter, r *http.Request) (context.Context, context.CancelFunc, http.ResponseWriter) {
  86. if !dd.Enabled() {
  87. return ctx, func() {}, rw
  88. }
  89. span := tracer.StartSpan(
  90. "request",
  91. tracer.Measured(),
  92. tracer.SpanType("web"),
  93. tracer.Tag(ext.HTTPMethod, r.Method),
  94. tracer.Tag(ext.HTTPURL, r.RequestURI),
  95. )
  96. cancel := func() { span.Finish() }
  97. newRw := httpsnoop.Wrap(rw, httpsnoop.Hooks{
  98. WriteHeader: func(next httpsnoop.WriteHeaderFunc) httpsnoop.WriteHeaderFunc {
  99. return func(statusCode int) {
  100. span.SetTag(ext.HTTPCode, statusCode)
  101. next(statusCode)
  102. }
  103. },
  104. })
  105. return context.WithValue(ctx, spanCtxKey{}, span), cancel, newRw
  106. }
  107. func setMetadata(span *tracer.Span, key string, value any) {
  108. if len(key) == 0 || value == nil {
  109. return
  110. }
  111. if rv := reflect.ValueOf(value); rv.Kind() == reflect.Map && rv.Type().Key().Kind() == reflect.String {
  112. for _, k := range rv.MapKeys() {
  113. setMetadata(span, key+"."+k.String(), rv.MapIndex(k).Interface())
  114. }
  115. return
  116. }
  117. span.SetTag(key, value)
  118. }
  119. func (dd *DataDog) SetMetadata(ctx context.Context, key string, value any) {
  120. if !dd.Enabled() {
  121. return
  122. }
  123. if rootSpan, ok := ctx.Value(spanCtxKey{}).(*tracer.Span); ok {
  124. setMetadata(rootSpan, key, value)
  125. }
  126. }
  127. func (dd *DataDog) StartSpan(ctx context.Context, name string, meta map[string]any) context.CancelFunc {
  128. if !dd.Enabled() {
  129. return func() {}
  130. }
  131. if rootSpan, ok := ctx.Value(spanCtxKey{}).(*tracer.Span); ok {
  132. span := rootSpan.StartChild(name, tracer.Measured())
  133. for k, v := range meta {
  134. setMetadata(span, k, v)
  135. }
  136. return func() { span.Finish() }
  137. }
  138. return func() {}
  139. }
  140. func (dd *DataDog) SendError(ctx context.Context, errType string, err error) {
  141. if !dd.Enabled() {
  142. return
  143. }
  144. if rootSpan, ok := ctx.Value(spanCtxKey{}).(*tracer.Span); ok {
  145. rootSpan.SetTag(ext.Error, err)
  146. rootSpan.SetTag(ext.ErrorType, errformat.FormatErrType(errType, err))
  147. }
  148. }
  149. func (dd *DataDog) runMetricsCollector() {
  150. tick := time.NewTicker(dd.config.MetricsInterval)
  151. defer tick.Stop()
  152. for {
  153. select {
  154. case <-tick.C:
  155. dd.statsdClient.Gauge("imgproxy.workers", float64(dd.stats.WorkersNumber), nil, 1)
  156. dd.statsdClient.Gauge("imgproxy.requests_in_progress", dd.stats.RequestsInProgress(), nil, 1)
  157. dd.statsdClient.Gauge("imgproxy.images_in_progress", dd.stats.ImagesInProgress(), nil, 1)
  158. dd.statsdClient.Gauge("imgproxy.workers_utilization", dd.stats.WorkersUtilization(), nil, 1)
  159. dd.statsdClient.Gauge("imgproxy.vips.memory", vips.GetMem(), nil, 1)
  160. dd.statsdClient.Gauge("imgproxy.vips.max_memory", vips.GetMemHighwater(), nil, 1)
  161. dd.statsdClient.Gauge("imgproxy.vips.allocs", vips.GetAllocs(), nil, 1)
  162. case <-dd.statsdClientStop:
  163. return
  164. }
  165. }
  166. }