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