userver: userver/logging/log_helper.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
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 <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