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 <userver/formats/common/meta.hpp>
17#include <userver/logging/fwd.hpp>
18#include <userver/logging/level.hpp>
19#include <userver/logging/log_extra.hpp>
20#include <userver/utils/impl/source_location.hpp>
21#include <userver/utils/meta.hpp>
22
23USERVER_NAMESPACE_BEGIN
24
25namespace logging {
26
27namespace impl {
28
29class TagWriter;
30
31struct Noop {};
32
33struct HexBase {
34 std::uint64_t value;
35
36 template <typename Unsigned,
37 typename = std::enable_if_t<std::is_unsigned_v<Unsigned>>>
38 explicit constexpr HexBase(Unsigned value) noexcept : value(value) {
39 static_assert(sizeof(Unsigned) <= sizeof(value));
40 }
41
42 template <typename T>
43 explicit HexBase(T* pointer) noexcept
44 : 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
67/// Stream-like tskv-formatted log message builder.
68///
69/// Users can add LogHelper& operator<<(LogHelper&, ) overloads to use a faster
70/// localeless logging, rather than outputting data through the ostream
71/// operator.
72class LogHelper final {
73 public:
74 /// @brief Constructs LogHelper with span logging
75 /// @param logger to log to
76 /// @param level message log level
77 /// @param location source location that will be written to logs
78 LogHelper(LoggerRef logger, Level level,
79 const utils::impl::SourceLocation& location =
80 utils::impl::SourceLocation::Current()) noexcept;
81
82 /// @brief Constructs LogHelper with span logging
83 /// @param logger to log to (logging to nullptr does not output messages)
84 /// @param level message log level
85 /// @param location source location that will be written to logs
86 LogHelper(const LoggerPtr& logger, Level level,
87 const utils::impl::SourceLocation& location =
88 utils::impl::SourceLocation::Current()) noexcept;
89
90 ~LogHelper();
91
92 LogHelper(LogHelper&&) = delete;
93 LogHelper(const LogHelper&) = delete;
94 LogHelper& operator=(LogHelper&&) = delete;
95 LogHelper& operator=(const LogHelper&) = delete;
96
97 // Helper function that could be called on LogHelper&& to get LogHelper&.
98 LogHelper& AsLvalue() noexcept { return *this; }
99
100 bool IsLimitReached() const noexcept;
101
102 template <typename T>
103 LogHelper& operator<<(const T& value) {
104 if constexpr (std::is_constructible_v<std::string_view, T>) {
105 // noexcept if the conversion is noexcept
106 *this << std::string_view{value};
107 } else if constexpr (std::is_signed_v<T>) {
108 using LongLong = long long;
109 *this << LongLong{value};
110 } else if constexpr (std::is_unsigned_v<T>) {
111 using UnsignedLongLong = unsigned long long;
112 *this << UnsignedLongLong{value};
113 } else if constexpr (std::is_base_of_v<std::exception, T>) {
114 *this << static_cast<const std::exception&>(value);
115 } else if constexpr (meta::kIsOstreamWritable<T>) {
116 // may throw a non std::exception based exception
117 Stream() << value;
118 FlushStream();
119 } else if constexpr (meta::kIsRange<T> &&
120 !formats::common::kIsFormatValue<T>) {
121 // may throw a non std::exception based exception
122 PutRange(value);
123 } else {
124 static_assert(!sizeof(T),
125 "Please implement logging for your type: "
126 "logging::LogHelper& operator<<(logging::LogHelper& lh, "
127 "const T& value)");
128 }
129
130 return *this;
131 }
132
133 LogHelper& operator<<(char value) noexcept;
134 LogHelper& operator<<(std::string_view value) noexcept;
135 LogHelper& operator<<(float value) noexcept;
136 LogHelper& operator<<(double value) noexcept;
137 LogHelper& operator<<(long double value) noexcept;
138 LogHelper& operator<<(unsigned long long value) noexcept;
139 LogHelper& operator<<(long long value) noexcept;
140 LogHelper& operator<<(bool value) noexcept;
141 LogHelper& operator<<(const std::exception& value) noexcept;
142
143 /// Extends internal LogExtra
144 LogHelper& operator<<(const LogExtra& extra) noexcept;
145
146 /// Extends internal LogExtra
147 LogHelper& operator<<(LogExtra&& extra) noexcept;
148
149 LogHelper& operator<<(const LogExtra::Value& value) noexcept;
150
151 LogHelper& operator<<(Hex hex) noexcept;
152
153 LogHelper& operator<<(HexShort hex) noexcept;
154
155 LogHelper& operator<<(Quoted value) noexcept;
156
157 /// @cond
158 // For internal use only!
159 operator impl::Noop() const noexcept { return {}; }
160
161 struct InternalTag;
162
163 // For internal use only!
164 impl::TagWriter GetTagWriterAfterText(InternalTag);
165 /// @endcond
166
167 private:
168 friend class impl::TagWriter;
169
170 struct Module;
171
172 void DoLog() noexcept;
173
174 void InternalLoggingError(std::string_view message) noexcept;
175
176 impl::TagWriter GetTagWriter();
177
178 void PutFloatingPoint(float value);
179 void PutFloatingPoint(double value);
180 void PutFloatingPoint(long double value);
181 void PutUnsigned(unsigned long long value);
182 void PutSigned(long long value);
183 void PutBoolean(bool value);
184 void Put(std::string_view value);
185 void Put(char value);
186
187 void PutRaw(std::string_view value_needs_no_escaping);
188 void PutException(const std::exception& ex);
189 void PutQuoted(std::string_view value);
190
191 template <typename T>
192 void PutRangeElement(const T& value);
193
194 template <typename T, typename U>
195 void PutMapElement(const std::pair<const T, U>& value);
196
197 template <typename T>
198 void PutRange(const T& range);
199
200 std::ostream& Stream();
201 void FlushStream();
202
203 class Impl;
204 std::unique_ptr<Impl> pimpl_;
205};
206
207inline LogHelper& operator<<(LogHelper& lh, std::error_code ec) {
208 lh << ec.category().name() << ':' << ec.value() << " (" << ec.message()
209 << ')';
210 return lh;
211}
212
213template <typename T>
214LogHelper& operator<<(LogHelper& lh, const std::atomic<T>& value) {
215 return lh << value.load();
216}
217
218template <typename T>
219LogHelper& operator<<(LogHelper& lh, const T* value) noexcept {
220 if (value == nullptr) {
221 lh << std::string_view{"(null)"};
222 } else if constexpr (std::is_same_v<T, char>) {
223 lh << std::string_view{value};
224 } else {
225 lh << Hex{value};
226 }
227 return lh;
228}
229
230template <typename T>
231LogHelper& operator<<(LogHelper& lh, T* value) {
232 return lh << static_cast<const T*>(value);
233}
234
235template <typename T>
236LogHelper& operator<<(LogHelper& lh, const std::optional<T>& value) {
237 if (value)
238 lh << *value;
239 else
240 lh << "(none)";
241 return lh;
242}
243
244template <class Result, class... Args>
245LogHelper& operator<<(LogHelper& lh, Result (*)(Args...)) {
246 static_assert(!sizeof(Result),
247 "Outputting functions or std::ostream formatters is forbidden");
248 return lh;
249}
250
251LogHelper& operator<<(LogHelper& lh, std::chrono::system_clock::time_point tp);
252LogHelper& operator<<(LogHelper& lh, std::chrono::seconds value);
253LogHelper& operator<<(LogHelper& lh, std::chrono::milliseconds value);
254LogHelper& operator<<(LogHelper& lh, std::chrono::microseconds value);
255LogHelper& operator<<(LogHelper& lh, std::chrono::nanoseconds value);
256LogHelper& operator<<(LogHelper& lh, std::chrono::minutes value);
257LogHelper& operator<<(LogHelper& lh, std::chrono::nanoseconds value);
258LogHelper& operator<<(LogHelper& lh, std::chrono::hours value);
259
260template <typename T>
261void LogHelper::PutRangeElement(const T& value) {
262 if constexpr (std::is_constructible_v<std::string_view, T>) {
263 *this << Quoted{value};
264 } else {
265 *this << value;
266 }
267}
268
269template <typename T, typename U>
270void LogHelper::PutMapElement(const std::pair<const T, U>& value) {
271 PutRangeElement(value.first);
272 *this << ": ";
273 PutRangeElement(value.second);
274}
275
276template <typename T>
277void LogHelper::PutRange(const T& range) {
278 static_assert(meta::kIsRange<T>);
279 using std::begin;
280 using std::end;
281
282 constexpr std::string_view kSeparator = ", ";
283 *this << '[';
284
285 bool is_first = true;
286 auto curr = begin(range);
287 const auto end_iter = end(range);
288
289 while (curr != end_iter) {
290 if (IsLimitReached()) {
291 break;
292 }
293 if (is_first) {
294 is_first = false;
295 } else {
296 *this << kSeparator;
297 }
298
299 if constexpr (meta::kIsMap<T>) {
300 PutMapElement(*curr);
301 } else {
302 PutRangeElement(*curr);
303 }
304 ++curr;
305 }
306
307 const auto extra_elements = std::distance(curr, end_iter);
308
309 if (extra_elements != 0) {
310 if (!is_first) {
311 *this << kSeparator;
312 }
313 *this << "..." << extra_elements << " more";
314 }
315
316 *this << ']';
317}
318
319} // namespace logging
320
321USERVER_NAMESPACE_END