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 <vector>
14 
15 // Clang doesn't currently support source_location, but in order to provide
16 // support for compiling an application with Clang while lg2 was compiled with
17 // GCC we need to provide compile support *both* source_location and
18 // experimental::source_location.
19 //
20 // Note: The experimental::source_location code will turn into a no-op for
21 //       simplicity.  This is simply to allow compilation.
22 #if __has_builtin(__builtin_source_location)
23 #include <experimental/source_location>
24 #endif
25 
26 namespace lg2::details
27 {
28 /** Convert unsigned to string using format flags. */
29 static std::string value_to_string(uint64_t f, uint64_t v)
30 {
31     switch (f & (hex | bin | dec).value)
32     {
33         // For binary, use bitset<>::to_string.
34         // Treat values without a field-length format flag as 64 bit.
35         case bin.value:
36         {
37             switch (f & (field8 | field16 | field32 | field64).value)
38             {
39                 case field8.value:
40                 {
41                     return "0b" + std::bitset<8>(v).to_string();
42                 }
43                 case field16.value:
44                 {
45                     return "0b" + std::bitset<16>(v).to_string();
46                 }
47                 case field32.value:
48                 {
49                     return "0b" + std::bitset<32>(v).to_string();
50                 }
51                 case field64.value:
52                 default:
53                 {
54                     return "0b" + std::bitset<64>(v).to_string();
55                 }
56             }
57         }
58 
59         // For hex, use the appropriate sprintf.
60         case hex.value:
61         {
62             char value[19];
63             const char* format = nullptr;
64 
65             switch (f & (field8 | field16 | field32 | field64).value)
66             {
67                 case field8.value:
68                 {
69                     format = "0x%02" PRIx64;
70                     break;
71                 }
72 
73                 case field16.value:
74                 {
75                     format = "0x%04" PRIx64;
76                     break;
77                 }
78 
79                 case field32.value:
80                 {
81                     format = "0x%08" PRIx64;
82                     break;
83                 }
84 
85                 case field64.value:
86                 {
87                     format = "0x%016" PRIx64;
88                     break;
89                 }
90 
91                 default:
92                 {
93                     format = "0x%" PRIx64;
94                     break;
95                 }
96             }
97 
98             snprintf(value, sizeof(value), format, v);
99             return value;
100         }
101 
102         // For dec, use the simple to_string.
103         case dec.value:
104         default:
105         {
106             return std::to_string(v);
107         }
108     }
109 }
110 
111 /** Convert signed to string using format flags. */
112 static std::string value_to_string(uint64_t f, int64_t v)
113 {
114     // If hex or bin was requested just use the unsigned formatting
115     // rules. (What should a negative binary number look like otherwise?)
116     if (f & (hex | bin).value)
117     {
118         return value_to_string(f, static_cast<uint64_t>(v));
119     }
120     return std::to_string(v);
121 }
122 
123 /** Convert float to string using format flags. */
124 static std::string value_to_string(uint64_t, double v)
125 {
126     // No format flags supported for floats.
127     return std::to_string(v);
128 }
129 
130 // Positions of various strings in an iovec.
131 static constexpr size_t pos_msg = 0;
132 static constexpr size_t pos_fmtmsg = 1;
133 static constexpr size_t pos_prio = 2;
134 static constexpr size_t pos_file = 3;
135 static constexpr size_t pos_line = 4;
136 static constexpr size_t pos_func = 5;
137 static constexpr size_t static_locs = pos_func + 1;
138 
139 /** No-op output of a message. */
140 static void noop_extra_output(level, const lg2::source_location&,
141                               const std::string&)
142 {}
143 
144 /** std::cerr output of a message. */
145 static void cerr_extra_output(level l, const lg2::source_location& s,
146                               const std::string& m)
147 {
148     static const char* const defaultFormat = []() {
149         const char* f = getenv("LG2_FORMAT");
150         if (nullptr == f)
151         {
152             f = "<%l> %m";
153         }
154         return f;
155     }();
156 
157     const char* format = defaultFormat;
158 
159     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;
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 = (isatty(fileno(stderr)) ||
213                                    nullptr != getenv("LG2_FORCE_STDERR"))
214                                       ? cerr_extra_output
215                                       : noop_extra_output;
216 
217 // Do_log implementation.
218 void do_log(level l, const lg2::source_location& s, const char* m, ...)
219 {
220     using namespace std::string_literals;
221 
222     std::vector<std::string> strings{static_locs};
223 
224     std::string message{m};
225 
226     // Assign all the static fields.
227     strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m;
228     strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast<uint64_t>(l));
229     strings[pos_file] = "CODE_FILE="s + s.file_name();
230     strings[pos_line] = "CODE_LINE="s + std::to_string(s.line());
231     strings[pos_func] = "CODE_FUNC="s + s.function_name();
232 
233     // Handle all the va_list args.
234     std::va_list args;
235     va_start(args, m);
236     while (true)
237     {
238         // Get the header out.
239         auto h_ptr = va_arg(args, const char*);
240         if (h_ptr == nullptr)
241         {
242             break;
243         }
244         std::string h{h_ptr};
245 
246         // Get the format flag.
247         auto f = va_arg(args, uint64_t);
248 
249         // Handle the value depending on which type format flag it has.
250         std::string value = {};
251         switch (f & (signed_val | unsigned_val | str | floating).value)
252         {
253             case signed_val.value:
254             {
255                 auto v = va_arg(args, int64_t);
256                 value = value_to_string(f, v);
257                 break;
258             }
259 
260             case unsigned_val.value:
261             {
262                 auto v = va_arg(args, uint64_t);
263                 value = value_to_string(f, v);
264                 break;
265             }
266 
267             case str.value:
268             {
269                 value = va_arg(args, const char*);
270                 break;
271             }
272 
273             case floating.value:
274             {
275                 auto v = va_arg(args, double);
276                 value = value_to_string(f, v);
277                 break;
278             }
279         }
280 
281         // Create the field for this value.
282         strings.emplace_back(h + '=' + value);
283 
284         // Check for {HEADER} in the message and replace with value.
285         auto h_brace = '{' + h + '}';
286         if (auto start = message.find(h_brace); start != std::string::npos)
287         {
288             message.replace(start, h_brace.size(), value);
289         }
290     }
291     va_end(args);
292 
293     // Add the final message into the strings array.
294     strings[pos_msg] = "MESSAGE="s + message.data();
295 
296     // Trasform strings -> iovec.
297     std::vector<iovec> iov{};
298     std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) {
299         return iovec{s.data(), s.length()};
300     });
301 
302     // Output the iovec.
303     sd_journal_sendv(iov.data(), strings.size());
304     extra_output_method(l, s, message);
305 }
306 
307 // If std::source_location is supported, provide an additional
308 // std::experimental::source_location implementation that does nothing so that
309 // lg2 users can compile with Clang even if lg2 was compiled with GCC.  This
310 // is a no-op implementation that simply allows compile support since some
311 // people like to compile with Clang for additional / stricter checks.
312 #if __has_builtin(__builtin_source_location)
313 void do_log(level, const std::experimental::source_location&, const char*, ...)
314 {}
315 #endif
316 
317 } // namespace lg2::details
318