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 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 // 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 }
316 #endif
317 
318 } // namespace lg2::details
319