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