Library / SDK
jupp0r/prometheus-cpp avatar
jupp0r/prometheus-cpp

prometheus-cpp: metrics-driven development with a C++ client library

Prometheus Client Library for Modern C++

1,124 stars357 forksC++NOASSERTION

At a glance

What is it?
prometheus-cpp is a C++17 client library for the Prometheus data model, with a builder API for counter families, a registry that owns the metrics and an exposer that serves the scrape endpoint. It builds with either CMake or Bazel, does not vendor its dependencies, and the last tagged release was in November 2024.
Who is it for?
prometheus-cpp is a good pick for a C++ service that needs to expose Prometheus metrics without pulling in a client from another language, because the counter family and registry model is the Prometheus data model rather than a C++ reinvention, and the CMake and Bazel build paths are both tested in CI; it is the wrong pick if your service is a library rather than a long-running process, since a scrape endpoint needs something to serve it, and before you adopt it you should check the dependency situation, because the library vendors nothing and expects civetweb plus zlib, libcurl and the test frameworks to be installed and discoverable first.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 28 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Three objects: family, registry, exposer

prometheus-cpp is a client library for the Prometheus data model, aimed at enabling metrics-driven development for C++ services. The data model it implements is described as a powerful abstraction on which to collect and expose metrics.

The API is built from three ideas, and the example program shows all of them in a few lines.

The Exposer is the HTTP server. You construct it with an address:

cpp
Exposer exposer{"127.0.0.1:8080"};

The Registry holds the metrics. It is created as a shared pointer, and the comment in the example is explicit that keeping the object alive is the user's responsibility:

cpp
auto registry = std::make_shared<Registry>();

And the metric families are built with a builder and registered:

cpp
auto& packet_counter = BuildCounter()
                           .Name("observed_packets_total")
                           .Help("Number of observed packets")
                           .Register(*registry);

The comments explain the family concept, which is the part that trips people up when they arrive from other client libraries. Families combine values with the same name but distinct label dimensions. So a single family named observed_packets_total holds every combination of protocol and direction, and you add a labelled instance to it:

cpp
auto& tcp_rx_counter =
    packet_counter.Add({{"protocol", "tcp"}, {"direction", "rx"}});

The instance returned by Add is what you increment, and the comment says incrementing those is very cheap. That is the performance claim in one line, and it is the reason the family and instance split exists at all.

Finally, the exposer is told to serve the registry:

cpp
exposer.RegisterCollectable(registry);

So the flow is: build a family, register it, add labelled instances, increment them, and the exposer serves the whole thing on request.

Low cardinality is called out in the code comments

The example has a second counter, and the comment above it is a warning rather than an explanation.

cpp
// add a counter whose dimensional data is not known at compile time
// nevertheless dimensional values should only occur in low cardinality:
// https://prometheus.io/docs/practices/naming/#labels
auto& http_requests_counter = BuildCounter()
                                  .Name("http_requests_total")
                                  .Help("Number of HTTP requests")
                                  .Register(*registry);

A metric whose labels are not known at compile time has to be created dynamically, and the example shows the cost:

cpp
// dynamically calling Family<T>.Add() works but is slow and should be
// avoided
http_requests_counter.Add({{"method", method}}).Increment();

So the library distinguishes two paths. A labelled instance created once and held in a variable is cheap to increment, and a labelled instance created on the fly is slow. The first is what the four protocol and direction counters do, with the instances created once outside the loop. The second is what the HTTP request counter does, and the comment tells you to avoid it.

The reason is cardinality, and the link in the comment points at the Prometheus naming practices. Every distinct label value combination is a separate time series in Prometheus, so a label holding a user id or a request path creates an unbounded number of series and takes the whole scrape target down. The library cannot enforce a limit for you, so it documents the rule next to the two ways of violating it.

The first comment in the example also links the metric naming best practices, which is a small sign that the author expects you to read the documentation rather than just copy the snippet.

CMake and Bazel, with no vendored dependencies

