commit ef8d5b0b03ec8768233b4d45a1388b7bd091ffda Author: luocai Date: Fri Jul 21 10:47:31 2023 +0800 Add class 'BoostLog'. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2ea110c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.15) + +project(Kylin) + +add_subdirectory(Universal) \ No newline at end of file diff --git a/Universal/BoostLog.cpp b/Universal/BoostLog.cpp new file mode 100644 index 0000000..61c6703 --- /dev/null +++ b/Universal/BoostLog.cpp @@ -0,0 +1,68 @@ +#include "BoostLog.h" +#include +#include +#include +#include +#ifdef ANDROID +#include "AndroidBoostLog.h" +#endif + +static void logFormatter(boost::log::record_view const &record, boost::log::formatting_ostream &ostream); + +BOOST_LOG_GLOBAL_LOGGER_INIT(location_logger, LocationLogger) { + LocationLogger lg; + auto consoleSink = boost::log::add_console_log(); + consoleSink->set_formatter(&logFormatter); + boost::log::add_common_attributes(); + return lg; +} + +void initBoostLog(const std::string &name, boost::log::trivial::severity_level filter) { + static bool initialized = false; + using namespace boost::log; + if (!initialized) { + boost::log::core::get()->set_filter(boost::log::trivial::severity >= filter); + add_common_attributes(); +#ifdef ANDROID + using AndroidSink = boost::log::sinks::synchronous_sink; + auto sink = boost::make_shared(); + sink->set_formatter(&androidLogFormatter); + boost::log::core::get()->add_sink(sink); +#else + std::ostringstream oss; + oss << name << "_%Y-%m-%d_%H.%M.%S_%N.log"; + auto fileSink = + add_file_log(keywords::file_name = oss.str(), keywords::auto_flush = true, keywords::target = "logs"); + fileSink->set_formatter(&logFormatter); +#endif + initialized = true; + } +} + +static void logFormatter(const boost::log::record_view &record, boost::log::formatting_ostream &ostream) { + using namespace boost::log; + + std::string level; + boost::log::formatting_ostream oss(level); + oss << "[" << record[boost::log::trivial::severity] << "]"; + + auto dateTimeFormatter = expressions::stream << expressions::format_date_time( + "TimeStamp", "[%m-%d %H:%M:%S.%f]"); + + dateTimeFormatter(record, ostream); + ostream << "[" << boost::log::extract("ThreadID", record) + << "]"; + ostream << std::setw(8) << std::left << level; + auto &&category = record[AmassKeywords::category]; + if (!category.empty()) ostream << " [" << category << "]"; + const auto &filename = record[AmassKeywords::filename]; + if (!filename.empty()) { +#ifdef QT_CREATOR_CONSOLE + ostream << " [file:///" << filename << ":" << record[AmassKeywords::line] << "] "; +#else + ostream << " [" << filename << ":" << record[AmassKeywords::line] << "] "; +#endif + } + // Finally, put the record message to the stream + ostream << record[expressions::smessage]; +} diff --git a/Universal/BoostLog.h b/Universal/BoostLog.h new file mode 100644 index 0000000..53fab16 --- /dev/null +++ b/Universal/BoostLog.h @@ -0,0 +1,182 @@ +#ifndef BOOSTLOG_H +#define BOOSTLOG_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +/* what i expected. +[2020-09-08 17:12:35.229152] [0x00007fa07fc94800] [info] Hello world! +[2020-09-08 17:12:35.229204] [0x00007fa07fc94800] [info] server ip: 127.0.0.1,port: 8080 +[2020-09-08 17:12:35.229211] [0x00007fa07fc94800] [warning] server ip: 127.0.0.1,port: 8080 +[2020-09-08 17:12:35.230961] [0x00007fa07fc94800] [info] server start successful . +*/ + +// clang-format off + +#define LOG_IF(lvl,predic) \ + if(predic) LOG(lvl) + +#define LOG_IF_EVERY_N(lvl,predic,n) \ + if(predic) for(static NumberPredicator pred(n);(!!pred);) \ + LOG(lvl) + +#define LOG_EVERY_N(lvl,n) \ + LOG_IF_EVERY_N(lvl,true,n) + +#define LOG_IF_FIRST_N(lvl,predic,n) \ + if(predic) for(static CountPredicator pred(n);(!!pred);) \ + LOG(lvl) + +#define LOG_FIRST_N(lvl,n) \ + LOG_IF_FIRST_N(lvl,true,n) + + +#define LOG_IF_EVERY_T(lvl,predic,n) \ + if(predic) for(static TimePredicator pred(n);(!!pred);) \ + LOG(lvl) + +#define LOG_EVERY_T(lvl,n) \ + LOG_IF_EVERY_T(lvl,true,n) + +// clang-format on +#ifndef LOG_FILTER_LEVEL +#define LOG_FILTER_LEVEL (boost::log::trivial::info) +#endif + +void initBoostLog( + const std::string &name = "app", + boost::log::trivial::severity_level filter = static_cast(LOG_FILTER_LEVEL)); + +namespace AmassKeywords { + +BOOST_PARAMETER_KEYWORD(FilenameNS, FilenameTag) +BOOST_PARAMETER_KEYWORD(LineNS, LineTag) +BOOST_PARAMETER_KEYWORD(CategoryNS, CategoryTag) + +BOOST_LOG_ATTRIBUTE_KEYWORD(filename, "Filename", std::string) +BOOST_LOG_ATTRIBUTE_KEYWORD(line, "Line", size_t) +BOOST_LOG_ATTRIBUTE_KEYWORD(category, "Category", std::string) + +constexpr const char *pathEnd(const char *path) { + return *path ? pathEnd(path + 1) : path; +} +constexpr bool hasSlant(const char *path) { + return (*path == '\\' || *path == '/') ? true : (*path ? hasSlant(path + 1) : false); +} +constexpr const char *rightSlant(const char *path) { + return (*path == '\\' || *path == '/') ? (path + 1) : rightSlant(path - 1); +} +constexpr const char *fileName(const char *path) { + return hasSlant(path) ? rightSlant(pathEnd(path)) : path; +} + +} // namespace AmassKeywords + +template +class CategoryTaggerFeature : public BaseT { +public: + typedef typename BaseT::char_type char_type; + typedef typename BaseT::threading_model threading_model; + CategoryTaggerFeature() = default; + CategoryTaggerFeature(const CategoryTaggerFeature &obj); + + template + CategoryTaggerFeature(const ArgsT &args); + + typedef typename boost::log::strictest_lock, typename BaseT::open_record_lock, + typename BaseT::add_attribute_lock, + typename BaseT::remove_attribute_lock>::type open_record_lock; + +protected: + template + boost::log::record open_record_unlocked(const ArgsT &args); +}; + +struct CategoryTagger : public boost::mpl::quote1 {}; + +template +class FilenameTaggerFeature : public BaseT { +public: + typedef typename BaseT::char_type char_type; + typedef typename BaseT::threading_model threading_model; + FilenameTaggerFeature() = default; + FilenameTaggerFeature(const FilenameTaggerFeature &obj); + + template + FilenameTaggerFeature(const ArgsT &args); + + typedef typename boost::log::strictest_lock, typename BaseT::open_record_lock, + typename BaseT::add_attribute_lock, + typename BaseT::remove_attribute_lock>::type open_record_lock; + +protected: + template + boost::log::record open_record_unlocked(const ArgsT &args); +}; + +struct FilenameTagger : public boost::mpl::quote1 {}; + +template +class LineTaggerFeature : public BaseT { +public: + typedef typename BaseT::char_type char_type; + typedef typename BaseT::threading_model threading_model; + LineTaggerFeature() = default; + LineTaggerFeature(const LineTaggerFeature &obj); + + template + LineTaggerFeature(const ArgsT &args); + + typedef typename boost::log::strictest_lock, typename BaseT::open_record_lock, + typename BaseT::add_attribute_lock, + typename BaseT::remove_attribute_lock>::type open_record_lock; + +protected: + template + boost::log::record open_record_unlocked(const ArgsT &args); +}; + +struct LineTagger : public boost::mpl::quote1 {}; + +template +class LocationLogger + : public boost::log::sources::basic_composite_logger< + char, LocationLogger, boost::log::sources::multi_thread_model, + boost::log::sources::features, FilenameTagger, LineTagger, + CategoryTagger>> { + typedef typename LocationLogger::logger_base base_type; + +public: + BOOST_LOG_FORWARD_LOGGER_MEMBERS_TEMPLATE(LocationLogger) +}; + +BOOST_LOG_GLOBAL_LOGGER(location_logger, LocationLogger) + +// clang-format off + +#define LOG( lvl) \ + BOOST_LOG_STREAM_WITH_PARAMS(::location_logger::get(),\ + (::boost::log::keywords::severity = ::boost::log::trivial::lvl) \ + (AmassKeywords::FilenameTag = (AmassKeywords::fileName(__FILE__))) \ + (AmassKeywords::LineTag = __LINE__)) + +#define LOG_CAT( lvl, cat) \ + BOOST_LOG_STREAM_WITH_PARAMS(::location_logger::get(),\ + (::boost::log::keywords::severity = ::boost::log::trivial::lvl) \ + (AmassKeywords::FilenameTag = (AmassKeywords::fileName(__FILE__))) \ + (AmassKeywords::LineTag = __LINE__) \ + (AmassKeywords::CategoryTag = #cat)) + +// clang-format on + +#include "BoostLog.inl" +#endif // BOOSTLOG_H diff --git a/Universal/BoostLog.inl b/Universal/BoostLog.inl new file mode 100644 index 0000000..74c86ce --- /dev/null +++ b/Universal/BoostLog.inl @@ -0,0 +1,155 @@ +#ifndef BOOSTLOG_INL +#define BOOSTLOG_INL + +#include "BoostLog.h" +#include "StreamFormat.h" +#include "boost/log/sources/basic_logger.hpp" +#include "boost/log/trivial.hpp" +#include "boost/scope_exit.hpp" +#include +#include + +template +class LogPredicator { +public: + operator bool() const { + bool ret = m_status; + m_status = !m_status; + if (ret) { + ret = doOnceInterface(); + if (!ret) m_status = true; + } + return ret; + } + inline bool doOnceInterface() const { return static_cast(this)->doOnce(); } + +protected: + mutable bool m_status = true; +}; + +class NumberPredicator : public LogPredicator { +public: + NumberPredicator(int limit) : m_limit(limit) {} + inline bool doOnce() const { return (m_current++ % m_limit) == 0; } + +private: + int m_limit; + mutable int m_current{0}; +}; + +class CountPredicator : public LogPredicator { +public: + CountPredicator(int limit) : m_limit(limit) {} + inline bool doOnce() const { return m_current++ < m_limit; } + +private: + int m_limit; + mutable int m_current{0}; +}; + +class TimePredicator : public LogPredicator { +public: + TimePredicator(int milliseconds) : m_limit(std::chrono::milliseconds(milliseconds)) {} + + inline bool doOnce() const { + auto now = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(now - m_current); + bool ret = duration >= m_limit; + if (ret) m_current = now; + return ret; + } + +private: + std::chrono::milliseconds m_limit; + mutable decltype(std::chrono::system_clock::now()) m_current{std::chrono::system_clock::now()}; +}; + +template +CategoryTaggerFeature::CategoryTaggerFeature(const CategoryTaggerFeature &obj) + : BaseT(static_cast(obj)) { +} + +template +template +CategoryTaggerFeature::CategoryTaggerFeature(const ArgsT &args) : BaseT(args) { +} + +template +template +boost::log::record CategoryTaggerFeature::open_record_unlocked(const ArgsT &args) { + std::string tag_value = args[AmassKeywords::CategoryTag | std::string()]; + boost::log::attribute_set &attrs = BaseT::attributes(); + boost::log::attribute_set::iterator tag = attrs.end(); + if (!tag_value.empty()) { + // Add the tag as a new attribute + std::pair res = + BaseT::add_attribute_unlocked("Category", boost::log::attributes::constant(tag_value)); + if (res.second) tag = res.first; + } + + BOOST_SCOPE_EXIT_TPL((&tag)(&attrs)) { + if (tag != attrs.end()) attrs.erase(tag); + } + BOOST_SCOPE_EXIT_END + + return BaseT::open_record_unlocked(args); +} + +template +FilenameTaggerFeature::FilenameTaggerFeature(const FilenameTaggerFeature &obj) + : BaseT(static_cast(obj)) { +} + +template +template +FilenameTaggerFeature::FilenameTaggerFeature(const ArgsT &args) : BaseT(args) {} + +template +template +boost::log::record FilenameTaggerFeature::open_record_unlocked(const ArgsT &args) { + std::string tag_value = args[AmassKeywords::FilenameTag | std::string()]; + boost::log::attribute_set &attrs = BaseT::attributes(); + boost::log::attribute_set::iterator tag = attrs.end(); + if (!tag_value.empty()) { + // Add the tag as a new attribute + std::pair res = + BaseT::add_attribute_unlocked("Filename", boost::log::attributes::constant(tag_value)); + if (res.second) tag = res.first; + } + + BOOST_SCOPE_EXIT_TPL((&tag)(&attrs)) { + if (tag != attrs.end()) attrs.erase(tag); + } + BOOST_SCOPE_EXIT_END + + return BaseT::open_record_unlocked(args); +} + +template +template +LineTaggerFeature::LineTaggerFeature(const ArgsT &args) : BaseT(args) {} + +template +LineTaggerFeature::LineTaggerFeature(const LineTaggerFeature &obj) : BaseT(static_cast(obj)) {} + +template +template +boost::log::record LineTaggerFeature::open_record_unlocked(const ArgsT &args) { + size_t tag_value = args[AmassKeywords::LineTag | size_t()]; + boost::log::attribute_set &attrs = BaseT::attributes(); + boost::log::attribute_set::iterator tag = attrs.end(); + + // Add the tag as a new attribute + std::pair res = + BaseT::add_attribute_unlocked("Line", boost::log::attributes::constant(tag_value)); + if (res.second) tag = res.first; + + BOOST_SCOPE_EXIT_TPL((&tag)(&attrs)) { + if (tag != attrs.end()) attrs.erase(tag); + } + BOOST_SCOPE_EXIT_END + + return BaseT::open_record_unlocked(args); +} + +#endif // BOOSTLOG_INL diff --git a/Universal/CMakeLists.txt b/Universal/CMakeLists.txt new file mode 100644 index 0000000..4202338 --- /dev/null +++ b/Universal/CMakeLists.txt @@ -0,0 +1,10 @@ +find_package(Boost REQUIRED COMPONENTS log log_setup) + +add_library(Universal + BoostLog.h BoostLog.inl BoostLog.cpp + StreamFormat.h StreamFormat.inl StreamFormat.cpp +) + +target_link_libraries(Universal + PRIVATE ${Boost_LIBRARIES} +) \ No newline at end of file diff --git a/Universal/StreamFormat.cpp b/Universal/StreamFormat.cpp new file mode 100644 index 0000000..a739ae3 --- /dev/null +++ b/Universal/StreamFormat.cpp @@ -0,0 +1,11 @@ +#include "StreamFormat.h" +namespace std { +std::ostream &operator<<(std::ostream &stream, const std::chrono::milliseconds &element) { + stream << element.count() << "ms"; + return stream; +} +std::ostream &operator<<(std::ostream &stream, const std::chrono::microseconds &element) { + stream << element.count() << "us"; + return stream; +} +} // namespace std diff --git a/Universal/StreamFormat.h b/Universal/StreamFormat.h new file mode 100644 index 0000000..ff62935 --- /dev/null +++ b/Universal/StreamFormat.h @@ -0,0 +1,47 @@ +#ifndef STREAMFORMAT_H +#define STREAMFORMAT_H + +#include +#include +#include +#include + +namespace std { +template +std::ostream &operator<<(std::ostream &stream, const std::pair &element) { + stream << "[" << element.first << "," << element.second << "]"; + return stream; +} + +template +std::ostream &operator<<(std::ostream &stream, const std::vector &element) { + stream << "["; + for (const auto &e : element) { + stream << e << ","; + } + stream << "]"; + return stream; +} + +template +std::ostream &operator<<(std::ostream &stream, const std::list &element) { + stream << "["; + for (const auto &e : element) { + stream << e << ","; + } + stream << "]"; + return stream; +} + +std::ostream &operator<<(std::ostream &stream, const std::chrono::milliseconds &element); +std::ostream &operator<<(std::ostream &stream, const std::chrono::microseconds &element); +} // namespace std + +template +bool operator==(const std::pair &lhs, const std::pair &rhs) { + return lhs.first == rhs.first && lhs.second == rhs.second; +} + +#include "StreamFormat.inl" + +#endif // STREAMFORMAT_H diff --git a/Universal/StreamFormat.inl b/Universal/StreamFormat.inl new file mode 100644 index 0000000..c2d65d8 --- /dev/null +++ b/Universal/StreamFormat.inl @@ -0,0 +1 @@ +#include "StreamFormat.h" diff --git a/resource/deploy.sh b/resource/deploy.sh new file mode 100644 index 0000000..53b46d6 --- /dev/null +++ b/resource/deploy.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +base_path=$(pwd) +libraries_root="/opt/Libraries" +if [ $base_path == /home/* ] || [ -n "${DRONE}" ]; then + build_path=${base_path}/build +else + build_path=/tmp/build +fi +echo "build directory: $build_path" + +function cmake_scan() { + if [ ! -d ${build_path} ]; then + mkdir ${build_path} + fi + /opt/Qt/Tools/CMake/bin/cmake \ + -G Ninja \ + -S ${base_path} \ + -B ${build_path} \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBOOST_ROOT=${libraries_root}/boost_1_82_0 +} + +function build() { + if [ ! -f "${build_path}/CMakeCache.txt" ]; then + cmake_scan + fi + if [ $? -ne 0 ]; then + exit 1 + fi + /opt/Qt/Tools/CMake/bin/cmake \ + --build ${build_path} \ + --target all +} + +function main() { + local cmd=$1 + shift 1 + case $cmd in + build) + build + ;; + *) + build + ;; + esac +} + +main $@ \ No newline at end of file