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 <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. */ 132 static void noop_extra_output(level, const std::source_location&, 133 const std::string&) 134 {} 135 136 /** std::cerr output of a message. */ 137 static void cerr_extra_output(level l, const std::source_location& s, 138 const std::string& m) 139 { 140 static const char* const defaultFormat = []() { 141 const char* f = getenv("LG2_FORMAT"); 142 if (nullptr == f) 143 { 144 f = "<%l> %m"; 145 } 146 return f; 147 }(); 148 149 const char* format = defaultFormat; 150 151 std::stringstream stream; 152 153 while (*format) 154 { 155 if (*format != '%') 156 { 157 stream << *format; 158 ++format; 159 continue; 160 } 161 162 ++format; 163 switch (*format) 164 { 165 case '%': 166 case '\0': 167 stream << '%'; 168 break; 169 170 case 'f': 171 stream << s.function_name(); 172 break; 173 174 case 'F': 175 stream << s.file_name(); 176 break; 177 178 case 'l': 179 stream << static_cast<uint64_t>(l); 180 break; 181 182 case 'L': 183 stream << s.line(); 184 break; 185 186 case 'm': 187 stream << m; 188 break; 189 190 default: 191 stream << '%' << *format; 192 break; 193 } 194 195 if (*format != '\0') 196 { 197 ++format; 198 } 199 } 200 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; 208 } 209 210 // Use the cerr output method if we are on a TTY or if explicitly set via 211 // environment variable. 212 static auto extra_output_method = 213 (isatty(fileno(stderr)) || nullptr != getenv("LG2_FORCE_STDERR")) 214 ? cerr_extra_output 215 : noop_extra_output; 216 217 // Do_log implementation. 218 void do_log(level l, const std::source_location& s, const char* m, ...) 219 { 220 using namespace std::string_literals; 221 222 std::vector<std::string> strings{static_locs}; 223 224 std::string message{m}; 225 226 // Assign all the static fields. 227 strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m; 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; 235 va_start(args, m); 236 while (true) 237 { 238 // Get the header out. 239 auto h_ptr = va_arg(args, const char*); 240 if (h_ptr == nullptr) 241 { 242 break; 243 } 244 std::string h{h_ptr}; 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. 282 strings.emplace_back(h + '=' + value); 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. 303 sd_journal_sendv(iov.data(), strings.size()); 304 extra_output_method(l, s, message); 305 } 306 307 } // namespace lg2::details 308