#define SD_JOURNAL_SUPPRESS_LOCATION #include #include #include #include #include #include #include #include #include #include #include #include namespace lg2::details { /** Convert unsigned to string using format flags. */ static std::string value_to_string(uint64_t f, uint64_t v) { switch (f & (hex | bin | dec).value) { // For binary, use bitset<>::to_string. // Treat values without a field-length format flag as 64 bit. case bin.value: { switch (f & (field8 | field16 | field32 | field64).value) { case field8.value: { return "0b" + std::bitset<8>(v).to_string(); } case field16.value: { return "0b" + std::bitset<16>(v).to_string(); } case field32.value: { return "0b" + std::bitset<32>(v).to_string(); } case field64.value: default: { return "0b" + std::bitset<64>(v).to_string(); } } } // For hex, use the appropriate sprintf. case hex.value: { char value[19]; const char* format = nullptr; switch (f & (field8 | field16 | field32 | field64).value) { case field8.value: { format = "0x%02" PRIx64; break; } case field16.value: { format = "0x%04" PRIx64; break; } case field32.value: { format = "0x%08" PRIx64; break; } case field64.value: { format = "0x%016" PRIx64; break; } default: { format = "0x%" PRIx64; break; } } snprintf(value, sizeof(value), format, v); return value; } // For dec, use the simple to_string. case dec.value: default: { return std::to_string(v); } } } /** Convert signed to string using format flags. */ static std::string value_to_string(uint64_t f, int64_t v) { // If hex or bin was requested just use the unsigned formatting // rules. (What should a negative binary number look like otherwise?) if (f & (hex | bin).value) { return value_to_string(f, static_cast(v)); } return std::to_string(v); } /** Convert float to string using format flags. */ static std::string value_to_string(uint64_t, double v) { // No format flags supported for floats. return std::to_string(v); } // Positions of various strings in an iovec. static constexpr size_t pos_msg = 0; static constexpr size_t pos_fmtmsg = 1; static constexpr size_t pos_prio = 2; static constexpr size_t pos_file = 3; static constexpr size_t pos_line = 4; static constexpr size_t pos_func = 5; static constexpr size_t static_locs = pos_func + 1; /** No-op output of a message. */ static void noop_extra_output(level, const std::source_location&, const std::string&) {} /** std::cerr output of a message. */ static void cerr_extra_output(level l, const std::source_location& s, const std::string& m) { static const char* const defaultFormat = []() { const char* f = getenv("LG2_FORMAT"); if (nullptr == f) { f = "<%l> %m"; } return f; }(); const char* format = defaultFormat; std::stringstream stream; while (*format) { if (*format != '%') { stream << *format; ++format; continue; } ++format; switch (*format) { case '%': case '\0': stream << '%'; break; case 'f': stream << s.function_name(); break; case 'F': stream << s.file_name(); break; case 'l': stream << static_cast(l); break; case 'L': stream << s.line(); break; case 'm': stream << m; break; default: stream << '%' << *format; break; } if (*format != '\0') { ++format; } } static std::mutex mutex; // Prevent multiple threads from clobbering the stderr lines of each other std::scoped_lock lock(mutex); // Ensure this line makes it out before releasing mutex for next line std::cerr << stream.str() << std::endl; } // Use the cerr output method if we are on a TTY or if explicitly set via // environment variable. static auto extra_output_method = (isatty(fileno(stderr)) || nullptr != getenv("LG2_FORCE_STDERR")) ? cerr_extra_output : noop_extra_output; // Do_log implementation. void do_log(level l, const std::source_location& s, const char* m, ...) { using namespace std::string_literals; std::vector strings{static_locs}; std::string message{m}; // Assign all the static fields. strings[pos_fmtmsg] = "LOG2_FMTMSG="s + m; strings[pos_prio] = "PRIORITY="s + std::to_string(static_cast(l)); strings[pos_file] = "CODE_FILE="s + s.file_name(); strings[pos_line] = "CODE_LINE="s + std::to_string(s.line()); strings[pos_func] = "CODE_FUNC="s + s.function_name(); // Handle all the va_list args. std::va_list args; va_start(args, m); while (true) { // Get the header out. auto h_ptr = va_arg(args, const char*); if (h_ptr == nullptr) { break; } std::string h{h_ptr}; // Get the format flag. auto f = va_arg(args, uint64_t); // Handle the value depending on which type format flag it has. std::string value = {}; switch (f & (signed_val | unsigned_val | str | floating).value) { case signed_val.value: { auto v = va_arg(args, int64_t); value = value_to_string(f, v); break; } case unsigned_val.value: { auto v = va_arg(args, uint64_t); value = value_to_string(f, v); break; } case str.value: { value = va_arg(args, const char*); break; } case floating.value: { auto v = va_arg(args, double); value = value_to_string(f, v); break; } } // Create the field for this value. strings.emplace_back(h + '=' + value); // Check for {HEADER} in the message and replace with value. auto h_brace = '{' + h + '}'; if (auto start = message.find(h_brace); start != std::string::npos) { message.replace(start, h_brace.size(), value); } } va_end(args); // Add the final message into the strings array. strings[pos_msg] = "MESSAGE="s + message.data(); // Trasform strings -> iovec. std::vector iov{}; std::ranges::transform(strings, std::back_inserter(iov), [](auto& s) { return iovec{s.data(), s.length()}; }); // Output the iovec. sd_journal_sendv(iov.data(), strings.size()); extra_output_method(l, s, message); } } // namespace lg2::details