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. */ 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. */ 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. */ 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. */ 133 static void noop_extra_output(level, const std::source_location&, 134 const std::string&) 135 {} 136 137 /** std::cerr output of a message. */ 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. 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