userver: userver/ugrpc/protobuf_logging.hpp Source File
Loading...
Searching...
No Matches
protobuf_logging.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @brief Public API for protobuf logging utilities.
5
6#include <limits>
7
8#include <fmt/format.h>
9#include <google/protobuf/message.h>
10#include <grpcpp/support/status.h>
11
12USERVER_NAMESPACE_BEGIN
13
14namespace ugrpc {
15
16inline constexpr std::size_t kDefaultLoggingStringLimit = 1024;
17
18/// @brief Convert protobuf message to a limited JSON string for logging.
19///
20/// The message is serialized to [ProtoJSON](https://protobuf.dev/programming-guides/json/) with the following
21/// debugging tweaks:
22/// - Fields marked with the `[debug_redact]` option are hidden: their value is replaced with a `[REDACTED]` marker.
23/// - Serialization stops early once `limit` bytes have been produced instead of serializing the whole message and
24/// only then truncating, which saves CPU on large messages. The already-open JSON containers are closed, so the
25/// truncated part before the marker stays a well-formed JSON document.
26/// - When truncated, the string ends with a `...(truncated)` marker to indicate that the output was cut off.
27///
28/// @param message The protobuf message to convert.
29/// @param limit Maximum size of the resulting string (excluding the truncation marker).
30/// Avoid setting this to very large values as it may cause OOM (Out of Memory) issues.
31/// @returns JSON representation of the message, truncated if necessary.
32///
33/// @warning This is a debug representation of protobuf that is unstable and should only be used for diagnostics.
34/// The order of keys in maps is unstable; the format itself can change even within a single run.
35/// You CANNOT parse back from this (possibly truncated) representation.
36/// You CANNOT use it for equality match with reference values in gtest.
37///
38/// @note This function is `noexcept` and is safe to call from logging code and other no-throw contexts. If @a message
39/// cannot be serialized to ProtoJSON, the error is not propagated: the returned string instead has the form
40/// `serialization failed: <error description>`.
42 const google::protobuf::Message& message,
43 std::size_t limit = kDefaultLoggingStringLimit
44) noexcept;
45
46/// @brief Convert protobuf message to an unlimited JSON string for logging.
47///
48/// Convenience overload equivalent to calling @ref ToLimitedLoggingString with no effective size limit: the whole
49/// message is serialized and the result never carries a `...(truncated)` marker. See @ref ToLimitedLoggingString for
50/// the exact serialization behavior, the `noexcept` guarantee and the error-handling contract.
51///
52/// @param message The protobuf message to convert.
53/// @returns JSON representation of the whole message.
54///
55/// @warning Serializes the entire message, so avoid this overload on unbounded input; prefer @ref
56/// ToLimitedLoggingString for logging.
57inline std::string ToUnlimitedLoggingString(const google::protobuf::Message& message) noexcept {
58 return ToLimitedLoggingString(message, /*limit*/ std::numeric_limits<std::size_t>::max());
59}
60
61/// @brief Get error details from `grpc::Status` for logging with size limit.
62/// @param status The `grpc::Status` to extract details from.
63/// @param limit Maximum size of the `"details"` part: forwarded as-is to the nested @ref ToLimitedLoggingString
64/// call for the attached `google.rpc.Status`. `code`/`message` are not size-limited.
65/// Avoid setting this to very large values as it may cause OOM (Out of Memory) issues.
66/// @returns JSON object with a `"code"` key (always present, e.g. `{"code":"OK"}`), plus a `"message"` key when
67/// `status.error_message()` is non-empty, plus a `"details"` key when `status` carries a parseable
68/// `google.rpc.Status`, holding the JSON produced by
69/// @ref ToLimitedLoggingString(const google::protobuf::Message&, std::size_t) for that attached status. For an OK
70/// status both `message` and `details` are normally absent, so the result is just `{"code":"OK"}`.
71///
72/// @warning This is a debug representation of protobuf that is unstable and should only be used for diagnostics.
73/// The order of keys in maps is unstable; the format itself can change even within a single run.
74/// You CANNOT parse back from this representation.
75/// You CANNOT use it for equality match with reference values in gtest.
76/// @warning If `<details>` ends up truncated (or its own serialization fails), the `...(truncated)` marker (or a
77/// `serialization failed: ...` message) is embedded as raw, unquoted text in place of the `"details"` value, which
78/// makes the overall result invalid JSON in that case. `code` and `message` are always properly JSON-escaped and
79/// cannot break the surrounding JSON.
80///
81/// @note This function does not itself catch exceptions; its `noexcept` guarantee relies on
82/// `formats::json::StringBuilder` and the nested
83/// @ref ToLimitedLoggingString(const google::protobuf::Message&, std::size_t) call (used to produce `<details>`)
84/// never throwing.
85std::string ToLimitedLoggingString(const grpc::Status& status, std::size_t limit = kDefaultLoggingStringLimit) noexcept;
86
87/// @brief Get error details from `grpc::Status` for logging without size limit.
88///
89/// Convenience overload equivalent to calling @ref ToLimitedLoggingString with no effective size limit: the whole
90/// details part is serialized and never carries a `...(truncated)` marker. See @ref ToLimitedLoggingString for the
91/// output format, the `noexcept` guarantee and the error-handling contract.
92///
93/// @param status The `grpc::Status` to extract details from.
94/// @returns JSON representation of the status with its full details.
95///
96/// @warning Serializes the entire details part, so avoid this overload on unbounded input; prefer @ref
97/// ToLimitedLoggingString for logging.
98inline std::string ToUnlimitedLoggingString(const grpc::Status& status) noexcept {
99 return ToLimitedLoggingString(status, /*limit=*/std::numeric_limits<std::size_t>::max());
100}
101
102} // namespace ugrpc
103
104USERVER_NAMESPACE_END
105
106namespace fmt {
107
108/// @brief `fmt::format` support for protobuf messages
109template <typename T>
110requires std::is_base_of_v<google::protobuf::Message, std::decay_t<T>>
111struct formatter<T> {
112 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
113
114 template <typename FormatContext>
115 auto format(const T& message, FormatContext& ctx) const {
116 return fmt::format_to(ctx.out(), "{}", USERVER_NAMESPACE::ugrpc::ToLimitedLoggingString(message));
117 }
118};
119
120} // namespace fmt