1 #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 
14 // 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 
25 namespace lg2::details
26 {
27 /** Convert unsigned to string using format flags. */
28 static 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. */
111 static 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. */
123 static 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.
130 static constexpr size_t pos_msg = 0;
131 static constexpr size_t pos_fmtmsg = 1;
132 static constexpr size_t pos_prio = 2;
133 static constexpr size_t pos_file = 3;
134 static constexpr size_t pos_line = 4;
135 static constexpr size_t pos_func = 5;
136 static constexpr size_t static_locs = pos_func + 1;
137 
138 /** No-op output of a message. */
139 static void noop_extra_output(level, const lg2::source_location&,
140                               const std::string&)
141 {
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.
211 static auto extra_output_method =
212     isatty(fileno(stderr)) ? cerr_extra_output : noop_extra_output;
213 
214 // Do_log implementation.
215 void do_log(level l, const lg2::source_location& s, const char* m, ...)
216 {
217     using namespace std::string_literals;
218 
219     std::vector<std::string> strings{static_locs};
220 
221     std::string message{m};
222 
223     // Assign all the static fields.
224     strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m;
225     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;
232     va_start(args, m);
233     while (true)
234     {
235         // Get the header out.
236         auto h_ptr = va_arg(args, const char*);
237         if (h_ptr == nullptr)
238         {
239             break;
240         }
241         std::string h{h_ptr};
242 
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.
279         strings.emplace_back(h + '=' + value);
280 
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.
300     sd_journal_sendv(iov.data(), strings.size());
301     extra_output_method(l, s, message);
302 }
303 
304 // 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)
310 void do_log(level, const std::experimental::source_location&, const char*, ...)
311 {
312 }
313 #endif
314 
315 } // namespace lg2::details
316