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