Track Spotify Metrics: Use Docker with Prometheus and Grafana
Source: Dev.to
After adding .Net Aspire Service Defaults and .Net Aspire AppHost to WinampToSpotify project I have added following code to run Prometheus and Grafana on local with Docker Desktop. Docker Desktop is required to run to start code below. // Prometheus container scraping the app // Grafana container using Prometheus as data source prometheus.yml is the default prometheus configuration I created OpenTelemetryLib project and created ServiceCollection extension method which configures OTEL export endpoints. OpenTelemetry, OpenTelemetry.Exporter.Console, OpenTelemetry.Exporter.OpenTelemetryProtocol, OpenTelemetry.Exporter.Prometheus.HttpListener, OpenTelemetry.Instrumentation.Process nuget packages installed. Spotify metrics class created to register spotify service related metrics. I started to keep track number of total tracks added by each folder. WinamptoSpotifyMetricsManager class helps to register metrics which uses IMeterFactory to register. winamptospotifyweb.spotifyservice.totaltracksadded metric exported to Aspire Dashboard, Prometheus and Grafana. Code changes can be found dotnet aspire added and opentelemetry and metrics added commits. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("./prometheus/prometheus.yml", "/etc/prometheus/prometheus.yml")
.WithEndpoint(port: 9090, targetPort: 9090)
.WithArgs("--config.file=/etc/prometheus/prometheus.yml",
"--web.enable-otlp-receiver"); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("./prometheus/prometheus.yml", "/etc/prometheus/prometheus.yml")
.WithEndpoint(port: 9090, targetPort: 9090)
.WithArgs("--config.file=/etc/prometheus/prometheus.yml",
"--web.enable-otlp-receiver"); CODE_BLOCK:
var prometheus = builder.AddContainer("prometheus", "prom/prometheus")
.WithBindMount("./prometheus/prometheus.yml", "/etc/prometheus/prometheus.yml")
.WithEndpoint(port: 9090, targetPort: 9090)
.WithArgs("--config.file=/etc/prometheus/prometheus.yml",
"--web.enable-otlp-receiver"); CODE_BLOCK:
var grafana = builder.AddContainer("grafana", "grafana/grafana")
.WithVolume("grafana-storage", "/var/lib/grafana") // Persists dashboards, users, DB
.WithVolume("grafana-provisioning", "/etc/grafana/provisioning", isReadOnly: true) // Optional: Provisioning YAML/JSON
.WithEndpoint(port: 3000, targetPort: 3000); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
var grafana = builder.AddContainer("grafana", "grafana/grafana")
.WithVolume("grafana-storage", "/var/lib/grafana") // Persists dashboards, users, DB
.WithVolume("grafana-provisioning", "/etc/grafana/provisioning", isReadOnly: true) // Optional: Provisioning YAML/JSON
.WithEndpoint(port: 3000, targetPort: 3000); CODE_BLOCK:
var grafana = builder.AddContainer("grafana", "grafana/grafana")
.WithVolume("grafana-storage", "/var/lib/grafana") // Persists dashboards, users, DB
.WithVolume("grafana-provisioning", "/etc/grafana/provisioning", isReadOnly: true) // Optional: Provisioning YAML/JSON
.WithEndpoint(port: 3000, targetPort: 3000); COMMAND_BLOCK:
global: scrape_interval: 15s evaluation_interval: 15s
scrape_configs: - job_name: "otel-collector" static_configs: - targets: ["localhost:9090"] # Adjust if using Docker, e.g., host.docker.internal:9464 metrics_path: /metrics Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
global: scrape_interval: 15s evaluation_interval: 15s
scrape_configs: - job_name: "otel-collector" static_configs: - targets: ["localhost:9090"] # Adjust if using Docker, e.g., host.docker.internal:9464 metrics_path: /metrics COMMAND_BLOCK:
global: scrape_interval: 15s evaluation_interval: 15s
scrape_configs: - job_name: "otel-collector" static_configs: - targets: ["localhost:9090"] # Adjust if using Docker, e.g., host.docker.internal:9464 metrics_path: /metrics CODE_BLOCK:
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() .SetResourceBuilder( ResourceBuilder.CreateDefault().AddService("winamptospotifyweb", serviceVersion: "1.0.0")) .AddMeter(WinamptoSpotifyMetricsManager.MeterName) .AddOtlpExporter((options, metricReader) => { options.Protocol = OtlpExportProtocol.Grpc; // 4317 as the grpc port. options.ExportProcessorType = ExportProcessorType.Batch; options.Endpoint = endpoint; metricReader.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 60000; // 1 minute metricReader.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds = 30000; // half a minute }) //Aspire Dashboard export .AddOtlpExporter((exporterOptions, metricReaderOptions) => { exporterOptions.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics"); exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf; metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; }); //Prometheus export Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() .SetResourceBuilder( ResourceBuilder.CreateDefault().AddService("winamptospotifyweb", serviceVersion: "1.0.0")) .AddMeter(WinamptoSpotifyMetricsManager.MeterName) .AddOtlpExporter((options, metricReader) => { options.Protocol = OtlpExportProtocol.Grpc; // 4317 as the grpc port. options.ExportProcessorType = ExportProcessorType.Batch; options.Endpoint = endpoint; metricReader.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 60000; // 1 minute metricReader.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds = 30000; // half a minute }) //Aspire Dashboard export .AddOtlpExporter((exporterOptions, metricReaderOptions) => { exporterOptions.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics"); exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf; metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; }); //Prometheus export CODE_BLOCK:
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder() .SetResourceBuilder( ResourceBuilder.CreateDefault().AddService("winamptospotifyweb", serviceVersion: "1.0.0")) .AddMeter(WinamptoSpotifyMetricsManager.MeterName) .AddOtlpExporter((options, metricReader) => { options.Protocol = OtlpExportProtocol.Grpc; // 4317 as the grpc port. options.ExportProcessorType = ExportProcessorType.Batch; options.Endpoint = endpoint; metricReader.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 60000; // 1 minute metricReader.PeriodicExportingMetricReaderOptions.ExportTimeoutMilliseconds = 30000; // half a minute }) //Aspire Dashboard export .AddOtlpExporter((exporterOptions, metricReaderOptions) => { exporterOptions.Endpoint = new Uri("http://localhost:9090/api/v1/otlp/v1/metrics"); exporterOptions.Protocol = OtlpExportProtocol.HttpProtobuf; metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; }); //Prometheus export COMMAND_BLOCK:
public class SpotifyServiceMetrics : IWinampToSpotifyWebMetrics
{ private readonly ISpotifyService _spotifyService; public SpotifyServiceMetrics(ISpotifyService spotifyService) { _spotifyService = spotifyService; } public void RegisterMetrics(Meter meter) { var tracksAddedMetric = meter.CreateObservableGauge("winamptospotifyweb.spotifyservice.totaltracksadded", () => _spotifyService.GetPlaylistSummary().TotalTracksAdded, "unitless", "Number of tracks added"); }
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
public class SpotifyServiceMetrics : IWinampToSpotifyWebMetrics
{ private readonly ISpotifyService _spotifyService; public SpotifyServiceMetrics(ISpotifyService spotifyService) { _spotifyService = spotifyService; } public void RegisterMetrics(Meter meter) { var tracksAddedMetric = meter.CreateObservableGauge("winamptospotifyweb.spotifyservice.totaltracksadded", () => _spotifyService.GetPlaylistSummary().TotalTracksAdded, "unitless", "Number of tracks added"); }
} COMMAND_BLOCK:
public class SpotifyServiceMetrics : IWinampToSpotifyWebMetrics
{ private readonly ISpotifyService _spotifyService; public SpotifyServiceMetrics(ISpotifyService spotifyService) { _spotifyService = spotifyService; } public void RegisterMetrics(Meter meter) { var tracksAddedMetric = meter.CreateObservableGauge("winamptospotifyweb.spotifyservice.totaltracksadded", () => _spotifyService.GetPlaylistSummary().TotalTracksAdded, "unitless", "Number of tracks added"); }
} COMMAND_BLOCK:
public WinamptoSpotifyMetricsManager(IEnumerable<IWinampToSpotifyWebMetrics> metrics, IMeterFactory meterFactory)
{ _metrics = metrics.ToImmutableList(); _meter = meterFactory.Create(new MeterOptions(MeterName));
}` `/// <summary>
/// Registers all custom metrics contained within the WinampToSpotify instance.
/// </summary>
public void Start()
{ foreach (var metric in _metrics) { metric.RegisterMetrics(_meter); }
} Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
public WinamptoSpotifyMetricsManager(IEnumerable<IWinampToSpotifyWebMetrics> metrics, IMeterFactory meterFactory)
{ _metrics = metrics.ToImmutableList(); _meter = meterFactory.Create(new MeterOptions(MeterName));
}` `/// <summary>
/// Registers all custom metrics contained within the WinampToSpotify instance.
/// </summary>
public void Start()
{ foreach (var metric in _metrics) { metric.RegisterMetrics(_meter); }
} COMMAND_BLOCK:
public WinamptoSpotifyMetricsManager(IEnumerable<IWinampToSpotifyWebMetrics> metrics, IMeterFactory meterFactory)
{ _metrics = metrics.ToImmutableList(); _meter = meterFactory.Create(new MeterOptions(MeterName));
}` `/// <summary>
/// Registers all custom metrics contained within the WinampToSpotify instance.
/// </summary>
public void Start()
{ foreach (var metric in _metrics) { metric.RegisterMetrics(_meter); }
}