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. 211 static auto extra_output_method = 212 isatty(fileno(stderr)) ? cerr_extra_output : noop_extra_output; 213 214 // Do_log implementation. 215 void do_log(level l, const lg2::source_location& s, const char* m, ...) 216 { 217 using namespace std::string_literals; 218 219 std::vector<std::string> strings{static_locs}; 220 221 std::string message{m}; 222 223 // Assign all the static fields. 224 strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m; 225 strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast<uint64_t>(l)); 226 strings[pos_file] = "CODE_FILE="s + s.file_name(); 227 strings[pos_line] = "CODE_LINE="s + std::to_string(s.line()); 228 strings[pos_func] = "CODE_FUNC="s + s.function_name(); 229 230 // Handle all the va_list args. 231 std::va_list args; 232 va_start(args, m); 233 while (true) 234 { 235 // Get the header out. 236 auto h_ptr = va_arg(args, const char*); 237 if (h_ptr == nullptr) 238 { 239 break; 240 } 241 std::string h{h_ptr}; 242 243 // Get the format flag. 244 auto f = va_arg(args, uint64_t); 245 246 // Handle the value depending on which type format flag it has. 247 std::string value = {}; 248 switch (f & (signed_val | unsigned_val | str | floating).value) 249 { 250 case signed_val.value: 251 { 252 auto v = va_arg(args, int64_t); 253 value = value_to_string(f, v); 254 break; 255 } 256 257 case unsigned_val.value: 258 { 259 auto v = va_arg(args, uint64_t); 260 value = value_to_string(f, v); 261 break; 262 } 263 264 case str.value: 265 { 266 value = va_arg(args, const char*); 267 break; 268 } 269 270 case floating.value: 271 { 272 auto v = va_arg(args, double); 273 value = value_to_string(f, v); 274 break; 275 } 276 } 277 278 // Create the field for this value. 279 strings.emplace_back(h + '=' + value); 280 281 // Check for {HEADER} in the message and replace with value. 282 auto h_brace = '{' + h + '}'; 283 if (auto start = message.find(h_brace); start != std::string::npos) 284 { 285 message.replace(start, h_brace.size(), value); 286 } 287 } 288 va_end(args); 289 290 // Add the final message into the strings array. 291 strings[pos_msg] = "MESSAGE="s + message.data(); 292 293 // Trasform strings -> iovec. 294 std::vector<iovec> iov{}; 295 std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) { 296 return iovec{s.data(), s.length()}; 297 }); 298 299 // Output the iovec. 300 sd_journal_sendv(iov.data(), strings.size()); 301 extra_output_method(l, s, message); 302 } 303 304 // If std::source_location is supported, provide an additional 305 // std::experimental::source_location implementation that does nothing so that 306 // lg2 users can compile with Clang even if lg2 was compiled with GCC. This 307 // is a no-op implementation that simply allows compile support since some 308 // people like to compile with Clang for additional / stricter checks. 309 #if __has_builtin(__builtin_source_location) 310 void do_log(level, const std::experimental::source_location&, const char*, ...) 311 { 312 } 313 #endif 314 315 } // namespace lg2::details 316