include/ylt/easylog/record.hpp (231 lines of code) (raw):

/* * Copyright (c) 2023, Alibaba Group Holding Limited; * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <charconv> #include <chrono> #include <cstring> #include "ylt/util/time_util.h" #if __has_include(<memory_resource>) #include <memory_resource> #endif #if defined(__linux__) || defined(__FreeBSD__) #include <sys/syscall.h> #include <unistd.h> #elif defined(__rtems__) #include <rtems.h> #endif #include <sstream> #include <string> #include <string_view> #include <utility> #include "ylt/util/dragonbox_to_chars.h" #include "ylt/util/meta_string.hpp" #include "ylt/util/type_traits.h" #if defined(_WIN32) #ifndef _WINDOWS_ #ifndef WIN32_LEAN_AND_MEAN // Sorry for the inconvenience. Please include any // related headers you need manually. // (https://stackoverflow.com/a/8294669) #define WIN32_LEAN_AND_MEAN // Prevent inclusion of WinSock2.h #endif #include <Windows.h> // Force inclusion of WinGDI here to resolve name conflict #endif #ifdef ERROR // Should be true unless someone else undef'd it already #undef ERROR // Windows GDI defines this macro; make it a global enum so it // doesn't conflict with our code enum { ERROR = 0 }; #endif #endif namespace easylog { namespace detail { template <class T> constexpr inline bool c_array_v = std::is_array_v<std::remove_cvref_t<T>> && (std::extent_v<std::remove_cvref_t<T>> > 0); template <typename Type, typename = void> struct has_data : std::false_type {}; template <typename T> struct has_data<T, std::void_t<decltype(std::declval<std::string>().append( std::declval<T>().data()))>> : std::true_type {}; template <typename T> constexpr inline bool has_data_v = has_data<std::remove_cvref_t<T>>::value; template <typename Type, typename = void> struct has_str : std::false_type {}; template <typename T> struct has_str<T, std::void_t<decltype(std::declval<std::string>().append( std::declval<T>().str()))>> : std::true_type {}; template <typename T> constexpr inline bool has_str_v = has_str<std::remove_cvref_t<T>>::value; } // namespace detail enum class Severity { NONE, TRACE, DEBUG, INFO, WARN, WARNING = WARN, ERROR, CRITICAL, FATAL = CRITICAL, }; inline std::string_view severity_str(Severity severity) { switch (severity) { case Severity::TRACE: return "TRACE "; case Severity::DEBUG: return "DEBUG "; case Severity::INFO: return "INFO "; case Severity::WARN: return "WARNING "; case Severity::ERROR: return "ERROR "; case Severity::CRITICAL: return "CRITICAL"; default: return "NONE "; } } class record_t { public: record_t() = default; record_t(auto tm_point, Severity severity, std::string_view str) : tm_point_(tm_point), severity_(severity), tid_(_get_tid()), file_str_(str) { ss_.reserve(64); } record_t(record_t &&) = default; record_t &operator=(record_t &&) = default; Severity get_severity() const { return severity_; } const char *get_message() { ss_.push_back('\n'); return ss_.data(); } std::string_view get_file_str() const { return file_str_; } unsigned int get_tid() const { return tid_; } auto get_time_point() const { return tm_point_; } record_t &ref() { return *this; } template <typename T> record_t &operator<<(const T &data) { using U = std::remove_cvref_t<T>; if constexpr (std::is_floating_point_v<U>) { char temp[40]; const auto end = jkj::dragonbox::to_chars(data, temp); ss_.append(temp, std::distance(temp, end)); } else if constexpr (std::is_same_v<bool, U>) { data ? ss_.append("true") : ss_.append("false"); } else if constexpr (std::is_same_v<char, U>) { ss_.push_back(data); } else if constexpr (std::is_enum_v<U>) { int val = (int)data; *this << val; } else if constexpr (std::is_integral_v<U>) { char buf[32]; auto [ptr, ec] = std::to_chars(buf, buf + 32, data); ss_.append(buf, std::distance(buf, ptr)); } else if constexpr (std::is_pointer_v<U>) { char buf[32] = {"0x"}; auto [ptr, ec] = std::to_chars(buf + 2, buf + 32, (uintptr_t)data, 16); ss_.append(buf, std::distance(buf, ptr)); } else if constexpr (std::is_same_v<std::string, U> || std::is_same_v<std::string_view, U>) { ss_.append(data.data(), data.size()); } else if constexpr (detail::c_array_v<U>) { ss_.append(data); } else if constexpr (detail::has_data_v<U>) { ss_.append(data.data()); } else if constexpr (detail::has_str_v<U>) { ss_.append(data.str()); } else if constexpr (std::is_same_v<std::chrono::system_clock::time_point, U>) { ss_.append(ylt::time_util::get_local_time_str(data)); } else { std::stringstream ss; ss << data; ss_.append(std::move(ss).str()); } return *this; } template <typename... Args> record_t &sprintf(const char *fmt, Args &&...args) { printf_string_format(fmt, std::forward<Args>(args)...); return *this; } template <typename String> record_t &format(String &&str) { ss_.append(str.data()); return *this; } private: template <typename... Args> void printf_string_format(const char *fmt, Args &&...args) { size_t size = snprintf(nullptr, 0, fmt, std::forward<Args>(args)...); #ifdef YLT_ENABLE_PMR #if __has_include(<memory_resource>) char arr[1024]; std::pmr::monotonic_buffer_resource resource(arr, 1024); std::pmr::string buf{&resource}; #endif #else std::string buf; #endif buf.reserve(size + 1); buf.resize(size); snprintf(&buf[0], size + 1, fmt, args...); ss_.append(buf); } unsigned int _get_tid() { static thread_local unsigned int tid = get_tid_impl(); return tid; } unsigned int get_tid_impl() { #ifdef _WIN32 return std::hash<std::thread::id>{}(std::this_thread::get_id()); #elif defined(__linux__) return static_cast<unsigned int>(::syscall(__NR_gettid)); #elif defined(__FreeBSD__) long tid; syscall(SYS_thr_self, &tid); return static_cast<unsigned int>(tid); #elif defined(__rtems__) return rtems_task_self(); #elif defined(__APPLE__) uint64_t tid64; pthread_threadid_np(NULL, &tid64); return static_cast<unsigned int>(tid64); #else return 0; #endif } std::chrono::system_clock::time_point tm_point_; Severity severity_; unsigned int tid_; std::string file_str_; #ifdef YLT_ENABLE_PMR #if __has_include(<memory_resource>) char arr_[1024]; std::pmr::monotonic_buffer_resource resource_; std::pmr::string ss_{&resource_}; #endif #else std::string ss_; #endif }; #define TO_STR(s) #s #define GET_STRING(filename, line) \ [] { \ constexpr auto path = refvalue::meta_string{filename}; \ constexpr size_t pos = \ path.rfind(std::filesystem::path::preferred_separator); \ constexpr auto name = path.substr<pos + 1>(); \ constexpr auto prefix = name + ":" + TO_STR(line); \ return "[" + prefix + "] "; \ }() } // namespace easylog