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 <vector> 14 15 // Clang doesn't currently support source_location, but in order to provide 16 // support for compiling an application with Clang while lg2 was compiled with 17 // GCC we need to provide compile support *both* source_location and 18 // experimental::source_location. 19 // 20 // Note: The experimental::source_location code will turn into a no-op for 21 // simplicity. This is simply to allow compilation. 22 #if __has_builtin(__builtin_source_location) 23 #include <experimental/source_location> 24 #endif 25 26 namespace lg2::details 27 { 28 /** Convert unsigned to string using format flags. */ 29 static std::string value_to_string(uint64_t f, uint64_t v) 30 { 31 switch (f & (hex | bin | dec).value) 32 { 33 // For binary, use bitset<>::to_string. 34 // Treat values without a field-length format flag as 64 bit. 35 case bin.value: 36 { 37 switch (f & (field8 | field16 | field32 | field64).value) 38 { 39 case field8.value: 40 { 41 return "0b" + std::bitset<8>(v).to_string(); 42 } 43 case field16.value: 44 { 45 return "0b" + std::bitset<16>(v).to_string(); 46 } 47 case field32.value: 48 { 49 return "0b" + std::bitset<32>(v).to_string(); 50 } 51 case field64.value: 52 default: 53 { 54 return "0b" + std::bitset<64>(v).to_string(); 55 } 56 } 57 } 58 59 // For hex, use the appropriate sprintf. 60 case hex.value: 61 { 62 char value[19]; 63 const char* format = nullptr; 64 65 switch (f & (field8 | field16 | field32 | field64).value) 66 { 67 case field8.value: 68 { 69 format = "0x%02" PRIx64; 70 break; 71 } 72 73 case field16.value: 74 { 75 format = "0x%04" PRIx64; 76 break; 77 } 78 79 case field32.value: 80 { 81 format = "0x%08" PRIx64; 82 break; 83 } 84 85 case field64.value: 86 { 87 format = "0x%016" PRIx64; 88 break; 89 } 90 91 default: 92 { 93 format = "0x%" PRIx64; 94 break; 95 } 96 } 97 98 snprintf(value, sizeof(value), format, v); 99 return value; 100 } 101 102 // For dec, use the simple to_string. 103 case dec.value: 104 default: 105 { 106 return std::to_string(v); 107 } 108 } 109 } 110 111 /** Convert signed to string using format flags. */ 112 static std::string value_to_string(uint64_t f, int64_t v) 113 { 114 // If hex or bin was requested just use the unsigned formatting 115 // rules. (What should a negative binary number look like otherwise?) 116 if (f & (hex | bin).value) 117 { 118 return value_to_string(f, static_cast<uint64_t>(v)); 119 } 120 return std::to_string(v); 121 } 122 123 /** Convert float to string using format flags. */ 124 static std::string value_to_string(uint64_t, double v) 125 { 126 // No format flags supported for floats. 127 return std::to_string(v); 128 } 129 130 // Positions of various strings in an iovec. 131 static constexpr size_t pos_msg = 0; 132 static constexpr size_t pos_fmtmsg = 1; 133 static constexpr size_t pos_prio = 2; 134 static constexpr size_t pos_file = 3; 135 static constexpr size_t pos_line = 4; 136 static constexpr size_t pos_func = 5; 137 static constexpr size_t static_locs = pos_func + 1; 138 139 /** No-op output of a message. */ 140 static void noop_extra_output(level, const lg2::source_location&, 141 const std::string&) 142 {} 143 144 /** std::cerr output of a message. */ 145 static void cerr_extra_output(level l, const lg2::source_location& s, 146 const std::string& m) 147 { 148 static const char* const defaultFormat = []() { 149 const char* f = getenv("LG2_FORMAT"); 150 if (nullptr == f) 151 { 152 f = "<%l> %m"; 153 } 154 return f; 155 }(); 156 157 const char* format = defaultFormat; 158 159 while (*format) 160 { 161 if (*format != '%') 162 { 163 std::cerr << *format; 164 ++format; 165 continue; 166 } 167 168 ++format; 169 switch (*format) 170 { 171 case '%': 172 case '\0': 173 std::cerr << '%'; 174 break; 175 176 case 'f': 177 std::cerr << s.function_name(); 178 break; 179 180 case 'F': 181 std::cerr << s.file_name(); 182 break; 183 184 case 'l': 185 std::cerr << static_cast<uint64_t>(l); 186 break; 187 188 case 'L': 189 std::cerr << s.line(); 190 break; 191 192 case 'm': 193 std::cerr << m; 194 break; 195 196 default: 197 std::cerr << '%' << *format; 198 break; 199 } 200 201 if (*format != '\0') 202 { 203 ++format; 204 } 205 } 206 207 std::cerr << 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 = (isatty(fileno(stderr)) || 213 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 lg2::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 // If std::source_location is supported, provide an additional 308 // std::experimental::source_location implementation that does nothing so that 309 // lg2 users can compile with Clang even if lg2 was compiled with GCC. This 310 // is a no-op implementation that simply allows compile support since some 311 // people like to compile with Clang for additional / stricter checks. 312 #if __has_builtin(__builtin_source_location) 313 void do_log(level, const std::experimental::source_location&, const char*, ...) 314 {} 315 #endif 316 317 } // namespace lg2::details 318