There are two supported build systems, CMake and Bazel, and both are tested in CI and should work on master and for all releases.

The CMake path is the more detailed one, and it starts with a constraint that catches people out: prometheus-cpp does not vendor its dependencies. It expects the following libraries to already be installed and discoverable via find_package().

The required dependency is civetweb, which is the HTTP server the exposer is built on. The rest are optional and tied to CMake options: googletest for ENABLE_TESTING, the Google benchmark library for benchmarks, zlib for ENABLE_COMPRESSION, and libcurl for ENABLE_PUSH. The example configuration turns the last two off to keep the dependency list minimal.

shell
mkdir _build
cd _build

# run cmake
cmake .. -DBUILD_SHARED_LIBS=ON -DENABLE_PUSH=OFF -DENABLE_COMPRESSION=OFF

# build
cmake --build . --parallel 4

# run tests
ctest -V

# install the libraries and headers
cmake --install .

The build is straightforward once the dependencies are in place. The documented package managers for providing them are vcpkg, Conan, the system's package manager, or a manually installed copy.

The Bazel path is even shorter. You need a recent bazel with Modules support, and the command is:

shell
bazel test //...

The repository carries both build systems in full, with BUILD.bazel at the root, MODULE.bazel and MODULE.bazel.lock for the Bazel module system, a.bazelrc and.bazelignore, and a bazel/ directory, alongside CMakeLists.txt and a cmake/ directory. The module support requirement is worth noting, since the older WORKSPACE approach is present but the current path is bzlmod.

The C++17 requirement is the only stated compiler constraint.

Where the pieces live in the source tree

The top level of the repository maps directly onto the layering of the library, and reading the directory names tells you the architecture.

There are core/, push/ and pull/ directories, which correspond to the three ways metrics move. The pull side is the scrape model, where Prometheus comes and asks and the exposer serves the registry. The push side is the alternative where the service sends metrics to a collector instead, which is what ENABLE_PUSH and libcurl are for. The README makes the point that the library offers the possibility for metrics to be collected by Prometheus, but other push/pull collections can be added as plugins.

That plugin framing matters for the architecture. The registry is not tied to the exposition mechanism, and the exposer is one implementation of a collectable interface. If your service already has a way of shipping telemetry somewhere else, the core does not force you to use HTTP scraping.

Then there is util/, which holds the shared helpers, and doc/ for the documentation sources. The .clang-format file at the root means the project enforces a consistent style, and the coveralls badge in the README points at a coverage service with the master branch, so coverage is tracked on the default branch.

The project also has a CI badge for each build system, one for Bazel and one for CMake, which is a detail worth noticing. It means neither build system is a second-class citizen, and a regression in one shows up separately from the other. The documentation points at the GitHub Workflows directory if the documented instructions do not work for you.

Packaging and the several ways to consume it

Distribution is handled well, and there are several distinct paths.

The first is CPack, which generates an installer such as a Debian package or an RPM for the static or dynamic libraries:

shell
# run cmake
cmake -B_build -DCPACK_GENERATOR=DEB -DBUILD_SHARED_LIBS=ON # or OFF for static libraries

# build and package
cmake --build _build --target package --parallel $(nproc)

The output lands in the _build folder as an appropriately named.deb, and setting CPACK_GENERATOR to RPM produces an RPM instead. The BUILD_SHARED_LIBS switch is in the same command, so the same invocation produces either a shared or a static package.

The second is CMake consumption, which the documentation calls the preferred way because all the dependencies between the three prometheus-cpp libraries are handled correctly. That is the point about three libraries: the core, the push and the pull pieces depend on each other, and a hand-written link line gets that wrong. There is an example project with a minimal CMakeLists.txt in the cmake/project-import directory to copy from.

The third is a Launchpad PPA maintained by the prometheus-cpp team, which provides stable versions for Ubuntu, with the standard steps for adding a PPA.

