A CMake/C++ library that lets you write hashicorp/go-plugin plugins in C++, using gRPC as the transport (net/RPC is not supported).
go-plugin launches a plugin as a child process and communicates via gRPC over loopback TCP. The child process (plugin) signals readiness by printing a single handshake line to stdout:
CORE-PROTO|APP-PROTO|tcp|127.0.0.1:PORT|grpc|SERVER-CERT
This library handles:
- Magic-cookie validation – aborts startup if the binary is run directly rather than by a go-plugin host.
- Port selection – respects
PLUGIN_MIN_PORT/PLUGIN_MAX_PORTif set by the host. - Auto-mTLS – when the host sets
PLUGIN_CLIENT_CERT, a fresh server certificate is generated and mutual TLS is configured automatically. - Handshake line output – writes the correctly formatted line to stdout so the host can connect.
- Health-check service – the built-in gRPC health-check service is registered automatically.
- Logging the host can read – see below.
A host parses a plugin's standard error as hclog JSON and reads nothing else. A line in any other shape reaches the host's logs as one opaque string at the host's own level, with the plugin's severity and fields buried inside it — so a plugin error cannot surface as an error, and nothing downstream can filter on a field.
go_plugin::log writes that format. It depends on nothing but the standard library:
#include "go_plugin/log.hpp"
go_plugin::log::Info("sink opened", {{"rate", 48000}, {"path", pipe_path}});
go_plugin::log::Error("write failed", {{"error", strerror(errno)}});A plugin that already logs through a library keeps its call sites and installs a bridge. Each backend is a separate target, so a plugin links only the one it uses:
| Backend | Target | Header | Build with | Install with |
|---|---|---|---|---|
Abseil (LOG/VLOG) |
go_plugin::go_plugin_log_absl |
go_plugin/log_absl.hpp |
-DGO_PLUGIN_LOG_ABSL=ON |
go_plugin::log::InstallAbslBridge() |
absl::InitializeLog();
go_plugin::log::InstallAbslBridge(); // LOG(WARNING) now reaches the host as a warningAbseil has no debug or trace severity of its own — they exist only as VLOG verbosities — so the bridge maps VLOG(1)
to debug and VLOG(2) and above to trace, and it stops Abseil writing its own copy of each line to standard error.
To add another backend, translate its records into go_plugin::log::Submit and add a target beside the Abseil one;
nothing in the core changes.
A C library that writes its own diagnostics can be routed through the same path rather than left to print unattributed text. FFmpeg, for example, takes a callback, which lets the library's own name travel as a field instead of a pointer address that makes every line unique:
av_log_set_level(AV_LOG_WARNING);
av_log_set_callback([](void *avcl, int level, const char *fmt, va_list args) {
// format into a buffer, then:
go_plugin::log::Write(LevelFor(level), text, {{"avclass", av_default_item_name(avcl)}});
});- CMake ≥ 3.20
- A C++17 compiler
- vcpkg with the
VCPKG_ROOTenvironment variable set (or pass-DCMAKE_TOOLCHAIN_FILE)
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DCMAKE_BUILD_TYPE=Release
cmake --build build -jThat builds the library alone. The tests and the example are opt-in, so a consumer installs neither gtest nor the protobuf code generators they need:
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DVCPKG_MANIFEST_FEATURES=tests \
-DGO_PLUGIN_LOG_ABSL=ON \
-DGO_PLUGIN_BUILD_TESTS=ON \
-DGO_PLUGIN_BUILD_EXAMPLES=ON
cmake --build build -jcd build
ctest --output-on-failureBuild the project first, then:
cd example/host
go run . ../../build/example/greeter_plugin