userver: userver/logging/log_helper.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
log_helper.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/logging/log_helper.hpp
4/// @brief @copybrief logging::LogHelper
5
6#include <chrono>
7#include <exception>
8#include <iosfwd>
9#include <iterator>
10#include <memory>
11#include <optional>
12#include <string_view>
13#include <system_error>
14#include <type_traits>
15
16#include <fmt/core.h>
17
18#include <userver/formats/common/meta.hpp>
19#include <userver/logging/fwd.hpp>
20#include <userver/logging/level.hpp>
21#include <userver/logging/log_extra.hpp>
22#include <userver/utils/impl/source_location.hpp>
23#include <userver/utils/meta.hpp>
24
25USERVER_NAMESPACE_BEGIN
26
27namespace logging {
28
29namespace impl {
30
31class TagWriter;
32
33struct Noop {};
34
35struct HexBase {
36 std::uint64_t value;
37
38 template <typename Unsigned, typename = std::enable_if_t<std::is_unsigned_v<Unsigned>>>
39 explicit constexpr HexBase(Unsigned value) noexcept : value(value) {
40 static_assert(sizeof(Unsigned) <= sizeof(value));
41 }
42
43 template <typename T>
44 explicit HexBase(T* pointer) noexcept : HexBase(reinterpret_cast<std::uintptr_t>(pointer)) {
45 static_assert(sizeof(std::uintptr_t) <= sizeof(value));
46 }
47};
48
49} // namespace impl
50
51/// Formats value in a hex mode with the fixed length representation.
52struct Hex final : impl::HexBase {
53 using impl::HexBase::HexBase;
54};
55
56/// Formats value in a hex mode with the shortest representation, just like
57/// std::to_chars does.
58struct HexShort final : impl::HexBase {
59 using impl::HexBase::HexBase;
60};
61
62/// Formats a string as quoted, escaping the '\' and '"' symbols.
63struct Quoted final {
64 std::string_view string;
65};
66
67enum class LogClass {
68 kLog,
69 kTrace,
70};
71
72/// Stream-like tskv-formatted log message builder.
73///
74/// Users can add LogHelper& operator<<(LogHelper&, ) overloads to use a faster
75/// localeless logging, rather than outputting data through the ostream
76/// operator.
77class LogHelper final {
78public:
79 /// @brief Constructs LogHelper with span logging
80 /// @param logger to log to
81 /// @param level message log level
82 /// @param log_class whether this LogHelper will be used to write a log record or a span
83 /// @param location source location that will be written to logs
85 LoggerRef logger,
86 Level level,
87 LogClass log_class = LogClass::kLog,
88 const utils::impl::SourceLocation& location = utils::impl::SourceLocation::Current()
89 ) noexcept;
90
91 /// @brief Constructs LogHelper with span logging
92 /// @param logger to log to (logging to nullptr does not output messages)
93 /// @param level message log level
94 /// @param log_class whether this LogHelper will be used to write a log record or a span
95 /// @param location source location that will be written to logs
97 const LoggerPtr& logger,
98 Level level,
99 LogClass log_class = LogClass::kLog,
100 const utils::impl::SourceLocation& location = utils::impl::SourceLocation::Current()
101 ) noexcept;
102
103 ~LogHelper();
104
105 LogHelper(LogHelper&&) = delete;
106 LogHelper(const LogHelper&) = delete;
107 LogHelper& operator=(LogHelper&&) = delete;
108 LogHelper& operator=(const LogHelper&) = delete;
109
110 // Helper function that could be called on LogHelper&& to get LogHelper&.
111 LogHelper& AsLvalue() noexcept { return *this; }
112
113 /// @cond
114 template <typename... Args>
115 LogHelper& AsLvalue(fmt::format_string<Args...> fmt, Args&&... args) noexcept {
116 VFormat(fmt::string_view(fmt), fmt::make_format_args(args...));
117 return *this;
118 }
119 /// @endcond
120
121 bool IsLimitReached() const noexcept;
122
123 template <typename T>
124 LogHelper& operator<<(const T& value) {
125 if constexpr (std::is_constructible_v<std::string_view, T>) {
126 // noexcept if the conversion is noexcept
127 *this << std::string_view{value};
128 } else if constexpr (std::is_signed_v<T>) {
129 using LongLong = long long;
130 *this << LongLong{value};
131 } else if constexpr (std::is_unsigned_v<T>) {
132 using UnsignedLongLong = unsigned long long;
133 *this << UnsignedLongLong{value};
134 } else if constexpr (std::is_base_of_v<std::exception, T>) {
135 *this << static_cast<const std::exception&>(value);
136 } else if constexpr (meta::kIsOstreamWritable<T>) {
137 // may throw a non std::exception based exception
138 Stream() << value;
139 FlushStream();
140 } else if constexpr (meta::kIsRange<T> && !formats::common::kIsFormatValue<T>) {
141 // may throw a non std::exception based exception
142 PutRange(value);
143 } else {
144 static_assert(
145 !sizeof(T),
146 "Please implement logging for your type: "
147 "logging::LogHelper& operator<<(logging::LogHelper& lh, const T& value)"
148 );
149 }
150
151 return *this;
152 }
153
154 LogHelper& operator<<(char value) noexcept;
155 LogHelper& operator<<(std::string_view value) noexcept;
156 LogHelper& operator<<(float value) noexcept;
157 LogHelper& operator<<(double value) noexcept;
158 LogHelper& operator<<(long double value) noexcept;
159 LogHelper& operator<<(unsigned long long value) noexcept;
160 LogHelper& operator<<(long long value) noexcept;
161 LogHelper& operator<<(bool value) noexcept;
162 LogHelper& operator<<(const std::exception& value) noexcept;
163
164 /// Extends internal LogExtra
165 LogHelper& operator<<(const LogExtra& extra) noexcept;
166
167 /// Extends internal LogExtra
168 LogHelper& operator<<(LogExtra&& extra) noexcept;
169
170 LogHelper& operator<<(Hex hex) noexcept;
171
172 LogHelper& operator<<(HexShort hex) noexcept;
173
174 LogHelper& operator<<(Quoted value) noexcept;
175
176 LogHelper& PutTag(std::string_view key, const LogExtra::Value& value) noexcept;
177 LogHelper& PutSwTag(std::string_view key, std::string_view value) noexcept;
178
179 /// @brief Formats a log message using the specified format string and arguments.
180 /// @param fmt The format string as per fmtlib.
181 /// @param args Arguments to be formatted into the log message.
182 /// @return A reference to the LogHelper object for chaining.
183 template <typename... Args>
184 LogHelper& Format(fmt::format_string<Args...> fmt, Args&&... args) noexcept;
185
186 /// @cond
187 // For internal use only!
188 operator impl::Noop() const noexcept { return {}; }
189
190 struct InternalTag;
191
192 // For internal use only!
193 impl::TagWriter GetTagWriter();
194
195 /// @endcond
196
197private:
198 friend class impl::TagWriter;
199
200 void DoLog() noexcept;
201
202 void InternalLoggingError(std::string_view message) noexcept;
203
204 void PutFloatingPoint(float value);
205 void PutFloatingPoint(double value);
206 void PutFloatingPoint(long double value);
207 void PutUnsigned(unsigned long long value);
208 void PutSigned(long long value);
209 void PutBoolean(bool value);
210 void Put(std::string_view value);
211 void Put(char value);
212
213 void PutRaw(std::string_view value_needs_no_escaping);
214 void PutException(const std::exception& ex);
215 void PutQuoted(std::string_view value);
216
217 void VFormat(fmt::string_view fmt, fmt::format_args args) noexcept;
218
219 template <typename T>
220 void PutRangeElement(const T& value);
221
222 template <typename T, typename U>
223 void PutMapElement(const std::pair<const T, U>& value);
224
225 template <typename T>
226 void PutRange(const T& range);
227
228 std::ostream& Stream();
229 void FlushStream();
230
231 class Impl;
232 std::unique_ptr<Impl> pimpl_;
233};
234
235inline LogHelper& operator<<(LogHelper& lh, std::error_code ec) {
236 lh << ec.category().name() << ':' << ec.value() << " (" << ec.message() << ')';
237 return lh;
238}
239
240template <typename T>
241LogHelper& operator<<(LogHelper& lh, const std::atomic<T>& value) {
242 return lh << value.load();
243}
244
245template <typename T>
246LogHelper& operator<<(LogHelper& lh, const T* value) noexcept {
247 if (value == nullptr) {
248 lh << std::string_view{"(null)"};
249 } else if constexpr (std::is_same_v<T, char>) {
250 lh << std::string_view{value};
251 } else {
252 lh << Hex{value};
253 }
254 return lh;
255}
256
257template <typename T>
258LogHelper& operator<<(LogHelper& lh, T* value) {
259 static_assert(
260 !std::is_function_v<T>,
261 "An attempt to log the function address is denied. If you really know what you're doing, cast it to void*."
262 );
263 return lh << static_cast<const T*>(value);
264}
265
266template <typename T>
267LogHelper& operator<<(LogHelper& lh, const std::optional<T>& value) {
268 if (value)
269 lh << *value;
270 else
271 lh << "(none)";
272 return lh;
273}
274
275template <typename Fun, typename = std::enable_if_t<std::is_invocable_r_v<void, Fun, LogHelper&>>>
276LogHelper& operator<<(LogHelper& lh, Fun&& value) {
277 std::forward<Fun>(value)(lh);
278 return lh;
279}
280
281template <class Result, class... Args>
282LogHelper& operator<<(LogHelper& lh, Result (*)(Args...)) {
283 static_assert(!sizeof(Result), "Outputting functions or std::ostream formatters is forbidden");
284 return lh;
285}
286
287LogHelper& operator<<(LogHelper& lh, std::chrono::system_clock::time_point tp);
288LogHelper& operator<<(LogHelper& lh, std::chrono::seconds value);
289LogHelper& operator<<(LogHelper& lh, std::chrono::milliseconds value);
290LogHelper& operator<<(LogHelper& lh, std::chrono::microseconds value);
291LogHelper& operator<<(LogHelper& lh, std::chrono::nanoseconds value);
292LogHelper& operator<<(LogHelper& lh, std::chrono::minutes value);
293LogHelper& operator<<(LogHelper& lh, std::chrono::nanoseconds value);
294LogHelper& operator<<(LogHelper& lh, std::chrono::hours value);
295
296template <typename T>
297void LogHelper::PutRangeElement(const T& value) {
298 if constexpr (std::is_constructible_v<std::string_view, T>) {
299 *this << Quoted{value};
300 } else {
301 *this << value;
302 }
303}
304
305template <typename T, typename U>
306void LogHelper::PutMapElement(const std::pair<const T, U>& value) {
307 PutRangeElement(value.first);
308 *this << ": ";
309 PutRangeElement(value.second);
310}
311
312template <typename T>
313void LogHelper::PutRange(const T& range) {
314 static_assert(meta::kIsRange<T>);
315 using std::begin;
316 using std::end;
317
318 static_assert(
319 !std::is_same_v<meta::RangeValueType<T>, char>,
320 "You should either manually convert type to 'std::string_view' or provide 'operator<<' specialization for your "
321 "type: 'logging::LogHelper& operator<<(logging::LogHelper& lh, const T& value)' or make your type convertible "
322 "to 'std::string_view'"
323 );
324
325 constexpr std::string_view kSeparator = ", ";
326 *this << '[';
327
328 bool is_first = true;
329 auto curr = begin(range);
330 const auto end_iter = end(range);
331
332 while (curr != end_iter) {
333 if (IsLimitReached()) {
334 break;
335 }
336 if (is_first) {
337 is_first = false;
338 } else {
339 *this << kSeparator;
340 }
341
342 if constexpr (meta::kIsMap<T>) {
343 PutMapElement(*curr);
344 } else {
345 PutRangeElement(*curr);
346 }
347 ++curr;
348 }
349
350 const auto extra_elements = std::distance(curr, end_iter);
351
352 if (extra_elements != 0) {
353 if (!is_first) {
354 *this << kSeparator;
355 }
356 *this << "..." << extra_elements << " more";
357 }
358
359 *this << ']';
360}
361
362template <typename... Args>
363LogHelper& LogHelper::Format(fmt::format_string<Args...> fmt, Args&&... args) noexcept {
364 VFormat(fmt::string_view(fmt), fmt::make_format_args(args...));
365 return *this;
366}
367
368} // namespace logging
369
370USERVER_NAMESPACE_END