1#pragma once23#include <concepts>4#include <type_traits>56#include <chrono>7#include <functional>8#include <iomanip>9#include <sstream>10#include <string>11#include <utility>12#include <vector>1314namespace logfmtxx15{16enum class level { debug, info, warn, error };1718namespace details19{20template <typename T>21concept as_string_trait = requires(T t) {22 { t.as_string() } -> std::same_as<std::string>;23};2425template <typename T>26concept serializable_trait =27 (std::is_convertible_v<T, std::string> || as_string_trait<T> ||28 std::is_arithmetic_v<T> ||29 std::is_same_v<T, std::chrono::system_clock::time_point> ||30 std::is_same_v<T, level>);3132template <serializable_trait T> std::string serialize(const T &value)33{34 if constexpr (std::is_convertible_v<T, std::string>) {35 auto stream = std::ostringstream{};36 stream << std::quoted(value);37 return stream.str();38 } else if constexpr (as_string_trait<T>) {39 auto stream = std::ostringstream{};40 stream << std::quoted(value.as_string());41 return stream.str();42 } else if constexpr (std::is_arithmetic_v<T>) {43 return std::to_string(value);44 } else if constexpr (std::is_same_v<45 T, std::chrono::system_clock::time_point>) {46 auto time = std::chrono::system_clock::to_time_t(value);47 auto stream = std::ostringstream{};48 stream << std::put_time(std::gmtime(&time),49 "%Y-%m-%dT%H:%M:%SZ");50 return stream.str();51 } else if constexpr (std::is_same_v<T, level>) {52 switch (value) {53 case level::debug:54 return "debug";55 case level::info:56 return "info";57 case level::warn:58 return "warn";59 case level::error:60 return "error";61 }62 }6364 return "";65}6667using field_kv_type = std::pair<std::string, std::string>;6869struct record {70 level lvl;71 std::chrono::system_clock::time_point ts;72 std::string msg;7374 field_kv_type *global_ctx;75 std::size_t global_ctx_size;7677 field_kv_type *local_ctx;78 std::size_t local_ctx_size;79};80} // namespace details8182template <details::serializable_trait T> struct field {83 std::string key;84 T value;85};8687template <typename T>88concept printer_trait = requires(T t, const std::string &msg) {89 { t(msg) };90};9192template <typename clock_type = std::chrono::system_clock,93 printer_trait printer_type = std::function<void(const std::string &)>>94class logger95{96 public:97 template <typename... Args>98 logger(printer_type printer, field<Args>... fields) : m_printer(printer)99 {100 m_extras.reserve(sizeof...(Args));101 (m_extras.push_back(102 {fields.key, details::serialize(fields.value)}),103 ...);104 }105106 template <typename... Args>107 void log(level level, const std::string &message, field<Args>... fields)108 {109 std::array<details::field_kv_type, sizeof...(Args)> local_ctx =110 {details::field_kv_type{111 fields.key, details::serialize(fields.value)}...};112113 auto record =114 details::record{.lvl = level,115 .ts = clock_type::now(),116 .msg = message,117 .global_ctx = m_extras.data(),118 .global_ctx_size = m_extras.size(),119 .local_ctx = local_ctx.data(),120 .local_ctx_size = local_ctx.size()};121122 m_printer(format(record));123 }124125 template <typename... Args>126 void debug(const std::string &message, field<Args>... fields)127 {128 log(level::debug, message, fields...);129 }130131 template <typename... Args>132 void info(const std::string &message, field<Args>... fields)133 {134 log(level::info, message, fields...);135 }136137 template <typename... Args>138 void warn(const std::string &message, field<Args>... fields)139 {140 log(level::warn, message, fields...);141 }142143 template <typename... Args>144 void error(const std::string &message, field<Args>... fields)145 {146 log(level::error, message, fields...);147 }148149 private:150 std::string format(const details::record &record)151 {152 auto stream = std::ostringstream{};153154 stream << "time=" << details::serialize(record.ts) << " ";155 stream << "level=" << details::serialize(record.lvl) << " ";156 stream << "message=" << std::quoted(record.msg);157158 for (std::size_t i = 0; i < record.global_ctx_size; ++i) {159 const auto &[key, value] = record.global_ctx[i];160 stream << " " << key << "=" << value;161 }162163 for (std::size_t i = 0; i < record.local_ctx_size; ++i) {164 const auto &[key, value] = record.local_ctx[i];165 stream << " " << key << "=" << value;166 }167168 return stream.str();169 }170171 private:172 printer_type m_printer;173 std::vector<details::field_kv_type> m_extras;174};175} // namespace logfmtxx