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.
- Stopping on request – go-plugin's controller is served, so a host asking the plugin to stop shuts the server
down and returns
Wait(), lettingmainunwind instead of being killed after the host's grace period. - Parent-death watch – if the host process dies without asking, the server shuts down on its own.
- 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.
go_plugin::log::SetAbslLevel(level) sets the least severe level a plugin logs at, through Abseil as much as through
Write: Abseil's minimum severity, and the VLOG verbosity that the same mapping gives debug and trace. It is for a
plugin whose host moves its level while it runs.
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