1 #define SD_JOURNAL_SUPPRESS_LOCATION 2 3 #include <systemd/sd-journal.h> 4 #include <unistd.h> 5 6 #include <algorithm> 7 #include <bitset> 8 #include <cstdarg> 9 #include <cstdio> 10 #include <iostream> 11 #include <phosphor-logging/lg2.hpp> 12 #include <vector> 13 14 // Clang doesn't currently support source_location, but in order to provide 15 // support for compiling an application with Clang while lg2 was compiled with 16 // GCC we need to provide compile support *both* source_location and 17 // experimental::source_location. 18 // 19 // Note: The experimental::source_location code will turn into a no-op for 20 // simplicity. This is simply to allow compilation. 21 #if __has_builtin(__builtin_source_location) 22 #include <experimental/source_location> 23 #endif 24 25 namespace lg2::details 26 { 27 /** Convert unsigned to string using format flags. */ 28 static std::string value_to_string(uint64_t f, uint64_t v) 29 { 30 switch (f & (hex | bin | dec).value) 31 { 32 // For binary, use bitset<>::to_string. 33 // Treat values without a field-length format flag as 64 bit. 34 case bin.value: 35 { 36 switch (f & (field8 | field16 | field32 | field64).value) 37 { 38 case field8.value: 39 { 40 return "0b" + std::bitset<8>(v).to_string(); 41 } 42 case field16.value: 43 { 44 return "0b" + std::bitset<16>(v).to_string(); 45 } 46 case field32.value: 47 { 48 return "0b" + std::bitset<32>(v).to_string(); 49 } 50 case field64.value: 51 default: 52 { 53 return "0b" + std::bitset<64>(v).to_string(); 54 } 55 } 56 } 57 58 // For hex, use the appropriate sprintf. 59 case hex.value: 60 { 61 char value[19]; 62 const char* format = nullptr; 63 64 switch (f & (field8 | field16 | field32 | field64).value) 65 { 66 case field8.value: 67 { 68 format = "0x%02" PRIx64; 69 break; 70 } 71 72 case field16.value: 73 { 74 format = "0x%04" PRIx64; 75 break; 76 } 77 78 case field32.value: 79 { 80 format = "0x%08" PRIx64; 81 break; 82 } 83 84 case field64.value: 85 { 86 format = "0x%016" PRIx64; 87 break; 88 } 89 90 default: 91 { 92 format = "0x%" PRIx64; 93 break; 94 } 95 } 96 97 snprintf(value, sizeof(value), format, v); 98 return value; 99 } 100 101 // For dec, use the simple to_string. 102 case dec.value: 103 default: 104 { 105 return std::to_string(v); 106 } 107 } 108 } 109 110 /** Convert signed to string using format flags. */ 111 static std::string value_to_string(uint64_t f, int64_t v) 112 { 113 // If hex or bin was requested just use the unsigned formatting 114 // rules. (What should a negative binary number look like otherwise?) 115 if (f & (hex | bin).value) 116 { 117 return value_to_string(f, static_cast<uint64_t>(v)); 118 } 119 return std::to_string(v); 120 } 121 122 /** Convert float to string using format flags. */ 123 static std::string value_to_string(uint64_t, double v) 124 { 125 // No format flags supported for floats. 126 return std::to_string(v); 127 } 128 129 // Positions of various strings in an iovec. 130 static constexpr size_t pos_msg = 0; 131 static constexpr size_t pos_fmtmsg = 1; 132 static constexpr size_t pos_prio = 2; 133 static constexpr size_t pos_file = 3; 134 static constexpr size_t pos_line = 4; 135 static constexpr size_t pos_func = 5; 136 static constexpr size_t static_locs = pos_func + 1; 137 138 /** No-op output of a message. */ 139 static void noop_extra_output(level, const lg2::source_location&, 140 const std::string&) 141 { 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 = 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 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 } 316 #endif 317 318 } // namespace lg2::details 319