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