LOG(INFO, "User {user} has performed action {action}.", "admin", "login");
C++ Service Starter Pack
An inventory of the frameworks and libraries to use when writing a network service in C++.
IO Framework
Requirements:
-
Event driven
-
Supports multiple threads
-
Handles both network and disk IO
-
TCP required, preferably UDP and unix domain as well
If you’re building an IO-intensive, Linux-only service, strongly consider using Seastar. Its thread-per-core architecture is the best for database or load balancer-like services, and provides the ability to upgrade to DPDK/SPDK on bare metal if needed. However, Seastar requires that all potentially blocking calls made in any library must go through Seastar. Thus, it is more of a lifestyle than a framework. This will reduce your choice of potential metrics/logging/etc. library to only what Seastar provides, or you must write your own. If your service contains notable amounts of business logic, or is not critically sensitive to latency, then the advantages of Seastar are unlikely to be worth its costs.
What library to use here is potentially tied to one’s choice of RPC protocol. When using gRPC, it probably makes the most sense to just use grpc/grpc.
Metrics
Requirements:
Logging
My ideal logging framework permits easy semi-structured logging, while being efficient and flexible. An invocation would look like:
Which will get turned into two things: 1970-00-00 00:00:00 INFO User admin has performed action login. and { "date": "1970-00-00 00:00:00", "severity": "INFO", "user": "admin", "action": login }. The former is for humans to read, the latter is to be uploaded into a data warehouse for aggregation and analysis.
Flags
Requirements:
-
Parses command line arguments into flags
-
Generates informative documentation on their usage
-
Allows defining a variety of tuning knobs which are hidden from the user by default
-
Allows changing of flag values at runtime, preferably with annotation of which flags are safe to change at runtime.
Some projects define a configuration file which has the extended set of tuning knobs, instead of allowing them to be set as command line flags.
| Parsing | Documentation | Flags vs knobs | Runtime setting | |
|---|---|---|---|---|
gflags |
✅ |
❌ |
❌ |
✅ |
boost.program_options |
✅ |
❌ |
❌ |
❌ |
cxxopts |
✅ |
❌ |
❌ |
❌ |
Legend: ✅ = supported ⛏️ = not supported, but able to be built using the library ❌ = not supported, and the library does not permit this to be built
Testing
The combination of google/googletest, google/benchmark
, and google/googlemock
has always worked well enough for me that I’ve never looked elsewhere. They are tools that have not been significantly updated or upgraded for a long time. For a more modern set, consider catchorg/Catch2
, martinus/nanobench
, and rollbear/trompeloeil
, respectively. "Modern" does not mean uniformly better though.
For randomized testing, you need to answer the question "Can I generate all possible inputs myself?". If yes, use a property testing framework like emil-e/rapidcheck. If no, then use a coverage driven fuzzer like LibFuzzer.
Setting up sanitizers for both unit and randomized tests will improve coverage. Address Sanitizer and Undefined Behavior Sanitizer should be easiest to set up, as they can be easily applied to a subset of an executable. Thread Sanitizer is very useful when testing multi-threaded code, but causes a notable slowdown. Memory Sanitizer requires all code in the executable to be compiled with Memory Sanitizer, so all libraries used, including libstdc`/`libc. If any of these are too much hassle to set up for the project, then the tests can be run under valgrind instead, but will run notably slower.
Quality of Life
There’s a number of libraries that don’t satisfy a key necessity for a service, but just make development nicer or easier:
-
bombela/backward-cpp
- Informative stacktraces on crash
-
Neargye/magic_enum
- Reflection for enums
-
sharkdp/dbg-macro
- Simple/pretty macro for printf debugging
Special Purpose
There’s a number of libraries that fall into the category of "If you’re doing a lot of X, then you should use Y."
-
Hashing: Cyan4973/xxHash
-
Regex: intel/hyperscan
See discussion of this page on Reddit, Hacker News, and Lobsters.