Introduction to Metrics in Server-Side Swift
In this Server-Side Swift tutorial you will learn how to use Vapor built-in metrics and how to create custom ones. The data is pulled and stored by Prometheus and visualized in beautiful graphs using Grafana. By Jari Koopman.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Introduction to Metrics in Server-Side Swift
30 mins
- Getting Started
- Monitoring Your App in Production
- The Database: Prometheus
- The Front End: Grafana
- Enabling Basic Metrics in Vapor 4
- Bringing it All Together
- Trying Prometheus
- Creating a Dashboard
- Creating and Using Custom Metrics
- Measuring Time
- Updating Your Dashboards With Custom Metrics
- Where to Go From Here?
It’s probably a bit optimistic to believe production applications will never have issues. There are ways to gain insight and help combat issues, errors and crashes in production, with the two main ones logging and metrics.
Logging is pretty self-explanatory. It allows your application to output messages when something happens, good or bad. The messages focus on a single action and offer you fine-grained insight into what exactly went wrong in your application.
On the other hand, metrics don’t look at a single action but can aggregate information about these actions, such as the number of HTTP requests your application is receiving, grouped by the request path and response status. This gives you a quick and easy way to see how your Server-Side Swift application is doing. When the metrics report a rising trend in 5xx response codes or an increase in response times, it’s time to dive into the logging to see what’s going on.
In this tutorial, you’ll learn how to:
- Enable default built-in Server-Side Swift metrics in Vapor.
- Create custom metrics.
- Export metrics using Prometheus.
- Create beautiful dashboards with your metrics.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial.
The sample app, BirdTracker, allows you to track birds spotted in certain sections of a national park. Along with the Vapor app, there are docker and other config files you’ll use to set up a Prometheus server and create dashboards.
Be sure to check out Docker on MacOS: Getting Started before continuing this tutorial.
Monitoring Your App in Production
Before you start adding metrics to your apps, it’s good to understand their purpose, significance and industry standards.
As mentioned before, applications will break, databases will fail, and don’t even start to think about all the weird things your users might try to do. Implementing metrics allows you to get a quick, high-level overview of your application’s state. The most common metrics for web applications are requests per minute, response codes per minute and request duration. In this tutorial, you’ll export these metrics, along with custom ones.
The Database: Prometheus
To aggregate and report all these metrics, you’ll use Prometheus, an open-source monitoring system. Prometheus works using a pull model, meaning it asks your application for metrics, pulls them in and stores them. Prometheus then allows you to query this data using its PromQL querying language, designed for querying metrics.
Along with simple metric aggregation, Prometheus allows you to set up alerting as well. Alerting allows you to set up rules and boundaries for your metrics, such as that http requests with a 500 status code can’t be more than 5 percent of the total requests. If this condition became true, Prometheus would send up a flare, indicating something was wrong. Setting up alerts falls outside of the scope of this tutorial, but if you’d like to learn more, check out the docs.
The pull model works by asking for the current value of every metric. It then stores these instants together as a time series that can be queried, grouped and filtered. In most cases, metrics are served on a /metrics
endpoint, describing each metric in the following way:
- A line designating the metric’s type and name.
- An optional line with help text for the metrics.
- Lines describing the current value of the metric, optionally split based on labels.
You can use labels to split your metrics further. For example, in the case of a request counter, useful labels are the HTTP status, request path and request method. When retrieving the metrics from Prometheus, use these labels to split successful and unsuccessful requests.
For example, a simple counter with a single label will look something like this:
# TYPE my_counter counter # HELP my_counter Counting ever upwards my_counter{label="yes"} 1500 my_counter{label="no"} 1000
The Front End: Grafana
Of course, storing and querying metrics isn’t enough. You’ll also want to make them visible. In this tutorial, you’ll use Grafana. Grafana and Prometheus have built-in integrations with each other, so graphing your metrics in a dashboard is easier than ever. Grafana lets you create graphs, gauges and other visuals and use them in beautiful dashboards.
Enabling Basic Metrics in Vapor 4
With the theory all covered, it’s time to set up metrics of your own. Open the starter project and navigate to your Package.swift. Add the following package as a new dependency:
.package(url: "https://github.com/MrLotU/SwiftPrometheus.git", from: "1.0.0-alpha.15"),
Also, add it under the dependencies of the App
target as follows:
.product(name: "SwiftPrometheus", package: "SwiftPrometheus"),
This will pull in two packages: SwiftPrometheus and its dependency swift-metrics. Swift Metrics is a package provided by the SSWG, containing generic metric APIs to create counters, recorders, gauges and timers. Libraries such as Vapor, Fluent and others can create these to provide metrics without it mattering what you, the end dev, want to use as a metrics back end. This is where SwiftPrometheus comes in. SwiftPrometheus implements the APIs provided by Swift Metrics in a Prometheus-specific way, allowing you to expose metrics in a Prometheus fashion.
Adding the Endpoint
Open routes.swift and add the following inside the routes function, below the controller registration:
// 1
app.get("metrics") { req -> EventLoopFuture<String> in
// 2
let promise = req.eventLoop.makePromise(of: String.self)
// 3
DispatchQueue.global().async {
do {
try MetricsSystem.prometheus().collect(into: promise)
} catch {
promise.fail(error)
}
}
// 4
return promise.futureResult
}
Then, add import Metrics
and import Prometheus
to the top of the file.
This code:
- Adds a new
/metrics
endpoint that Prometheus can scrape. - Creates a promise the metrics will be written to.
- On a background queue, collects the Prometheus metrics into the promise or fails with an error.
- Returns the promise’s future result as the route response.
Using the Built-in Metrics
The final step to enable the built-in metrics is to tell Swift Metrics to use SwiftPrometheus as its back end. In configure.swift add the following lines below the line adding the database:
let promClient = PrometheusMetricsFactory(client: PrometheusClient())
MetricsSystem.bootstrap(promClient)
Don’t forget to add import Metrics
and import Prometheus
to the top of the file.
This code instantiates a new PrometheusMetricsFactory
, a wrapper around a PrometheusClient
to make it work with Swift Metrics, and feeds it to Swift Metrics’ MetricsSystem
as its back end.
Now, build and run your app in Xcode by clicking the Play button at the top left or in Terminal by typing swift run
.
In a browser, refresh your API a couple times by navigating to localhost:8080. The browser should look like this.
Go to /metrics. You’ll see two metrics reported: http_requests_total
, a counter for HTTP requests, and http_requests_duration_seconds
, a summary of request durations. The output in the browser should look something like this:
# TYPE http_requests_total counter http_requests_total{status="404", path="vapor_route_undefined", method="undefined"} 6 # TYPE http_request_duration_seconds summary http_request_duration_seconds{status="200", quantile="0.01", path="/metrics", method="GET"} 0.001937932 http_request_duration_seconds{status="200", quantile="0.05", path="/metrics", method="GET"} 0.002231176 http_request_duration_seconds{status="200", quantile="0.5", path="/metrics", method="GET"} 0.0030973375 http_request_duration_seconds{status="200", quantile="0.9", path="/metrics", method="GET"} 0.003550826 http_request_duration_seconds{status="200", quantile="0.95", path="/metrics", method="GET"} 0.0036431995 http_request_duration_seconds{status="200", quantile="0.99", path="/metrics", method="GET"} 0.003878795 http_request_duration_seconds{status="200", quantile="0.999", path="/metrics", method="GET"} 0.004442741 http_request_duration_seconds_count{status="200", path="/metrics", method="GET"} 1072 http_request_duration_seconds_sum{status="200", path="/metrics", method="GET"} 3.148946925