mush

git clone git://git.lin.moe/mush.git

  1#pragma once
  2
  3#include <concepts>
  4#include <type_traits>
  5
  6#include <chrono>
  7#include <functional>
  8#include <iomanip>
  9#include <sstream>
 10#include <string>
 11#include <utility>
 12#include <vector>
 13
 14namespace logfmtxx
 15{
 16enum class level { debug, info, warn, error };
 17
 18namespace details
 19{
 20template <typename T>
 21concept as_string_trait = requires(T t) {
 22	{ t.as_string() } -> std::same_as<std::string>;
 23};
 24
 25template <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>);
 31
 32template <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	}
 63
 64	return "";
 65}
 66
 67using field_kv_type = std::pair<std::string, std::string>;
 68
 69struct record {
 70	level lvl;
 71	std::chrono::system_clock::time_point ts;
 72	std::string msg;
 73
 74	field_kv_type *global_ctx;
 75	std::size_t global_ctx_size;
 76
 77	field_kv_type *local_ctx;
 78	std::size_t local_ctx_size;
 79};
 80} // namespace details
 81
 82template <details::serializable_trait T> struct field {
 83	std::string key;
 84	T value;
 85};
 86
 87template <typename T>
 88concept printer_trait = requires(T t, const std::string &msg) {
 89	{ t(msg) };
 90};
 91
 92template <typename clock_type = std::chrono::system_clock,
 93	  printer_trait printer_type = std::function<void(const std::string &)>>
 94class logger
 95{
 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	}
105
106	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)}...};
112
113		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()};
121
122		m_printer(format(record));
123	}
124
125	template <typename... Args>
126	void debug(const std::string &message, field<Args>... fields)
127	{
128		log(level::debug, message, fields...);
129	}
130
131	template <typename... Args>
132	void info(const std::string &message, field<Args>... fields)
133	{
134		log(level::info, message, fields...);
135	}
136
137	template <typename... Args>
138	void warn(const std::string &message, field<Args>... fields)
139	{
140		log(level::warn, message, fields...);
141	}
142
143	template <typename... Args>
144	void error(const std::string &message, field<Args>... fields)
145	{
146		log(level::error, message, fields...);
147	}
148
149      private:
150	std::string format(const details::record &record)
151	{
152		auto stream = std::ostringstream{};
153
154		stream << "time=" << details::serialize(record.ts) << " ";
155		stream << "level=" << details::serialize(record.lvl) << " ";
156		stream << "message=" << std::quoted(record.msg);
157
158		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		}
162
163		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		}
167
168		return stream.str();
169	}
170
171      private:
172	printer_type m_printer;
173	std::vector<details::field_kv_type> m_extras;
174};
175} // namespace logfmtxx