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