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