Browse Source

Use RecordCustomMetrics instead of telemetry sdk (#1550)

Victor Sokolov 3 days ago
parent
commit
bec004c652
4 changed files with 24 additions and 101 deletions
  1. 6 0
      CHANGELOG.v4.md
  2. 0 1
      go.mod
  3. 0 2
      go.sum
  4. 18 98
      monitoring/newrelic/newrelic.go

+ 6 - 0
CHANGELOG.v4.md

@@ -1,5 +1,11 @@
 # 📑 Changelog (version/4 dev)
 
+## 2025-10-02
+
+### 🆕 Added
+
+- Custom metrics are now reported as timeslices (see [New Relic’s documentation](https://docs.newrelic.com/docs/apm/agents/manage-apm-agents/agent-data/collect-custom-metrics/) for details). Metric names have been changed from "imgproxy.X" to "Custom/imgproxy/X".
+
 ## 2025-10-01
 
 ### 🆕 Added

+ 0 - 1
go.mod

@@ -35,7 +35,6 @@ require (
 	github.com/mattn/go-isatty v0.0.20
 	github.com/ncw/swift/v2 v2.0.4
 	github.com/newrelic/go-agent/v3 v3.40.1
-	github.com/newrelic/newrelic-telemetry-sdk-go v0.8.1
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.23.2
 	github.com/shirou/gopsutil v3.21.11+incompatible

+ 0 - 2
go.sum

@@ -348,8 +348,6 @@ github.com/ncw/swift/v2 v2.0.4 h1:hHWVFxn5/YaTWAASmn4qyq2p6OyP/Hm3vMLzkjEqR7w=
 github.com/ncw/swift/v2 v2.0.4/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
 github.com/newrelic/go-agent/v3 v3.40.1 h1:8nb4R252Fpuc3oySvlHpDwqySqaPWL5nf7ZVEhqtUeA=
 github.com/newrelic/go-agent/v3 v3.40.1/go.mod h1:X0TLXDo+ttefTIue1V96Y5seb8H6wqf6uUq4UpPsYj8=
-github.com/newrelic/newrelic-telemetry-sdk-go v0.8.1 h1:6OX5VXMuj2salqNBc41eXKz6K+nV6OB/hhlGnAKCbwU=
-github.com/newrelic/newrelic-telemetry-sdk-go v0.8.1/go.mod h1:2kY6OeOxrJ+RIQlVjWDc/pZlT3MIf30prs6drzMfJ6E=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=

+ 18 - 98
monitoring/newrelic/newrelic.go

@@ -3,14 +3,11 @@ package newrelic
 import (
 	"context"
 	"fmt"
-	"log/slog"
 	"net/http"
 	"reflect"
-	"regexp"
 	"time"
 
 	"github.com/newrelic/go-agent/v3/newrelic"
-	"github.com/newrelic/newrelic-telemetry-sdk-go/telemetry"
 
 	"github.com/imgproxy/imgproxy/v3/monitoring/errformat"
 	"github.com/imgproxy/imgproxy/v3/monitoring/stats"
@@ -25,21 +22,14 @@ type attributable interface {
 	AddAttribute(key string, value any)
 }
 
-const (
-	// Metric API endpoints. NOTE: Possibly, this should be configurable?
-	defaultMetricURL = "https://metric-api.newrelic.com/metric/v1"
-	euMetricURL      = "https://metric-api.eu.newrelic.com/metric/v1"
-)
-
 type NewRelic struct {
 	stats  *stats.Stats
 	config *Config
 
-	app       *newrelic.Application
-	harvester *telemetry.Harvester
+	app *newrelic.Application
 
-	harvesterCtx       context.Context
-	harvesterCtxCancel context.CancelFunc
+	metricsCtx       context.Context
+	metricsCtxCancel context.CancelFunc
 }
 
 func New(config *Config, stats *stats.Stats) (*NewRelic, error) {
@@ -69,42 +59,8 @@ func New(config *Config, stats *stats.Stats) (*NewRelic, error) {
 		return nil, fmt.Errorf("can't init New Relic agent: %s", err)
 	}
 
-	// Initialize New Relic Telemetry SDK harvester
-	harvesterAttributes := map[string]any{"appName": config.AppName}
-	for k, v := range config.Labels {
-		harvesterAttributes[k] = v
-	}
-
-	// Choose metrics endpoint based on license key pattern
-	licenseEuRegex := regexp.MustCompile(`(^eu.+?)x`)
-
-	metricsURL := defaultMetricURL
-	if licenseEuRegex.MatchString(config.Key) {
-		metricsURL = euMetricURL
-	}
-
-	// Initialize error logger
-	errLogger := slog.NewLogLogger(
-		slog.With("source", "newrelic").Handler(),
-		slog.LevelWarn,
-	)
-
-	// Create harvester
-	harvester, err := telemetry.NewHarvester(
-		telemetry.ConfigAPIKey(config.Key),
-		telemetry.ConfigCommonAttributes(harvesterAttributes),
-		telemetry.ConfigHarvestPeriod(0), // Don't harvest automatically
-		telemetry.ConfigMetricsURLOverride(metricsURL),
-		telemetry.ConfigBasicErrorLogger(errLogger.Writer()),
-	)
-	if err == nil {
-		// In case, there were no errors while starting the harvester, start the metrics collector
-		nl.harvester = harvester
-		nl.harvesterCtx, nl.harvesterCtxCancel = context.WithCancel(context.Background())
-		go nl.runMetricsCollector()
-	} else {
-		slog.Warn(fmt.Sprintf("Can't init New Relic telemetry harvester: %s", err))
-	}
+	nl.metricsCtx, nl.metricsCtxCancel = context.WithCancel(context.Background())
+	go nl.runMetricsCollector()
 
 	return nl, nil
 }
@@ -116,13 +72,12 @@ func (nl *NewRelic) Enabled() bool {
 
 // Stop stops the New Relic APM agent and Telemetry SDK harvester
 func (nl *NewRelic) Stop(ctx context.Context) {
-	if nl.app != nil {
-		nl.app.Shutdown(5 * time.Second)
+	if nl.metricsCtxCancel != nil {
+		nl.metricsCtxCancel()
 	}
 
-	if nl.harvester != nil {
-		nl.harvesterCtxCancel()
-		nl.harvester.HarvestNow(ctx)
+	if nl.app != nil {
+		nl.app.Shutdown(5 * time.Second)
 	}
 }
 
@@ -215,50 +170,15 @@ func (nl *NewRelic) runMetricsCollector() {
 	for {
 		select {
 		case <-tick.C:
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.workers",
-				Value:     float64(nl.stats.WorkersNumber),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.requests_in_progress",
-				Value:     nl.stats.RequestsInProgress(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.images_in_progress",
-				Value:     nl.stats.ImagesInProgress(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.workers_utilization",
-				Value:     nl.stats.WorkersUtilization(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.vips.memory",
-				Value:     vips.GetMem(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.vips.max_memory",
-				Value:     vips.GetMemHighwater(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.RecordMetric(telemetry.Gauge{
-				Name:      "imgproxy.vips.allocs",
-				Value:     vips.GetAllocs(),
-				Timestamp: time.Now(),
-			})
-
-			nl.harvester.HarvestNow(nl.harvesterCtx)
-		case <-nl.harvesterCtx.Done():
+			nl.app.RecordCustomMetric("imgproxy/workers", float64(nl.stats.WorkersNumber))
+			nl.app.RecordCustomMetric("imgproxy/requests_in_progress", float64(nl.stats.RequestsInProgress()))
+			nl.app.RecordCustomMetric("imgproxy/images_in_progress", float64(nl.stats.ImagesInProgress()))
+			nl.app.RecordCustomMetric("imgproxy/workers_utilization", nl.stats.WorkersUtilization())
+
+			nl.app.RecordCustomMetric("imgproxy/vips/memory", float64(vips.GetMem()))
+			nl.app.RecordCustomMetric("imgproxy/vips/max_memory", float64(vips.GetMemHighwater()))
+			nl.app.RecordCustomMetric("imgproxy/vips/allocs", float64(vips.GetAllocs()))
+		case <-nl.metricsCtx.Done():
 			return
 		}
 	}