| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 1 | #define SD_JOURNAL_SUPPRESS_LOCATION | 
|  | 2 |  | 
|  | 3 | #include <systemd/sd-journal.h> | 
|  | 4 | #include <unistd.h> | 
|  | 5 |  | 
| Patrick Williams | 2544b41 | 2022-10-04 08:41:06 -0500 | [diff] [blame] | 6 | #include <phosphor-logging/lg2.hpp> | 
|  | 7 |  | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 8 | #include <algorithm> | 
|  | 9 | #include <bitset> | 
|  | 10 | #include <cstdarg> | 
|  | 11 | #include <cstdio> | 
|  | 12 | #include <iostream> | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 13 | #include <mutex> | 
| Patrick Williams | 7f5e441 | 2023-06-23 06:46:06 -0500 | [diff] [blame] | 14 | #include <source_location> | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 15 | #include <sstream> | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 16 | #include <vector> | 
|  | 17 |  | 
|  | 18 | namespace lg2::details | 
|  | 19 | { | 
|  | 20 | /** Convert unsigned to string using format flags. */ | 
|  | 21 | static std::string value_to_string(uint64_t f, uint64_t v) | 
|  | 22 | { | 
|  | 23 | switch (f & (hex | bin | dec).value) | 
|  | 24 | { | 
|  | 25 | // For binary, use bitset<>::to_string. | 
|  | 26 | // Treat values without a field-length format flag as 64 bit. | 
|  | 27 | case bin.value: | 
|  | 28 | { | 
|  | 29 | switch (f & (field8 | field16 | field32 | field64).value) | 
|  | 30 | { | 
|  | 31 | case field8.value: | 
|  | 32 | { | 
|  | 33 | return "0b" + std::bitset<8>(v).to_string(); | 
|  | 34 | } | 
|  | 35 | case field16.value: | 
|  | 36 | { | 
|  | 37 | return "0b" + std::bitset<16>(v).to_string(); | 
|  | 38 | } | 
|  | 39 | case field32.value: | 
|  | 40 | { | 
|  | 41 | return "0b" + std::bitset<32>(v).to_string(); | 
|  | 42 | } | 
|  | 43 | case field64.value: | 
|  | 44 | default: | 
|  | 45 | { | 
|  | 46 | return "0b" + std::bitset<64>(v).to_string(); | 
|  | 47 | } | 
|  | 48 | } | 
|  | 49 | } | 
|  | 50 |  | 
|  | 51 | // For hex, use the appropriate sprintf. | 
|  | 52 | case hex.value: | 
|  | 53 | { | 
|  | 54 | char value[19]; | 
|  | 55 | const char* format = nullptr; | 
|  | 56 |  | 
|  | 57 | switch (f & (field8 | field16 | field32 | field64).value) | 
|  | 58 | { | 
|  | 59 | case field8.value: | 
|  | 60 | { | 
|  | 61 | format = "0x%02" PRIx64; | 
|  | 62 | break; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | case field16.value: | 
|  | 66 | { | 
|  | 67 | format = "0x%04" PRIx64; | 
|  | 68 | break; | 
|  | 69 | } | 
|  | 70 |  | 
|  | 71 | case field32.value: | 
|  | 72 | { | 
|  | 73 | format = "0x%08" PRIx64; | 
|  | 74 | break; | 
|  | 75 | } | 
|  | 76 |  | 
|  | 77 | case field64.value: | 
|  | 78 | { | 
|  | 79 | format = "0x%016" PRIx64; | 
|  | 80 | break; | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | default: | 
|  | 84 | { | 
|  | 85 | format = "0x%" PRIx64; | 
|  | 86 | break; | 
|  | 87 | } | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | snprintf(value, sizeof(value), format, v); | 
|  | 91 | return value; | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | // For dec, use the simple to_string. | 
|  | 95 | case dec.value: | 
|  | 96 | default: | 
|  | 97 | { | 
|  | 98 | return std::to_string(v); | 
|  | 99 | } | 
|  | 100 | } | 
|  | 101 | } | 
|  | 102 |  | 
|  | 103 | /** Convert signed to string using format flags. */ | 
|  | 104 | static std::string value_to_string(uint64_t f, int64_t v) | 
|  | 105 | { | 
|  | 106 | // If hex or bin was requested just use the unsigned formatting | 
|  | 107 | // rules. (What should a negative binary number look like otherwise?) | 
|  | 108 | if (f & (hex | bin).value) | 
|  | 109 | { | 
|  | 110 | return value_to_string(f, static_cast<uint64_t>(v)); | 
|  | 111 | } | 
|  | 112 | return std::to_string(v); | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | /** Convert float to string using format flags. */ | 
|  | 116 | static std::string value_to_string(uint64_t, double v) | 
|  | 117 | { | 
|  | 118 | // No format flags supported for floats. | 
|  | 119 | return std::to_string(v); | 
|  | 120 | } | 
|  | 121 |  | 
|  | 122 | // Positions of various strings in an iovec. | 
|  | 123 | static constexpr size_t pos_msg = 0; | 
|  | 124 | static constexpr size_t pos_fmtmsg = 1; | 
|  | 125 | static constexpr size_t pos_prio = 2; | 
|  | 126 | static constexpr size_t pos_file = 3; | 
|  | 127 | static constexpr size_t pos_line = 4; | 
|  | 128 | static constexpr size_t pos_func = 5; | 
|  | 129 | static constexpr size_t static_locs = pos_func + 1; | 
|  | 130 |  | 
|  | 131 | /** No-op output of a message. */ | 
| Patrick Williams | 7f5e441 | 2023-06-23 06:46:06 -0500 | [diff] [blame] | 132 | static void noop_extra_output(level, const std::source_location&, | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 133 | const std::string&) | 
| Patrick Williams | 2544b41 | 2022-10-04 08:41:06 -0500 | [diff] [blame] | 134 | {} | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 135 |  | 
|  | 136 | /** std::cerr output of a message. */ | 
| Patrick Williams | 7f5e441 | 2023-06-23 06:46:06 -0500 | [diff] [blame] | 137 | static void cerr_extra_output(level l, const std::source_location& s, | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 138 | const std::string& m) | 
|  | 139 | { | 
| Jonathan Doman | a9e4f59 | 2021-11-19 00:05:17 -0800 | [diff] [blame] | 140 | static const char* const defaultFormat = []() { | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 141 | const char* f = getenv("LG2_FORMAT"); | 
|  | 142 | if (nullptr == f) | 
|  | 143 | { | 
|  | 144 | f = "<%l> %m"; | 
|  | 145 | } | 
|  | 146 | return f; | 
|  | 147 | }(); | 
|  | 148 |  | 
| Jonathan Doman | a9e4f59 | 2021-11-19 00:05:17 -0800 | [diff] [blame] | 149 | const char* format = defaultFormat; | 
|  | 150 |  | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 151 | std::stringstream stream; | 
|  | 152 |  | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 153 | while (*format) | 
|  | 154 | { | 
|  | 155 | if (*format != '%') | 
|  | 156 | { | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 157 | stream << *format; | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 158 | ++format; | 
|  | 159 | continue; | 
|  | 160 | } | 
|  | 161 |  | 
|  | 162 | ++format; | 
|  | 163 | switch (*format) | 
|  | 164 | { | 
|  | 165 | case '%': | 
|  | 166 | case '\0': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 167 | stream << '%'; | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 168 | break; | 
|  | 169 |  | 
|  | 170 | case 'f': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 171 | stream << s.function_name(); | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 172 | break; | 
|  | 173 |  | 
|  | 174 | case 'F': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 175 | stream << s.file_name(); | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 176 | break; | 
|  | 177 |  | 
|  | 178 | case 'l': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 179 | stream << static_cast<uint64_t>(l); | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 180 | break; | 
|  | 181 |  | 
|  | 182 | case 'L': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 183 | stream << s.line(); | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 184 | break; | 
|  | 185 |  | 
|  | 186 | case 'm': | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 187 | stream << m; | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 188 | break; | 
|  | 189 |  | 
|  | 190 | default: | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 191 | stream << '%' << *format; | 
| Patrick Williams | 5dffc2d | 2021-11-14 06:33:21 -0600 | [diff] [blame] | 192 | break; | 
|  | 193 | } | 
|  | 194 |  | 
|  | 195 | if (*format != '\0') | 
|  | 196 | { | 
|  | 197 | ++format; | 
|  | 198 | } | 
|  | 199 | } | 
|  | 200 |  | 
| Josh Lehan | 230f9f9 | 2023-07-20 22:21:06 +0000 | [diff] [blame] | 201 | static std::mutex mutex; | 
|  | 202 |  | 
|  | 203 | // Prevent multiple threads from clobbering the stderr lines of each other | 
|  | 204 | std::scoped_lock lock(mutex); | 
|  | 205 |  | 
|  | 206 | // Ensure this line makes it out before releasing mutex for next line | 
|  | 207 | std::cerr << stream.str() << std::endl; | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 208 | } | 
|  | 209 |  | 
| Zane Shelley | ea06f8e | 2022-03-25 16:09:23 -0500 | [diff] [blame] | 210 | // Use the cerr output method if we are on a TTY or if explicitly set via | 
|  | 211 | // environment variable. | 
| Patrick Williams | 2544b41 | 2022-10-04 08:41:06 -0500 | [diff] [blame] | 212 | static auto extra_output_method = (isatty(fileno(stderr)) || | 
|  | 213 | nullptr != getenv("LG2_FORCE_STDERR")) | 
|  | 214 | ? cerr_extra_output | 
|  | 215 | : noop_extra_output; | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 216 |  | 
|  | 217 | // Do_log implementation. | 
| Patrick Williams | 7f5e441 | 2023-06-23 06:46:06 -0500 | [diff] [blame] | 218 | void do_log(level l, const std::source_location& s, const char* m, ...) | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 219 | { | 
|  | 220 | using namespace std::string_literals; | 
|  | 221 |  | 
| Patrick Williams | b1811b3 | 2021-08-26 12:19:01 -0500 | [diff] [blame] | 222 | std::vector<std::string> strings{static_locs}; | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 223 |  | 
|  | 224 | std::string message{m}; | 
|  | 225 |  | 
|  | 226 | // Assign all the static fields. | 
| Patrick Williams | b5988ff | 2021-08-26 11:58:36 -0500 | [diff] [blame] | 227 | strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m; | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 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; | 
| Patrick Williams | b1811b3 | 2021-08-26 12:19:01 -0500 | [diff] [blame] | 235 | va_start(args, m); | 
|  | 236 | while (true) | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 237 | { | 
|  | 238 | // Get the header out. | 
| Patrick Williams | b1811b3 | 2021-08-26 12:19:01 -0500 | [diff] [blame] | 239 | auto h_ptr = va_arg(args, const char*); | 
|  | 240 | if (h_ptr == nullptr) | 
|  | 241 | { | 
|  | 242 | break; | 
|  | 243 | } | 
|  | 244 | std::string h{h_ptr}; | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 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. | 
| Patrick Williams | b1811b3 | 2021-08-26 12:19:01 -0500 | [diff] [blame] | 282 | strings.emplace_back(h + '=' + value); | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 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. | 
| Patrick Williams | b1811b3 | 2021-08-26 12:19:01 -0500 | [diff] [blame] | 303 | sd_journal_sendv(iov.data(), strings.size()); | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 304 | extra_output_method(l, s, message); | 
|  | 305 | } | 
|  | 306 |  | 
| Patrick Williams | b2a3aa2 | 2021-07-27 13:30:52 -0500 | [diff] [blame] | 307 | } // namespace lg2::details |