Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,17 @@ func main() {
// Add custom metrics to the registry
ghcrRegistry := metrics.NewGHCRRegistry(metricsRegistry)

// Create collector
ghcrCollector := collectors.NewGHCRCollector(cfg, ghcrRegistry)

// Create and run application using promexporter
// Create and build application using promexporter
application := app.New("GHCR Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(metricsRegistry).
WithCollector(ghcrCollector).
WithVersionInfo(version.Version, version.Commit, version.BuildDate).
Build()

// Create collector with app reference for tracing
ghcrCollector := collectors.NewGHCRCollector(cfg, ghcrRegistry, application)
application.WithCollector(ghcrCollector)

if err := application.Run(); err != nil {
slog.Error("Application failed", "error", err)
os.Exit(1)
Expand Down
39 changes: 38 additions & 1 deletion internal/collectors/ghcr_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import (

"ghcr-exporter/internal/config"
"ghcr-exporter/internal/metrics"
"github.com/d0ugal/promexporter/app"
"github.com/d0ugal/promexporter/tracing"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
)

type GHCRCollector struct {
config *config.Config
metrics *metrics.GHCRRegistry
app *app.App
client *http.Client
token string
}
Expand Down Expand Up @@ -69,10 +73,11 @@ type GHCRVersionResponse struct {
} `json:"package_files"`
}

func NewGHCRCollector(cfg *config.Config, registry *metrics.GHCRRegistry) *GHCRCollector {
func NewGHCRCollector(cfg *config.Config, registry *metrics.GHCRRegistry, app *app.App) *GHCRCollector {
return &GHCRCollector{
config: cfg,
metrics: registry,
app: app,
client: &http.Client{
Timeout: 30 * time.Second,
},
Expand Down Expand Up @@ -125,6 +130,23 @@ func (gc *GHCRCollector) collectSinglePackage(ctx context.Context, name string,
startTime := time.Now()
interval := gc.config.GetPackageInterval(pkg)

// Create span for collection cycle
tracer := gc.app.GetTracer()

var collectorSpan *tracing.CollectorSpan

if tracer != nil && tracer.IsEnabled() {
collectorSpan = tracer.NewCollectorSpan(ctx, "ghcr-collector", "collect-package")

collectorSpan.SetAttributes(
attribute.String("package.name", name),
attribute.String("package.owner", pkg.Owner),
attribute.String("package.repo", pkg.Repo),
attribute.Int("package.interval", interval),
)
defer collectorSpan.End()
}

// If repo is not specified, discover all repos for the owner
if pkg.Repo == "" {
gc.collectOwnerPackages(ctx, name, pkg)
Expand All @@ -139,6 +161,11 @@ func (gc *GHCRCollector) collectSinglePackage(ctx context.Context, name string,
}, 3, 2*time.Second)
if err != nil {
slog.Error("Failed to collect package metrics after retries", "name", name, "error", err)

if collectorSpan != nil {
collectorSpan.RecordError(err, attribute.String("package.name", name))
}

gc.metrics.CollectionFailedCounter.With(prometheus.Labels{
"repo": name,
"interval": strconv.Itoa(interval),
Expand Down Expand Up @@ -167,6 +194,16 @@ func (gc *GHCRCollector) collectSinglePackage(ctx context.Context, name string,
"interval": strconv.Itoa(interval),
}).Set(float64(time.Now().Unix()))

if collectorSpan != nil {
collectorSpan.SetAttributes(
attribute.Float64("collection.duration_seconds", duration),
)
collectorSpan.AddEvent("collection_completed",
attribute.String("package.name", name),
attribute.Float64("duration_seconds", duration),
)
}

slog.Info("GHCR package metrics collection completed", "name", name, "duration", duration)
}

Expand Down
17 changes: 15 additions & 2 deletions internal/collectors/ghcr_collector_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"ghcr-exporter/internal/config"
"ghcr-exporter/internal/metrics"
"github.com/d0ugal/promexporter/app"
promexporter_config "github.com/d0ugal/promexporter/config"
promexporter_metrics "github.com/d0ugal/promexporter/metrics"
"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -79,7 +80,13 @@ func TestGHCRCollectorIntegration(t *testing.T) {
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)

collector := NewGHCRCollector(cfg, registry)
// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)

// Override the client to use our test server
collector.client = server.Client()
Expand Down Expand Up @@ -267,7 +274,13 @@ func TestGHCRCollectorErrorHandling(t *testing.T) {
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)

collector := NewGHCRCollector(cfg, registry)
// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)
collector.client = server.Client()

// Test error handling without panicking
Expand Down
35 changes: 31 additions & 4 deletions internal/collectors/ghcr_collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"ghcr-exporter/internal/config"
"ghcr-exporter/internal/metrics"
"github.com/d0ugal/promexporter/app"
promexporter_metrics "github.com/d0ugal/promexporter/metrics"
"github.com/prometheus/client_golang/prometheus"
)
Expand All @@ -22,7 +23,13 @@ func TestNewGHCRCollector(t *testing.T) {
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)

collector := NewGHCRCollector(cfg, registry)
// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)

if collector == nil {
t.Fatal("Expected collector to be created, got nil")
Expand Down Expand Up @@ -53,7 +60,13 @@ func TestGHCRCollectorStart(t *testing.T) {
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)

collector := NewGHCRCollector(cfg, registry)
// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)

ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
Expand Down Expand Up @@ -91,7 +104,14 @@ func TestGetPackageDownloadStats(t *testing.T) {
// Create a mock base registry for testing
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)
collector := NewGHCRCollector(cfg, registry)

// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)

// Override the client to use our test server
collector.client = server.Client()
Expand Down Expand Up @@ -192,7 +212,14 @@ func TestGetPackageDownloadStatsHTTPError(t *testing.T) {
// Create a mock base registry for testing
baseRegistry := promexporter_metrics.NewRegistry("test_exporter_info")
registry := metrics.NewGHCRRegistry(baseRegistry)
collector := NewGHCRCollector(cfg, registry)

// Create a minimal app instance for testing
testApp := app.New("Test Exporter").
WithConfig(&cfg.BaseConfig).
WithMetrics(baseRegistry).
Build()

collector := NewGHCRCollector(cfg, registry, testApp)
collector.client = server.Client()

// Test that we get an error when the HTTP request fails
Expand Down
Binary file removed main
Binary file not shown.