The fourth and fifth are vcpkg and Conan. The vcpkg port has been tested on Linux, macOS and Windows, and the Conan package is in the ConanCenter repository. The Bazel Central Registry also provides the package.

So a consumer has a real choice, and the fact that the CMake path is called preferred tells you something about where the friction is: the other package managers work, but the CMake one is the one the author considers correct.

The documentation site is jupp0r.github.io/prometheus-cpp, and it is where the detailed interface documentation lives.

The release gap and what it means for adoption

The project is not archived, and the last push was on 2026-09-26, so the code is still being worked on. The release history is thinner, though: v1.3.0 from 2024-11-03, v1.2.4 from 2024-03-02 and v1.2.3 from 2024-02-07. So the newest tagged release is close to two years old, while master has moved on since.

That distinction is one you have to make explicitly when you depend on this library, because there are two things you can pin. The tag v1.3.0 is what the package managers serve, and master is what the CI badges and the coverage report are tracking. A project that wants a stable dependency takes the tag and accepts whatever fixes landed afterwards; a project that wants the current state tracks master and accepts that nothing is version-pinned.

The licence is another thing to check rather than assume. The repository licence field is one that GitHub cannot classify, with the terms in the LICENSE file, and the top level listing does include a LICENSE file. Since this is a library you link into a service, the terms of that file are the thing to read, not the field in the metadata.

The other thing to weigh is the dependency model, because it is a deliberate choice rather than a limitation. Not vendoring means the build is smaller and the dependency versions are yours to control, which is what you want in a service that already has a package manager and a policy about what enters the build. The cost is that a fresh CI machine needs those packages installed before the build works, and a developer new to the project hits that on the first attempt.

Editorial conclusion

prometheus-cpp is a good pick for a C++ service that needs to expose Prometheus metrics without pulling in a client from another language, because the counter family and registry model is the Prometheus data model rather than a C++ reinvention, and the CMake and Bazel build paths are both tested in CI; it is the wrong pick if your service is a library rather than a long-running process, since a scrape endpoint needs something to serve it, and before you adopt it you should check the dependency situation, because the library vendors nothing and expects civetweb plus zlib, libcurl and the test frameworks to be installed and discoverable first.

Frequently asked questions

How do I build prometheus-cpp with CMake?

Create a build directory, run cmake with the options you need, build with cmake --build . --parallel 4, run ctest -V and install with cmake --install .. The library does not vendor its dependencies, so civetweb must be installed and discoverable via find_package, and zlib, libcurl, googletest and the benchmark library are optional and tied to their ENABLE_ options.

What is the difference between a metric family and an instance in prometheus-cpp?

A family combines values that share a name but have distinct label dimensions, and it is built with BuildCounter().Name().Help().Register(). You call Add on the family with a label set to get an instance, and incrementing a held instance is cheap while calling Add dynamically for each use is slow and should be avoided.

Does prometheus-cpp support push gateway collection?

Yes. The library offers the possibility for metrics to be collected by Prometheus, but other push and pull collections can be added as plugins, and the source tree separates core, push and pull directories. Push support is enabled with the ENABLE_PUSH CMake option, which requires libcurl.

How can I consume prometheus-cpp in my project?

Through CMake, which the documentation calls preferred because the dependencies between the three prometheus-cpp libraries are handled correctly, with an example project in the cmake/project-import directory. There is also an Ubuntu PPA, a vcpkg port tested on Linux, macOS and Windows, a Conan package in ConanCenter, and a Bazel Central Registry entry.

What is the latest release of prometheus-cpp?

The most recent tagged release is v1.3.0 from 2024-11-03, before that v1.2.4 and v1.2.3. The repository is not archived and the last push was on 2026-09-26, so master has moved on since that tag. A C++17 compliant compiler is required.

Official sources

  1. Issues
  2. jupp0r/prometheus-cpp on GitHub
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jupp0r-prometheus-cpp.svg)](https://hysenlabs.com/projects/jupp0r-prometheus-cpp)

Was this useful?