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