| #define SD_JOURNAL_SUPPRESS_LOCATION |
| |
| #include <systemd/sd-journal.h> |
| #include <unistd.h> |
| |
| #include <phosphor-logging/lg2.hpp> |
| |
| #include <algorithm> |
| #include <bitset> |
| #include <cstdarg> |
| #include <cstdio> |
| #include <iostream> |
| #include <mutex> |
| #include <source_location> |
| #include <sstream> |
| #include <vector> |
| |
| 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<uint64_t>(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<uint64_t>(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<std::string> 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<uint64_t>(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<iovec> 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 |