1 #define SD_JOURNAL_SUPPRESS_LOCATION
2
3 #include <systemd/sd-journal.h>
4 #include <unistd.h>
5
6 #include <phosphor-logging/lg2.hpp>
7
8 #include <algorithm>
9 #include <bitset>
10 #include <cstdarg>
11 #include <cstdio>
12 #include <iostream>
13 #include <mutex>
14 #include <source_location>
15 #include <sstream>
16 #include <vector>
17
18 namespace lg2::details
19 {
20 /** Convert unsigned to string using format flags. */
value_to_string(uint64_t f,uint64_t v)21 static std::string value_to_string(uint64_t f, uint64_t v)
22 {
23 switch (f & (hex | bin | dec).value)
24 {
25 // For binary, use bitset<>::to_string.
26 // Treat values without a field-length format flag as 64 bit.
27 case bin.value:
28 {
29 switch (f & (field8 | field16 | field32 | field64).value)
30 {
31 case field8.value:
32 {
33 return "0b" + std::bitset<8>(v).to_string();
34 }
35 case field16.value:
36 {
37 return "0b" + std::bitset<16>(v).to_string();
38 }
39 case field32.value:
40 {
41 return "0b" + std::bitset<32>(v).to_string();
42 }
43 case field64.value:
44 default:
45 {
46 return "0b" + std::bitset<64>(v).to_string();
47 }
48 }
49 }
50
51 // For hex, use the appropriate sprintf.
52 case hex.value:
53 {
54 char value[19];
55 const char* format = nullptr;
56
57 switch (f & (field8 | field16 | field32 | field64).value)
58 {
59 case field8.value:
60 {
61 format = "0x%02" PRIx64;
62 break;
63 }
64
65 case field16.value:
66 {
67 format = "0x%04" PRIx64;
68 break;
69 }
70
71 case field32.value:
72 {
73 format = "0x%08" PRIx64;
74 break;
75 }
76
77 case field64.value:
78 {
79 format = "0x%016" PRIx64;
80 break;
81 }
82
83 default:
84 {
85 format = "0x%" PRIx64;
86 break;
87 }
88 }
89
90 snprintf(value, sizeof(value), format, v);
91 return value;
92 }
93
94 // For dec, use the simple to_string.
95 case dec.value:
96 default:
97 {
98 return std::to_string(v);
99 }
100 }
101 }
102
103 /** Convert signed to string using format flags. */
value_to_string(uint64_t f,int64_t v)104 static std::string value_to_string(uint64_t f, int64_t v)
105 {
106 // If hex or bin was requested just use the unsigned formatting
107 // rules. (What should a negative binary number look like otherwise?)
108 if (f & (hex | bin).value)
109 {
110 return value_to_string(f, static_cast<uint64_t>(v));
111 }
112 return std::to_string(v);
113 }
114
115 /** Convert float to string using format flags. */
value_to_string(uint64_t,double v)116 static std::string value_to_string(uint64_t, double v)
117 {
118 // No format flags supported for floats.
119 return std::to_string(v);
120 }
121
122 // Positions of various strings in an iovec.
123 static constexpr size_t pos_msg = 0;
124 static constexpr size_t pos_fmtmsg = 1;
125 static constexpr size_t pos_prio = 2;
126 static constexpr size_t pos_file = 3;
127 static constexpr size_t pos_line = 4;
128 static constexpr size_t pos_func = 5;
129 static constexpr size_t static_locs = pos_func + 1;
130
131 /** No-op output of a message. */
noop_extra_output(level,const std::source_location &,const std::string &)132 static void noop_extra_output(level, const std::source_location&,
133 const std::string&)
134 {}
135
136 /** std::cerr output of a message. */
cerr_extra_output(level l,const std::source_location & s,const std::string & m)137 static void cerr_extra_output(level l, const std::source_location& s,
138 const std::string& m)
139 {
140 static const char* const defaultFormat = []() {
141 const char* f = getenv("LG2_FORMAT");
142 if (nullptr == f)
143 {
144 f = "<%l> %m";
145 }
146 return f;
147 }();
148
149 const char* format = defaultFormat;
150
151 std::stringstream stream;
152
153 while (*format)
154 {
155 if (*format != '%')
156 {
157 stream << *format;
158 ++format;
159 continue;
160 }
161
162 ++format;
163 switch (*format)
164 {
165 case '%':
166 case '\0':
167 stream << '%';
168 break;
169
170 case 'f':
171 stream << s.function_name();
172 break;
173
174 case 'F':
175 stream << s.file_name();
176 break;
177
178 case 'l':
179 stream << static_cast<uint64_t>(l);
180 break;
181
182 case 'L':
183 stream << s.line();
184 break;
185
186 case 'm':
187 stream << m;
188 break;
189
190 default:
191 stream << '%' << *format;
192 break;
193 }
194
195 if (*format != '\0')
196 {
197 ++format;
198 }
199 }
200
201 static std::mutex mutex;
202
203 // Prevent multiple threads from clobbering the stderr lines of each other
204 std::scoped_lock lock(mutex);
205
206 // Ensure this line makes it out before releasing mutex for next line
207 std::cerr << stream.str() << std::endl;
208 }
209
210 // Use the cerr output method if we are on a TTY or if explicitly set via
211 // environment variable.
212 static auto extra_output_method =
213 (isatty(fileno(stderr)) || nullptr != getenv("LG2_FORCE_STDERR"))
214 ? cerr_extra_output
215 : noop_extra_output;
216
217 // Skip sending debug messages to journald if "DEBUG_INVOCATION" is not set
218 // per systemd.exec manpage.
219 static auto send_debug_to_journal = nullptr != getenv("DEBUG_INVOCATION");
220
221 // Do_log implementation.
do_log(level l,const std::source_location & s,const char * m,...)222 void do_log(level l, const std::source_location& s, const char* m, ...)
223 {
224 using namespace std::string_literals;
225
226 std::vector<std::string> strings{static_locs};
227
228 std::string message{m};
229
230 // Assign all the static fields.
231 strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m;
232 strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast<uint64_t>(l));
233 strings[pos_file] = "CODE_FILE="s + s.file_name();
234 strings[pos_line] = "CODE_LINE="s + std::to_string(s.line());
235 strings[pos_func] = "CODE_FUNC="s + s.function_name();
236
237 // Handle all the va_list args.
238 std::va_list args;
239 va_start(args, m);
240 while (true)
241 {
242 // Get the header out.
243 auto h_ptr = va_arg(args, const char*);
244 if (h_ptr == nullptr)
245 {
246 break;
247 }
248 std::string h{h_ptr};
249
250 // Get the format flag.
251 auto f = va_arg(args, uint64_t);
252
253 // Handle the value depending on which type format flag it has.
254 std::string value = {};
255 switch (f & (signed_val | unsigned_val | str | floating).value)
256 {
257 case signed_val.value:
258 {
259 auto v = va_arg(args, int64_t);
260 value = value_to_string(f, v);
261 break;
262 }
263
264 case unsigned_val.value:
265 {
266 auto v = va_arg(args, uint64_t);
267 value = value_to_string(f, v);
268 break;
269 }
270
271 case str.value:
272 {
273 value = va_arg(args, const char*);
274 break;
275 }
276
277 case floating.value:
278 {
279 auto v = va_arg(args, double);
280 value = value_to_string(f, v);
281 break;
282 }
283 }
284
285 // Create the field for this value.
286 strings.emplace_back(h + '=' + value);
287
288 // Check for {HEADER} in the message and replace with value.
289 auto h_brace = '{' + h + '}';
290 if (auto start = message.find(h_brace); start != std::string::npos)
291 {
292 message.replace(start, h_brace.size(), value);
293 }
294 }
295 va_end(args);
296
297 // Add the final message into the strings array.
298 strings[pos_msg] = "MESSAGE="s + message.data();
299
300 // Trasform strings -> iovec.
301 std::vector<iovec> iov{};
302 std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) {
303 return iovec{s.data(), s.length()};
304 });
305
306 // Output the iovec.
307 if (send_debug_to_journal || l != level::debug)
308 {
309 sd_journal_sendv(iov.data(), strings.size());
310 }
311 extra_output_method(l, s, message);
312 }
313
314 } // namespace lg2::details
315