blob: 5a80b413d2d278b474b5985f868753b25982affd [file] [log] [blame]
Patrick Williamsb2a3aa22021-07-27 13:30:52 -05001#define SD_JOURNAL_SUPPRESS_LOCATION
2
3#include <systemd/sd-journal.h>
4#include <unistd.h>
5
Patrick Williams2544b412022-10-04 08:41:06 -05006#include <phosphor-logging/lg2.hpp>
7
Patrick Williamsb2a3aa22021-07-27 13:30:52 -05008#include <algorithm>
9#include <bitset>
10#include <cstdarg>
11#include <cstdio>
12#include <iostream>
Josh Lehan230f9f92023-07-20 22:21:06 +000013#include <mutex>
Patrick Williams7f5e4412023-06-23 06:46:06 -050014#include <source_location>
Josh Lehan230f9f92023-07-20 22:21:06 +000015#include <sstream>
Piotr Sulewski9fd25af2025-09-23 13:18:59 +020016#include <utility>
Patrick Williamsb2a3aa22021-07-27 13:30:52 -050017#include <vector>
18
19namespace lg2::details
20{
21/** Convert unsigned to string using format flags. */
22static std::string value_to_string(uint64_t f, uint64_t v)
23{
24 switch (f & (hex | bin | dec).value)
25 {
26 // For binary, use bitset<>::to_string.
27 // Treat values without a field-length format flag as 64 bit.
28 case bin.value:
29 {
30 switch (f & (field8 | field16 | field32 | field64).value)
31 {
32 case field8.value:
33 {
34 return "0b" + std::bitset<8>(v).to_string();
35 }
36 case field16.value:
37 {
38 return "0b" + std::bitset<16>(v).to_string();
39 }
40 case field32.value:
41 {
42 return "0b" + std::bitset<32>(v).to_string();
43 }
44 case field64.value:
45 default:
46 {
47 return "0b" + std::bitset<64>(v).to_string();
48 }
49 }
50 }
51
52 // For hex, use the appropriate sprintf.
53 case hex.value:
54 {
55 char value[19];
56 const char* format = nullptr;
57
58 switch (f & (field8 | field16 | field32 | field64).value)
59 {
60 case field8.value:
61 {
62 format = "0x%02" PRIx64;
63 break;
64 }
65
66 case field16.value:
67 {
68 format = "0x%04" PRIx64;
69 break;
70 }
71
72 case field32.value:
73 {
74 format = "0x%08" PRIx64;
75 break;
76 }
77
78 case field64.value:
79 {
80 format = "0x%016" PRIx64;
81 break;
82 }
83
84 default:
85 {
86 format = "0x%" PRIx64;
87 break;
88 }
89 }
90
91 snprintf(value, sizeof(value), format, v);
92 return value;
93 }
94
95 // For dec, use the simple to_string.
96 case dec.value:
97 default:
98 {
99 return std::to_string(v);
100 }
101 }
102}
103
104/** Convert signed to string using format flags. */
105static std::string value_to_string(uint64_t f, int64_t v)
106{
107 // If hex or bin was requested just use the unsigned formatting
108 // rules. (What should a negative binary number look like otherwise?)
109 if (f & (hex | bin).value)
110 {
111 return value_to_string(f, static_cast<uint64_t>(v));
112 }
113 return std::to_string(v);
114}
115
116/** Convert float to string using format flags. */
117static std::string value_to_string(uint64_t, double v)
118{
119 // No format flags supported for floats.
120 return std::to_string(v);
121}
122
123// Positions of various strings in an iovec.
124static constexpr size_t pos_msg = 0;
125static constexpr size_t pos_fmtmsg = 1;
126static constexpr size_t pos_prio = 2;
127static constexpr size_t pos_file = 3;
128static constexpr size_t pos_line = 4;
129static constexpr size_t pos_func = 5;
130static constexpr size_t static_locs = pos_func + 1;
131
132/** No-op output of a message. */
Patrick Williams7f5e4412023-06-23 06:46:06 -0500133static void noop_extra_output(level, const std::source_location&,
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500134 const std::string&)
Patrick Williams2544b412022-10-04 08:41:06 -0500135{}
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500136
137/** std::cerr output of a message. */
Patrick Williams7f5e4412023-06-23 06:46:06 -0500138static void cerr_extra_output(level l, const std::source_location& s,
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500139 const std::string& m)
140{
Piotr Sulewski9fd25af2025-09-23 13:18:59 +0200141 static const int maxLogLevel = []() {
142 const char* logLevel = getenv("LG2_LOG_LEVEL");
143 if (logLevel == nullptr)
144 {
145 // Default to logging all levels if not set
146 return std::to_underlying(level::debug);
147 }
148 try
149 {
150 int level = std::stoi(logLevel);
151 return std::clamp(level, std::to_underlying(level::emergency),
152 std::to_underlying(level::debug));
153 }
154 catch (const std::exception&)
155 {
156 return std::to_underlying(level::debug);
157 }
158 }();
159
160 if (std::to_underlying(l) > maxLogLevel)
161 {
162 return;
163 }
164
Jonathan Domana9e4f592021-11-19 00:05:17 -0800165 static const char* const defaultFormat = []() {
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600166 const char* f = getenv("LG2_FORMAT");
167 if (nullptr == f)
168 {
169 f = "<%l> %m";
170 }
171 return f;
172 }();
173
Jonathan Domana9e4f592021-11-19 00:05:17 -0800174 const char* format = defaultFormat;
175
Josh Lehan230f9f92023-07-20 22:21:06 +0000176 std::stringstream stream;
177
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600178 while (*format)
179 {
180 if (*format != '%')
181 {
Josh Lehan230f9f92023-07-20 22:21:06 +0000182 stream << *format;
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600183 ++format;
184 continue;
185 }
186
187 ++format;
188 switch (*format)
189 {
190 case '%':
191 case '\0':
Josh Lehan230f9f92023-07-20 22:21:06 +0000192 stream << '%';
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600193 break;
194
195 case 'f':
Josh Lehan230f9f92023-07-20 22:21:06 +0000196 stream << s.function_name();
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600197 break;
198
199 case 'F':
Josh Lehan230f9f92023-07-20 22:21:06 +0000200 stream << s.file_name();
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600201 break;
202
203 case 'l':
Josh Lehan230f9f92023-07-20 22:21:06 +0000204 stream << static_cast<uint64_t>(l);
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600205 break;
206
207 case 'L':
Josh Lehan230f9f92023-07-20 22:21:06 +0000208 stream << s.line();
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600209 break;
210
211 case 'm':
Josh Lehan230f9f92023-07-20 22:21:06 +0000212 stream << m;
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600213 break;
214
215 default:
Josh Lehan230f9f92023-07-20 22:21:06 +0000216 stream << '%' << *format;
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600217 break;
218 }
219
220 if (*format != '\0')
221 {
222 ++format;
223 }
224 }
225
Josh Lehan230f9f92023-07-20 22:21:06 +0000226 static std::mutex mutex;
227
228 // Prevent multiple threads from clobbering the stderr lines of each other
229 std::scoped_lock lock(mutex);
230
231 // Ensure this line makes it out before releasing mutex for next line
232 std::cerr << stream.str() << std::endl;
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500233}
234
Zane Shelleyea06f8e2022-03-25 16:09:23 -0500235// Use the cerr output method if we are on a TTY or if explicitly set via
236// environment variable.
Patrick Williams075c7922024-08-16 15:19:49 -0400237static auto extra_output_method =
238 (isatty(fileno(stderr)) || nullptr != getenv("LG2_FORCE_STDERR"))
239 ? cerr_extra_output
240 : noop_extra_output;
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500241
Patrick Williams38575792024-12-03 08:22:47 -0500242// Skip sending debug messages to journald if "DEBUG_INVOCATION" is not set
243// per systemd.exec manpage.
244static auto send_debug_to_journal = nullptr != getenv("DEBUG_INVOCATION");
245
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500246// Do_log implementation.
Patrick Williams7f5e4412023-06-23 06:46:06 -0500247void do_log(level l, const std::source_location& s, const char* m, ...)
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500248{
249 using namespace std::string_literals;
250
Patrick Williamsb1811b32021-08-26 12:19:01 -0500251 std::vector<std::string> strings{static_locs};
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500252
253 std::string message{m};
254
255 // Assign all the static fields.
Patrick Williamsb5988ff2021-08-26 11:58:36 -0500256 strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m;
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500257 strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast<uint64_t>(l));
258 strings[pos_file] = "CODE_FILE="s + s.file_name();
259 strings[pos_line] = "CODE_LINE="s + std::to_string(s.line());
260 strings[pos_func] = "CODE_FUNC="s + s.function_name();
261
262 // Handle all the va_list args.
263 std::va_list args;
Patrick Williamsb1811b32021-08-26 12:19:01 -0500264 va_start(args, m);
265 while (true)
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500266 {
267 // Get the header out.
Patrick Williamsb1811b32021-08-26 12:19:01 -0500268 auto h_ptr = va_arg(args, const char*);
269 if (h_ptr == nullptr)
270 {
271 break;
272 }
273 std::string h{h_ptr};
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500274
275 // Get the format flag.
276 auto f = va_arg(args, uint64_t);
277
278 // Handle the value depending on which type format flag it has.
279 std::string value = {};
280 switch (f & (signed_val | unsigned_val | str | floating).value)
281 {
282 case signed_val.value:
283 {
284 auto v = va_arg(args, int64_t);
285 value = value_to_string(f, v);
286 break;
287 }
288
289 case unsigned_val.value:
290 {
291 auto v = va_arg(args, uint64_t);
292 value = value_to_string(f, v);
293 break;
294 }
295
296 case str.value:
297 {
298 value = va_arg(args, const char*);
299 break;
300 }
301
302 case floating.value:
303 {
304 auto v = va_arg(args, double);
305 value = value_to_string(f, v);
306 break;
307 }
308 }
309
310 // Create the field for this value.
Patrick Williamsb1811b32021-08-26 12:19:01 -0500311 strings.emplace_back(h + '=' + value);
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500312
313 // Check for {HEADER} in the message and replace with value.
314 auto h_brace = '{' + h + '}';
315 if (auto start = message.find(h_brace); start != std::string::npos)
316 {
317 message.replace(start, h_brace.size(), value);
318 }
319 }
320 va_end(args);
321
322 // Add the final message into the strings array.
323 strings[pos_msg] = "MESSAGE="s + message.data();
324
325 // Trasform strings -> iovec.
326 std::vector<iovec> iov{};
327 std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) {
328 return iovec{s.data(), s.length()};
329 });
330
331 // Output the iovec.
Patrick Williams38575792024-12-03 08:22:47 -0500332 if (send_debug_to_journal || l != level::debug)
333 {
334 sd_journal_sendv(iov.data(), strings.size());
335 }
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500336 extra_output_method(l, s, message);
337}
338
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500339} // namespace lg2::details