Observability in Your Application: Metrics with OpenTelemetry

Search for a command to run...

No comments yet. Be the first to comment.
Hi! Usually, I listen to a song while writing something — but this time, I’m not listening to anything. Just chilling and letting my thoughts flow. Lately, I’ve stopped listening to music on the train, while doing the dishes, cleaning the house, or e...

Hello there! (Imagine I'm saying this in Obi-Wan Kenobi’s voice :D) I'm writing this newsletter while listening to a wonderful piece by Yann Tiersen. I'll tell you why I chose this song in the music section. Even if you haven't heard of Yann Tiersen,...

Note: This post was originally shared in my newsletter. If you enjoy it, don’t forget to subscribe for future letters! Hello there :D I've been chilling to "The Curse" by Agnes Obel while writing this, so expect a laid-back vibe throughout. A Cool Te...

Note: This post was originally shared in my newsletter. If you enjoy it, don’t forget to subscribe for future letters! I am writing this email while listening to Fade Into You by Mazzy Star. It’s a chill and relaxed song—if you’re in that mood, I hig...

Understanding an application's behavior in real-time is essential when deploying it. Think of it as checking the weather before heading out—you want to know the state of your app at a glance.
Key indicators such as requests per minute (RPM), resource usage, response times, and custom business metrics are invaluable. Metrics give you this insight, enabling you to monitor and optimize your application's performance.
In this post, we’ll explore using OpenTelemetry (Otel) metrics and set up a pipeline to visualize them in Grafana.
Before diving into implementation, let’s understand the basic terms in OpenTelemetry Metrics:
MeterProvider: A singleton object that manages all meters in your application. Think of it as the provider for meters.
Meters: Groups of instruments used to collect related metrics. For example, you might use one meter per microservice or domain.
Instrument: A specific type of metric, such as counters, histograms, or gauges.
Metric: The actual data collected. For example, incrementing a counter when an endpoint is called represents a metric.
These are the foundational concepts you need to work with OpenTelemetry metrics.
OpenTelemetry supports three primary types of metrics:
Synchronous Metrics: These are recorded at the moment an event occurs. For example, tracking the number of HTTP requests.
Additive Metrics: Instruments like UpDownCounter track values that can go up and down, such as active users.
Monotonic Metrics: A subset of additive metrics that only increase over time, like total orders placed.
We’ll implement metrics for an Order API that:
Creates orders.
Ships them.
Retrieves them by ID.
We’ll track:
Requests per minute (RPM).
Response times.
Custom metrics, like the number of shipped and canceled orders in the past week.
Our setup includes OpenTelemetry, Prometheus, and Grafana:
OpenTelemetry collects metrics.
Prometheus scrapes and stores metrics.
Grafana visualizes them in dashboards.
First, initialize the MeterProvider:
func InitMeter() (*metric2.MeterProvider, error) {
ctx := context.Background()
metricExporter, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint("localhost:4318"), otlpmetrichttp.WithInsecure())
if err != nil {
return nil, err
}
meterProvider := metric2.NewMeterProvider(
metric2.WithReader(metric2.NewPeriodicReader(metricExporter)),
metric2.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("Order Api"),
)),
)
otel.SetMeterProvider(meterProvider)
return meterProvider, nil
}
This creates a global object for your app to use. Add this to your app’s initialization:
mp, err := trace.InitMeter()
if err != nil {
log.Fatal("Failed to initialize meter provider")
}
defer func() { _ = mp.Shutdown(context.Background()) }()
We’ll use FiberPrometheus middleware to export standard HTTP metrics like:
http_requests_total
http_request_duration_seconds
prometheus := fiberprometheus.New("order-api")
prometheus.RegisterAt(app, "/metrics")
app.Use(prometheus.Middleware)
This will enable Prometheus to scrape metrics from the /metrics endpoint.
Adding Custom Metrics: Shipped Orders
For custom metrics, like shipped orders, define and register them:
func InitMetrics(db *gorm.DB) {
meterProvider := otel.GetMeterProvider()
meter := meterProvider.Meter("Order Api")
initShippedOrdersMetric(db, meter)
}
func initShippedOrdersMetric(db *gorm.DB, meter metric.Meter) error {
shippedOrdersMetric, err := meter.Int64ObservableGauge(
"shipped_orders_count_metric",
metric.WithDescription("Counts the number of shipped orders"),
)
if err != nil {
log.Fatalf("Failed to create metric: %v", err)
}
_, err = meter.RegisterCallback(
func(ctx context.Context, observer metric.Observer) error {
var count int64
result := db.WithContext(ctx).Model(&entity.Order{}).
Where("is_shipped = ? AND created_at > NOW() - INTERVAL '7 DAYS'", true).
Count(&count)
if result.Error != nil {
log.Printf("Database query error: %v", result.Error)
}
observer.ObserveInt64(shippedOrdersMetric, count)
return nil
},
shippedOrdersMetric,
)
return err
}
This captures the count of shipped orders in the past 7 days and exports it as a custom metric.
Visualizing Metrics in Grafana
Once Prometheus scrapes the metrics, you can visualize them in Grafana.
Open Grafana at http://localhost:3000 and log in.
Create a new dashboard from the sidebar.
Requests per Minute (RPM):
sum(rate(http_requests_total[1m])) by (job) * 60
Use the "Time Series" visualization type.
95th Percentile Response Time:
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[1m])) by (le))
Shipped Orders (Custom Metric): For the shipped_orders_count_metric, use a single-value panel to display the current count.
With OpenTelemetry, Prometheus, and Grafana, you can track key performance metrics and custom business data in real time. Metrics like RPM, response times, and shipped orders help ensure your app runs smoothly and efficiently.
The complete codebase and setup instructions are available on GitHub. Explore and adapt it to your app for enhanced observability!
If you want to learn more about Golang and backend development, subscribe to my newsletter at firatkomurcu.com
May the force be with you!