blob: 3276499a0c9881dab30a4796d59608160e4f314f [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
6#include <algorithm>
7#include <bitset>
8#include <cstdarg>
9#include <cstdio>
10#include <iostream>
11#include <phosphor-logging/lg2.hpp>
12#include <vector>
13
Patrick Williams7460ce02022-03-03 11:29:03 -060014// Clang doesn't currently support source_location, but in order to provide
15// support for compiling an application with Clang while lg2 was compiled with
16// GCC we need to provide compile support *both* source_location and
17// experimental::source_location.
18//
19// Note: The experimental::source_location code will turn into a no-op for
20// simplicity. This is simply to allow compilation.
21#if __has_builtin(__builtin_source_location)
22#include <experimental/source_location>
23#endif
24
Patrick Williamsb2a3aa22021-07-27 13:30:52 -050025namespace lg2::details
26{
27/** Convert unsigned to string using format flags. */
28static std::string value_to_string(uint64_t f, uint64_t v)
29{
30 switch (f & (hex | bin | dec).value)
31 {
32 // For binary, use bitset<>::to_string.
33 // Treat values without a field-length format flag as 64 bit.
34 case bin.value:
35 {
36 switch (f & (field8 | field16 | field32 | field64).value)
37 {
38 case field8.value:
39 {
40 return "0b" + std::bitset<8>(v).to_string();
41 }
42 case field16.value:
43 {
44 return "0b" + std::bitset<16>(v).to_string();
45 }
46 case field32.value:
47 {
48 return "0b" + std::bitset<32>(v).to_string();
49 }
50 case field64.value:
51 default:
52 {
53 return "0b" + std::bitset<64>(v).to_string();
54 }
55 }
56 }
57
58 // For hex, use the appropriate sprintf.
59 case hex.value:
60 {
61 char value[19];
62 const char* format = nullptr;
63
64 switch (f & (field8 | field16 | field32 | field64).value)
65 {
66 case field8.value:
67 {
68 format = "0x%02" PRIx64;
69 break;
70 }
71
72 case field16.value:
73 {
74 format = "0x%04" PRIx64;
75 break;
76 }
77
78 case field32.value:
79 {
80 format = "0x%08" PRIx64;
81 break;
82 }
83
84 case field64.value:
85 {
86 format = "0x%016" PRIx64;
87 break;
88 }
89
90 default:
91 {
92 format = "0x%" PRIx64;
93 break;
94 }
95 }
96
97 snprintf(value, sizeof(value), format, v);
98 return value;
99 }
100
101 // For dec, use the simple to_string.
102 case dec.value:
103 default:
104 {
105 return std::to_string(v);
106 }
107 }
108}
109
110/** Convert signed to string using format flags. */
111static std::string value_to_string(uint64_t f, int64_t v)
112{
113 // If hex or bin was requested just use the unsigned formatting
114 // rules. (What should a negative binary number look like otherwise?)
115 if (f & (hex | bin).value)
116 {
117 return value_to_string(f, static_cast<uint64_t>(v));
118 }
119 return std::to_string(v);
120}
121
122/** Convert float to string using format flags. */
123static std::string value_to_string(uint64_t, double v)
124{
125 // No format flags supported for floats.
126 return std::to_string(v);
127}
128
129// Positions of various strings in an iovec.
130static constexpr size_t pos_msg = 0;
131static constexpr size_t pos_fmtmsg = 1;
132static constexpr size_t pos_prio = 2;
133static constexpr size_t pos_file = 3;
134static constexpr size_t pos_line = 4;
135static constexpr size_t pos_func = 5;
136static constexpr size_t static_locs = pos_func + 1;
137
138/** No-op output of a message. */
Patrick Williamsa91a62b2021-08-28 14:17:55 -0500139static void noop_extra_output(level, const lg2::source_location&,
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500140 const std::string&)
141{
142}
143
144/** std::cerr output of a message. */
Patrick Williamsa91a62b2021-08-28 14:17:55 -0500145static void cerr_extra_output(level l, const lg2::source_location& s,
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500146 const std::string& m)
147{
Jonathan Domana9e4f592021-11-19 00:05:17 -0800148 static const char* const defaultFormat = []() {
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600149 const char* f = getenv("LG2_FORMAT");
150 if (nullptr == f)
151 {
152 f = "<%l> %m";
153 }
154 return f;
155 }();
156
Jonathan Domana9e4f592021-11-19 00:05:17 -0800157 const char* format = defaultFormat;
158
Patrick Williams5dffc2d2021-11-14 06:33:21 -0600159 while (*format)
160 {
161 if (*format != '%')
162 {
163 std::cerr << *format;
164 ++format;
165 continue;
166 }
167
168 ++format;
169 switch (*format)
170 {
171 case '%':
172 case '\0':
173 std::cerr << '%';
174 break;
175
176 case 'f':
177 std::cerr << s.function_name();
178 break;
179
180 case 'F':
181 std::cerr << s.file_name();
182 break;
183
184 case 'l':
185 std::cerr << static_cast<uint64_t>(l);
186 break;
187
188 case 'L':
189 std::cerr << s.line();
190 break;
191
192 case 'm':
193 std::cerr << m;
194 break;
195
196 default:
197 std::cerr << '%' << *format;
198 break;
199 }
200
201 if (*format != '\0')
202 {
203 ++format;
204 }
205 }
206
207 std::cerr << std::endl;
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500208}
209
210// Use the cerr output method if we are on a TTY.
211static auto extra_output_method =
212 isatty(fileno(stderr)) ? cerr_extra_output : noop_extra_output;
213
214// Do_log implementation.
Patrick Williamsa91a62b2021-08-28 14:17:55 -0500215void do_log(level l, const lg2::source_location& s, const char* m, ...)
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500216{
217 using namespace std::string_literals;
218
Patrick Williamsb1811b32021-08-26 12:19:01 -0500219 std::vector<std::string> strings{static_locs};
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500220
221 std::string message{m};
222
223 // Assign all the static fields.
Patrick Williamsb5988ff2021-08-26 11:58:36 -0500224 strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m;
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500225 strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast<uint64_t>(l));
226 strings[pos_file] = "CODE_FILE="s + s.file_name();
227 strings[pos_line] = "CODE_LINE="s + std::to_string(s.line());
228 strings[pos_func] = "CODE_FUNC="s + s.function_name();
229
230 // Handle all the va_list args.
231 std::va_list args;
Patrick Williamsb1811b32021-08-26 12:19:01 -0500232 va_start(args, m);
233 while (true)
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500234 {
235 // Get the header out.
Patrick Williamsb1811b32021-08-26 12:19:01 -0500236 auto h_ptr = va_arg(args, const char*);
237 if (h_ptr == nullptr)
238 {
239 break;
240 }
241 std::string h{h_ptr};
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500242
243 // Get the format flag.
244 auto f = va_arg(args, uint64_t);
245
246 // Handle the value depending on which type format flag it has.
247 std::string value = {};
248 switch (f & (signed_val | unsigned_val | str | floating).value)
249 {
250 case signed_val.value:
251 {
252 auto v = va_arg(args, int64_t);
253 value = value_to_string(f, v);
254 break;
255 }
256
257 case unsigned_val.value:
258 {
259 auto v = va_arg(args, uint64_t);
260 value = value_to_string(f, v);
261 break;
262 }
263
264 case str.value:
265 {
266 value = va_arg(args, const char*);
267 break;
268 }
269
270 case floating.value:
271 {
272 auto v = va_arg(args, double);
273 value = value_to_string(f, v);
274 break;
275 }
276 }
277
278 // Create the field for this value.
Patrick Williamsb1811b32021-08-26 12:19:01 -0500279 strings.emplace_back(h + '=' + value);
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500280
281 // Check for {HEADER} in the message and replace with value.
282 auto h_brace = '{' + h + '}';
283 if (auto start = message.find(h_brace); start != std::string::npos)
284 {
285 message.replace(start, h_brace.size(), value);
286 }
287 }
288 va_end(args);
289
290 // Add the final message into the strings array.
291 strings[pos_msg] = "MESSAGE="s + message.data();
292
293 // Trasform strings -> iovec.
294 std::vector<iovec> iov{};
295 std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) {
296 return iovec{s.data(), s.length()};
297 });
298
299 // Output the iovec.
Patrick Williamsb1811b32021-08-26 12:19:01 -0500300 sd_journal_sendv(iov.data(), strings.size());
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500301 extra_output_method(l, s, message);
302}
303
Patrick Williams7460ce02022-03-03 11:29:03 -0600304// If std::source_location is supported, provide an additional
305// std::experimental::source_location implementation that does nothing so that
306// lg2 users can compile with Clang even if lg2 was compiled with GCC. This
307// is a no-op implementation that simply allows compile support since some
308// people like to compile with Clang for additional / stricter checks.
309#if __has_builtin(__builtin_source_location)
310void do_log(level, const std::experimental::source_location&, const char*, ...)
311{
312}
313#endif
314
Patrick Williamsb2a3aa22021-07-27 13:30:52 -0500315} // namespace lg2::details