| @@ -1,5 +1,7 @@ | |||||
| tclap/** linguist-vendored | tclap/** linguist-vendored | ||||
| spdlog/** linguist-vendored | |||||
| proto/** linguist-generated | proto/** linguist-generated | ||||
| *.h linguist-language=C++ | *.h linguist-language=C++ | ||||
| @@ -15,6 +15,10 @@ | |||||
| #include <atomic> | #include <atomic> | ||||
| #include <queue> | #include <queue> | ||||
| #include <spdlog/spdlog.h> | |||||
| #include <spdlog/sinks/basic_file_sink.h> | |||||
| #include <spdlog/sinks/stdout_color_sinks.h> | |||||
| #include "Message2Server.pb.h" | #include "Message2Server.pb.h" | ||||
| #include "Message2Clients.pb.h" | #include "Message2Clients.pb.h" | ||||
| #include "MessageType.pb.h" | #include "MessageType.pb.h" | ||||
| @@ -30,8 +34,12 @@ | |||||
| class Logic : public ILogic | class Logic : public ILogic | ||||
| { | { | ||||
| private: | private: | ||||
| // 日志组件 | |||||
| std::shared_ptr<spdlog::logger> logger; | |||||
| // 通信组件 | // 通信组件 | ||||
| std::unique_ptr<Communication> pComm; | std::unique_ptr<Communication> pComm; | ||||
| // ID、阵营记录 | // ID、阵营记录 | ||||
| int64_t playerID; | int64_t playerID; | ||||
| THUAI6::PlayerType playerType; | THUAI6::PlayerType playerType; | ||||
| @@ -148,7 +156,7 @@ public: | |||||
| } | } | ||||
| // Main函数同上 | // Main函数同上 | ||||
| void Main(CreateAIFunc createAI, std::string IP, std::string port, bool level, std::string filename); | |||||
| void Main(CreateAIFunc createAI, std::string IP, std::string port, bool debug, bool level); | |||||
| }; | }; | ||||
| #endif | #endif | ||||
| @@ -1,12 +1,14 @@ | |||||
| #include "logic.h" | #include "logic.h" | ||||
| #include "structures.h" | #include "structures.h" | ||||
| #include <grpcpp/grpcpp.h> | #include <grpcpp/grpcpp.h> | ||||
| #include <spdlog/spdlog.h> | |||||
| #include <spdlog/sinks/basic_file_sink.h> | |||||
| #include <spdlog/sinks/stdout_color_sinks.h> | |||||
| #include <functional> | #include <functional> | ||||
| #include "utils.hpp" | #include "utils.hpp" | ||||
| #include "Communication.h" | #include "Communication.h" | ||||
| extern const bool asynchronous; | extern const bool asynchronous; | ||||
| extern const THUAI6::PlayerType playerType; | |||||
| Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::ButcherType butcher, THUAI6::HumanType human) : | Logic::Logic(THUAI6::PlayerType type, int64_t ID, THUAI6::ButcherType butcher, THUAI6::HumanType human) : | ||||
| playerType(type), | playerType(type), | ||||
| @@ -23,6 +25,7 @@ std::vector<std::shared_ptr<const THUAI6::Butcher>> Logic::GetButchers() const | |||||
| std::lock_guard<std::mutex> lock(mtxBuffer); | std::lock_guard<std::mutex> lock(mtxBuffer); | ||||
| std::vector<std::shared_ptr<const THUAI6::Butcher>> temp; | std::vector<std::shared_ptr<const THUAI6::Butcher>> temp; | ||||
| temp.assign(currentState->butchers.begin(), currentState->butchers.end()); | temp.assign(currentState->butchers.begin(), currentState->butchers.end()); | ||||
| logger->debug("Called GetButchers"); | |||||
| return temp; | return temp; | ||||
| } | } | ||||
| @@ -31,6 +34,7 @@ std::vector<std::shared_ptr<const THUAI6::Human>> Logic::GetHumans() const | |||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| std::vector<std::shared_ptr<const THUAI6::Human>> temp; | std::vector<std::shared_ptr<const THUAI6::Human>> temp; | ||||
| temp.assign(currentState->humans.begin(), currentState->humans.end()); | temp.assign(currentState->humans.begin(), currentState->humans.end()); | ||||
| logger->debug("Called GetHumans"); | |||||
| return temp; | return temp; | ||||
| } | } | ||||
| @@ -39,110 +43,131 @@ std::vector<std::shared_ptr<const THUAI6::Prop>> Logic::GetProps() const | |||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| std::vector<std::shared_ptr<const THUAI6::Prop>> temp; | std::vector<std::shared_ptr<const THUAI6::Prop>> temp; | ||||
| temp.assign(currentState->props.begin(), currentState->props.end()); | temp.assign(currentState->props.begin(), currentState->props.end()); | ||||
| logger->debug("Called GetProps"); | |||||
| return temp; | return temp; | ||||
| } | } | ||||
| std::shared_ptr<const THUAI6::Human> Logic::HumanGetSelfInfo() const | std::shared_ptr<const THUAI6::Human> Logic::HumanGetSelfInfo() const | ||||
| { | { | ||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| logger->debug("Called HumanGetSelfInfo"); | |||||
| return currentState->humanSelf; | return currentState->humanSelf; | ||||
| } | } | ||||
| std::shared_ptr<const THUAI6::Butcher> Logic::ButcherGetSelfInfo() const | std::shared_ptr<const THUAI6::Butcher> Logic::ButcherGetSelfInfo() const | ||||
| { | { | ||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| logger->debug("Called ButcherGetSelfInfo"); | |||||
| return currentState->butcherSelf; | return currentState->butcherSelf; | ||||
| } | } | ||||
| std::vector<std::vector<THUAI6::PlaceType>> Logic::GetFullMap() const | std::vector<std::vector<THUAI6::PlaceType>> Logic::GetFullMap() const | ||||
| { | { | ||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| logger->debug("Called GetFullMap"); | |||||
| return currentState->gamemap; | return currentState->gamemap; | ||||
| } | } | ||||
| THUAI6::PlaceType Logic::GetPlaceType(int32_t CellX, int32_t CellY) const | THUAI6::PlaceType Logic::GetPlaceType(int32_t CellX, int32_t CellY) const | ||||
| { | { | ||||
| std::unique_lock<std::mutex> lock(mtxBuffer); | std::unique_lock<std::mutex> lock(mtxBuffer); | ||||
| logger->debug("Called GetPlaceType"); | |||||
| return currentState->gamemap[CellX][CellY]; | return currentState->gamemap[CellX][CellY]; | ||||
| } | } | ||||
| bool Logic::Move(int64_t time, double angle) | bool Logic::Move(int64_t time, double angle) | ||||
| { | { | ||||
| logger->debug("Called Move"); | |||||
| return pComm->Move(time, angle, playerID); | return pComm->Move(time, angle, playerID); | ||||
| } | } | ||||
| bool Logic::PickProp(THUAI6::PropType prop) | bool Logic::PickProp(THUAI6::PropType prop) | ||||
| { | { | ||||
| logger->debug("Called PickProp"); | |||||
| return pComm->PickProp(prop, playerID); | return pComm->PickProp(prop, playerID); | ||||
| } | } | ||||
| bool Logic::UseProp() | bool Logic::UseProp() | ||||
| { | { | ||||
| logger->debug("Called UseProp"); | |||||
| return pComm->UseProp(playerID); | return pComm->UseProp(playerID); | ||||
| } | } | ||||
| bool Logic::UseSkill() | bool Logic::UseSkill() | ||||
| { | { | ||||
| logger->debug("Called UseSkill"); | |||||
| return pComm->UseSkill(playerID); | return pComm->UseSkill(playerID); | ||||
| } | } | ||||
| bool Logic::SendMessage(int64_t toID, std::string message) | bool Logic::SendMessage(int64_t toID, std::string message) | ||||
| { | { | ||||
| logger->debug("Called SendMessage"); | |||||
| return pComm->SendMessage(toID, message, playerID); | return pComm->SendMessage(toID, message, playerID); | ||||
| } | } | ||||
| bool Logic::HaveMessage() | bool Logic::HaveMessage() | ||||
| { | { | ||||
| logger->debug("Called HaveMessage"); | |||||
| return pComm->HaveMessage(); | return pComm->HaveMessage(); | ||||
| } | } | ||||
| std::optional<std::pair<int64_t, std::string>> Logic::GetMessage() | std::optional<std::pair<int64_t, std::string>> Logic::GetMessage() | ||||
| { | { | ||||
| logger->debug("Called GetMessage"); | |||||
| return pComm->GetMessage(); | return pComm->GetMessage(); | ||||
| } | } | ||||
| bool Logic::Escape() | bool Logic::Escape() | ||||
| { | { | ||||
| logger->debug("Called Escape"); | |||||
| return pComm->Escape(playerID); | return pComm->Escape(playerID); | ||||
| } | } | ||||
| bool Logic::StartFixMachine() | bool Logic::StartFixMachine() | ||||
| { | { | ||||
| logger->debug("Called StartFixMachine"); | |||||
| return pComm->StartFixMachine(playerID); | return pComm->StartFixMachine(playerID); | ||||
| } | } | ||||
| bool Logic::EndFixMachine() | bool Logic::EndFixMachine() | ||||
| { | { | ||||
| logger->debug("Called EndFixMachine"); | |||||
| return pComm->EndFixMachine(playerID); | return pComm->EndFixMachine(playerID); | ||||
| } | } | ||||
| bool Logic::StartSaveHuman() | bool Logic::StartSaveHuman() | ||||
| { | { | ||||
| logger->debug("Called StartSaveHuman"); | |||||
| return pComm->StartSaveHuman(playerID); | return pComm->StartSaveHuman(playerID); | ||||
| } | } | ||||
| bool Logic::EndSaveHuman() | bool Logic::EndSaveHuman() | ||||
| { | { | ||||
| logger->debug("Called EndSaveHuman"); | |||||
| return pComm->EndSaveHuman(playerID); | return pComm->EndSaveHuman(playerID); | ||||
| } | } | ||||
| bool Logic::Attack(double angle) | bool Logic::Attack(double angle) | ||||
| { | { | ||||
| logger->debug("Called Attack"); | |||||
| return pComm->Attack(angle, playerID); | return pComm->Attack(angle, playerID); | ||||
| } | } | ||||
| bool Logic::CarryHuman() | bool Logic::CarryHuman() | ||||
| { | { | ||||
| logger->debug("Called CarryHuman"); | |||||
| return pComm->CarryHuman(playerID); | return pComm->CarryHuman(playerID); | ||||
| } | } | ||||
| bool Logic::ReleaseHuman() | bool Logic::ReleaseHuman() | ||||
| { | { | ||||
| logger->debug("Called ReleaseHuman"); | |||||
| return pComm->ReleaseHuman(playerID); | return pComm->ReleaseHuman(playerID); | ||||
| } | } | ||||
| bool Logic::HangHuman() | bool Logic::HangHuman() | ||||
| { | { | ||||
| logger->debug("Called HangHuman"); | |||||
| return pComm->HangHuman(playerID); | return pComm->HangHuman(playerID); | ||||
| } | } | ||||
| @@ -156,19 +181,20 @@ void Logic::ProcessMessage() | |||||
| { | { | ||||
| auto messageThread = [&]() | auto messageThread = [&]() | ||||
| { | { | ||||
| std::cout << "Join Player!" << std::endl; | |||||
| logger->info("Message thread start!"); | |||||
| pComm->AddPlayer(playerID, playerType, humanType, butcherType); | pComm->AddPlayer(playerID, playerType, humanType, butcherType); | ||||
| logger->info("Join the player!"); | |||||
| while (gameState != THUAI6::GameState::GameEnd) | while (gameState != THUAI6::GameState::GameEnd) | ||||
| { | { | ||||
| if (pComm->HaveMessage2Client()) | if (pComm->HaveMessage2Client()) | ||||
| { | { | ||||
| std::cout << "Get Message!" << std::endl; | |||||
| logger->debug("Get message from server!"); | |||||
| auto clientMsg = pComm->GetMessage2Client(); | auto clientMsg = pComm->GetMessage2Client(); | ||||
| gameState = Proto2THUAI6::gameStateDict[clientMsg.game_state()]; | gameState = Proto2THUAI6::gameStateDict[clientMsg.game_state()]; | ||||
| switch (gameState) | switch (gameState) | ||||
| { | { | ||||
| case THUAI6::GameState::GameStart: | case THUAI6::GameState::GameStart: | ||||
| std::cout << "Game Start!" << std::endl; | |||||
| logger->info("Game Start!"); | |||||
| // 重新读取玩家的guid,guid确保人类在前屠夫在后 | // 重新读取玩家的guid,guid确保人类在前屠夫在后 | ||||
| playerGUIDs.clear(); | playerGUIDs.clear(); | ||||
| @@ -205,10 +231,10 @@ void Logic::ProcessMessage() | |||||
| counterBuffer = -1; | counterBuffer = -1; | ||||
| } | } | ||||
| cvBuffer.notify_one(); | cvBuffer.notify_one(); | ||||
| std::cout << "Game End!" << std::endl; | |||||
| logger->info("Game End!"); | |||||
| break; | break; | ||||
| default: | default: | ||||
| std::cerr << "Invalid GameState!" << std::endl; | |||||
| logger->debug("Unknown GameState!"); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -227,7 +253,7 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message) | |||||
| bufferState->butchers.clear(); | bufferState->butchers.clear(); | ||||
| bufferState->props.clear(); | bufferState->props.clear(); | ||||
| std::cout << "Buffer clear!" << std::endl; | |||||
| logger->debug("Buffer cleared!"); | |||||
| // 读取新的信息 | // 读取新的信息 | ||||
| // 读取消息的选择待补充,之后需要另外判断;具体做法应该是先读到自己,然后按照自己的视野做处理。此处暂时全部读了进来 | // 读取消息的选择待补充,之后需要另外判断;具体做法应该是先读到自己,然后按照自己的视野做处理。此处暂时全部读了进来 | ||||
| bufferState->gamemap = Proto2THUAI6::Protobuf2THUAI6Map(message.map_message()); | bufferState->gamemap = Proto2THUAI6::Protobuf2THUAI6Map(message.map_message()); | ||||
| @@ -271,7 +297,7 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message) | |||||
| if (barrier) | if (barrier) | ||||
| continue; | continue; | ||||
| bufferState->butchers.push_back(Proto2THUAI6::Protobuf2THUAI6Butcher(item)); | bufferState->butchers.push_back(Proto2THUAI6::Protobuf2THUAI6Butcher(item)); | ||||
| std::cout << "Add Butcher!" << std::endl; | |||||
| logger->debug("Add Butcher!"); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -315,17 +341,21 @@ void Logic::LoadBuffer(protobuf::MessageToClient& message) | |||||
| if (barrier) | if (barrier) | ||||
| continue; | continue; | ||||
| bufferState->humans.push_back(Proto2THUAI6::Protobuf2THUAI6Human(item)); | bufferState->humans.push_back(Proto2THUAI6::Protobuf2THUAI6Human(item)); | ||||
| std::cout << "Add Human!" << std::endl; | |||||
| logger->debug("Add Human!"); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| for (const auto& item : message.prop_message()) | for (const auto& item : message.prop_message()) | ||||
| { | |||||
| bufferState->props.push_back(Proto2THUAI6::Protobuf2THUAI6Prop(item)); | bufferState->props.push_back(Proto2THUAI6::Protobuf2THUAI6Prop(item)); | ||||
| logger->debug("Add Prop!"); | |||||
| } | |||||
| if (asynchronous) | if (asynchronous) | ||||
| { | { | ||||
| { | { | ||||
| std::lock_guard<std::mutex> lock(mtxState); | std::lock_guard<std::mutex> lock(mtxState); | ||||
| std::swap(currentState, bufferState); | std::swap(currentState, bufferState); | ||||
| logger->info("Update State!"); | |||||
| } | } | ||||
| freshed = true; | freshed = true; | ||||
| } | } | ||||
| @@ -350,6 +380,7 @@ void Logic::Update() noexcept | |||||
| std::swap(currentState, bufferState); | std::swap(currentState, bufferState); | ||||
| bufferUpdated = false; | bufferUpdated = false; | ||||
| counterState = counterBuffer; | counterState = counterBuffer; | ||||
| logger->info("Update State!"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -395,13 +426,31 @@ const std::vector<int64_t> Logic::GetPlayerGUIDs() const | |||||
| bool Logic::TryConnection() | bool Logic::TryConnection() | ||||
| { | { | ||||
| std::cout << "Trying to connect to server..." << std::endl; | |||||
| logger->info("Try to connect to server..."); | |||||
| bool result = pComm->TryConnection(playerID); | bool result = pComm->TryConnection(playerID); | ||||
| return result; | return result; | ||||
| } | } | ||||
| void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool level, std::string filename) | |||||
| void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool debug, bool level) | |||||
| { | { | ||||
| // 建立日志组件 | |||||
| if (debug) | |||||
| { | |||||
| auto file_logger = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/logic-log.txt", true); | |||||
| auto stdout_logger = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); | |||||
| if (level) | |||||
| stdout_logger->set_level(spdlog::level::warn); | |||||
| else | |||||
| stdout_logger->set_level(spdlog::level::info); | |||||
| file_logger->set_level(spdlog::level::trace); | |||||
| logger = std::make_shared<spdlog::logger>("logicLogger", spdlog::sinks_init_list{file_logger, stdout_logger}); | |||||
| } | |||||
| else | |||||
| { | |||||
| auto logger = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/logic-log.txt", true); | |||||
| logger->set_level(spdlog::level::trace); | |||||
| } | |||||
| // 建立与服务器之间通信的组件 | // 建立与服务器之间通信的组件 | ||||
| pComm = std::make_unique<Communication>(IP, port); | pComm = std::make_unique<Communication>(IP, port); | ||||
| @@ -445,10 +494,11 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool l | |||||
| // 连接服务器 | // 连接服务器 | ||||
| if (TryConnection()) | if (TryConnection()) | ||||
| { | { | ||||
| std::cout << "Connect to the server successfully, AI thread will be start." << std::endl; | |||||
| logger->info("Connect to the server successfully, AI thread will be start."); | |||||
| if (tAI.joinable()) | if (tAI.joinable()) | ||||
| { | { | ||||
| std::cout << "Join the AI thread." << std::endl; | |||||
| logger->info("Join the AI thread!"); | |||||
| // 首先开启处理消息的线程 | // 首先开启处理消息的线程 | ||||
| ProcessMessage(); | ProcessMessage(); | ||||
| tAI.join(); | tAI.join(); | ||||
| @@ -456,7 +506,7 @@ void Logic::Main(CreateAIFunc createAI, std::string IP, std::string port, bool l | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| std::cout << "Connection error!" << std::endl; | |||||
| logger->error("Connect to the server failed, AI thread will not be start."); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -12,7 +12,7 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| int pID = 114514; | int pID = 114514; | ||||
| std::string sIP = "114.51.41.91"; | std::string sIP = "114.51.41.91"; | ||||
| std::string sPort = "9810"; | std::string sPort = "9810"; | ||||
| std::string filename = ""; | |||||
| bool print = false; | |||||
| bool level = false; | bool level = false; | ||||
| extern const THUAI6::PlayerType playerType; | extern const THUAI6::PlayerType playerType; | ||||
| extern const THUAI6::ButcherType butcherType; | extern const THUAI6::ButcherType butcherType; | ||||
| @@ -20,11 +20,11 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| // 仅供早期调试使用 | // 仅供早期调试使用 | ||||
| { | { | ||||
| Logic logic(playerType, pID, butcherType, humanType); | Logic logic(playerType, pID, butcherType, humanType); | ||||
| logic.Main(AIBuilder, sIP, sPort, level, filename); | |||||
| logic.Main(AIBuilder, sIP, sPort, print, level); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| // 使用cmdline的版本 | |||||
| // 使用cmdline的正式版本 | |||||
| try | try | ||||
| { | { | ||||
| TCLAP::CmdLine cmd("THUAI6 C++ interface commandline parameter introduction"); | TCLAP::CmdLine cmd("THUAI6 C++ interface commandline parameter introduction"); | ||||
| @@ -40,16 +40,13 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| TCLAP::ValueArg<int> playerID("p", "playerID", "Player ID 0,1,2,3 valid only", true, -1, &playerIdConstraint); | TCLAP::ValueArg<int> playerID("p", "playerID", "Player ID 0,1,2,3 valid only", true, -1, &playerIdConstraint); | ||||
| cmd.add(playerID); | cmd.add(playerID); | ||||
| std::string DebugDesc = "Set this flag to use API for debugging.\n" | |||||
| "If \"-f\" is not set, the log will be printed on the screen.\n" | |||||
| "Or you could specify a file to store it."; | |||||
| std::string DebugDesc = "Set this flag to print the debug log on the screen.\n" | |||||
| "The log will always be saved to ./logs folder.\n"; | |||||
| TCLAP::SwitchArg debug("d", "debug", DebugDesc); | TCLAP::SwitchArg debug("d", "debug", DebugDesc); | ||||
| cmd.add(debug); | cmd.add(debug); | ||||
| TCLAP::ValueArg<std::string> FileName("f", "filename", "Specify a file to store the log.", false, "", "string"); | |||||
| cmd.add(FileName); | |||||
| TCLAP::SwitchArg warning("w", "warning", "Warn of some obviously invalid operations (only when \"-d\" is set)."); | |||||
| TCLAP::SwitchArg warning("w", "warning", "Set this flag to only print warning on the screen.\n" | |||||
| "This flag will be ignored if the debug flag is not set\n"); | |||||
| cmd.add(warning); | cmd.add(warning); | ||||
| cmd.parse(argc, argv); | cmd.parse(argc, argv); | ||||
| @@ -61,9 +58,9 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| bool w = warning.getValue(); | bool w = warning.getValue(); | ||||
| if (d) | if (d) | ||||
| { | { | ||||
| print = true; | |||||
| level = w; | level = w; | ||||
| } | } | ||||
| filename = FileName.getValue(); | |||||
| } | } | ||||
| catch (TCLAP::ArgException& e) | catch (TCLAP::ArgException& e) | ||||
| { | { | ||||
| @@ -71,7 +68,7 @@ int THUAI6Main(int argc, char** argv, CreateAIFunc AIBuilder) | |||||
| return 1; | return 1; | ||||
| } | } | ||||
| Logic logic(playerType, pID, butcherType, humanType); | Logic logic(playerType, pID, butcherType, humanType); | ||||
| logic.Main(AIBuilder, sIP, sPort, level, filename); | |||||
| logic.Main(AIBuilder, sIP, sPort, print, level); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -18,7 +18,7 @@ message(STATUS "Using gRPC ${gRPC_VERSION}") | |||||
| add_executable(capi ${CPP_LIST} ${PROTO_CPP_LIST}) | add_executable(capi ${CPP_LIST} ${PROTO_CPP_LIST}) | ||||
| target_include_directories(capi PUBLIC ${PROJECT_SOURCE_DIR}/proto ${PROJECT_SOURCE_DIR}/API/include ${PROJECT_SOURCE_DIR}/tclap/include) | |||||
| target_include_directories(capi PUBLIC ${PROJECT_SOURCE_DIR}/proto ${PROJECT_SOURCE_DIR}/API/include ${PROJECT_SOURCE_DIR}/tclap/include ${PROJECT_SOURCE_DIR}/spdlog/include) | |||||
| target_link_libraries(capi | target_link_libraries(capi | ||||
| protobuf::libprotobuf | protobuf::libprotobuf | ||||
| @@ -0,0 +1,103 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // | |||||
| // Async logging using global thread pool | |||||
| // All loggers created here share same global thread pool. | |||||
| // Each log message is pushed to a queue along with a shared pointer to the | |||||
| // logger. | |||||
| // If a logger deleted while having pending messages in the queue, it's actual | |||||
| // destruction will defer | |||||
| // until all its messages are processed by the thread pool. | |||||
| // This is because each message in the queue holds a shared_ptr to the | |||||
| // originating logger. | |||||
| #include <spdlog/async_logger.h> | |||||
| #include <spdlog/details/registry.h> | |||||
| #include <spdlog/details/thread_pool.h> | |||||
| #include <memory> | |||||
| #include <mutex> | |||||
| #include <functional> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| static const size_t default_async_q_size = 8192; | |||||
| } | |||||
| // async logger factory - creates async loggers backed with thread pool. | |||||
| // if a global thread pool doesn't already exist, create it with default queue | |||||
| // size of 8192 items and single thread. | |||||
| template<async_overflow_policy OverflowPolicy = async_overflow_policy::block> | |||||
| struct async_factory_impl | |||||
| { | |||||
| template<typename Sink, typename... SinkArgs> | |||||
| static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs&&... args) | |||||
| { | |||||
| auto& registry_inst = details::registry::instance(); | |||||
| // create global thread pool if not already exists.. | |||||
| auto& mutex = registry_inst.tp_mutex(); | |||||
| std::lock_guard<std::recursive_mutex> tp_lock(mutex); | |||||
| auto tp = registry_inst.get_tp(); | |||||
| if (tp == nullptr) | |||||
| { | |||||
| tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U); | |||||
| registry_inst.set_tp(tp); | |||||
| } | |||||
| auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
| auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy); | |||||
| registry_inst.initialize_logger(new_logger); | |||||
| return new_logger; | |||||
| } | |||||
| }; | |||||
| using async_factory = async_factory_impl<async_overflow_policy::block>; | |||||
| using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; | |||||
| template<typename Sink, typename... SinkArgs> | |||||
| inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs&&... sink_args) | |||||
| { | |||||
| return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
| } | |||||
| template<typename Sink, typename... SinkArgs> | |||||
| inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs&&... sink_args) | |||||
| { | |||||
| return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||||
| } | |||||
| // set global thread pool. | |||||
| inline void init_thread_pool( | |||||
| size_t q_size, size_t thread_count, std::function<void()> on_thread_start, std::function<void()> on_thread_stop | |||||
| ) | |||||
| { | |||||
| auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start, on_thread_stop); | |||||
| details::registry::instance().set_tp(std::move(tp)); | |||||
| } | |||||
| inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start) | |||||
| { | |||||
| init_thread_pool(q_size, thread_count, on_thread_start, [] {}); | |||||
| } | |||||
| inline void init_thread_pool(size_t q_size, size_t thread_count) | |||||
| { | |||||
| init_thread_pool( | |||||
| q_size, thread_count, [] {}, [] {} | |||||
| ); | |||||
| } | |||||
| // get the global thread pool. | |||||
| inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() | |||||
| { | |||||
| return details::registry::instance().get_tp(); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,96 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/async_logger.h> | |||||
| #endif | |||||
| #include <spdlog/sinks/sink.h> | |||||
| #include <spdlog/details/thread_pool.h> | |||||
| #include <memory> | |||||
| #include <string> | |||||
| SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
| std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy | |||||
| ) : | |||||
| async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE spdlog::async_logger::async_logger( | |||||
| std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy | |||||
| ) : | |||||
| async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) | |||||
| { | |||||
| } | |||||
| // send the log message to the thread pool | |||||
| SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg& msg) | |||||
| { | |||||
| if (auto pool_ptr = thread_pool_.lock()) | |||||
| { | |||||
| pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); | |||||
| } | |||||
| else | |||||
| { | |||||
| throw_spdlog_ex("async log: thread pool doesn't exist anymore"); | |||||
| } | |||||
| } | |||||
| // send flush request to the thread pool | |||||
| SPDLOG_INLINE void spdlog::async_logger::flush_() | |||||
| { | |||||
| if (auto pool_ptr = thread_pool_.lock()) | |||||
| { | |||||
| pool_ptr->post_flush(shared_from_this(), overflow_policy_); | |||||
| } | |||||
| else | |||||
| { | |||||
| throw_spdlog_ex("async flush: thread pool doesn't exist anymore"); | |||||
| } | |||||
| } | |||||
| // | |||||
| // backend functions - called from the thread pool to do the actual job | |||||
| // | |||||
| SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg& msg) | |||||
| { | |||||
| for (auto& sink : sinks_) | |||||
| { | |||||
| if (sink->should_log(msg.level)) | |||||
| { | |||||
| SPDLOG_TRY | |||||
| { | |||||
| sink->log(msg); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(msg.source) | |||||
| } | |||||
| } | |||||
| if (should_flush_(msg)) | |||||
| { | |||||
| backend_flush_(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void spdlog::async_logger::backend_flush_() | |||||
| { | |||||
| for (auto& sink : sinks_) | |||||
| { | |||||
| SPDLOG_TRY | |||||
| { | |||||
| sink->flush(); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(source_loc()) | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) | |||||
| { | |||||
| auto cloned = std::make_shared<spdlog::async_logger>(*this); | |||||
| cloned->name_ = std::move(new_name); | |||||
| return cloned; | |||||
| } | |||||
| @@ -0,0 +1,68 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // Fast asynchronous logger. | |||||
| // Uses pre allocated queue. | |||||
| // Creates a single back thread to pop messages from the queue and log them. | |||||
| // | |||||
| // Upon each log write the logger: | |||||
| // 1. Checks if its log level is enough to log the message | |||||
| // 2. Push a new copy of the message to a queue (or block the caller until | |||||
| // space is available in the queue) | |||||
| // Upon destruction, logs all remaining messages in the queue before | |||||
| // destructing.. | |||||
| #include <spdlog/logger.h> | |||||
| namespace spdlog | |||||
| { | |||||
| // Async overflow policy - block by default. | |||||
| enum class async_overflow_policy | |||||
| { | |||||
| block, // Block until message can be enqueued | |||||
| overrun_oldest // Discard oldest message in the queue if full when trying to | |||||
| // add new item. | |||||
| }; | |||||
| namespace details | |||||
| { | |||||
| class thread_pool; | |||||
| } | |||||
| class SPDLOG_API async_logger final : public std::enable_shared_from_this<async_logger>, public logger | |||||
| { | |||||
| friend class details::thread_pool; | |||||
| public: | |||||
| template<typename It> | |||||
| async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy = async_overflow_policy::block) : | |||||
| logger(std::move(logger_name), begin, end), | |||||
| thread_pool_(std::move(tp)), | |||||
| overflow_policy_(overflow_policy) | |||||
| { | |||||
| } | |||||
| async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
| async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy = async_overflow_policy::block); | |||||
| std::shared_ptr<logger> clone(std::string new_name) override; | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override; | |||||
| void flush_() override; | |||||
| void backend_sink_it_(const details::log_msg& incoming_log_msg); | |||||
| void backend_flush_(); | |||||
| private: | |||||
| std::weak_ptr<details::thread_pool> thread_pool_; | |||||
| async_overflow_policy overflow_policy_; | |||||
| }; | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "async_logger-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,46 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/cfg/helpers.h> | |||||
| #include <spdlog/details/registry.h> | |||||
| // | |||||
| // Init log levels using each argv entry that starts with "SPDLOG_LEVEL=" | |||||
| // | |||||
| // set all loggers to debug level: | |||||
| // example.exe "SPDLOG_LEVEL=debug" | |||||
| // set logger1 to trace level | |||||
| // example.exe "SPDLOG_LEVEL=logger1=trace" | |||||
| // turn off all logging except for logger1 and logger2: | |||||
| // example.exe "SPDLOG_LEVEL=off,logger1=debug,logger2=info" | |||||
| namespace spdlog | |||||
| { | |||||
| namespace cfg | |||||
| { | |||||
| // search for SPDLOG_LEVEL= in the args and use it to init the levels | |||||
| inline void load_argv_levels(int argc, const char** argv) | |||||
| { | |||||
| const std::string spdlog_level_prefix = "SPDLOG_LEVEL="; | |||||
| for (int i = 1; i < argc; i++) | |||||
| { | |||||
| std::string arg = argv[i]; | |||||
| if (arg.find(spdlog_level_prefix) == 0) | |||||
| { | |||||
| auto levels_string = arg.substr(spdlog_level_prefix.size()); | |||||
| helpers::load_levels(levels_string); | |||||
| } | |||||
| } | |||||
| } | |||||
| inline void load_argv_levels(int argc, char** argv) | |||||
| { | |||||
| load_argv_levels(argc, const_cast<const char**>(argv)); | |||||
| } | |||||
| } // namespace cfg | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,40 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/cfg/helpers.h> | |||||
| #include <spdlog/details/registry.h> | |||||
| #include <spdlog/details/os.h> | |||||
| // | |||||
| // Init levels and patterns from env variables SPDLOG_LEVEL | |||||
| // Inspired from Rust's "env_logger" crate (https://crates.io/crates/env_logger). | |||||
| // Note - fallback to "info" level on unrecognized levels | |||||
| // | |||||
| // Examples: | |||||
| // | |||||
| // set global level to debug: | |||||
| // export SPDLOG_LEVEL=debug | |||||
| // | |||||
| // turn off all logging except for logger1: | |||||
| // export SPDLOG_LEVEL="*=off,logger1=debug" | |||||
| // | |||||
| // turn off all logging except for logger1 and logger2: | |||||
| // export SPDLOG_LEVEL="off,logger1=debug,logger2=info" | |||||
| namespace spdlog | |||||
| { | |||||
| namespace cfg | |||||
| { | |||||
| inline void load_env_levels() | |||||
| { | |||||
| auto env_val = details::os::getenv("SPDLOG_LEVEL"); | |||||
| if (!env_val.empty()) | |||||
| { | |||||
| helpers::load_levels(env_val); | |||||
| } | |||||
| } | |||||
| } // namespace cfg | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,125 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/cfg/helpers.h> | |||||
| #endif | |||||
| #include <spdlog/spdlog.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/details/registry.h> | |||||
| #include <algorithm> | |||||
| #include <string> | |||||
| #include <utility> | |||||
| #include <sstream> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace cfg | |||||
| { | |||||
| namespace helpers | |||||
| { | |||||
| // inplace convert to lowercase | |||||
| inline std::string& to_lower_(std::string& str) | |||||
| { | |||||
| std::transform( | |||||
| str.begin(), str.end(), str.begin(), [](char ch) | |||||
| { return static_cast<char>((ch >= 'A' && ch <= 'Z') ? ch + ('a' - 'A') : ch); } | |||||
| ); | |||||
| return str; | |||||
| } | |||||
| // inplace trim spaces | |||||
| inline std::string& trim_(std::string& str) | |||||
| { | |||||
| const char* spaces = " \n\r\t"; | |||||
| str.erase(str.find_last_not_of(spaces) + 1); | |||||
| str.erase(0, str.find_first_not_of(spaces)); | |||||
| return str; | |||||
| } | |||||
| // return (name,value) trimmed pair from given "name=value" string. | |||||
| // return empty string on missing parts | |||||
| // "key=val" => ("key", "val") | |||||
| // " key = val " => ("key", "val") | |||||
| // "key=" => ("key", "") | |||||
| // "val" => ("", "val") | |||||
| inline std::pair<std::string, std::string> extract_kv_(char sep, const std::string& str) | |||||
| { | |||||
| auto n = str.find(sep); | |||||
| std::string k, v; | |||||
| if (n == std::string::npos) | |||||
| { | |||||
| v = str; | |||||
| } | |||||
| else | |||||
| { | |||||
| k = str.substr(0, n); | |||||
| v = str.substr(n + 1); | |||||
| } | |||||
| return std::make_pair(trim_(k), trim_(v)); | |||||
| } | |||||
| // return vector of key/value pairs from sequence of "K1=V1,K2=V2,.." | |||||
| // "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...} | |||||
| inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string& str) | |||||
| { | |||||
| std::string token; | |||||
| std::istringstream token_stream(str); | |||||
| std::unordered_map<std::string, std::string> rv{}; | |||||
| while (std::getline(token_stream, token, ',')) | |||||
| { | |||||
| if (token.empty()) | |||||
| { | |||||
| continue; | |||||
| } | |||||
| auto kv = extract_kv_('=', token); | |||||
| rv[kv.first] = kv.second; | |||||
| } | |||||
| return rv; | |||||
| } | |||||
| SPDLOG_INLINE void load_levels(const std::string& input) | |||||
| { | |||||
| if (input.empty() || input.size() > 512) | |||||
| { | |||||
| return; | |||||
| } | |||||
| auto key_vals = extract_key_vals_(input); | |||||
| std::unordered_map<std::string, level::level_enum> levels; | |||||
| level::level_enum global_level = level::info; | |||||
| bool global_level_found = false; | |||||
| for (auto& name_level : key_vals) | |||||
| { | |||||
| auto& logger_name = name_level.first; | |||||
| auto level_name = to_lower_(name_level.second); | |||||
| auto level = level::from_str(level_name); | |||||
| // ignore unrecognized level names | |||||
| if (level == level::off && level_name != "off") | |||||
| { | |||||
| continue; | |||||
| } | |||||
| if (logger_name.empty()) // no logger name indicate global level | |||||
| { | |||||
| global_level_found = true; | |||||
| global_level = level; | |||||
| } | |||||
| else | |||||
| { | |||||
| levels[logger_name] = level; | |||||
| } | |||||
| } | |||||
| details::registry::instance().set_levels(std::move(levels), global_level_found ? &global_level : nullptr); | |||||
| } | |||||
| } // namespace helpers | |||||
| } // namespace cfg | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,32 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <unordered_map> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace cfg | |||||
| { | |||||
| namespace helpers | |||||
| { | |||||
| // | |||||
| // Init levels from given string | |||||
| // | |||||
| // Examples: | |||||
| // | |||||
| // set global level to debug: "debug" | |||||
| // turn off all logging except for logger1: "off,logger1=debug" | |||||
| // turn off all logging except for logger1 and logger2: "off,logger1=debug,logger2=info" | |||||
| // | |||||
| SPDLOG_API void load_levels(const std::string& txt); | |||||
| } // namespace helpers | |||||
| } // namespace cfg | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "helpers-inl.h" | |||||
| #endif // SPDLOG_HEADER_ONLY | |||||
| @@ -0,0 +1,85 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/common.h> | |||||
| #endif | |||||
| #include <algorithm> | |||||
| #include <iterator> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace level | |||||
| { | |||||
| #if __cplusplus >= 201703L | |||||
| constexpr | |||||
| #endif | |||||
| static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES; | |||||
| static const char* short_level_names[] SPDLOG_SHORT_LEVEL_NAMES; | |||||
| SPDLOG_INLINE const string_view_t& to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return level_string_views[l]; | |||||
| } | |||||
| SPDLOG_INLINE const char* to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return short_level_names[l]; | |||||
| } | |||||
| SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string& name) SPDLOG_NOEXCEPT | |||||
| { | |||||
| auto it = std::find(std::begin(level_string_views), std::end(level_string_views), name); | |||||
| if (it != std::end(level_string_views)) | |||||
| return static_cast<level::level_enum>(std::distance(std::begin(level_string_views), it)); | |||||
| // check also for "warn" and "err" before giving up.. | |||||
| if (name == "warn") | |||||
| { | |||||
| return level::warn; | |||||
| } | |||||
| if (name == "err") | |||||
| { | |||||
| return level::err; | |||||
| } | |||||
| return level::off; | |||||
| } | |||||
| } // namespace level | |||||
| SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg) : | |||||
| msg_(std::move(msg)) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string& msg, int last_errno) | |||||
| { | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| msg_ = std::system_error(std::error_code(last_errno, std::generic_category()), msg).what(); | |||||
| #else | |||||
| memory_buf_t outbuf; | |||||
| fmt::format_system_error(outbuf, last_errno, msg.c_str()); | |||||
| msg_ = fmt::to_string(outbuf); | |||||
| #endif | |||||
| } | |||||
| SPDLOG_INLINE const char* spdlog_ex::what() const SPDLOG_NOEXCEPT | |||||
| { | |||||
| return msg_.c_str(); | |||||
| } | |||||
| SPDLOG_INLINE void throw_spdlog_ex(const std::string& msg, int last_errno) | |||||
| { | |||||
| SPDLOG_THROW(spdlog_ex(msg, last_errno)); | |||||
| } | |||||
| SPDLOG_INLINE void throw_spdlog_ex(std::string msg) | |||||
| { | |||||
| SPDLOG_THROW(spdlog_ex(std::move(msg))); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,420 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/tweakme.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <atomic> | |||||
| #include <chrono> | |||||
| #include <initializer_list> | |||||
| #include <memory> | |||||
| #include <exception> | |||||
| #include <string> | |||||
| #include <type_traits> | |||||
| #include <functional> | |||||
| #include <cstdio> | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| #include <version> | |||||
| #if __cpp_lib_format >= 202207L | |||||
| #include <format> | |||||
| #else | |||||
| #include <string_view> | |||||
| #endif | |||||
| #endif | |||||
| #ifdef SPDLOG_COMPILED_LIB | |||||
| #undef SPDLOG_HEADER_ONLY | |||||
| #if defined(SPDLOG_SHARED_LIB) | |||||
| #if defined(_WIN32) | |||||
| #ifdef spdlog_EXPORTS | |||||
| #define SPDLOG_API __declspec(dllexport) | |||||
| #else // !spdlog_EXPORTS | |||||
| #define SPDLOG_API __declspec(dllimport) | |||||
| #endif | |||||
| #else // !defined(_WIN32) | |||||
| #define SPDLOG_API __attribute__((visibility("default"))) | |||||
| #endif | |||||
| #else // !defined(SPDLOG_SHARED_LIB) | |||||
| #define SPDLOG_API | |||||
| #endif | |||||
| #define SPDLOG_INLINE | |||||
| #else // !defined(SPDLOG_COMPILED_LIB) | |||||
| #define SPDLOG_API | |||||
| #define SPDLOG_HEADER_ONLY | |||||
| #define SPDLOG_INLINE inline | |||||
| #endif // #ifdef SPDLOG_COMPILED_LIB | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION >= 80000 // backward compatibility with fmt versions older than 8 | |||||
| #define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string) | |||||
| #define SPDLOG_FMT_STRING(format_string) FMT_STRING(format_string) | |||||
| #if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) | |||||
| #include <spdlog/fmt/xchar.h> | |||||
| #endif | |||||
| #else | |||||
| #define SPDLOG_FMT_RUNTIME(format_string) format_string | |||||
| #define SPDLOG_FMT_STRING(format_string) format_string | |||||
| #endif | |||||
| // visual studio up to 2013 does not support noexcept nor constexpr | |||||
| #if defined(_MSC_VER) && (_MSC_VER < 1900) | |||||
| #define SPDLOG_NOEXCEPT _NOEXCEPT | |||||
| #define SPDLOG_CONSTEXPR | |||||
| #define SPDLOG_CONSTEXPR_FUNC inline | |||||
| #else | |||||
| #define SPDLOG_NOEXCEPT noexcept | |||||
| #define SPDLOG_CONSTEXPR constexpr | |||||
| #if __cplusplus >= 201402L | |||||
| #define SPDLOG_CONSTEXPR_FUNC constexpr | |||||
| #else | |||||
| #define SPDLOG_CONSTEXPR_FUNC inline | |||||
| #endif | |||||
| #endif | |||||
| #if defined(__GNUC__) || defined(__clang__) | |||||
| #define SPDLOG_DEPRECATED __attribute__((deprecated)) | |||||
| #elif defined(_MSC_VER) | |||||
| #define SPDLOG_DEPRECATED __declspec(deprecated) | |||||
| #else | |||||
| #define SPDLOG_DEPRECATED | |||||
| #endif | |||||
| // disable thread local on msvc 2013 | |||||
| #ifndef SPDLOG_NO_TLS | |||||
| #if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) | |||||
| #define SPDLOG_NO_TLS 1 | |||||
| #endif | |||||
| #endif | |||||
| #ifndef SPDLOG_FUNCTION | |||||
| #define SPDLOG_FUNCTION static_cast<const char*>(__FUNCTION__) | |||||
| #endif | |||||
| #ifdef SPDLOG_NO_EXCEPTIONS | |||||
| #define SPDLOG_TRY | |||||
| #define SPDLOG_THROW(ex) \ | |||||
| do \ | |||||
| { \ | |||||
| printf("spdlog fatal error: %s\n", ex.what()); \ | |||||
| std::abort(); \ | |||||
| } while (0) | |||||
| #define SPDLOG_CATCH_STD | |||||
| #else | |||||
| #define SPDLOG_TRY try | |||||
| #define SPDLOG_THROW(ex) throw(ex) | |||||
| #define SPDLOG_CATCH_STD \ | |||||
| catch (const std::exception&) \ | |||||
| { \ | |||||
| } | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| class formatter; | |||||
| namespace sinks | |||||
| { | |||||
| class sink; | |||||
| } | |||||
| #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
| using filename_t = std::wstring; | |||||
| // allow macro expansion to occur in SPDLOG_FILENAME_T | |||||
| #define SPDLOG_FILENAME_T_INNER(s) L##s | |||||
| #define SPDLOG_FILENAME_T(s) SPDLOG_FILENAME_T_INNER(s) | |||||
| #else | |||||
| using filename_t = std::string; | |||||
| #define SPDLOG_FILENAME_T(s) s | |||||
| #endif | |||||
| using log_clock = std::chrono::system_clock; | |||||
| using sink_ptr = std::shared_ptr<sinks::sink>; | |||||
| using sinks_init_list = std::initializer_list<sink_ptr>; | |||||
| using err_handler = std::function<void(const std::string& err_msg)>; | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| namespace fmt_lib = std; | |||||
| using string_view_t = std::string_view; | |||||
| using memory_buf_t = std::string; | |||||
| template<typename... Args> | |||||
| #if __cpp_lib_format >= 202207L | |||||
| using format_string_t = std::format_string<Args...>; | |||||
| #else | |||||
| using format_string_t = std::string_view; | |||||
| #endif | |||||
| template<class T, class Char = char> | |||||
| struct is_convertible_to_basic_format_string : std::integral_constant<bool, std::is_convertible<T, std::basic_string_view<Char>>::value> | |||||
| { | |||||
| }; | |||||
| #if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) | |||||
| using wstring_view_t = std::wstring_view; | |||||
| using wmemory_buf_t = std::wstring; | |||||
| template<typename... Args> | |||||
| #if __cpp_lib_format >= 202207L | |||||
| using wformat_string_t = std::wformat_string<Args...>; | |||||
| #else | |||||
| using wformat_string_t = std::wstring_view; | |||||
| #endif | |||||
| #endif | |||||
| #define SPDLOG_BUF_TO_STRING(x) x | |||||
| #else // use fmt lib instead of std::format | |||||
| namespace fmt_lib = fmt; | |||||
| using string_view_t = fmt::basic_string_view<char>; | |||||
| using memory_buf_t = fmt::basic_memory_buffer<char, 250>; | |||||
| template<typename... Args> | |||||
| using format_string_t = fmt::format_string<Args...>; | |||||
| template<class T> | |||||
| using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; | |||||
| // clang doesn't like SFINAE disabled constructor in std::is_convertible<> so have to repeat the condition from basic_format_string here, | |||||
| // in addition, fmt::basic_runtime<Char> is only convertible to basic_format_string<Char> but not basic_string_view<Char> | |||||
| template<class T, class Char = char> | |||||
| struct is_convertible_to_basic_format_string : std::integral_constant<bool, std::is_convertible<T, fmt::basic_string_view<Char>>::value || std::is_same<remove_cvref_t<T>, fmt::basic_runtime<Char>>::value> | |||||
| { | |||||
| }; | |||||
| #if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) | |||||
| using wstring_view_t = fmt::basic_string_view<wchar_t>; | |||||
| using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>; | |||||
| template<typename... Args> | |||||
| using wformat_string_t = fmt::wformat_string<Args...>; | |||||
| #endif | |||||
| #define SPDLOG_BUF_TO_STRING(x) fmt::to_string(x) | |||||
| #endif | |||||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| #ifndef _WIN32 | |||||
| #error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
| #endif // _WIN32 | |||||
| #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| template<class T> | |||||
| struct is_convertible_to_any_format_string : std::integral_constant<bool, is_convertible_to_basic_format_string<T, char>::value || is_convertible_to_basic_format_string<T, wchar_t>::value> | |||||
| { | |||||
| }; | |||||
| #if defined(SPDLOG_NO_ATOMIC_LEVELS) | |||||
| using level_t = details::null_atomic_int; | |||||
| #else | |||||
| using level_t = std::atomic<int>; | |||||
| #endif | |||||
| #define SPDLOG_LEVEL_TRACE 0 | |||||
| #define SPDLOG_LEVEL_DEBUG 1 | |||||
| #define SPDLOG_LEVEL_INFO 2 | |||||
| #define SPDLOG_LEVEL_WARN 3 | |||||
| #define SPDLOG_LEVEL_ERROR 4 | |||||
| #define SPDLOG_LEVEL_CRITICAL 5 | |||||
| #define SPDLOG_LEVEL_OFF 6 | |||||
| #if !defined(SPDLOG_ACTIVE_LEVEL) | |||||
| #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO | |||||
| #endif | |||||
| // Log level enum | |||||
| namespace level | |||||
| { | |||||
| enum level_enum : int | |||||
| { | |||||
| trace = SPDLOG_LEVEL_TRACE, | |||||
| debug = SPDLOG_LEVEL_DEBUG, | |||||
| info = SPDLOG_LEVEL_INFO, | |||||
| warn = SPDLOG_LEVEL_WARN, | |||||
| err = SPDLOG_LEVEL_ERROR, | |||||
| critical = SPDLOG_LEVEL_CRITICAL, | |||||
| off = SPDLOG_LEVEL_OFF, | |||||
| n_levels | |||||
| }; | |||||
| #define SPDLOG_LEVEL_NAME_TRACE spdlog::string_view_t("trace", 5) | |||||
| #define SPDLOG_LEVEL_NAME_DEBUG spdlog::string_view_t("debug", 5) | |||||
| #define SPDLOG_LEVEL_NAME_INFO spdlog::string_view_t("info", 4) | |||||
| #define SPDLOG_LEVEL_NAME_WARNING spdlog::string_view_t("warning", 7) | |||||
| #define SPDLOG_LEVEL_NAME_ERROR spdlog::string_view_t("error", 5) | |||||
| #define SPDLOG_LEVEL_NAME_CRITICAL spdlog::string_view_t("critical", 8) | |||||
| #define SPDLOG_LEVEL_NAME_OFF spdlog::string_view_t("off", 3) | |||||
| #if !defined(SPDLOG_LEVEL_NAMES) | |||||
| #define SPDLOG_LEVEL_NAMES \ | |||||
| { \ | |||||
| SPDLOG_LEVEL_NAME_TRACE, SPDLOG_LEVEL_NAME_DEBUG, SPDLOG_LEVEL_NAME_INFO, SPDLOG_LEVEL_NAME_WARNING, SPDLOG_LEVEL_NAME_ERROR, \ | |||||
| SPDLOG_LEVEL_NAME_CRITICAL, SPDLOG_LEVEL_NAME_OFF \ | |||||
| } | |||||
| #endif | |||||
| #if !defined(SPDLOG_SHORT_LEVEL_NAMES) | |||||
| #define SPDLOG_SHORT_LEVEL_NAMES \ | |||||
| { \ | |||||
| "T", "D", "I", "W", "E", "C", "O" \ | |||||
| } | |||||
| #endif | |||||
| SPDLOG_API const string_view_t& to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API const char* to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API spdlog::level::level_enum from_str(const std::string& name) SPDLOG_NOEXCEPT; | |||||
| } // namespace level | |||||
| // | |||||
| // Color mode used by sinks with color support. | |||||
| // | |||||
| enum class color_mode | |||||
| { | |||||
| always, | |||||
| automatic, | |||||
| never | |||||
| }; | |||||
| // | |||||
| // Pattern time - specific time getting to use for pattern_formatter. | |||||
| // local time by default | |||||
| // | |||||
| enum class pattern_time_type | |||||
| { | |||||
| local, // log localtime | |||||
| utc // log utc | |||||
| }; | |||||
| // | |||||
| // Log exception | |||||
| // | |||||
| class SPDLOG_API spdlog_ex : public std::exception | |||||
| { | |||||
| public: | |||||
| explicit spdlog_ex(std::string msg); | |||||
| spdlog_ex(const std::string& msg, int last_errno); | |||||
| const char* what() const SPDLOG_NOEXCEPT override; | |||||
| private: | |||||
| std::string msg_; | |||||
| }; | |||||
| [[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string& msg, int last_errno); | |||||
| [[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg); | |||||
| struct source_loc | |||||
| { | |||||
| SPDLOG_CONSTEXPR source_loc() = default; | |||||
| SPDLOG_CONSTEXPR source_loc(const char* filename_in, int line_in, const char* funcname_in) : | |||||
| filename{filename_in}, | |||||
| line{line_in}, | |||||
| funcname{funcname_in} | |||||
| { | |||||
| } | |||||
| SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT | |||||
| { | |||||
| return line == 0; | |||||
| } | |||||
| const char* filename{nullptr}; | |||||
| int line{0}; | |||||
| const char* funcname{nullptr}; | |||||
| }; | |||||
| struct file_event_handlers | |||||
| { | |||||
| file_event_handlers() : | |||||
| before_open(nullptr), | |||||
| after_open(nullptr), | |||||
| before_close(nullptr), | |||||
| after_close(nullptr) | |||||
| { | |||||
| } | |||||
| std::function<void(const filename_t& filename)> before_open; | |||||
| std::function<void(const filename_t& filename, std::FILE* file_stream)> after_open; | |||||
| std::function<void(const filename_t& filename, std::FILE* file_stream)> before_close; | |||||
| std::function<void(const filename_t& filename)> after_close; | |||||
| }; | |||||
| namespace details | |||||
| { | |||||
| // to_string_view | |||||
| SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(const memory_buf_t& buf) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return spdlog::string_view_t{buf.data(), buf.size()}; | |||||
| } | |||||
| SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(spdlog::string_view_t str) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return str; | |||||
| } | |||||
| #if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) | |||||
| SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(const wmemory_buf_t& buf) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return spdlog::wstring_view_t{buf.data(), buf.size()}; | |||||
| } | |||||
| SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view_t str) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return str; | |||||
| } | |||||
| #endif | |||||
| #ifndef SPDLOG_USE_STD_FORMAT | |||||
| template<typename T, typename... Args> | |||||
| inline fmt::basic_string_view<T> to_string_view(fmt::basic_format_string<T, Args...> fmt) | |||||
| { | |||||
| return fmt; | |||||
| } | |||||
| #elif __cpp_lib_format >= 202207L | |||||
| template<typename T, typename... Args> | |||||
| SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view(std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return fmt.get(); | |||||
| } | |||||
| #endif | |||||
| // make_unique support for pre c++14 | |||||
| #if __cplusplus >= 201402L // C++14 and beyond | |||||
| using std::enable_if_t; | |||||
| using std::make_unique; | |||||
| #else | |||||
| template<bool B, class T = void> | |||||
| using enable_if_t = typename std::enable_if<B, T>::type; | |||||
| template<typename T, typename... Args> | |||||
| std::unique_ptr<T> make_unique(Args&&... args) | |||||
| { | |||||
| static_assert(!std::is_array<T>::value, "arrays not supported"); | |||||
| return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); | |||||
| } | |||||
| #endif | |||||
| // to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) | |||||
| template<typename T, typename U, enable_if_t<!std::is_same<T, U>::value, int> = 0> | |||||
| constexpr T conditional_static_cast(U value) | |||||
| { | |||||
| return static_cast<T>(value); | |||||
| } | |||||
| template<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0> | |||||
| constexpr T conditional_static_cast(U value) | |||||
| { | |||||
| return value; | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "common-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,77 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/backtracer.h> | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE backtracer::backtracer(const backtracer& other) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(other.mutex_); | |||||
| enabled_ = other.enabled(); | |||||
| messages_ = other.messages_; | |||||
| } | |||||
| SPDLOG_INLINE backtracer::backtracer(backtracer&& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(other.mutex_); | |||||
| enabled_ = other.enabled(); | |||||
| messages_ = std::move(other.messages_); | |||||
| } | |||||
| SPDLOG_INLINE backtracer& backtracer::operator=(backtracer other) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(mutex_); | |||||
| enabled_ = other.enabled(); | |||||
| messages_ = std::move(other.messages_); | |||||
| return *this; | |||||
| } | |||||
| SPDLOG_INLINE void backtracer::enable(size_t size) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock{mutex_}; | |||||
| enabled_.store(true, std::memory_order_relaxed); | |||||
| messages_ = circular_q<log_msg_buffer>{size}; | |||||
| } | |||||
| SPDLOG_INLINE void backtracer::disable() | |||||
| { | |||||
| std::lock_guard<std::mutex> lock{mutex_}; | |||||
| enabled_.store(false, std::memory_order_relaxed); | |||||
| } | |||||
| SPDLOG_INLINE bool backtracer::enabled() const | |||||
| { | |||||
| return enabled_.load(std::memory_order_relaxed); | |||||
| } | |||||
| SPDLOG_INLINE void backtracer::push_back(const log_msg& msg) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock{mutex_}; | |||||
| messages_.push_back(log_msg_buffer{msg}); | |||||
| } | |||||
| SPDLOG_INLINE bool backtracer::empty() const | |||||
| { | |||||
| std::lock_guard<std::mutex> lock{mutex_}; | |||||
| return messages_.empty(); | |||||
| } | |||||
| // pop all items in the q and apply the given fun on each of them. | |||||
| SPDLOG_INLINE void backtracer::foreach_pop(std::function<void(const details::log_msg&)> fun) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock{mutex_}; | |||||
| while (!messages_.empty()) | |||||
| { | |||||
| auto& front_msg = messages_.front(); | |||||
| fun(front_msg); | |||||
| messages_.pop_front(); | |||||
| } | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,48 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/log_msg_buffer.h> | |||||
| #include <spdlog/details/circular_q.h> | |||||
| #include <atomic> | |||||
| #include <mutex> | |||||
| #include <functional> | |||||
| // Store log messages in circular buffer. | |||||
| // Useful for storing debug data in case of error/warning happens. | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class SPDLOG_API backtracer | |||||
| { | |||||
| mutable std::mutex mutex_; | |||||
| std::atomic<bool> enabled_{false}; | |||||
| circular_q<log_msg_buffer> messages_; | |||||
| public: | |||||
| backtracer() = default; | |||||
| backtracer(const backtracer& other); | |||||
| backtracer(backtracer&& other) SPDLOG_NOEXCEPT; | |||||
| backtracer& operator=(backtracer other); | |||||
| void enable(size_t size); | |||||
| void disable(); | |||||
| bool enabled() const; | |||||
| void push_back(const log_msg& msg); | |||||
| bool empty() const; | |||||
| // pop all items in the q and apply the given fun on each of them. | |||||
| void foreach_pop(std::function<void(const details::log_msg&)> fun); | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "backtracer-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,150 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // circular q view of std::vector. | |||||
| #pragma once | |||||
| #include <vector> | |||||
| #include <cassert> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| template<typename T> | |||||
| class circular_q | |||||
| { | |||||
| size_t max_items_ = 0; | |||||
| typename std::vector<T>::size_type head_ = 0; | |||||
| typename std::vector<T>::size_type tail_ = 0; | |||||
| size_t overrun_counter_ = 0; | |||||
| std::vector<T> v_; | |||||
| public: | |||||
| using value_type = T; | |||||
| // empty ctor - create a disabled queue with no elements allocated at all | |||||
| circular_q() = default; | |||||
| explicit circular_q(size_t max_items) : | |||||
| max_items_(max_items + 1) // one item is reserved as marker for full q | |||||
| , | |||||
| v_(max_items_) | |||||
| { | |||||
| } | |||||
| circular_q(const circular_q&) = default; | |||||
| circular_q& operator=(const circular_q&) = default; | |||||
| // move cannot be default, | |||||
| // since we need to reset head_, tail_, etc to zero in the moved object | |||||
| circular_q(circular_q&& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| copy_moveable(std::move(other)); | |||||
| } | |||||
| circular_q& operator=(circular_q&& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| copy_moveable(std::move(other)); | |||||
| return *this; | |||||
| } | |||||
| // push back, overrun (oldest) item if no room left | |||||
| void push_back(T&& item) | |||||
| { | |||||
| if (max_items_ > 0) | |||||
| { | |||||
| v_[tail_] = std::move(item); | |||||
| tail_ = (tail_ + 1) % max_items_; | |||||
| if (tail_ == head_) // overrun last item if full | |||||
| { | |||||
| head_ = (head_ + 1) % max_items_; | |||||
| ++overrun_counter_; | |||||
| } | |||||
| } | |||||
| } | |||||
| // Return reference to the front item. | |||||
| // If there are no elements in the container, the behavior is undefined. | |||||
| const T& front() const | |||||
| { | |||||
| return v_[head_]; | |||||
| } | |||||
| T& front() | |||||
| { | |||||
| return v_[head_]; | |||||
| } | |||||
| // Return number of elements actually stored | |||||
| size_t size() const | |||||
| { | |||||
| if (tail_ >= head_) | |||||
| { | |||||
| return tail_ - head_; | |||||
| } | |||||
| else | |||||
| { | |||||
| return max_items_ - (head_ - tail_); | |||||
| } | |||||
| } | |||||
| // Return const reference to item by index. | |||||
| // If index is out of range 0…size()-1, the behavior is undefined. | |||||
| const T& at(size_t i) const | |||||
| { | |||||
| assert(i < size()); | |||||
| return v_[(head_ + i) % max_items_]; | |||||
| } | |||||
| // Pop item from front. | |||||
| // If there are no elements in the container, the behavior is undefined. | |||||
| void pop_front() | |||||
| { | |||||
| head_ = (head_ + 1) % max_items_; | |||||
| } | |||||
| bool empty() const | |||||
| { | |||||
| return tail_ == head_; | |||||
| } | |||||
| bool full() const | |||||
| { | |||||
| // head is ahead of the tail by 1 | |||||
| if (max_items_ > 0) | |||||
| { | |||||
| return ((tail_ + 1) % max_items_) == head_; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| size_t overrun_counter() const | |||||
| { | |||||
| return overrun_counter_; | |||||
| } | |||||
| void reset_overrun_counter() | |||||
| { | |||||
| overrun_counter_ = 0; | |||||
| } | |||||
| private: | |||||
| // copy from other&& and reset it to disabled state | |||||
| void copy_moveable(circular_q&& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| max_items_ = other.max_items_; | |||||
| head_ = other.head_; | |||||
| tail_ = other.tail_; | |||||
| overrun_counter_ = other.overrun_counter_; | |||||
| v_ = std::move(other.v_); | |||||
| // put &&other in disabled, but valid state | |||||
| other.max_items_ = 0; | |||||
| other.head_ = other.tail_ = 0; | |||||
| other.overrun_counter_ = 0; | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,34 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <mutex> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| struct console_mutex | |||||
| { | |||||
| using mutex_t = std::mutex; | |||||
| static mutex_t& mutex() | |||||
| { | |||||
| static mutex_t s_mutex; | |||||
| return s_mutex; | |||||
| } | |||||
| }; | |||||
| struct console_nullmutex | |||||
| { | |||||
| using mutex_t = null_mutex; | |||||
| static mutex_t& mutex() | |||||
| { | |||||
| static mutex_t s_mutex; | |||||
| return s_mutex; | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,175 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #endif | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/common.h> | |||||
| #include <cerrno> | |||||
| #include <chrono> | |||||
| #include <cstdio> | |||||
| #include <string> | |||||
| #include <thread> | |||||
| #include <tuple> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE file_helper::file_helper(const file_event_handlers& event_handlers) : | |||||
| event_handlers_(event_handlers) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE file_helper::~file_helper() | |||||
| { | |||||
| close(); | |||||
| } | |||||
| SPDLOG_INLINE void file_helper::open(const filename_t& fname, bool truncate) | |||||
| { | |||||
| close(); | |||||
| filename_ = fname; | |||||
| auto* mode = SPDLOG_FILENAME_T("ab"); | |||||
| auto* trunc_mode = SPDLOG_FILENAME_T("wb"); | |||||
| if (event_handlers_.before_open) | |||||
| { | |||||
| event_handlers_.before_open(filename_); | |||||
| } | |||||
| for (int tries = 0; tries < open_tries_; ++tries) | |||||
| { | |||||
| // create containing folder if not exists already. | |||||
| os::create_dir(os::dir_name(fname)); | |||||
| if (truncate) | |||||
| { | |||||
| // Truncate by opening-and-closing a tmp file in "wb" mode, always | |||||
| // opening the actual log-we-write-to in "ab" mode, since that | |||||
| // interacts more politely with eternal processes that might | |||||
| // rotate/truncate the file underneath us. | |||||
| std::FILE* tmp; | |||||
| if (os::fopen_s(&tmp, fname, trunc_mode)) | |||||
| { | |||||
| continue; | |||||
| } | |||||
| std::fclose(tmp); | |||||
| } | |||||
| if (!os::fopen_s(&fd_, fname, mode)) | |||||
| { | |||||
| if (event_handlers_.after_open) | |||||
| { | |||||
| event_handlers_.after_open(filename_, fd_); | |||||
| } | |||||
| return; | |||||
| } | |||||
| details::os::sleep_for_millis(open_interval_); | |||||
| } | |||||
| throw_spdlog_ex("Failed opening file " + os::filename_to_str(filename_) + " for writing", errno); | |||||
| } | |||||
| SPDLOG_INLINE void file_helper::reopen(bool truncate) | |||||
| { | |||||
| if (filename_.empty()) | |||||
| { | |||||
| throw_spdlog_ex("Failed re opening file - was not opened before"); | |||||
| } | |||||
| this->open(filename_, truncate); | |||||
| } | |||||
| SPDLOG_INLINE void file_helper::flush() | |||||
| { | |||||
| if (std::fflush(fd_) != 0) | |||||
| { | |||||
| throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void file_helper::close() | |||||
| { | |||||
| if (fd_ != nullptr) | |||||
| { | |||||
| if (event_handlers_.before_close) | |||||
| { | |||||
| event_handlers_.before_close(filename_, fd_); | |||||
| } | |||||
| std::fclose(fd_); | |||||
| fd_ = nullptr; | |||||
| if (event_handlers_.after_close) | |||||
| { | |||||
| event_handlers_.after_close(filename_); | |||||
| } | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void file_helper::write(const memory_buf_t& buf) | |||||
| { | |||||
| size_t msg_size = buf.size(); | |||||
| auto data = buf.data(); | |||||
| if (std::fwrite(data, 1, msg_size, fd_) != msg_size) | |||||
| { | |||||
| throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE size_t file_helper::size() const | |||||
| { | |||||
| if (fd_ == nullptr) | |||||
| { | |||||
| throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_)); | |||||
| } | |||||
| return os::filesize(fd_); | |||||
| } | |||||
| SPDLOG_INLINE const filename_t& file_helper::filename() const | |||||
| { | |||||
| return filename_; | |||||
| } | |||||
| // | |||||
| // return file path and its extension: | |||||
| // | |||||
| // "mylog.txt" => ("mylog", ".txt") | |||||
| // "mylog" => ("mylog", "") | |||||
| // "mylog." => ("mylog.", "") | |||||
| // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
| // | |||||
| // the starting dot in filenames is ignored (hidden files): | |||||
| // | |||||
| // ".mylog" => (".mylog". "") | |||||
| // "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
| // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
| SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension(const filename_t& fname) | |||||
| { | |||||
| auto ext_index = fname.rfind('.'); | |||||
| // no valid extension found - return whole path and empty string as | |||||
| // extension | |||||
| if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) | |||||
| { | |||||
| return std::make_tuple(fname, filename_t()); | |||||
| } | |||||
| // treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" | |||||
| auto folder_index = fname.find_last_of(details::os::folder_seps_filename); | |||||
| if (folder_index != filename_t::npos && folder_index >= ext_index - 1) | |||||
| { | |||||
| return std::make_tuple(fname, filename_t()); | |||||
| } | |||||
| // finally - return a valid base and extension tuple | |||||
| return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,63 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <tuple> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| // Helper class for file sinks. | |||||
| // When failing to open a file, retry several times(5) with a delay interval(10 ms). | |||||
| // Throw spdlog_ex exception on errors. | |||||
| class SPDLOG_API file_helper | |||||
| { | |||||
| public: | |||||
| file_helper() = default; | |||||
| explicit file_helper(const file_event_handlers& event_handlers); | |||||
| file_helper(const file_helper&) = delete; | |||||
| file_helper& operator=(const file_helper&) = delete; | |||||
| ~file_helper(); | |||||
| void open(const filename_t& fname, bool truncate = false); | |||||
| void reopen(bool truncate); | |||||
| void flush(); | |||||
| void close(); | |||||
| void write(const memory_buf_t& buf); | |||||
| size_t size() const; | |||||
| const filename_t& filename() const; | |||||
| // | |||||
| // return file path and its extension: | |||||
| // | |||||
| // "mylog.txt" => ("mylog", ".txt") | |||||
| // "mylog" => ("mylog", "") | |||||
| // "mylog." => ("mylog.", "") | |||||
| // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") | |||||
| // | |||||
| // the starting dot in filenames is ignored (hidden files): | |||||
| // | |||||
| // ".mylog" => (".mylog". "") | |||||
| // "my_folder/.mylog" => ("my_folder/.mylog", "") | |||||
| // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") | |||||
| static std::tuple<filename_t, filename_t> split_by_extension(const filename_t& fname); | |||||
| private: | |||||
| const int open_tries_ = 5; | |||||
| const unsigned int open_interval_ = 10; | |||||
| std::FILE* fd_{nullptr}; | |||||
| filename_t filename_; | |||||
| file_event_handlers event_handlers_; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "file_helper-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,167 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <chrono> | |||||
| #include <type_traits> | |||||
| #include <iterator> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <spdlog/common.h> | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| #include <charconv> | |||||
| #include <limits> | |||||
| #endif | |||||
| // Some fmt helpers to efficiently format and pad ints and strings | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| namespace fmt_helper | |||||
| { | |||||
| inline void append_string_view(spdlog::string_view_t view, memory_buf_t& dest) | |||||
| { | |||||
| auto* buf_ptr = view.data(); | |||||
| dest.append(buf_ptr, buf_ptr + view.size()); | |||||
| } | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| template<typename T> | |||||
| inline void append_int(T n, memory_buf_t& dest) | |||||
| { | |||||
| // Buffer should be large enough to hold all digits (digits10 + 1) and a sign | |||||
| SPDLOG_CONSTEXPR const auto BUF_SIZE = std::numeric_limits<T>::digits10 + 2; | |||||
| char buf[BUF_SIZE]; | |||||
| auto [ptr, ec] = std::to_chars(buf, buf + BUF_SIZE, n, 10); | |||||
| if (ec == std::errc()) | |||||
| { | |||||
| dest.append(buf, ptr); | |||||
| } | |||||
| else | |||||
| { | |||||
| throw_spdlog_ex("Failed to format int", static_cast<int>(ec)); | |||||
| } | |||||
| } | |||||
| #else | |||||
| template<typename T> | |||||
| inline void append_int(T n, memory_buf_t& dest) | |||||
| { | |||||
| fmt::format_int i(n); | |||||
| dest.append(i.data(), i.data() + i.size()); | |||||
| } | |||||
| #endif | |||||
| template<typename T> | |||||
| SPDLOG_CONSTEXPR_FUNC unsigned int count_digits_fallback(T n) | |||||
| { | |||||
| // taken from fmt: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/format.h#L899-L912 | |||||
| unsigned int count = 1; | |||||
| for (;;) | |||||
| { | |||||
| // Integer division is slow so do it for a group of four digits instead | |||||
| // of for every digit. The idea comes from the talk by Alexandrescu | |||||
| // "Three Optimization Tips for C++". See speed-test for a comparison. | |||||
| if (n < 10) | |||||
| return count; | |||||
| if (n < 100) | |||||
| return count + 1; | |||||
| if (n < 1000) | |||||
| return count + 2; | |||||
| if (n < 10000) | |||||
| return count + 3; | |||||
| n /= 10000u; | |||||
| count += 4; | |||||
| } | |||||
| } | |||||
| template<typename T> | |||||
| inline unsigned int count_digits(T n) | |||||
| { | |||||
| using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| return count_digits_fallback(static_cast<count_type>(n)); | |||||
| #else | |||||
| return static_cast<unsigned int>(fmt:: | |||||
| // fmt 7.0.0 renamed the internal namespace to detail. | |||||
| // See: https://github.com/fmtlib/fmt/issues/1538 | |||||
| #if FMT_VERSION < 70000 | |||||
| internal | |||||
| #else | |||||
| detail | |||||
| #endif | |||||
| ::count_digits(static_cast<count_type>(n))); | |||||
| #endif | |||||
| } | |||||
| inline void pad2(int n, memory_buf_t& dest) | |||||
| { | |||||
| if (n >= 0 && n < 100) // 0-99 | |||||
| { | |||||
| dest.push_back(static_cast<char>('0' + n / 10)); | |||||
| dest.push_back(static_cast<char>('0' + n % 10)); | |||||
| } | |||||
| else // unlikely, but just in case, let fmt deal with it | |||||
| { | |||||
| fmt_lib::format_to(std::back_inserter(dest), SPDLOG_FMT_STRING("{:02}"), n); | |||||
| } | |||||
| } | |||||
| template<typename T> | |||||
| inline void pad_uint(T n, unsigned int width, memory_buf_t& dest) | |||||
| { | |||||
| static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T"); | |||||
| for (auto digits = count_digits(n); digits < width; digits++) | |||||
| { | |||||
| dest.push_back('0'); | |||||
| } | |||||
| append_int(n, dest); | |||||
| } | |||||
| template<typename T> | |||||
| inline void pad3(T n, memory_buf_t& dest) | |||||
| { | |||||
| static_assert(std::is_unsigned<T>::value, "pad3 must get unsigned T"); | |||||
| if (n < 1000) | |||||
| { | |||||
| dest.push_back(static_cast<char>(n / 100 + '0')); | |||||
| n = n % 100; | |||||
| dest.push_back(static_cast<char>((n / 10) + '0')); | |||||
| dest.push_back(static_cast<char>((n % 10) + '0')); | |||||
| } | |||||
| else | |||||
| { | |||||
| append_int(n, dest); | |||||
| } | |||||
| } | |||||
| template<typename T> | |||||
| inline void pad6(T n, memory_buf_t& dest) | |||||
| { | |||||
| pad_uint(n, 6, dest); | |||||
| } | |||||
| template<typename T> | |||||
| inline void pad9(T n, memory_buf_t& dest) | |||||
| { | |||||
| pad_uint(n, 9, dest); | |||||
| } | |||||
| // return fraction of a second of the given time_point. | |||||
| // e.g. | |||||
| // fraction<std::milliseconds>(tp) -> will return the millis part of the second | |||||
| template<typename ToDuration> | |||||
| inline ToDuration time_fraction(log_clock::time_point tp) | |||||
| { | |||||
| using std::chrono::duration_cast; | |||||
| using std::chrono::seconds; | |||||
| auto duration = tp.time_since_epoch(); | |||||
| auto secs = duration_cast<seconds>(duration); | |||||
| return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); | |||||
| } | |||||
| } // namespace fmt_helper | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,44 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #endif | |||||
| #include <spdlog/details/os.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE log_msg::log_msg(spdlog::log_clock::time_point log_time, spdlog::source_loc loc, string_view_t a_logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) : | |||||
| logger_name(a_logger_name), | |||||
| level(lvl), | |||||
| time(log_time) | |||||
| #ifndef SPDLOG_NO_THREAD_ID | |||||
| , | |||||
| thread_id(os::thread_id()) | |||||
| #endif | |||||
| , | |||||
| source(loc), | |||||
| payload(msg) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE log_msg::log_msg( | |||||
| spdlog::source_loc loc, string_view_t a_logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg | |||||
| ) : | |||||
| log_msg(os::now(), loc, a_logger_name, lvl, msg) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE log_msg::log_msg(string_view_t a_logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) : | |||||
| log_msg(os::now(), source_loc{}, a_logger_name, lvl, msg) | |||||
| { | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,39 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| struct SPDLOG_API log_msg | |||||
| { | |||||
| log_msg() = default; | |||||
| log_msg(log_clock::time_point log_time, source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
| log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
| log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg); | |||||
| log_msg(const log_msg& other) = default; | |||||
| log_msg& operator=(const log_msg& other) = default; | |||||
| string_view_t logger_name; | |||||
| level::level_enum level{level::off}; | |||||
| log_clock::time_point time; | |||||
| size_t thread_id{0}; | |||||
| // wrapping the formatted text with color (updated by pattern_formatter). | |||||
| mutable size_t color_range_start{0}; | |||||
| mutable size_t color_range_end{0}; | |||||
| source_loc source; | |||||
| string_view_t payload; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "log_msg-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,60 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/log_msg_buffer.h> | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg& orig_msg) : | |||||
| log_msg{orig_msg} | |||||
| { | |||||
| buffer.append(logger_name.begin(), logger_name.end()); | |||||
| buffer.append(payload.begin(), payload.end()); | |||||
| update_string_views(); | |||||
| } | |||||
| SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg_buffer& other) : | |||||
| log_msg{other} | |||||
| { | |||||
| buffer.append(logger_name.begin(), logger_name.end()); | |||||
| buffer.append(payload.begin(), payload.end()); | |||||
| update_string_views(); | |||||
| } | |||||
| SPDLOG_INLINE log_msg_buffer::log_msg_buffer(log_msg_buffer&& other) SPDLOG_NOEXCEPT : log_msg{other}, buffer{std::move(other.buffer)} | |||||
| { | |||||
| update_string_views(); | |||||
| } | |||||
| SPDLOG_INLINE log_msg_buffer& log_msg_buffer::operator=(const log_msg_buffer& other) | |||||
| { | |||||
| log_msg::operator=(other); | |||||
| buffer.clear(); | |||||
| buffer.append(other.buffer.data(), other.buffer.data() + other.buffer.size()); | |||||
| update_string_views(); | |||||
| return *this; | |||||
| } | |||||
| SPDLOG_INLINE log_msg_buffer& log_msg_buffer::operator=(log_msg_buffer&& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| log_msg::operator=(other); | |||||
| buffer = std::move(other.buffer); | |||||
| update_string_views(); | |||||
| return *this; | |||||
| } | |||||
| SPDLOG_INLINE void log_msg_buffer::update_string_views() | |||||
| { | |||||
| logger_name = string_view_t{buffer.data(), logger_name.size()}; | |||||
| payload = string_view_t{buffer.data() + logger_name.size(), payload.size()}; | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,35 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/log_msg.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| // Extend log_msg with internal buffer to store its payload. | |||||
| // This is needed since log_msg holds string_views that points to stack data. | |||||
| class SPDLOG_API log_msg_buffer : public log_msg | |||||
| { | |||||
| memory_buf_t buffer; | |||||
| void update_string_views(); | |||||
| public: | |||||
| log_msg_buffer() = default; | |||||
| explicit log_msg_buffer(const log_msg& orig_msg); | |||||
| log_msg_buffer(const log_msg_buffer& other); | |||||
| log_msg_buffer(log_msg_buffer&& other) SPDLOG_NOEXCEPT; | |||||
| log_msg_buffer& operator=(const log_msg_buffer& other); | |||||
| log_msg_buffer& operator=(log_msg_buffer&& other) SPDLOG_NOEXCEPT; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "log_msg_buffer-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,163 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // multi producer-multi consumer blocking queue. | |||||
| // enqueue(..) - will block until room found to put the new message. | |||||
| // enqueue_nowait(..) - will return immediately with false if no room left in | |||||
| // the queue. | |||||
| // dequeue_for(..) - will block until the queue is not empty or timeout have | |||||
| // passed. | |||||
| #include <spdlog/details/circular_q.h> | |||||
| #include <condition_variable> | |||||
| #include <mutex> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| template<typename T> | |||||
| class mpmc_blocking_queue | |||||
| { | |||||
| public: | |||||
| using item_type = T; | |||||
| explicit mpmc_blocking_queue(size_t max_items) : | |||||
| q_(max_items) | |||||
| { | |||||
| } | |||||
| #ifndef __MINGW32__ | |||||
| // try to enqueue and block if no room left | |||||
| void enqueue(T&& item) | |||||
| { | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| pop_cv_.wait(lock, [this] | |||||
| { return !this->q_.full(); }); | |||||
| q_.push_back(std::move(item)); | |||||
| } | |||||
| push_cv_.notify_one(); | |||||
| } | |||||
| // enqueue immediately. overrun oldest message in the queue if no room left. | |||||
| void enqueue_nowait(T&& item) | |||||
| { | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| q_.push_back(std::move(item)); | |||||
| } | |||||
| push_cv_.notify_one(); | |||||
| } | |||||
| // dequeue with a timeout. | |||||
| // Return true, if succeeded dequeue item, false otherwise | |||||
| bool dequeue_for(T& popped_item, std::chrono::milliseconds wait_duration) | |||||
| { | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| if (!push_cv_.wait_for(lock, wait_duration, [this] | |||||
| { return !this->q_.empty(); })) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| popped_item = std::move(q_.front()); | |||||
| q_.pop_front(); | |||||
| } | |||||
| pop_cv_.notify_one(); | |||||
| return true; | |||||
| } | |||||
| // blocking dequeue without a timeout. | |||||
| void dequeue(T& popped_item) | |||||
| { | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| push_cv_.wait(lock, [this] | |||||
| { return !this->q_.empty(); }); | |||||
| popped_item = std::move(q_.front()); | |||||
| q_.pop_front(); | |||||
| } | |||||
| pop_cv_.notify_one(); | |||||
| } | |||||
| #else | |||||
| // apparently mingw deadlocks if the mutex is released before cv.notify_one(), | |||||
| // so release the mutex at the very end each function. | |||||
| // try to enqueue and block if no room left | |||||
| void enqueue(T&& item) | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| pop_cv_.wait(lock, [this] | |||||
| { return !this->q_.full(); }); | |||||
| q_.push_back(std::move(item)); | |||||
| push_cv_.notify_one(); | |||||
| } | |||||
| // enqueue immediately. overrun oldest message in the queue if no room left. | |||||
| void enqueue_nowait(T&& item) | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| q_.push_back(std::move(item)); | |||||
| push_cv_.notify_one(); | |||||
| } | |||||
| // dequeue with a timeout. | |||||
| // Return true, if succeeded dequeue item, false otherwise | |||||
| bool dequeue_for(T& popped_item, std::chrono::milliseconds wait_duration) | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| if (!push_cv_.wait_for(lock, wait_duration, [this] | |||||
| { return !this->q_.empty(); })) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| popped_item = std::move(q_.front()); | |||||
| q_.pop_front(); | |||||
| pop_cv_.notify_one(); | |||||
| return true; | |||||
| } | |||||
| // blocking dequeue without a timeout. | |||||
| void dequeue(T& popped_item) | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| push_cv_.wait(lock, [this] | |||||
| { return !this->q_.empty(); }); | |||||
| popped_item = std::move(q_.front()); | |||||
| q_.pop_front(); | |||||
| pop_cv_.notify_one(); | |||||
| } | |||||
| #endif | |||||
| size_t overrun_counter() | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| return q_.overrun_counter(); | |||||
| } | |||||
| size_t size() | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| return q_.size(); | |||||
| } | |||||
| void reset_overrun_counter() | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||||
| q_.reset_overrun_counter(); | |||||
| } | |||||
| private: | |||||
| std::mutex queue_mutex_; | |||||
| std::condition_variable push_cv_; | |||||
| std::condition_variable pop_cv_; | |||||
| spdlog::details::circular_q<T> q_; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,52 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <atomic> | |||||
| #include <utility> | |||||
| // null, no cost dummy "mutex" and dummy "atomic" int | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| struct null_mutex | |||||
| { | |||||
| void lock() const | |||||
| { | |||||
| } | |||||
| void unlock() const | |||||
| { | |||||
| } | |||||
| }; | |||||
| struct null_atomic_int | |||||
| { | |||||
| int value; | |||||
| null_atomic_int() = default; | |||||
| explicit null_atomic_int(int new_value) : | |||||
| value(new_value) | |||||
| { | |||||
| } | |||||
| int load(std::memory_order = std::memory_order_relaxed) const | |||||
| { | |||||
| return value; | |||||
| } | |||||
| void store(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
| { | |||||
| value = new_value; | |||||
| } | |||||
| int exchange(int new_value, std::memory_order = std::memory_order_relaxed) | |||||
| { | |||||
| std::swap(new_value, value); | |||||
| return new_value; // return value before the call | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,605 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/os.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <algorithm> | |||||
| #include <chrono> | |||||
| #include <cstdio> | |||||
| #include <cstdlib> | |||||
| #include <cstring> | |||||
| #include <ctime> | |||||
| #include <string> | |||||
| #include <thread> | |||||
| #include <array> | |||||
| #include <sys/stat.h> | |||||
| #include <sys/types.h> | |||||
| #ifdef _WIN32 | |||||
| #include <io.h> // _get_osfhandle and _isatty support | |||||
| #include <process.h> // _get_pid support | |||||
| #include <spdlog/details/windows_include.h> | |||||
| #ifdef __MINGW32__ | |||||
| #include <share.h> | |||||
| #endif | |||||
| #if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES) | |||||
| #include <limits> | |||||
| #endif | |||||
| #include <direct.h> // for _mkdir/_wmkdir | |||||
| #else // unix | |||||
| #include <fcntl.h> | |||||
| #include <unistd.h> | |||||
| #ifdef __linux__ | |||||
| #include <sys/syscall.h> //Use gettid() syscall under linux to get thread id | |||||
| #elif defined(_AIX) | |||||
| #include <pthread.h> // for pthread_getthrds_np | |||||
| #elif defined(__DragonFly__) || defined(__FreeBSD__) | |||||
| #include <pthread_np.h> // for pthread_getthreadid_np | |||||
| #elif defined(__NetBSD__) | |||||
| #include <lwp.h> // for _lwp_self | |||||
| #elif defined(__sun) | |||||
| #include <thread.h> // for thr_self | |||||
| #endif | |||||
| #endif // unix | |||||
| #ifndef __has_feature // Clang - feature checking macros. | |||||
| #define __has_feature(x) 0 // Compatibility with non-clang compilers. | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| namespace os | |||||
| { | |||||
| SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT | |||||
| { | |||||
| #if defined __linux__ && defined SPDLOG_CLOCK_COARSE | |||||
| timespec ts; | |||||
| ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); | |||||
| return std::chrono::time_point<log_clock, typename log_clock::duration>( | |||||
| std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec)) | |||||
| ); | |||||
| #else | |||||
| return log_clock::now(); | |||||
| #endif | |||||
| } | |||||
| SPDLOG_INLINE std::tm localtime(const std::time_t& time_tt) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| std::tm tm; | |||||
| ::localtime_s(&tm, &time_tt); | |||||
| #else | |||||
| std::tm tm; | |||||
| ::localtime_r(&time_tt, &tm); | |||||
| #endif | |||||
| return tm; | |||||
| } | |||||
| SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT | |||||
| { | |||||
| std::time_t now_t = ::time(nullptr); | |||||
| return localtime(now_t); | |||||
| } | |||||
| SPDLOG_INLINE std::tm gmtime(const std::time_t& time_tt) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| std::tm tm; | |||||
| ::gmtime_s(&tm, &time_tt); | |||||
| #else | |||||
| std::tm tm; | |||||
| ::gmtime_r(&time_tt, &tm); | |||||
| #endif | |||||
| return tm; | |||||
| } | |||||
| SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT | |||||
| { | |||||
| std::time_t now_t = ::time(nullptr); | |||||
| return gmtime(now_t); | |||||
| } | |||||
| // fopen_s on non windows for writing | |||||
| SPDLOG_INLINE bool fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| #ifdef SPDLOG_WCHAR_FILENAMES | |||||
| *fp = ::_wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
| #else | |||||
| *fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); | |||||
| #endif | |||||
| #if defined(SPDLOG_PREVENT_CHILD_FD) | |||||
| if (*fp != nullptr) | |||||
| { | |||||
| auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(::_fileno(*fp))); | |||||
| if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) | |||||
| { | |||||
| ::fclose(*fp); | |||||
| *fp = nullptr; | |||||
| } | |||||
| } | |||||
| #endif | |||||
| #else // unix | |||||
| #if defined(SPDLOG_PREVENT_CHILD_FD) | |||||
| const int mode_flag = mode == SPDLOG_FILENAME_T("ab") ? O_APPEND : O_TRUNC; | |||||
| const int fd = ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644)); | |||||
| if (fd == -1) | |||||
| { | |||||
| return true; | |||||
| } | |||||
| *fp = ::fdopen(fd, mode.c_str()); | |||||
| if (*fp == nullptr) | |||||
| { | |||||
| ::close(fd); | |||||
| } | |||||
| #else | |||||
| *fp = ::fopen((filename.c_str()), mode.c_str()); | |||||
| #endif | |||||
| #endif | |||||
| return *fp == nullptr; | |||||
| } | |||||
| SPDLOG_INLINE int remove(const filename_t& filename) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
| return ::_wremove(filename.c_str()); | |||||
| #else | |||||
| return std::remove(filename.c_str()); | |||||
| #endif | |||||
| } | |||||
| SPDLOG_INLINE int remove_if_exists(const filename_t& filename) SPDLOG_NOEXCEPT | |||||
| { | |||||
| return path_exists(filename) ? remove(filename) : 0; | |||||
| } | |||||
| SPDLOG_INLINE int rename(const filename_t& filename1, const filename_t& filename2) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
| return ::_wrename(filename1.c_str(), filename2.c_str()); | |||||
| #else | |||||
| return std::rename(filename1.c_str(), filename2.c_str()); | |||||
| #endif | |||||
| } | |||||
| // Return true if path exists (file or directory) | |||||
| SPDLOG_INLINE bool path_exists(const filename_t& filename) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| #ifdef SPDLOG_WCHAR_FILENAMES | |||||
| auto attribs = ::GetFileAttributesW(filename.c_str()); | |||||
| #else | |||||
| auto attribs = ::GetFileAttributesA(filename.c_str()); | |||||
| #endif | |||||
| return attribs != INVALID_FILE_ATTRIBUTES; | |||||
| #else // common linux/unix all have the stat system call | |||||
| struct stat buffer; | |||||
| return (::stat(filename.c_str(), &buffer) == 0); | |||||
| #endif | |||||
| } | |||||
| #ifdef _MSC_VER | |||||
| // avoid warning about unreachable statement at the end of filesize() | |||||
| #pragma warning(push) | |||||
| #pragma warning(disable : 4702) | |||||
| #endif | |||||
| // Return file size according to open FILE* object | |||||
| SPDLOG_INLINE size_t filesize(FILE* f) | |||||
| { | |||||
| if (f == nullptr) | |||||
| { | |||||
| throw_spdlog_ex("Failed getting file size. fd is null"); | |||||
| } | |||||
| #if defined(_WIN32) && !defined(__CYGWIN__) | |||||
| int fd = ::_fileno(f); | |||||
| #if defined(_WIN64) // 64 bits | |||||
| __int64 ret = ::_filelengthi64(fd); | |||||
| if (ret >= 0) | |||||
| { | |||||
| return static_cast<size_t>(ret); | |||||
| } | |||||
| #else // windows 32 bits | |||||
| long ret = ::_filelength(fd); | |||||
| if (ret >= 0) | |||||
| { | |||||
| return static_cast<size_t>(ret); | |||||
| } | |||||
| #endif | |||||
| #else // unix | |||||
| // OpenBSD and AIX doesn't compile with :: before the fileno(..) | |||||
| #if defined(__OpenBSD__) || defined(_AIX) | |||||
| int fd = fileno(f); | |||||
| #else | |||||
| int fd = ::fileno(f); | |||||
| #endif | |||||
| // 64 bits(but not in osx, linux/musl or cygwin, where fstat64 is deprecated) | |||||
| #if ((defined(__linux__) && defined(__GLIBC__)) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) | |||||
| struct stat64 st; | |||||
| if (::fstat64(fd, &st) == 0) | |||||
| { | |||||
| return static_cast<size_t>(st.st_size); | |||||
| } | |||||
| #else // other unix or linux 32 bits or cygwin | |||||
| struct stat st; | |||||
| if (::fstat(fd, &st) == 0) | |||||
| { | |||||
| return static_cast<size_t>(st.st_size); | |||||
| } | |||||
| #endif | |||||
| #endif | |||||
| throw_spdlog_ex("Failed getting file size from fd", errno); | |||||
| return 0; // will not be reached. | |||||
| } | |||||
| #ifdef _MSC_VER | |||||
| #pragma warning(pop) | |||||
| #endif | |||||
| // Return utc offset in minutes or throw spdlog_ex on failure | |||||
| SPDLOG_INLINE int utc_minutes_offset(const std::tm& tm) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| #if _WIN32_WINNT < _WIN32_WINNT_WS08 | |||||
| TIME_ZONE_INFORMATION tzinfo; | |||||
| auto rv = ::GetTimeZoneInformation(&tzinfo); | |||||
| #else | |||||
| DYNAMIC_TIME_ZONE_INFORMATION tzinfo; | |||||
| auto rv = ::GetDynamicTimeZoneInformation(&tzinfo); | |||||
| #endif | |||||
| if (rv == TIME_ZONE_ID_INVALID) | |||||
| throw_spdlog_ex("Failed getting timezone info. ", errno); | |||||
| int offset = -tzinfo.Bias; | |||||
| if (tm.tm_isdst) | |||||
| { | |||||
| offset -= tzinfo.DaylightBias; | |||||
| } | |||||
| else | |||||
| { | |||||
| offset -= tzinfo.StandardBias; | |||||
| } | |||||
| return offset; | |||||
| #else | |||||
| #if defined(sun) || defined(__sun) || defined(_AIX) || (defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || (!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE)) | |||||
| // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris | |||||
| struct helper | |||||
| { | |||||
| static long int calculate_gmt_offset(const std::tm& localtm = details::os::localtime(), const std::tm& gmtm = details::os::gmtime()) | |||||
| { | |||||
| int local_year = localtm.tm_year + (1900 - 1); | |||||
| int gmt_year = gmtm.tm_year + (1900 - 1); | |||||
| long int days = ( | |||||
| // difference in day of year | |||||
| localtm.tm_yday - | |||||
| gmtm.tm_yday | |||||
| // + intervening leap days | |||||
| + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + | |||||
| ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) | |||||
| // + difference in years * 365 */ | |||||
| + static_cast<long int>(local_year - gmt_year) * 365 | |||||
| ); | |||||
| long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); | |||||
| long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); | |||||
| long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); | |||||
| return secs; | |||||
| } | |||||
| }; | |||||
| auto offset_seconds = helper::calculate_gmt_offset(tm); | |||||
| #else | |||||
| auto offset_seconds = tm.tm_gmtoff; | |||||
| #endif | |||||
| return static_cast<int>(offset_seconds / 60); | |||||
| #endif | |||||
| } | |||||
| // Return current thread id as size_t | |||||
| // It exists because the std::this_thread::get_id() is much slower(especially | |||||
| // under VS 2013) | |||||
| SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| return static_cast<size_t>(::GetCurrentThreadId()); | |||||
| #elif defined(__linux__) | |||||
| #if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) | |||||
| #define SYS_gettid __NR_gettid | |||||
| #endif | |||||
| return static_cast<size_t>(::syscall(SYS_gettid)); | |||||
| #elif defined(_AIX) | |||||
| struct __pthrdsinfo buf; | |||||
| int reg_size = 0; | |||||
| pthread_t pt = pthread_self(); | |||||
| int retval = pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_TID, &buf, sizeof(buf), NULL, ®_size); | |||||
| int tid = (!retval) ? buf.__pi_tid : 0; | |||||
| return static_cast<size_t>(tid); | |||||
| #elif defined(__DragonFly__) || defined(__FreeBSD__) | |||||
| return static_cast<size_t>(::pthread_getthreadid_np()); | |||||
| #elif defined(__NetBSD__) | |||||
| return static_cast<size_t>(::_lwp_self()); | |||||
| #elif defined(__OpenBSD__) | |||||
| return static_cast<size_t>(::getthrid()); | |||||
| #elif defined(__sun) | |||||
| return static_cast<size_t>(::thr_self()); | |||||
| #elif __APPLE__ | |||||
| uint64_t tid; | |||||
| pthread_threadid_np(nullptr, &tid); | |||||
| return static_cast<size_t>(tid); | |||||
| #else // Default to standard C++11 (other Unix) | |||||
| return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); | |||||
| #endif | |||||
| } | |||||
| // Return current thread id as size_t (from thread local storage) | |||||
| SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT | |||||
| { | |||||
| #if defined(SPDLOG_NO_TLS) | |||||
| return _thread_id(); | |||||
| #else // cache thread id in tls | |||||
| static thread_local const size_t tid = _thread_id(); | |||||
| return tid; | |||||
| #endif | |||||
| } | |||||
| // This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
| // See https://github.com/gabime/spdlog/issues/609 | |||||
| SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #if defined(_WIN32) | |||||
| ::Sleep(milliseconds); | |||||
| #else | |||||
| std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); | |||||
| #endif | |||||
| } | |||||
| // wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) | |||||
| #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||||
| SPDLOG_INLINE std::string filename_to_str(const filename_t& filename) | |||||
| { | |||||
| memory_buf_t buf; | |||||
| wstr_to_utf8buf(filename, buf); | |||||
| return SPDLOG_BUF_TO_STRING(buf); | |||||
| } | |||||
| #else | |||||
| SPDLOG_INLINE std::string filename_to_str(const filename_t& filename) | |||||
| { | |||||
| return filename; | |||||
| } | |||||
| #endif | |||||
| SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| return conditional_static_cast<int>(::GetCurrentProcessId()); | |||||
| #else | |||||
| return conditional_static_cast<int>(::getpid()); | |||||
| #endif | |||||
| } | |||||
| // Determine if the terminal supports colors | |||||
| // Based on: https://github.com/agauniyal/rang/ | |||||
| SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| return true; | |||||
| #else | |||||
| static const bool result = []() | |||||
| { | |||||
| const char* env_colorterm_p = std::getenv("COLORTERM"); | |||||
| if (env_colorterm_p != nullptr) | |||||
| { | |||||
| return true; | |||||
| } | |||||
| static constexpr std::array<const char*, 16> terms = {{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm", "alacritty", "vt102"}}; | |||||
| const char* env_term_p = std::getenv("TERM"); | |||||
| if (env_term_p == nullptr) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| return std::any_of(terms.begin(), terms.end(), [&](const char* term) | |||||
| { return std::strstr(env_term_p, term) != nullptr; }); | |||||
| }(); | |||||
| return result; | |||||
| #endif | |||||
| } | |||||
| // Determine if the terminal attached | |||||
| // Source: https://github.com/agauniyal/rang/ | |||||
| SPDLOG_INLINE bool in_terminal(FILE* file) SPDLOG_NOEXCEPT | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| return ::_isatty(_fileno(file)) != 0; | |||||
| #else | |||||
| return ::isatty(fileno(file)) != 0; | |||||
| #endif | |||||
| } | |||||
| #if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
| SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t& target) | |||||
| { | |||||
| if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) / 2 - 1) | |||||
| { | |||||
| throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8"); | |||||
| } | |||||
| int wstr_size = static_cast<int>(wstr.size()); | |||||
| if (wstr_size == 0) | |||||
| { | |||||
| target.resize(0); | |||||
| return; | |||||
| } | |||||
| int result_size = static_cast<int>(target.capacity()); | |||||
| if ((wstr_size + 1) * 2 > result_size) | |||||
| { | |||||
| result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL); | |||||
| } | |||||
| if (result_size > 0) | |||||
| { | |||||
| target.resize(result_size); | |||||
| result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), result_size, NULL, NULL); | |||||
| if (result_size > 0) | |||||
| { | |||||
| target.resize(result_size); | |||||
| return; | |||||
| } | |||||
| } | |||||
| throw_spdlog_ex(fmt_lib::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError())); | |||||
| } | |||||
| SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t& target) | |||||
| { | |||||
| if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) - 1) | |||||
| { | |||||
| throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16"); | |||||
| } | |||||
| int str_size = static_cast<int>(str.size()); | |||||
| if (str_size == 0) | |||||
| { | |||||
| target.resize(0); | |||||
| return; | |||||
| } | |||||
| int result_size = static_cast<int>(target.capacity()); | |||||
| if (str_size + 1 > result_size) | |||||
| { | |||||
| result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, NULL, 0); | |||||
| } | |||||
| if (result_size > 0) | |||||
| { | |||||
| target.resize(result_size); | |||||
| result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, target.data(), result_size); | |||||
| if (result_size > 0) | |||||
| { | |||||
| target.resize(result_size); | |||||
| return; | |||||
| } | |||||
| } | |||||
| throw_spdlog_ex(fmt_lib::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError())); | |||||
| } | |||||
| #endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
| // return true on success | |||||
| static SPDLOG_INLINE bool mkdir_(const filename_t& path) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| #ifdef SPDLOG_WCHAR_FILENAMES | |||||
| return ::_wmkdir(path.c_str()) == 0; | |||||
| #else | |||||
| return ::_mkdir(path.c_str()) == 0; | |||||
| #endif | |||||
| #else | |||||
| return ::mkdir(path.c_str(), mode_t(0755)) == 0; | |||||
| #endif | |||||
| } | |||||
| // create the given directory - and all directories leading to it | |||||
| // return true on success or if the directory already exists | |||||
| SPDLOG_INLINE bool create_dir(const filename_t& path) | |||||
| { | |||||
| if (path_exists(path)) | |||||
| { | |||||
| return true; | |||||
| } | |||||
| if (path.empty()) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| size_t search_offset = 0; | |||||
| do | |||||
| { | |||||
| auto token_pos = path.find_first_of(folder_seps_filename, search_offset); | |||||
| // treat the entire path as a folder if no folder separator not found | |||||
| if (token_pos == filename_t::npos) | |||||
| { | |||||
| token_pos = path.size(); | |||||
| } | |||||
| auto subdir = path.substr(0, token_pos); | |||||
| if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) | |||||
| { | |||||
| return false; // return error if failed creating dir | |||||
| } | |||||
| search_offset = token_pos + 1; | |||||
| } while (search_offset < path.size()); | |||||
| return true; | |||||
| } | |||||
| // Return directory name from given path or empty string | |||||
| // "abc/file" => "abc" | |||||
| // "abc/" => "abc" | |||||
| // "abc" => "" | |||||
| // "abc///" => "abc//" | |||||
| SPDLOG_INLINE filename_t dir_name(const filename_t& path) | |||||
| { | |||||
| auto pos = path.find_last_of(folder_seps_filename); | |||||
| return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; | |||||
| } | |||||
| std::string SPDLOG_INLINE getenv(const char* field) | |||||
| { | |||||
| #if defined(_MSC_VER) | |||||
| #if defined(__cplusplus_winrt) | |||||
| return std::string{}; // not supported under uwp | |||||
| #else | |||||
| size_t len = 0; | |||||
| char buf[128]; | |||||
| bool ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0; | |||||
| return ok ? buf : std::string{}; | |||||
| #endif | |||||
| #else // revert to getenv | |||||
| char* buf = ::getenv(field); | |||||
| return buf ? buf : std::string{}; | |||||
| #endif | |||||
| } | |||||
| } // namespace os | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,121 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <ctime> // std::time_t | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| namespace os | |||||
| { | |||||
| SPDLOG_API spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API std::tm localtime(const std::time_t& time_tt) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API std::tm localtime() SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API std::tm gmtime(const std::time_t& time_tt) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API std::tm gmtime() SPDLOG_NOEXCEPT; | |||||
| // eol definition | |||||
| #if !defined(SPDLOG_EOL) | |||||
| #ifdef _WIN32 | |||||
| #define SPDLOG_EOL "\r\n" | |||||
| #else | |||||
| #define SPDLOG_EOL "\n" | |||||
| #endif | |||||
| #endif | |||||
| SPDLOG_CONSTEXPR static const char* default_eol = SPDLOG_EOL; | |||||
| // folder separator | |||||
| #if !defined(SPDLOG_FOLDER_SEPS) | |||||
| #ifdef _WIN32 | |||||
| #define SPDLOG_FOLDER_SEPS "\\/" | |||||
| #else | |||||
| #define SPDLOG_FOLDER_SEPS "/" | |||||
| #endif | |||||
| #endif | |||||
| SPDLOG_CONSTEXPR static const char folder_seps[] = SPDLOG_FOLDER_SEPS; | |||||
| SPDLOG_CONSTEXPR static const filename_t::value_type folder_seps_filename[] = SPDLOG_FILENAME_T(SPDLOG_FOLDER_SEPS); | |||||
| // fopen_s on non windows for writing | |||||
| SPDLOG_API bool fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode); | |||||
| // Remove filename. return 0 on success | |||||
| SPDLOG_API int remove(const filename_t& filename) SPDLOG_NOEXCEPT; | |||||
| // Remove file if exists. return 0 on success | |||||
| // Note: Non atomic (might return failure to delete if concurrently deleted by other process/thread) | |||||
| SPDLOG_API int remove_if_exists(const filename_t& filename) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API int rename(const filename_t& filename1, const filename_t& filename2) SPDLOG_NOEXCEPT; | |||||
| // Return if file exists. | |||||
| SPDLOG_API bool path_exists(const filename_t& filename) SPDLOG_NOEXCEPT; | |||||
| // Return file size according to open FILE* object | |||||
| SPDLOG_API size_t filesize(FILE* f); | |||||
| // Return utc offset in minutes or throw spdlog_ex on failure | |||||
| SPDLOG_API int utc_minutes_offset(const std::tm& tm = details::os::localtime()); | |||||
| // Return current thread id as size_t | |||||
| // It exists because the std::this_thread::get_id() is much slower(especially | |||||
| // under VS 2013) | |||||
| SPDLOG_API size_t _thread_id() SPDLOG_NOEXCEPT; | |||||
| // Return current thread id as size_t (from thread local storage) | |||||
| SPDLOG_API size_t thread_id() SPDLOG_NOEXCEPT; | |||||
| // This is avoid msvc issue in sleep_for that happens if the clock changes. | |||||
| // See https://github.com/gabime/spdlog/issues/609 | |||||
| SPDLOG_API void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT; | |||||
| SPDLOG_API std::string filename_to_str(const filename_t& filename); | |||||
| SPDLOG_API int pid() SPDLOG_NOEXCEPT; | |||||
| // Determine if the terminal supports colors | |||||
| // Source: https://github.com/agauniyal/rang/ | |||||
| SPDLOG_API bool is_color_terminal() SPDLOG_NOEXCEPT; | |||||
| // Determine if the terminal attached | |||||
| // Source: https://github.com/agauniyal/rang/ | |||||
| SPDLOG_API bool in_terminal(FILE* file) SPDLOG_NOEXCEPT; | |||||
| #if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||||
| SPDLOG_API void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t& target); | |||||
| SPDLOG_API void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t& target); | |||||
| #endif | |||||
| // Return directory name from given path or empty string | |||||
| // "abc/file" => "abc" | |||||
| // "abc/" => "abc" | |||||
| // "abc" => "" | |||||
| // "abc///" => "abc//" | |||||
| SPDLOG_API filename_t dir_name(const filename_t& path); | |||||
| // Create a dir from the given path. | |||||
| // Return true if succeeded or if this dir already exists. | |||||
| SPDLOG_API bool create_dir(const filename_t& path); | |||||
| // non thread safe, cross platform getenv/getenv_s | |||||
| // return empty string if field not found | |||||
| SPDLOG_API std::string getenv(const char* field); | |||||
| } // namespace os | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "os-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,30 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/periodic_worker.h> | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| // stop the worker thread and join it | |||||
| SPDLOG_INLINE periodic_worker::~periodic_worker() | |||||
| { | |||||
| if (worker_thread_.joinable()) | |||||
| { | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(mutex_); | |||||
| active_ = false; | |||||
| } | |||||
| cv_.notify_one(); | |||||
| worker_thread_.join(); | |||||
| } | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,62 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // periodic worker thread - periodically executes the given callback function. | |||||
| // | |||||
| // RAII over the owned thread: | |||||
| // creates the thread on construction. | |||||
| // stops and joins the thread on destruction (if the thread is executing a callback, wait for it to finish first). | |||||
| #include <chrono> | |||||
| #include <condition_variable> | |||||
| #include <functional> | |||||
| #include <mutex> | |||||
| #include <thread> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class SPDLOG_API periodic_worker | |||||
| { | |||||
| public: | |||||
| template<typename Rep, typename Period> | |||||
| periodic_worker(const std::function<void()>& callback_fun, std::chrono::duration<Rep, Period> interval) | |||||
| { | |||||
| active_ = (interval > std::chrono::duration<Rep, Period>::zero()); | |||||
| if (!active_) | |||||
| { | |||||
| return; | |||||
| } | |||||
| worker_thread_ = std::thread([this, callback_fun, interval]() | |||||
| { | |||||
| for (;;) | |||||
| { | |||||
| std::unique_lock<std::mutex> lock(this->mutex_); | |||||
| if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) | |||||
| { | |||||
| return; // active_ == false, so exit this thread | |||||
| } | |||||
| callback_fun(); | |||||
| } }); | |||||
| } | |||||
| periodic_worker(const periodic_worker&) = delete; | |||||
| periodic_worker& operator=(const periodic_worker&) = delete; | |||||
| // stop the worker thread and join it | |||||
| ~periodic_worker(); | |||||
| private: | |||||
| bool active_; | |||||
| std::thread worker_thread_; | |||||
| std::mutex mutex_; | |||||
| std::condition_variable cv_; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "periodic_worker-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,307 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/registry.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/periodic_worker.h> | |||||
| #include <spdlog/logger.h> | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
| // support for the default stdout color logger | |||||
| #ifdef _WIN32 | |||||
| #include <spdlog/sinks/wincolor_sink.h> | |||||
| #else | |||||
| #include <spdlog/sinks/ansicolor_sink.h> | |||||
| #endif | |||||
| #endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
| #include <chrono> | |||||
| #include <functional> | |||||
| #include <memory> | |||||
| #include <string> | |||||
| #include <unordered_map> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE registry::registry() : | |||||
| formatter_(new pattern_formatter()) | |||||
| { | |||||
| #ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
| // create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). | |||||
| #ifdef _WIN32 | |||||
| auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>(); | |||||
| #else | |||||
| auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); | |||||
| #endif | |||||
| const char* default_logger_name = ""; | |||||
| default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); | |||||
| loggers_[default_logger_name] = default_logger_; | |||||
| #endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||||
| } | |||||
| SPDLOG_INLINE registry::~registry() = default; | |||||
| SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| register_logger_(std::move(new_logger)); | |||||
| } | |||||
| SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| new_logger->set_formatter(formatter_->clone()); | |||||
| if (err_handler_) | |||||
| { | |||||
| new_logger->set_error_handler(err_handler_); | |||||
| } | |||||
| // set new level according to previously configured level or default level | |||||
| auto it = log_levels_.find(new_logger->name()); | |||||
| auto new_level = it != log_levels_.end() ? it->second : global_log_level_; | |||||
| new_logger->set_level(new_level); | |||||
| new_logger->flush_on(flush_level_); | |||||
| if (backtrace_n_messages_ > 0) | |||||
| { | |||||
| new_logger->enable_backtrace(backtrace_n_messages_); | |||||
| } | |||||
| if (automatic_registration_) | |||||
| { | |||||
| register_logger_(std::move(new_logger)); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE std::shared_ptr<logger> registry::get(const std::string& logger_name) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| auto found = loggers_.find(logger_name); | |||||
| return found == loggers_.end() ? nullptr : found->second; | |||||
| } | |||||
| SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| return default_logger_; | |||||
| } | |||||
| // Return raw ptr to the default logger. | |||||
| // To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
| // This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
| // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
| SPDLOG_INLINE logger* registry::get_default_raw() | |||||
| { | |||||
| return default_logger_.get(); | |||||
| } | |||||
| // set default logger. | |||||
| // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
| SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| // remove previous default logger from the map | |||||
| if (default_logger_ != nullptr) | |||||
| { | |||||
| loggers_.erase(default_logger_->name()); | |||||
| } | |||||
| if (new_default_logger != nullptr) | |||||
| { | |||||
| loggers_[new_default_logger->name()] = new_default_logger; | |||||
| } | |||||
| default_logger_ = std::move(new_default_logger); | |||||
| } | |||||
| SPDLOG_INLINE void registry::set_tp(std::shared_ptr<thread_pool> tp) | |||||
| { | |||||
| std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
| tp_ = std::move(tp); | |||||
| } | |||||
| SPDLOG_INLINE std::shared_ptr<thread_pool> registry::get_tp() | |||||
| { | |||||
| std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
| return tp_; | |||||
| } | |||||
| // Set global formatter. Each sink in each logger will get a clone of this object | |||||
| SPDLOG_INLINE void registry::set_formatter(std::unique_ptr<formatter> formatter) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| formatter_ = std::move(formatter); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->set_formatter(formatter_->clone()); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| backtrace_n_messages_ = n_messages; | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->enable_backtrace(n_messages); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::disable_backtrace() | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| backtrace_n_messages_ = 0; | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->disable_backtrace(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::set_level(level::level_enum log_level) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->set_level(log_level); | |||||
| } | |||||
| global_log_level_ = log_level; | |||||
| } | |||||
| SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->flush_on(log_level); | |||||
| } | |||||
| flush_level_ = log_level; | |||||
| } | |||||
| SPDLOG_INLINE void registry::set_error_handler(err_handler handler) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->set_error_handler(handler); | |||||
| } | |||||
| err_handler_ = std::move(handler); | |||||
| } | |||||
| SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)>& fun) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| fun(l.second); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::flush_all() | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| for (auto& l : loggers_) | |||||
| { | |||||
| l.second->flush(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::drop(const std::string& logger_name) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| loggers_.erase(logger_name); | |||||
| if (default_logger_ && default_logger_->name() == logger_name) | |||||
| { | |||||
| default_logger_.reset(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::drop_all() | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| loggers_.clear(); | |||||
| default_logger_.reset(); | |||||
| } | |||||
| // clean all resources and threads started by the registry | |||||
| SPDLOG_INLINE void registry::shutdown() | |||||
| { | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
| periodic_flusher_.reset(); | |||||
| } | |||||
| drop_all(); | |||||
| { | |||||
| std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||||
| tp_.reset(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE std::recursive_mutex& registry::tp_mutex() | |||||
| { | |||||
| return tp_mutex_; | |||||
| } | |||||
| SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registration) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| automatic_registration_ = automatic_registration; | |||||
| } | |||||
| SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum* global_level) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||||
| log_levels_ = std::move(levels); | |||||
| auto global_level_requested = global_level != nullptr; | |||||
| global_log_level_ = global_level_requested ? *global_level : global_log_level_; | |||||
| for (auto& logger : loggers_) | |||||
| { | |||||
| auto logger_entry = log_levels_.find(logger.first); | |||||
| if (logger_entry != log_levels_.end()) | |||||
| { | |||||
| logger.second->set_level(logger_entry->second); | |||||
| } | |||||
| else if (global_level_requested) | |||||
| { | |||||
| logger.second->set_level(*global_level); | |||||
| } | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE registry& registry::instance() | |||||
| { | |||||
| static registry s_instance; | |||||
| return s_instance; | |||||
| } | |||||
| SPDLOG_INLINE void registry::throw_if_exists_(const std::string& logger_name) | |||||
| { | |||||
| if (loggers_.find(logger_name) != loggers_.end()) | |||||
| { | |||||
| throw_spdlog_ex("logger with name '" + logger_name + "' already exists"); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) | |||||
| { | |||||
| auto logger_name = new_logger->name(); | |||||
| throw_if_exists_(logger_name); | |||||
| loggers_[logger_name] = std::move(new_logger); | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,124 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // Loggers registry of unique name->logger pointer | |||||
| // An attempt to create a logger with an already existing name will result with spdlog_ex exception. | |||||
| // If user requests a non existing logger, nullptr will be returned | |||||
| // This class is thread safe | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/periodic_worker.h> | |||||
| #include <chrono> | |||||
| #include <functional> | |||||
| #include <memory> | |||||
| #include <string> | |||||
| #include <unordered_map> | |||||
| #include <mutex> | |||||
| namespace spdlog | |||||
| { | |||||
| class logger; | |||||
| namespace details | |||||
| { | |||||
| class thread_pool; | |||||
| class SPDLOG_API registry | |||||
| { | |||||
| public: | |||||
| using log_levels = std::unordered_map<std::string, level::level_enum>; | |||||
| registry(const registry&) = delete; | |||||
| registry& operator=(const registry&) = delete; | |||||
| void register_logger(std::shared_ptr<logger> new_logger); | |||||
| void initialize_logger(std::shared_ptr<logger> new_logger); | |||||
| std::shared_ptr<logger> get(const std::string& logger_name); | |||||
| std::shared_ptr<logger> default_logger(); | |||||
| // Return raw ptr to the default logger. | |||||
| // To be used directly by the spdlog default api (e.g. spdlog::info) | |||||
| // This make the default API faster, but cannot be used concurrently with set_default_logger(). | |||||
| // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. | |||||
| logger* get_default_raw(); | |||||
| // set default logger. | |||||
| // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||||
| void set_default_logger(std::shared_ptr<logger> new_default_logger); | |||||
| void set_tp(std::shared_ptr<thread_pool> tp); | |||||
| std::shared_ptr<thread_pool> get_tp(); | |||||
| // Set global formatter. Each sink in each logger will get a clone of this object | |||||
| void set_formatter(std::unique_ptr<formatter> formatter); | |||||
| void enable_backtrace(size_t n_messages); | |||||
| void disable_backtrace(); | |||||
| void set_level(level::level_enum log_level); | |||||
| void flush_on(level::level_enum log_level); | |||||
| template<typename Rep, typename Period> | |||||
| void flush_every(std::chrono::duration<Rep, Period> interval) | |||||
| { | |||||
| std::lock_guard<std::mutex> lock(flusher_mutex_); | |||||
| auto clbk = [this]() | |||||
| { this->flush_all(); }; | |||||
| periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); | |||||
| } | |||||
| void set_error_handler(err_handler handler); | |||||
| void apply_all(const std::function<void(const std::shared_ptr<logger>)>& fun); | |||||
| void flush_all(); | |||||
| void drop(const std::string& logger_name); | |||||
| void drop_all(); | |||||
| // clean all resources and threads started by the registry | |||||
| void shutdown(); | |||||
| std::recursive_mutex& tp_mutex(); | |||||
| void set_automatic_registration(bool automatic_registration); | |||||
| // set levels for all existing/future loggers. global_level can be null if should not set. | |||||
| void set_levels(log_levels levels, level::level_enum* global_level); | |||||
| static registry& instance(); | |||||
| private: | |||||
| registry(); | |||||
| ~registry(); | |||||
| void throw_if_exists_(const std::string& logger_name); | |||||
| void register_logger_(std::shared_ptr<logger> new_logger); | |||||
| bool set_level_from_cfg_(logger* logger); | |||||
| std::mutex logger_map_mutex_, flusher_mutex_; | |||||
| std::recursive_mutex tp_mutex_; | |||||
| std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; | |||||
| log_levels log_levels_; | |||||
| std::unique_ptr<formatter> formatter_; | |||||
| spdlog::level::level_enum global_log_level_ = level::info; | |||||
| level::level_enum flush_level_ = level::off; | |||||
| err_handler err_handler_; | |||||
| std::shared_ptr<thread_pool> tp_; | |||||
| std::unique_ptr<periodic_worker> periodic_flusher_; | |||||
| std::shared_ptr<logger> default_logger_; | |||||
| bool automatic_registration_ = true; | |||||
| size_t backtrace_n_messages_ = 0; | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "registry-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,25 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include "registry.h" | |||||
| namespace spdlog | |||||
| { | |||||
| // Default logger factory- creates synchronous loggers | |||||
| class logger; | |||||
| struct synchronous_factory | |||||
| { | |||||
| template<typename Sink, typename... SinkArgs> | |||||
| static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs&&... args) | |||||
| { | |||||
| auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||||
| auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink)); | |||||
| details::registry::instance().initialize_logger(new_logger); | |||||
| return new_logger; | |||||
| } | |||||
| }; | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,162 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #define WIN32_LEAN_AND_MEAN | |||||
| // tcp client helper | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <winsock2.h> | |||||
| #include <windows.h> | |||||
| #include <ws2tcpip.h> | |||||
| #include <stdlib.h> | |||||
| #include <stdio.h> | |||||
| #include <string> | |||||
| #pragma comment(lib, "Ws2_32.lib") | |||||
| #pragma comment(lib, "Mswsock.lib") | |||||
| #pragma comment(lib, "AdvApi32.lib") | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class tcp_client | |||||
| { | |||||
| SOCKET socket_ = INVALID_SOCKET; | |||||
| static void init_winsock_() | |||||
| { | |||||
| WSADATA wsaData; | |||||
| auto rv = WSAStartup(MAKEWORD(2, 2), &wsaData); | |||||
| if (rv != 0) | |||||
| { | |||||
| throw_winsock_error_("WSAStartup failed", ::WSAGetLastError()); | |||||
| } | |||||
| } | |||||
| static void throw_winsock_error_(const std::string& msg, int last_error) | |||||
| { | |||||
| char buf[512]; | |||||
| ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL); | |||||
| throw_spdlog_ex(fmt_lib::format("tcp_sink - {}: {}", msg, buf)); | |||||
| } | |||||
| public: | |||||
| tcp_client() | |||||
| { | |||||
| init_winsock_(); | |||||
| } | |||||
| ~tcp_client() | |||||
| { | |||||
| close(); | |||||
| ::WSACleanup(); | |||||
| } | |||||
| bool is_connected() const | |||||
| { | |||||
| return socket_ != INVALID_SOCKET; | |||||
| } | |||||
| void close() | |||||
| { | |||||
| ::closesocket(socket_); | |||||
| socket_ = INVALID_SOCKET; | |||||
| } | |||||
| SOCKET fd() const | |||||
| { | |||||
| return socket_; | |||||
| } | |||||
| // try to connect or throw on failure | |||||
| void connect(const std::string& host, int port) | |||||
| { | |||||
| if (is_connected()) | |||||
| { | |||||
| close(); | |||||
| } | |||||
| struct addrinfo hints | |||||
| { | |||||
| }; | |||||
| ZeroMemory(&hints, sizeof(hints)); | |||||
| hints.ai_family = AF_INET; // IPv4 | |||||
| hints.ai_socktype = SOCK_STREAM; // TCP | |||||
| hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value | |||||
| hints.ai_protocol = 0; | |||||
| auto port_str = std::to_string(port); | |||||
| struct addrinfo* addrinfo_result; | |||||
| auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); | |||||
| int last_error = 0; | |||||
| if (rv != 0) | |||||
| { | |||||
| last_error = ::WSAGetLastError(); | |||||
| WSACleanup(); | |||||
| throw_winsock_error_("getaddrinfo failed", last_error); | |||||
| } | |||||
| // Try each address until we successfully connect(2). | |||||
| for (auto* rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) | |||||
| { | |||||
| socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); | |||||
| if (socket_ == INVALID_SOCKET) | |||||
| { | |||||
| last_error = ::WSAGetLastError(); | |||||
| WSACleanup(); | |||||
| continue; | |||||
| } | |||||
| if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) | |||||
| { | |||||
| break; | |||||
| } | |||||
| else | |||||
| { | |||||
| last_error = ::WSAGetLastError(); | |||||
| close(); | |||||
| } | |||||
| } | |||||
| ::freeaddrinfo(addrinfo_result); | |||||
| if (socket_ == INVALID_SOCKET) | |||||
| { | |||||
| WSACleanup(); | |||||
| throw_winsock_error_("connect failed", last_error); | |||||
| } | |||||
| // set TCP_NODELAY | |||||
| int enable_flag = 1; | |||||
| ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&enable_flag), sizeof(enable_flag)); | |||||
| } | |||||
| // Send exactly n_bytes of the given data. | |||||
| // On error close the connection and throw. | |||||
| void send(const char* data, size_t n_bytes) | |||||
| { | |||||
| size_t bytes_sent = 0; | |||||
| while (bytes_sent < n_bytes) | |||||
| { | |||||
| const int send_flags = 0; | |||||
| auto write_result = ::send(socket_, data + bytes_sent, (int)(n_bytes - bytes_sent), send_flags); | |||||
| if (write_result == SOCKET_ERROR) | |||||
| { | |||||
| int last_error = ::WSAGetLastError(); | |||||
| close(); | |||||
| throw_winsock_error_("send failed", last_error); | |||||
| } | |||||
| if (write_result == 0) // (probably should not happen but in any case..) | |||||
| { | |||||
| break; | |||||
| } | |||||
| bytes_sent += static_cast<size_t>(write_result); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,149 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifdef _WIN32 | |||||
| #error include tcp_client-windows.h instead | |||||
| #endif | |||||
| // tcp client helper | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <sys/socket.h> | |||||
| #include <arpa/inet.h> | |||||
| #include <unistd.h> | |||||
| #include <netdb.h> | |||||
| #include <netinet/tcp.h> | |||||
| #include <netinet/in.h> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class tcp_client | |||||
| { | |||||
| int socket_ = -1; | |||||
| public: | |||||
| bool is_connected() const | |||||
| { | |||||
| return socket_ != -1; | |||||
| } | |||||
| void close() | |||||
| { | |||||
| if (is_connected()) | |||||
| { | |||||
| ::close(socket_); | |||||
| socket_ = -1; | |||||
| } | |||||
| } | |||||
| int fd() const | |||||
| { | |||||
| return socket_; | |||||
| } | |||||
| ~tcp_client() | |||||
| { | |||||
| close(); | |||||
| } | |||||
| // try to connect or throw on failure | |||||
| void connect(const std::string& host, int port) | |||||
| { | |||||
| close(); | |||||
| struct addrinfo hints | |||||
| { | |||||
| }; | |||||
| memset(&hints, 0, sizeof(struct addrinfo)); | |||||
| hints.ai_family = AF_INET; // IPv4 | |||||
| hints.ai_socktype = SOCK_STREAM; // TCP | |||||
| hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value | |||||
| hints.ai_protocol = 0; | |||||
| auto port_str = std::to_string(port); | |||||
| struct addrinfo* addrinfo_result; | |||||
| auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); | |||||
| if (rv != 0) | |||||
| { | |||||
| throw_spdlog_ex(fmt_lib::format("::getaddrinfo failed: {}", gai_strerror(rv))); | |||||
| } | |||||
| // Try each address until we successfully connect(2). | |||||
| int last_errno = 0; | |||||
| for (auto* rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) | |||||
| { | |||||
| #if defined(SOCK_CLOEXEC) | |||||
| const int flags = SOCK_CLOEXEC; | |||||
| #else | |||||
| const int flags = 0; | |||||
| #endif | |||||
| socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol); | |||||
| if (socket_ == -1) | |||||
| { | |||||
| last_errno = errno; | |||||
| continue; | |||||
| } | |||||
| rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen); | |||||
| if (rv == 0) | |||||
| { | |||||
| break; | |||||
| } | |||||
| last_errno = errno; | |||||
| ::close(socket_); | |||||
| socket_ = -1; | |||||
| } | |||||
| ::freeaddrinfo(addrinfo_result); | |||||
| if (socket_ == -1) | |||||
| { | |||||
| throw_spdlog_ex("::connect failed", last_errno); | |||||
| } | |||||
| // set TCP_NODELAY | |||||
| int enable_flag = 1; | |||||
| ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&enable_flag), sizeof(enable_flag)); | |||||
| // prevent sigpipe on systems where MSG_NOSIGNAL is not available | |||||
| #if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) | |||||
| ::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&enable_flag), sizeof(enable_flag)); | |||||
| #endif | |||||
| #if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) | |||||
| #error "tcp_sink would raise SIGPIPE since niether SO_NOSIGPIPE nor MSG_NOSIGNAL are available" | |||||
| #endif | |||||
| } | |||||
| // Send exactly n_bytes of the given data. | |||||
| // On error close the connection and throw. | |||||
| void send(const char* data, size_t n_bytes) | |||||
| { | |||||
| size_t bytes_sent = 0; | |||||
| while (bytes_sent < n_bytes) | |||||
| { | |||||
| #if defined(MSG_NOSIGNAL) | |||||
| const int send_flags = MSG_NOSIGNAL; | |||||
| #else | |||||
| const int send_flags = 0; | |||||
| #endif | |||||
| auto write_result = ::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags); | |||||
| if (write_result < 0) | |||||
| { | |||||
| close(); | |||||
| throw_spdlog_ex("write(2) failed", errno); | |||||
| } | |||||
| if (write_result == 0) // (probably should not happen but in any case..) | |||||
| { | |||||
| break; | |||||
| } | |||||
| bytes_sent += static_cast<size_t>(write_result); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,149 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/details/thread_pool.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <cassert> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| SPDLOG_INLINE thread_pool::thread_pool( | |||||
| size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start, std::function<void()> on_thread_stop | |||||
| ) : | |||||
| q_(q_max_items) | |||||
| { | |||||
| if (threads_n == 0 || threads_n > 1000) | |||||
| { | |||||
| throw_spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " | |||||
| "range is 1-1000)"); | |||||
| } | |||||
| for (size_t i = 0; i < threads_n; i++) | |||||
| { | |||||
| threads_.emplace_back([this, on_thread_start, on_thread_stop] | |||||
| { | |||||
| on_thread_start(); | |||||
| this->thread_pool::worker_loop_(); | |||||
| on_thread_stop(); }); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start) : | |||||
| thread_pool(q_max_items, threads_n, on_thread_start, [] {}) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) : | |||||
| thread_pool( | |||||
| q_max_items, threads_n, [] {}, [] {} | |||||
| ) | |||||
| { | |||||
| } | |||||
| // message all threads to terminate gracefully join them | |||||
| SPDLOG_INLINE thread_pool::~thread_pool() | |||||
| { | |||||
| SPDLOG_TRY | |||||
| { | |||||
| for (size_t i = 0; i < threads_.size(); i++) | |||||
| { | |||||
| post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); | |||||
| } | |||||
| for (auto& t : threads_) | |||||
| { | |||||
| t.join(); | |||||
| } | |||||
| } | |||||
| SPDLOG_CATCH_STD | |||||
| } | |||||
| void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr&& worker_ptr, const details::log_msg& msg, async_overflow_policy overflow_policy) | |||||
| { | |||||
| async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg); | |||||
| post_async_msg_(std::move(async_m), overflow_policy); | |||||
| } | |||||
| void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr&& worker_ptr, async_overflow_policy overflow_policy) | |||||
| { | |||||
| post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); | |||||
| } | |||||
| size_t SPDLOG_INLINE thread_pool::overrun_counter() | |||||
| { | |||||
| return q_.overrun_counter(); | |||||
| } | |||||
| void SPDLOG_INLINE thread_pool::reset_overrun_counter() | |||||
| { | |||||
| q_.reset_overrun_counter(); | |||||
| } | |||||
| size_t SPDLOG_INLINE thread_pool::queue_size() | |||||
| { | |||||
| return q_.size(); | |||||
| } | |||||
| void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg&& new_msg, async_overflow_policy overflow_policy) | |||||
| { | |||||
| if (overflow_policy == async_overflow_policy::block) | |||||
| { | |||||
| q_.enqueue(std::move(new_msg)); | |||||
| } | |||||
| else | |||||
| { | |||||
| q_.enqueue_nowait(std::move(new_msg)); | |||||
| } | |||||
| } | |||||
| void SPDLOG_INLINE thread_pool::worker_loop_() | |||||
| { | |||||
| while (process_next_msg_()) | |||||
| { | |||||
| } | |||||
| } | |||||
| // process next message in the queue | |||||
| // return true if this thread should still be active (while no terminate msg | |||||
| // was received) | |||||
| bool SPDLOG_INLINE thread_pool::process_next_msg_() | |||||
| { | |||||
| async_msg incoming_async_msg; | |||||
| q_.dequeue(incoming_async_msg); | |||||
| switch (incoming_async_msg.msg_type) | |||||
| { | |||||
| case async_msg_type::log: | |||||
| { | |||||
| incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg); | |||||
| return true; | |||||
| } | |||||
| case async_msg_type::flush: | |||||
| { | |||||
| incoming_async_msg.worker_ptr->backend_flush_(); | |||||
| return true; | |||||
| } | |||||
| case async_msg_type::terminate: | |||||
| { | |||||
| return false; | |||||
| } | |||||
| default: | |||||
| { | |||||
| assert(false); | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,128 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/log_msg_buffer.h> | |||||
| #include <spdlog/details/mpmc_blocking_q.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <chrono> | |||||
| #include <memory> | |||||
| #include <thread> | |||||
| #include <vector> | |||||
| #include <functional> | |||||
| namespace spdlog | |||||
| { | |||||
| class async_logger; | |||||
| namespace details | |||||
| { | |||||
| using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; | |||||
| enum class async_msg_type | |||||
| { | |||||
| log, | |||||
| flush, | |||||
| terminate | |||||
| }; | |||||
| // Async msg to move to/from the queue | |||||
| // Movable only. should never be copied | |||||
| struct async_msg : log_msg_buffer | |||||
| { | |||||
| async_msg_type msg_type{async_msg_type::log}; | |||||
| async_logger_ptr worker_ptr; | |||||
| async_msg() = default; | |||||
| ~async_msg() = default; | |||||
| // should only be moved in or out of the queue.. | |||||
| async_msg(const async_msg&) = delete; | |||||
| // support for vs2013 move | |||||
| #if defined(_MSC_VER) && _MSC_VER <= 1800 | |||||
| async_msg(async_msg&& other) : | |||||
| log_msg_buffer(std::move(other)), | |||||
| msg_type(other.msg_type), | |||||
| worker_ptr(std::move(other.worker_ptr)) | |||||
| { | |||||
| } | |||||
| async_msg& operator=(async_msg&& other) | |||||
| { | |||||
| *static_cast<log_msg_buffer*>(this) = std::move(other); | |||||
| msg_type = other.msg_type; | |||||
| worker_ptr = std::move(other.worker_ptr); | |||||
| return *this; | |||||
| } | |||||
| #else // (_MSC_VER) && _MSC_VER <= 1800 | |||||
| async_msg(async_msg&&) = default; | |||||
| async_msg& operator=(async_msg&&) = default; | |||||
| #endif | |||||
| // construct from log_msg with given type | |||||
| async_msg(async_logger_ptr&& worker, async_msg_type the_type, const details::log_msg& m) : | |||||
| log_msg_buffer{m}, | |||||
| msg_type{the_type}, | |||||
| worker_ptr{std::move(worker)} | |||||
| { | |||||
| } | |||||
| async_msg(async_logger_ptr&& worker, async_msg_type the_type) : | |||||
| log_msg_buffer{}, | |||||
| msg_type{the_type}, | |||||
| worker_ptr{std::move(worker)} | |||||
| { | |||||
| } | |||||
| explicit async_msg(async_msg_type the_type) : | |||||
| async_msg{nullptr, the_type} | |||||
| { | |||||
| } | |||||
| }; | |||||
| class SPDLOG_API thread_pool | |||||
| { | |||||
| public: | |||||
| using item_type = async_msg; | |||||
| using q_type = details::mpmc_blocking_queue<item_type>; | |||||
| thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start, std::function<void()> on_thread_stop); | |||||
| thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start); | |||||
| thread_pool(size_t q_max_items, size_t threads_n); | |||||
| // message all threads to terminate gracefully and join them | |||||
| ~thread_pool(); | |||||
| thread_pool(const thread_pool&) = delete; | |||||
| thread_pool& operator=(thread_pool&&) = delete; | |||||
| void post_log(async_logger_ptr&& worker_ptr, const details::log_msg& msg, async_overflow_policy overflow_policy); | |||||
| void post_flush(async_logger_ptr&& worker_ptr, async_overflow_policy overflow_policy); | |||||
| size_t overrun_counter(); | |||||
| void reset_overrun_counter(); | |||||
| size_t queue_size(); | |||||
| private: | |||||
| q_type q_; | |||||
| std::vector<std::thread> threads_; | |||||
| void post_async_msg_(async_msg&& new_msg, async_overflow_policy overflow_policy); | |||||
| void worker_loop_(); | |||||
| // process next message in the queue | |||||
| // return true if this thread should still be active (while no terminate msg | |||||
| // was received) | |||||
| bool process_next_msg_(); | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "thread_pool-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,112 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // Helper RAII over winsock udp client socket. | |||||
| // Will throw on construction if socket creation failed. | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/details/windows_include.h> | |||||
| #include <winsock2.h> | |||||
| #include <ws2tcpip.h> | |||||
| #include <stdlib.h> | |||||
| #include <stdio.h> | |||||
| #include <string> | |||||
| #pragma comment(lib, "Ws2_32.lib") | |||||
| #pragma comment(lib, "Mswsock.lib") | |||||
| #pragma comment(lib, "AdvApi32.lib") | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class udp_client | |||||
| { | |||||
| static constexpr int TX_BUFFER_SIZE = 1024 * 10; | |||||
| SOCKET socket_ = INVALID_SOCKET; | |||||
| sockaddr_in addr_ = {0}; | |||||
| static void init_winsock_() | |||||
| { | |||||
| WSADATA wsaData; | |||||
| auto rv = ::WSAStartup(MAKEWORD(2, 2), &wsaData); | |||||
| if (rv != 0) | |||||
| { | |||||
| throw_winsock_error_("WSAStartup failed", ::WSAGetLastError()); | |||||
| } | |||||
| } | |||||
| static void throw_winsock_error_(const std::string& msg, int last_error) | |||||
| { | |||||
| char buf[512]; | |||||
| ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL); | |||||
| throw_spdlog_ex(fmt_lib::format("udp_sink - {}: {}", msg, buf)); | |||||
| } | |||||
| void cleanup_() | |||||
| { | |||||
| if (socket_ != INVALID_SOCKET) | |||||
| { | |||||
| ::closesocket(socket_); | |||||
| } | |||||
| socket_ = INVALID_SOCKET; | |||||
| ::WSACleanup(); | |||||
| } | |||||
| public: | |||||
| udp_client(const std::string& host, uint16_t port) | |||||
| { | |||||
| init_winsock_(); | |||||
| addr_.sin_family = PF_INET; | |||||
| addr_.sin_port = htons(port); | |||||
| addr_.sin_addr.s_addr = INADDR_ANY; | |||||
| if (InetPtonA(PF_INET, host.c_str(), &addr_.sin_addr.s_addr) != 1) | |||||
| { | |||||
| int last_error = ::WSAGetLastError(); | |||||
| ::WSACleanup(); | |||||
| throw_winsock_error_("error: Invalid address!", last_error); | |||||
| } | |||||
| socket_ = ::socket(PF_INET, SOCK_DGRAM, 0); | |||||
| if (socket_ == INVALID_SOCKET) | |||||
| { | |||||
| int last_error = ::WSAGetLastError(); | |||||
| ::WSACleanup(); | |||||
| throw_winsock_error_("error: Create Socket failed", last_error); | |||||
| } | |||||
| int option_value = TX_BUFFER_SIZE; | |||||
| if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&option_value), sizeof(option_value)) < 0) | |||||
| { | |||||
| int last_error = ::WSAGetLastError(); | |||||
| cleanup_(); | |||||
| throw_winsock_error_("error: setsockopt(SO_SNDBUF) Failed!", last_error); | |||||
| } | |||||
| } | |||||
| ~udp_client() | |||||
| { | |||||
| cleanup_(); | |||||
| } | |||||
| SOCKET fd() const | |||||
| { | |||||
| return socket_; | |||||
| } | |||||
| void send(const char* data, size_t n_bytes) | |||||
| { | |||||
| socklen_t tolen = sizeof(struct sockaddr); | |||||
| if (::sendto(socket_, data, static_cast<int>(n_bytes), 0, (struct sockaddr*)&addr_, tolen) == -1) | |||||
| { | |||||
| throw_spdlog_ex("sendto(2) failed", errno); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,96 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // Helper RAII over unix udp client socket. | |||||
| // Will throw on construction if the socket creation failed. | |||||
| #ifdef _WIN32 | |||||
| #error "include udp_client-windows.h instead" | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <sys/socket.h> | |||||
| #include <netinet/in.h> | |||||
| #include <arpa/inet.h> | |||||
| #include <unistd.h> | |||||
| #include <netdb.h> | |||||
| #include <netinet/udp.h> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| class udp_client | |||||
| { | |||||
| static constexpr int TX_BUFFER_SIZE = 1024 * 10; | |||||
| int socket_ = -1; | |||||
| struct sockaddr_in sockAddr_; | |||||
| void cleanup_() | |||||
| { | |||||
| if (socket_ != -1) | |||||
| { | |||||
| ::close(socket_); | |||||
| socket_ = -1; | |||||
| } | |||||
| } | |||||
| public: | |||||
| udp_client(const std::string& host, uint16_t port) | |||||
| { | |||||
| socket_ = ::socket(PF_INET, SOCK_DGRAM, 0); | |||||
| if (socket_ < 0) | |||||
| { | |||||
| throw_spdlog_ex("error: Create Socket Failed!"); | |||||
| } | |||||
| int option_value = TX_BUFFER_SIZE; | |||||
| if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&option_value), sizeof(option_value)) < 0) | |||||
| { | |||||
| cleanup_(); | |||||
| throw_spdlog_ex("error: setsockopt(SO_SNDBUF) Failed!"); | |||||
| } | |||||
| sockAddr_.sin_family = AF_INET; | |||||
| sockAddr_.sin_port = htons(port); | |||||
| if (::inet_aton(host.c_str(), &sockAddr_.sin_addr) == 0) | |||||
| { | |||||
| cleanup_(); | |||||
| throw_spdlog_ex("error: Invalid address!"); | |||||
| } | |||||
| ::memset(sockAddr_.sin_zero, 0x00, sizeof(sockAddr_.sin_zero)); | |||||
| } | |||||
| ~udp_client() | |||||
| { | |||||
| cleanup_(); | |||||
| } | |||||
| int fd() const | |||||
| { | |||||
| return socket_; | |||||
| } | |||||
| // Send exactly n_bytes of the given data. | |||||
| // On error close the connection and throw. | |||||
| void send(const char* data, size_t n_bytes) | |||||
| { | |||||
| ssize_t toslen = 0; | |||||
| socklen_t tolen = sizeof(struct sockaddr); | |||||
| if ((toslen = ::sendto(socket_, data, n_bytes, 0, (struct sockaddr*)&sockAddr_, tolen)) == -1) | |||||
| { | |||||
| throw_spdlog_ex("sendto(2) failed", errno); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace details | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,11 @@ | |||||
| #pragma once | |||||
| #ifndef NOMINMAX | |||||
| #define NOMINMAX // prevent windows redefining min/max | |||||
| #endif | |||||
| #ifndef WIN32_LEAN_AND_MEAN | |||||
| #define WIN32_LEAN_AND_MEAN | |||||
| #endif | |||||
| #include <windows.h> | |||||
| @@ -0,0 +1,251 @@ | |||||
| // | |||||
| // Copyright(c) 2015 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| #include <cctype> | |||||
| #include <spdlog/common.h> | |||||
| #if defined(__has_include) | |||||
| #if __has_include(<version>) | |||||
| #include <version> | |||||
| #endif | |||||
| #endif | |||||
| #if __cpp_lib_span >= 202002L | |||||
| #include <span> | |||||
| #endif | |||||
| // | |||||
| // Support for logging binary data as hex | |||||
| // format flags, any combination of the following: | |||||
| // {:X} - print in uppercase. | |||||
| // {:s} - don't separate each byte with space. | |||||
| // {:p} - don't print the position on each line start. | |||||
| // {:n} - don't split the output to lines. | |||||
| // {:a} - show ASCII if :n is not set | |||||
| // | |||||
| // Examples: | |||||
| // | |||||
| // std::vector<char> v(200, 0x0b); | |||||
| // logger->info("Some buffer {}", spdlog::to_hex(v)); | |||||
| // char buf[128]; | |||||
| // logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); | |||||
| // logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf), 16)); | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| template<typename It> | |||||
| class dump_info | |||||
| { | |||||
| public: | |||||
| dump_info(It range_begin, It range_end, size_t size_per_line) : | |||||
| begin_(range_begin), | |||||
| end_(range_end), | |||||
| size_per_line_(size_per_line) | |||||
| { | |||||
| } | |||||
| // do not use begin() and end() to avoid collision with fmt/ranges | |||||
| It get_begin() const | |||||
| { | |||||
| return begin_; | |||||
| } | |||||
| It get_end() const | |||||
| { | |||||
| return end_; | |||||
| } | |||||
| size_t size_per_line() const | |||||
| { | |||||
| return size_per_line_; | |||||
| } | |||||
| private: | |||||
| It begin_, end_; | |||||
| size_t size_per_line_; | |||||
| }; | |||||
| } // namespace details | |||||
| // create a dump_info that wraps the given container | |||||
| template<typename Container> | |||||
| inline details::dump_info<typename Container::const_iterator> to_hex(const Container& container, size_t size_per_line = 32) | |||||
| { | |||||
| static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); | |||||
| using Iter = typename Container::const_iterator; | |||||
| return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line); | |||||
| } | |||||
| #if __cpp_lib_span >= 202002L | |||||
| template<typename Value, size_t Extent> | |||||
| inline details::dump_info<typename std::span<Value, Extent>::iterator> to_hex( | |||||
| const std::span<Value, Extent>& container, size_t size_per_line = 32 | |||||
| ) | |||||
| { | |||||
| using Container = std::span<Value, Extent>; | |||||
| static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); | |||||
| using Iter = typename Container::iterator; | |||||
| return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line); | |||||
| } | |||||
| #endif | |||||
| // create dump_info from ranges | |||||
| template<typename It> | |||||
| inline details::dump_info<It> to_hex(const It range_begin, const It range_end, size_t size_per_line = 32) | |||||
| { | |||||
| return details::dump_info<It>(range_begin, range_end, size_per_line); | |||||
| } | |||||
| } // namespace spdlog | |||||
| namespace | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| std | |||||
| #else | |||||
| fmt | |||||
| #endif | |||||
| { | |||||
| template<typename T> | |||||
| struct formatter<spdlog::details::dump_info<T>, char> | |||||
| { | |||||
| const char delimiter = ' '; | |||||
| bool put_newlines = true; | |||||
| bool put_delimiters = true; | |||||
| bool use_uppercase = false; | |||||
| bool put_positions = true; // position on start of each line | |||||
| bool show_ascii = false; | |||||
| // parse the format string flags | |||||
| template<typename ParseContext> | |||||
| SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| auto it = ctx.begin(); | |||||
| while (it != ctx.end() && *it != '}') | |||||
| { | |||||
| switch (*it) | |||||
| { | |||||
| case 'X': | |||||
| use_uppercase = true; | |||||
| break; | |||||
| case 's': | |||||
| put_delimiters = false; | |||||
| break; | |||||
| case 'p': | |||||
| put_positions = false; | |||||
| break; | |||||
| case 'n': | |||||
| put_newlines = false; | |||||
| show_ascii = false; | |||||
| break; | |||||
| case 'a': | |||||
| if (put_newlines) | |||||
| { | |||||
| show_ascii = true; | |||||
| } | |||||
| break; | |||||
| } | |||||
| ++it; | |||||
| } | |||||
| return it; | |||||
| } | |||||
| // format the given bytes range as hex | |||||
| template<typename FormatContext, typename Container> | |||||
| auto format(const spdlog::details::dump_info<Container>& the_range, FormatContext& ctx) -> decltype(ctx.out()) | |||||
| { | |||||
| SPDLOG_CONSTEXPR const char* hex_upper = "0123456789ABCDEF"; | |||||
| SPDLOG_CONSTEXPR const char* hex_lower = "0123456789abcdef"; | |||||
| const char* hex_chars = use_uppercase ? hex_upper : hex_lower; | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000 | |||||
| auto inserter = ctx.begin(); | |||||
| #else | |||||
| auto inserter = ctx.out(); | |||||
| #endif | |||||
| int size_per_line = static_cast<int>(the_range.size_per_line()); | |||||
| auto start_of_line = the_range.get_begin(); | |||||
| for (auto i = the_range.get_begin(); i != the_range.get_end(); i++) | |||||
| { | |||||
| auto ch = static_cast<unsigned char>(*i); | |||||
| if (put_newlines && (i == the_range.get_begin() || i - start_of_line >= size_per_line)) | |||||
| { | |||||
| if (show_ascii && i != the_range.get_begin()) | |||||
| { | |||||
| *inserter++ = delimiter; | |||||
| *inserter++ = delimiter; | |||||
| for (auto j = start_of_line; j < i; j++) | |||||
| { | |||||
| auto pc = static_cast<unsigned char>(*j); | |||||
| *inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.'; | |||||
| } | |||||
| } | |||||
| put_newline(inserter, static_cast<size_t>(i - the_range.get_begin())); | |||||
| // put first byte without delimiter in front of it | |||||
| *inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
| *inserter++ = hex_chars[ch & 0x0f]; | |||||
| start_of_line = i; | |||||
| continue; | |||||
| } | |||||
| if (put_delimiters) | |||||
| { | |||||
| *inserter++ = delimiter; | |||||
| } | |||||
| *inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||||
| *inserter++ = hex_chars[ch & 0x0f]; | |||||
| } | |||||
| if (show_ascii) // add ascii to last line | |||||
| { | |||||
| if (the_range.get_end() - the_range.get_begin() > size_per_line) | |||||
| { | |||||
| auto blank_num = size_per_line - (the_range.get_end() - start_of_line); | |||||
| while (blank_num-- > 0) | |||||
| { | |||||
| *inserter++ = delimiter; | |||||
| *inserter++ = delimiter; | |||||
| if (put_delimiters) | |||||
| { | |||||
| *inserter++ = delimiter; | |||||
| } | |||||
| } | |||||
| } | |||||
| *inserter++ = delimiter; | |||||
| *inserter++ = delimiter; | |||||
| for (auto j = start_of_line; j != the_range.get_end(); j++) | |||||
| { | |||||
| auto pc = static_cast<unsigned char>(*j); | |||||
| *inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.'; | |||||
| } | |||||
| } | |||||
| return inserter; | |||||
| } | |||||
| // put newline(and position header) | |||||
| template<typename It> | |||||
| void put_newline(It inserter, std::size_t pos) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| *inserter++ = '\r'; | |||||
| #endif | |||||
| *inserter++ = '\n'; | |||||
| if (put_positions) | |||||
| { | |||||
| spdlog::fmt_lib::format_to(inserter, SPDLOG_FMT_STRING("{:04X}: "), pos); | |||||
| } | |||||
| } | |||||
| }; | |||||
| } // namespace std | |||||
| @@ -0,0 +1,273 @@ | |||||
| // Formatting library for C++ - dynamic format arguments | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_ARGS_H_ | |||||
| #define FMT_ARGS_H_ | |||||
| #include <functional> // std::reference_wrapper | |||||
| #include <memory> // std::unique_ptr | |||||
| #include <vector> | |||||
| #include "core.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| namespace detail | |||||
| { | |||||
| template<typename T> | |||||
| struct is_reference_wrapper : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| const T& unwrap(const T& v) | |||||
| { | |||||
| return v; | |||||
| } | |||||
| template<typename T> | |||||
| const T& unwrap(const std::reference_wrapper<T>& v) | |||||
| { | |||||
| return static_cast<const T&>(v); | |||||
| } | |||||
| class dynamic_arg_list | |||||
| { | |||||
| // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for | |||||
| // templates it doesn't complain about inability to deduce single translation | |||||
| // unit for placing vtable. So storage_node_base is made a fake template. | |||||
| template<typename = void> | |||||
| struct node | |||||
| { | |||||
| virtual ~node() = default; | |||||
| std::unique_ptr<node<>> next; | |||||
| }; | |||||
| template<typename T> | |||||
| struct typed_node : node<> | |||||
| { | |||||
| T value; | |||||
| template<typename Arg> | |||||
| FMT_CONSTEXPR typed_node(const Arg& arg) : | |||||
| value(arg) | |||||
| { | |||||
| } | |||||
| template<typename Char> | |||||
| FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg) : | |||||
| value(arg.data(), arg.size()) | |||||
| { | |||||
| } | |||||
| }; | |||||
| std::unique_ptr<node<>> head_; | |||||
| public: | |||||
| template<typename T, typename Arg> | |||||
| const T& push(const Arg& arg) | |||||
| { | |||||
| auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg)); | |||||
| auto& value = new_node->value; | |||||
| new_node->next = std::move(head_); | |||||
| head_ = std::move(new_node); | |||||
| return value; | |||||
| } | |||||
| }; | |||||
| } // namespace detail | |||||
| /** | |||||
| \rst | |||||
| A dynamic version of `fmt::format_arg_store`. | |||||
| It's equipped with a storage to potentially temporary objects which lifetimes | |||||
| could be shorter than the format arguments object. | |||||
| It can be implicitly converted into `~fmt::basic_format_args` for passing | |||||
| into type-erased formatting functions such as `~fmt::vformat`. | |||||
| \endrst | |||||
| */ | |||||
| template<typename Context> | |||||
| class dynamic_format_arg_store | |||||
| #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 | |||||
| // Workaround a GCC template argument substitution bug. | |||||
| : public basic_format_args<Context> | |||||
| #endif | |||||
| { | |||||
| private: | |||||
| using char_type = typename Context::char_type; | |||||
| template<typename T> | |||||
| struct need_copy | |||||
| { | |||||
| static constexpr detail::type mapped_type = | |||||
| detail::mapped_type_constant<T, Context>::value; | |||||
| enum | |||||
| { | |||||
| value = !(detail::is_reference_wrapper<T>::value || std::is_same<T, basic_string_view<char_type>>::value || std::is_same<T, detail::std_string_view<char_type>>::value || (mapped_type != detail::type::cstring_type && mapped_type != detail::type::string_type && mapped_type != detail::type::custom_type)) | |||||
| }; | |||||
| }; | |||||
| template<typename T> | |||||
| using stored_type = conditional_t< | |||||
| std::is_convertible<T, std::basic_string<char_type>>::value && | |||||
| !detail::is_reference_wrapper<T>::value, | |||||
| std::basic_string<char_type>, | |||||
| T>; | |||||
| // Storage of basic_format_arg must be contiguous. | |||||
| std::vector<basic_format_arg<Context>> data_; | |||||
| std::vector<detail::named_arg_info<char_type>> named_info_; | |||||
| // Storage of arguments not fitting into basic_format_arg must grow | |||||
| // without relocation because items in data_ refer to it. | |||||
| detail::dynamic_arg_list dynamic_args_; | |||||
| friend class basic_format_args<Context>; | |||||
| unsigned long long get_types() const | |||||
| { | |||||
| return detail::is_unpacked_bit | data_.size() | | |||||
| (named_info_.empty() ? 0ULL : static_cast<unsigned long long>(detail::has_named_args_bit)); | |||||
| } | |||||
| const basic_format_arg<Context>* data() const | |||||
| { | |||||
| return named_info_.empty() ? data_.data() : data_.data() + 1; | |||||
| } | |||||
| template<typename T> | |||||
| void emplace_arg(const T& arg) | |||||
| { | |||||
| data_.emplace_back(detail::make_arg<Context>(arg)); | |||||
| } | |||||
| template<typename T> | |||||
| void emplace_arg(const detail::named_arg<char_type, T>& arg) | |||||
| { | |||||
| if (named_info_.empty()) | |||||
| { | |||||
| constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; | |||||
| data_.insert(data_.begin(), {zero_ptr, 0}); | |||||
| } | |||||
| data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value))); | |||||
| auto pop_one = [](std::vector<basic_format_arg<Context>>* data) | |||||
| { | |||||
| data->pop_back(); | |||||
| }; | |||||
| std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> | |||||
| guard{&data_, pop_one}; | |||||
| named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); | |||||
| data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; | |||||
| guard.release(); | |||||
| } | |||||
| public: | |||||
| constexpr dynamic_format_arg_store() = default; | |||||
| /** | |||||
| \rst | |||||
| Adds an argument into the dynamic store for later passing to a formatting | |||||
| function. | |||||
| Note that custom types and string types (but not string views) are copied | |||||
| into the store dynamically allocating memory if necessary. | |||||
| **Example**:: | |||||
| fmt::dynamic_format_arg_store<fmt::format_context> store; | |||||
| store.push_back(42); | |||||
| store.push_back("abc"); | |||||
| store.push_back(1.5f); | |||||
| std::string result = fmt::vformat("{} and {} and {}", store); | |||||
| \endrst | |||||
| */ | |||||
| template<typename T> | |||||
| void push_back(const T& arg) | |||||
| { | |||||
| if (detail::const_check(need_copy<T>::value)) | |||||
| emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); | |||||
| else | |||||
| emplace_arg(detail::unwrap(arg)); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Adds a reference to the argument into the dynamic store for later passing to | |||||
| a formatting function. | |||||
| **Example**:: | |||||
| fmt::dynamic_format_arg_store<fmt::format_context> store; | |||||
| char band[] = "Rolling Stones"; | |||||
| store.push_back(std::cref(band)); | |||||
| band[9] = 'c'; // Changing str affects the output. | |||||
| std::string result = fmt::vformat("{}", store); | |||||
| // result == "Rolling Scones" | |||||
| \endrst | |||||
| */ | |||||
| template<typename T> | |||||
| void push_back(std::reference_wrapper<T> arg) | |||||
| { | |||||
| static_assert( | |||||
| need_copy<T>::value, | |||||
| "objects of built-in types and string views are always copied" | |||||
| ); | |||||
| emplace_arg(arg.get()); | |||||
| } | |||||
| /** | |||||
| Adds named argument into the dynamic store for later passing to a formatting | |||||
| function. ``std::reference_wrapper`` is supported to avoid copying of the | |||||
| argument. The name is always copied into the store. | |||||
| */ | |||||
| template<typename T> | |||||
| void push_back(const detail::named_arg<char_type, T>& arg) | |||||
| { | |||||
| const char_type* arg_name = | |||||
| dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); | |||||
| if (detail::const_check(need_copy<T>::value)) | |||||
| { | |||||
| emplace_arg( | |||||
| fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)) | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| emplace_arg(fmt::arg(arg_name, arg.value)); | |||||
| } | |||||
| } | |||||
| /** Erase all elements from the store */ | |||||
| void clear() | |||||
| { | |||||
| data_.clear(); | |||||
| named_info_.clear(); | |||||
| dynamic_args_ = detail::dynamic_arg_list(); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Reserves space to store at least *new_cap* arguments including | |||||
| *new_cap_named* named arguments. | |||||
| \endrst | |||||
| */ | |||||
| void reserve(size_t new_cap, size_t new_cap_named) | |||||
| { | |||||
| FMT_ASSERT(new_cap >= new_cap_named, "Set of arguments includes set of named arguments"); | |||||
| data_.reserve(new_cap); | |||||
| named_info_.reserve(new_cap_named); | |||||
| } | |||||
| }; | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_ARGS_H_ | |||||
| @@ -0,0 +1,763 @@ | |||||
| // Formatting library for C++ - color support | |||||
| // | |||||
| // Copyright (c) 2018 - present, Victor Zverovich and fmt contributors | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_COLOR_H_ | |||||
| #define FMT_COLOR_H_ | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| enum class color : uint32_t | |||||
| { | |||||
| alice_blue = 0xF0F8FF, // rgb(240,248,255) | |||||
| antique_white = 0xFAEBD7, // rgb(250,235,215) | |||||
| aqua = 0x00FFFF, // rgb(0,255,255) | |||||
| aquamarine = 0x7FFFD4, // rgb(127,255,212) | |||||
| azure = 0xF0FFFF, // rgb(240,255,255) | |||||
| beige = 0xF5F5DC, // rgb(245,245,220) | |||||
| bisque = 0xFFE4C4, // rgb(255,228,196) | |||||
| black = 0x000000, // rgb(0,0,0) | |||||
| blanched_almond = 0xFFEBCD, // rgb(255,235,205) | |||||
| blue = 0x0000FF, // rgb(0,0,255) | |||||
| blue_violet = 0x8A2BE2, // rgb(138,43,226) | |||||
| brown = 0xA52A2A, // rgb(165,42,42) | |||||
| burly_wood = 0xDEB887, // rgb(222,184,135) | |||||
| cadet_blue = 0x5F9EA0, // rgb(95,158,160) | |||||
| chartreuse = 0x7FFF00, // rgb(127,255,0) | |||||
| chocolate = 0xD2691E, // rgb(210,105,30) | |||||
| coral = 0xFF7F50, // rgb(255,127,80) | |||||
| cornflower_blue = 0x6495ED, // rgb(100,149,237) | |||||
| cornsilk = 0xFFF8DC, // rgb(255,248,220) | |||||
| crimson = 0xDC143C, // rgb(220,20,60) | |||||
| cyan = 0x00FFFF, // rgb(0,255,255) | |||||
| dark_blue = 0x00008B, // rgb(0,0,139) | |||||
| dark_cyan = 0x008B8B, // rgb(0,139,139) | |||||
| dark_golden_rod = 0xB8860B, // rgb(184,134,11) | |||||
| dark_gray = 0xA9A9A9, // rgb(169,169,169) | |||||
| dark_green = 0x006400, // rgb(0,100,0) | |||||
| dark_khaki = 0xBDB76B, // rgb(189,183,107) | |||||
| dark_magenta = 0x8B008B, // rgb(139,0,139) | |||||
| dark_olive_green = 0x556B2F, // rgb(85,107,47) | |||||
| dark_orange = 0xFF8C00, // rgb(255,140,0) | |||||
| dark_orchid = 0x9932CC, // rgb(153,50,204) | |||||
| dark_red = 0x8B0000, // rgb(139,0,0) | |||||
| dark_salmon = 0xE9967A, // rgb(233,150,122) | |||||
| dark_sea_green = 0x8FBC8F, // rgb(143,188,143) | |||||
| dark_slate_blue = 0x483D8B, // rgb(72,61,139) | |||||
| dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) | |||||
| dark_turquoise = 0x00CED1, // rgb(0,206,209) | |||||
| dark_violet = 0x9400D3, // rgb(148,0,211) | |||||
| deep_pink = 0xFF1493, // rgb(255,20,147) | |||||
| deep_sky_blue = 0x00BFFF, // rgb(0,191,255) | |||||
| dim_gray = 0x696969, // rgb(105,105,105) | |||||
| dodger_blue = 0x1E90FF, // rgb(30,144,255) | |||||
| fire_brick = 0xB22222, // rgb(178,34,34) | |||||
| floral_white = 0xFFFAF0, // rgb(255,250,240) | |||||
| forest_green = 0x228B22, // rgb(34,139,34) | |||||
| fuchsia = 0xFF00FF, // rgb(255,0,255) | |||||
| gainsboro = 0xDCDCDC, // rgb(220,220,220) | |||||
| ghost_white = 0xF8F8FF, // rgb(248,248,255) | |||||
| gold = 0xFFD700, // rgb(255,215,0) | |||||
| golden_rod = 0xDAA520, // rgb(218,165,32) | |||||
| gray = 0x808080, // rgb(128,128,128) | |||||
| green = 0x008000, // rgb(0,128,0) | |||||
| green_yellow = 0xADFF2F, // rgb(173,255,47) | |||||
| honey_dew = 0xF0FFF0, // rgb(240,255,240) | |||||
| hot_pink = 0xFF69B4, // rgb(255,105,180) | |||||
| indian_red = 0xCD5C5C, // rgb(205,92,92) | |||||
| indigo = 0x4B0082, // rgb(75,0,130) | |||||
| ivory = 0xFFFFF0, // rgb(255,255,240) | |||||
| khaki = 0xF0E68C, // rgb(240,230,140) | |||||
| lavender = 0xE6E6FA, // rgb(230,230,250) | |||||
| lavender_blush = 0xFFF0F5, // rgb(255,240,245) | |||||
| lawn_green = 0x7CFC00, // rgb(124,252,0) | |||||
| lemon_chiffon = 0xFFFACD, // rgb(255,250,205) | |||||
| light_blue = 0xADD8E6, // rgb(173,216,230) | |||||
| light_coral = 0xF08080, // rgb(240,128,128) | |||||
| light_cyan = 0xE0FFFF, // rgb(224,255,255) | |||||
| light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) | |||||
| light_gray = 0xD3D3D3, // rgb(211,211,211) | |||||
| light_green = 0x90EE90, // rgb(144,238,144) | |||||
| light_pink = 0xFFB6C1, // rgb(255,182,193) | |||||
| light_salmon = 0xFFA07A, // rgb(255,160,122) | |||||
| light_sea_green = 0x20B2AA, // rgb(32,178,170) | |||||
| light_sky_blue = 0x87CEFA, // rgb(135,206,250) | |||||
| light_slate_gray = 0x778899, // rgb(119,136,153) | |||||
| light_steel_blue = 0xB0C4DE, // rgb(176,196,222) | |||||
| light_yellow = 0xFFFFE0, // rgb(255,255,224) | |||||
| lime = 0x00FF00, // rgb(0,255,0) | |||||
| lime_green = 0x32CD32, // rgb(50,205,50) | |||||
| linen = 0xFAF0E6, // rgb(250,240,230) | |||||
| magenta = 0xFF00FF, // rgb(255,0,255) | |||||
| maroon = 0x800000, // rgb(128,0,0) | |||||
| medium_aquamarine = 0x66CDAA, // rgb(102,205,170) | |||||
| medium_blue = 0x0000CD, // rgb(0,0,205) | |||||
| medium_orchid = 0xBA55D3, // rgb(186,85,211) | |||||
| medium_purple = 0x9370DB, // rgb(147,112,219) | |||||
| medium_sea_green = 0x3CB371, // rgb(60,179,113) | |||||
| medium_slate_blue = 0x7B68EE, // rgb(123,104,238) | |||||
| medium_spring_green = 0x00FA9A, // rgb(0,250,154) | |||||
| medium_turquoise = 0x48D1CC, // rgb(72,209,204) | |||||
| medium_violet_red = 0xC71585, // rgb(199,21,133) | |||||
| midnight_blue = 0x191970, // rgb(25,25,112) | |||||
| mint_cream = 0xF5FFFA, // rgb(245,255,250) | |||||
| misty_rose = 0xFFE4E1, // rgb(255,228,225) | |||||
| moccasin = 0xFFE4B5, // rgb(255,228,181) | |||||
| navajo_white = 0xFFDEAD, // rgb(255,222,173) | |||||
| navy = 0x000080, // rgb(0,0,128) | |||||
| old_lace = 0xFDF5E6, // rgb(253,245,230) | |||||
| olive = 0x808000, // rgb(128,128,0) | |||||
| olive_drab = 0x6B8E23, // rgb(107,142,35) | |||||
| orange = 0xFFA500, // rgb(255,165,0) | |||||
| orange_red = 0xFF4500, // rgb(255,69,0) | |||||
| orchid = 0xDA70D6, // rgb(218,112,214) | |||||
| pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) | |||||
| pale_green = 0x98FB98, // rgb(152,251,152) | |||||
| pale_turquoise = 0xAFEEEE, // rgb(175,238,238) | |||||
| pale_violet_red = 0xDB7093, // rgb(219,112,147) | |||||
| papaya_whip = 0xFFEFD5, // rgb(255,239,213) | |||||
| peach_puff = 0xFFDAB9, // rgb(255,218,185) | |||||
| peru = 0xCD853F, // rgb(205,133,63) | |||||
| pink = 0xFFC0CB, // rgb(255,192,203) | |||||
| plum = 0xDDA0DD, // rgb(221,160,221) | |||||
| powder_blue = 0xB0E0E6, // rgb(176,224,230) | |||||
| purple = 0x800080, // rgb(128,0,128) | |||||
| rebecca_purple = 0x663399, // rgb(102,51,153) | |||||
| red = 0xFF0000, // rgb(255,0,0) | |||||
| rosy_brown = 0xBC8F8F, // rgb(188,143,143) | |||||
| royal_blue = 0x4169E1, // rgb(65,105,225) | |||||
| saddle_brown = 0x8B4513, // rgb(139,69,19) | |||||
| salmon = 0xFA8072, // rgb(250,128,114) | |||||
| sandy_brown = 0xF4A460, // rgb(244,164,96) | |||||
| sea_green = 0x2E8B57, // rgb(46,139,87) | |||||
| sea_shell = 0xFFF5EE, // rgb(255,245,238) | |||||
| sienna = 0xA0522D, // rgb(160,82,45) | |||||
| silver = 0xC0C0C0, // rgb(192,192,192) | |||||
| sky_blue = 0x87CEEB, // rgb(135,206,235) | |||||
| slate_blue = 0x6A5ACD, // rgb(106,90,205) | |||||
| slate_gray = 0x708090, // rgb(112,128,144) | |||||
| snow = 0xFFFAFA, // rgb(255,250,250) | |||||
| spring_green = 0x00FF7F, // rgb(0,255,127) | |||||
| steel_blue = 0x4682B4, // rgb(70,130,180) | |||||
| tan = 0xD2B48C, // rgb(210,180,140) | |||||
| teal = 0x008080, // rgb(0,128,128) | |||||
| thistle = 0xD8BFD8, // rgb(216,191,216) | |||||
| tomato = 0xFF6347, // rgb(255,99,71) | |||||
| turquoise = 0x40E0D0, // rgb(64,224,208) | |||||
| violet = 0xEE82EE, // rgb(238,130,238) | |||||
| wheat = 0xF5DEB3, // rgb(245,222,179) | |||||
| white = 0xFFFFFF, // rgb(255,255,255) | |||||
| white_smoke = 0xF5F5F5, // rgb(245,245,245) | |||||
| yellow = 0xFFFF00, // rgb(255,255,0) | |||||
| yellow_green = 0x9ACD32 // rgb(154,205,50) | |||||
| }; // enum class color | |||||
| enum class terminal_color : uint8_t | |||||
| { | |||||
| black = 30, | |||||
| red, | |||||
| green, | |||||
| yellow, | |||||
| blue, | |||||
| magenta, | |||||
| cyan, | |||||
| white, | |||||
| bright_black = 90, | |||||
| bright_red, | |||||
| bright_green, | |||||
| bright_yellow, | |||||
| bright_blue, | |||||
| bright_magenta, | |||||
| bright_cyan, | |||||
| bright_white | |||||
| }; | |||||
| enum class emphasis : uint8_t | |||||
| { | |||||
| bold = 1, | |||||
| faint = 1 << 1, | |||||
| italic = 1 << 2, | |||||
| underline = 1 << 3, | |||||
| blink = 1 << 4, | |||||
| reverse = 1 << 5, | |||||
| conceal = 1 << 6, | |||||
| strikethrough = 1 << 7, | |||||
| }; | |||||
| // rgb is a struct for red, green and blue colors. | |||||
| // Using the name "rgb" makes some editors show the color in a tooltip. | |||||
| struct rgb | |||||
| { | |||||
| FMT_CONSTEXPR rgb() : | |||||
| r(0), | |||||
| g(0), | |||||
| b(0) | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : | |||||
| r(r_), | |||||
| g(g_), | |||||
| b(b_) | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR rgb(uint32_t hex) : | |||||
| r((hex >> 16) & 0xFF), | |||||
| g((hex >> 8) & 0xFF), | |||||
| b(hex & 0xFF) | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR rgb(color hex) : | |||||
| r((uint32_t(hex) >> 16) & 0xFF), | |||||
| g((uint32_t(hex) >> 8) & 0xFF), | |||||
| b(uint32_t(hex) & 0xFF) | |||||
| { | |||||
| } | |||||
| uint8_t r; | |||||
| uint8_t g; | |||||
| uint8_t b; | |||||
| }; | |||||
| FMT_BEGIN_DETAIL_NAMESPACE | |||||
| // color is a struct of either a rgb color or a terminal color. | |||||
| struct color_type | |||||
| { | |||||
| FMT_CONSTEXPR color_type() noexcept : | |||||
| is_rgb(), | |||||
| value{} | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR color_type(color rgb_color) noexcept : | |||||
| is_rgb(true), | |||||
| value{} | |||||
| { | |||||
| value.rgb_color = static_cast<uint32_t>(rgb_color); | |||||
| } | |||||
| FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : | |||||
| is_rgb(true), | |||||
| value{} | |||||
| { | |||||
| value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | | |||||
| (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; | |||||
| } | |||||
| FMT_CONSTEXPR color_type(terminal_color term_color) noexcept | |||||
| : | |||||
| is_rgb(), | |||||
| value{} | |||||
| { | |||||
| value.term_color = static_cast<uint8_t>(term_color); | |||||
| } | |||||
| bool is_rgb; | |||||
| union color_union | |||||
| { | |||||
| uint8_t term_color; | |||||
| uint32_t rgb_color; | |||||
| } value; | |||||
| }; | |||||
| FMT_END_DETAIL_NAMESPACE | |||||
| /** A text style consisting of foreground and background colors and emphasis. */ | |||||
| class text_style | |||||
| { | |||||
| public: | |||||
| FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept | |||||
| : | |||||
| set_foreground_color(), | |||||
| set_background_color(), | |||||
| ems(em) | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) | |||||
| { | |||||
| if (!set_foreground_color) | |||||
| { | |||||
| set_foreground_color = rhs.set_foreground_color; | |||||
| foreground_color = rhs.foreground_color; | |||||
| } | |||||
| else if (rhs.set_foreground_color) | |||||
| { | |||||
| if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | |||||
| FMT_THROW(format_error("can't OR a terminal color")); | |||||
| foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; | |||||
| } | |||||
| if (!set_background_color) | |||||
| { | |||||
| set_background_color = rhs.set_background_color; | |||||
| background_color = rhs.background_color; | |||||
| } | |||||
| else if (rhs.set_background_color) | |||||
| { | |||||
| if (!background_color.is_rgb || !rhs.background_color.is_rgb) | |||||
| FMT_THROW(format_error("can't OR a terminal color")); | |||||
| background_color.value.rgb_color |= rhs.background_color.value.rgb_color; | |||||
| } | |||||
| ems = static_cast<emphasis>(static_cast<uint8_t>(ems) | static_cast<uint8_t>(rhs.ems)); | |||||
| return *this; | |||||
| } | |||||
| friend FMT_CONSTEXPR text_style operator|(text_style lhs, const text_style& rhs) | |||||
| { | |||||
| return lhs |= rhs; | |||||
| } | |||||
| FMT_CONSTEXPR bool has_foreground() const noexcept | |||||
| { | |||||
| return set_foreground_color; | |||||
| } | |||||
| FMT_CONSTEXPR bool has_background() const noexcept | |||||
| { | |||||
| return set_background_color; | |||||
| } | |||||
| FMT_CONSTEXPR bool has_emphasis() const noexcept | |||||
| { | |||||
| return static_cast<uint8_t>(ems) != 0; | |||||
| } | |||||
| FMT_CONSTEXPR detail::color_type get_foreground() const noexcept | |||||
| { | |||||
| FMT_ASSERT(has_foreground(), "no foreground specified for this style"); | |||||
| return foreground_color; | |||||
| } | |||||
| FMT_CONSTEXPR detail::color_type get_background() const noexcept | |||||
| { | |||||
| FMT_ASSERT(has_background(), "no background specified for this style"); | |||||
| return background_color; | |||||
| } | |||||
| FMT_CONSTEXPR emphasis get_emphasis() const noexcept | |||||
| { | |||||
| FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); | |||||
| return ems; | |||||
| } | |||||
| private: | |||||
| FMT_CONSTEXPR text_style(bool is_foreground, detail::color_type text_color) noexcept | |||||
| : | |||||
| set_foreground_color(), | |||||
| set_background_color(), | |||||
| ems() | |||||
| { | |||||
| if (is_foreground) | |||||
| { | |||||
| foreground_color = text_color; | |||||
| set_foreground_color = true; | |||||
| } | |||||
| else | |||||
| { | |||||
| background_color = text_color; | |||||
| set_background_color = true; | |||||
| } | |||||
| } | |||||
| friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept; | |||||
| friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept; | |||||
| detail::color_type foreground_color; | |||||
| detail::color_type background_color; | |||||
| bool set_foreground_color; | |||||
| bool set_background_color; | |||||
| emphasis ems; | |||||
| }; | |||||
| /** Creates a text style from the foreground (text) color. */ | |||||
| FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept | |||||
| { | |||||
| return text_style(true, foreground); | |||||
| } | |||||
| /** Creates a text style from the background color. */ | |||||
| FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept | |||||
| { | |||||
| return text_style(false, background); | |||||
| } | |||||
| FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept | |||||
| { | |||||
| return text_style(lhs) | rhs; | |||||
| } | |||||
| FMT_BEGIN_DETAIL_NAMESPACE | |||||
| template<typename Char> | |||||
| struct ansi_color_escape | |||||
| { | |||||
| FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, const char* esc) noexcept | |||||
| { | |||||
| // If we have a terminal color, we need to output another escape code | |||||
| // sequence. | |||||
| if (!text_color.is_rgb) | |||||
| { | |||||
| bool is_background = esc == string_view("\x1b[48;2;"); | |||||
| uint32_t value = text_color.value.term_color; | |||||
| // Background ASCII codes are the same as the foreground ones but with | |||||
| // 10 more. | |||||
| if (is_background) | |||||
| value += 10u; | |||||
| size_t index = 0; | |||||
| buffer[index++] = static_cast<Char>('\x1b'); | |||||
| buffer[index++] = static_cast<Char>('['); | |||||
| if (value >= 100u) | |||||
| { | |||||
| buffer[index++] = static_cast<Char>('1'); | |||||
| value %= 100u; | |||||
| } | |||||
| buffer[index++] = static_cast<Char>('0' + value / 10u); | |||||
| buffer[index++] = static_cast<Char>('0' + value % 10u); | |||||
| buffer[index++] = static_cast<Char>('m'); | |||||
| buffer[index++] = static_cast<Char>('\0'); | |||||
| return; | |||||
| } | |||||
| for (int i = 0; i < 7; i++) | |||||
| { | |||||
| buffer[i] = static_cast<Char>(esc[i]); | |||||
| } | |||||
| rgb color(text_color.value.rgb_color); | |||||
| to_esc(color.r, buffer + 7, ';'); | |||||
| to_esc(color.g, buffer + 11, ';'); | |||||
| to_esc(color.b, buffer + 15, 'm'); | |||||
| buffer[19] = static_cast<Char>(0); | |||||
| } | |||||
| FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept | |||||
| { | |||||
| uint8_t em_codes[num_emphases] = {}; | |||||
| if (has_emphasis(em, emphasis::bold)) | |||||
| em_codes[0] = 1; | |||||
| if (has_emphasis(em, emphasis::faint)) | |||||
| em_codes[1] = 2; | |||||
| if (has_emphasis(em, emphasis::italic)) | |||||
| em_codes[2] = 3; | |||||
| if (has_emphasis(em, emphasis::underline)) | |||||
| em_codes[3] = 4; | |||||
| if (has_emphasis(em, emphasis::blink)) | |||||
| em_codes[4] = 5; | |||||
| if (has_emphasis(em, emphasis::reverse)) | |||||
| em_codes[5] = 7; | |||||
| if (has_emphasis(em, emphasis::conceal)) | |||||
| em_codes[6] = 8; | |||||
| if (has_emphasis(em, emphasis::strikethrough)) | |||||
| em_codes[7] = 9; | |||||
| size_t index = 0; | |||||
| for (size_t i = 0; i < num_emphases; ++i) | |||||
| { | |||||
| if (!em_codes[i]) | |||||
| continue; | |||||
| buffer[index++] = static_cast<Char>('\x1b'); | |||||
| buffer[index++] = static_cast<Char>('['); | |||||
| buffer[index++] = static_cast<Char>('0' + em_codes[i]); | |||||
| buffer[index++] = static_cast<Char>('m'); | |||||
| } | |||||
| buffer[index++] = static_cast<Char>(0); | |||||
| } | |||||
| FMT_CONSTEXPR operator const Char*() const noexcept | |||||
| { | |||||
| return buffer; | |||||
| } | |||||
| FMT_CONSTEXPR const Char* begin() const noexcept | |||||
| { | |||||
| return buffer; | |||||
| } | |||||
| FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept | |||||
| { | |||||
| return buffer + std::char_traits<Char>::length(buffer); | |||||
| } | |||||
| private: | |||||
| static constexpr size_t num_emphases = 8; | |||||
| Char buffer[7u + 3u * num_emphases + 1u]; | |||||
| static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, char delimiter) noexcept | |||||
| { | |||||
| out[0] = static_cast<Char>('0' + c / 100); | |||||
| out[1] = static_cast<Char>('0' + c / 10 % 10); | |||||
| out[2] = static_cast<Char>('0' + c % 10); | |||||
| out[3] = static_cast<Char>(delimiter); | |||||
| } | |||||
| static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept | |||||
| { | |||||
| return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask); | |||||
| } | |||||
| }; | |||||
| template<typename Char> | |||||
| FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( | |||||
| detail::color_type foreground | |||||
| ) noexcept | |||||
| { | |||||
| return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); | |||||
| } | |||||
| template<typename Char> | |||||
| FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( | |||||
| detail::color_type background | |||||
| ) noexcept | |||||
| { | |||||
| return ansi_color_escape<Char>(background, "\x1b[48;2;"); | |||||
| } | |||||
| template<typename Char> | |||||
| FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) noexcept | |||||
| { | |||||
| return ansi_color_escape<Char>(em); | |||||
| } | |||||
| template<typename Char> | |||||
| inline void fputs(const Char* chars, FILE* stream) | |||||
| { | |||||
| int result = std::fputs(chars, stream); | |||||
| if (result < 0) | |||||
| FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); | |||||
| } | |||||
| template<> | |||||
| inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) | |||||
| { | |||||
| int result = std::fputws(chars, stream); | |||||
| if (result < 0) | |||||
| FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); | |||||
| } | |||||
| template<typename Char> | |||||
| inline void reset_color(FILE* stream) | |||||
| { | |||||
| fputs("\x1b[0m", stream); | |||||
| } | |||||
| template<> | |||||
| inline void reset_color<wchar_t>(FILE* stream) | |||||
| { | |||||
| fputs(L"\x1b[0m", stream); | |||||
| } | |||||
| template<typename Char> | |||||
| inline void reset_color(buffer<Char>& buffer) | |||||
| { | |||||
| auto reset_color = string_view("\x1b[0m"); | |||||
| buffer.append(reset_color.begin(), reset_color.end()); | |||||
| } | |||||
| template<typename T> | |||||
| struct styled_arg | |||||
| { | |||||
| const T& value; | |||||
| text_style style; | |||||
| }; | |||||
| template<typename Char> | |||||
| void vformat_to(buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) | |||||
| { | |||||
| bool has_style = false; | |||||
| if (ts.has_emphasis()) | |||||
| { | |||||
| has_style = true; | |||||
| auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis()); | |||||
| buf.append(emphasis.begin(), emphasis.end()); | |||||
| } | |||||
| if (ts.has_foreground()) | |||||
| { | |||||
| has_style = true; | |||||
| auto foreground = detail::make_foreground_color<Char>(ts.get_foreground()); | |||||
| buf.append(foreground.begin(), foreground.end()); | |||||
| } | |||||
| if (ts.has_background()) | |||||
| { | |||||
| has_style = true; | |||||
| auto background = detail::make_background_color<Char>(ts.get_background()); | |||||
| buf.append(background.begin(), background.end()); | |||||
| } | |||||
| detail::vformat_to(buf, format_str, args, {}); | |||||
| if (has_style) | |||||
| detail::reset_color<Char>(buf); | |||||
| } | |||||
| FMT_END_DETAIL_NAMESPACE | |||||
| template<typename S, typename Char = char_t<S>> | |||||
| void vprint(std::FILE* f, const text_style& ts, const S& format, basic_format_args<buffer_context<type_identity_t<Char>>> args) | |||||
| { | |||||
| basic_memory_buffer<Char> buf; | |||||
| detail::vformat_to(buf, ts, detail::to_string_view(format), args); | |||||
| if (detail::is_utf8()) | |||||
| { | |||||
| detail::print(f, basic_string_view<Char>(buf.begin(), buf.size())); | |||||
| } | |||||
| else | |||||
| { | |||||
| buf.push_back(Char(0)); | |||||
| detail::fputs(buf.data(), f); | |||||
| } | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Formats a string and prints it to the specified file stream using ANSI | |||||
| escape sequences to specify text formatting. | |||||
| **Example**:: | |||||
| fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
| "Elapsed time: {0:.2f} seconds", 1.23); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_string<S>::value)> | |||||
| void print(std::FILE* f, const text_style& ts, const S& format_str, const Args&... args) | |||||
| { | |||||
| vprint(f, ts, format_str, fmt::make_format_args<buffer_context<char_t<S>>>(args...)); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Formats a string and prints it to stdout using ANSI escape sequences to | |||||
| specify text formatting. | |||||
| **Example**:: | |||||
| fmt::print(fmt::emphasis::bold | fg(fmt::color::red), | |||||
| "Elapsed time: {0:.2f} seconds", 1.23); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_string<S>::value)> | |||||
| void print(const text_style& ts, const S& format_str, const Args&... args) | |||||
| { | |||||
| return print(stdout, ts, format_str, args...); | |||||
| } | |||||
| template<typename S, typename Char = char_t<S>> | |||||
| inline std::basic_string<Char> vformat( | |||||
| const text_style& ts, const S& format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args | |||||
| ) | |||||
| { | |||||
| basic_memory_buffer<Char> buf; | |||||
| detail::vformat_to(buf, ts, detail::to_string_view(format_str), args); | |||||
| return fmt::to_string(buf); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Formats arguments and returns the result as a string using ANSI | |||||
| escape sequences to specify text formatting. | |||||
| **Example**:: | |||||
| #include <fmt/color.h> | |||||
| std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), | |||||
| "The answer is {}", 42); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... Args, typename Char = char_t<S>> | |||||
| inline std::basic_string<Char> format(const text_style& ts, const S& format_str, const Args&... args) | |||||
| { | |||||
| return fmt::vformat(ts, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| /** | |||||
| Formats a string with the given text_style and writes the output to ``out``. | |||||
| */ | |||||
| template<typename OutputIt, typename Char, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> | |||||
| OutputIt vformat_to( | |||||
| OutputIt out, const text_style& ts, basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args | |||||
| ) | |||||
| { | |||||
| auto&& buf = detail::get_buffer<Char>(out); | |||||
| detail::vformat_to(buf, ts, format_str, args); | |||||
| return detail::get_iterator(buf); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Formats arguments with the given text_style, writes the result to the output | |||||
| iterator ``out`` and returns the iterator past the end of the output range. | |||||
| **Example**:: | |||||
| std::vector<char> out; | |||||
| fmt::format_to(std::back_inserter(out), | |||||
| fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); | |||||
| \endrst | |||||
| */ | |||||
| template<typename OutputIt, typename S, typename... Args, bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&& detail::is_string<S>::value> | |||||
| inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, Args&&... args) -> | |||||
| typename std::enable_if<enable, OutputIt>::type | |||||
| { | |||||
| return vformat_to(out, ts, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<char_t<S>>>(args...)); | |||||
| } | |||||
| template<typename T, typename Char> | |||||
| struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> | |||||
| { | |||||
| template<typename FormatContext> | |||||
| auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| const auto& ts = arg.style; | |||||
| const auto& value = arg.value; | |||||
| auto out = ctx.out(); | |||||
| bool has_style = false; | |||||
| if (ts.has_emphasis()) | |||||
| { | |||||
| has_style = true; | |||||
| auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis()); | |||||
| out = std::copy(emphasis.begin(), emphasis.end(), out); | |||||
| } | |||||
| if (ts.has_foreground()) | |||||
| { | |||||
| has_style = true; | |||||
| auto foreground = | |||||
| detail::make_foreground_color<Char>(ts.get_foreground()); | |||||
| out = std::copy(foreground.begin(), foreground.end(), out); | |||||
| } | |||||
| if (ts.has_background()) | |||||
| { | |||||
| has_style = true; | |||||
| auto background = | |||||
| detail::make_background_color<Char>(ts.get_background()); | |||||
| out = std::copy(background.begin(), background.end(), out); | |||||
| } | |||||
| out = formatter<T, Char>::format(value, ctx); | |||||
| if (has_style) | |||||
| { | |||||
| auto reset_color = string_view("\x1b[0m"); | |||||
| out = std::copy(reset_color.begin(), reset_color.end(), out); | |||||
| } | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| /** | |||||
| \rst | |||||
| Returns an argument that will be formatted using ANSI escape sequences, | |||||
| to be used in a formatting function. | |||||
| **Example**:: | |||||
| fmt::print("Elapsed time: {0:.2f} seconds", | |||||
| fmt::styled(1.23, fmt::fg(fmt::color::green) | | |||||
| fmt::bg(fmt::color::blue))); | |||||
| \endrst | |||||
| */ | |||||
| template<typename T> | |||||
| FMT_CONSTEXPR auto styled(const T& value, text_style ts) | |||||
| -> detail::styled_arg<remove_cvref_t<T>> | |||||
| { | |||||
| return detail::styled_arg<remove_cvref_t<T>>{value, ts}; | |||||
| } | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_COLOR_H_ | |||||
| @@ -0,0 +1,763 @@ | |||||
| // Formatting library for C++ - experimental format string compilation | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich and fmt contributors | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_COMPILE_H_ | |||||
| #define FMT_COMPILE_H_ | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| namespace detail | |||||
| { | |||||
| template<typename Char, typename InputIt> | |||||
| FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, counting_iterator it) | |||||
| { | |||||
| return it + (end - begin); | |||||
| } | |||||
| template<typename OutputIt> | |||||
| class truncating_iterator_base | |||||
| { | |||||
| protected: | |||||
| OutputIt out_; | |||||
| size_t limit_; | |||||
| size_t count_ = 0; | |||||
| truncating_iterator_base() : | |||||
| out_(), | |||||
| limit_(0) | |||||
| { | |||||
| } | |||||
| truncating_iterator_base(OutputIt out, size_t limit) : | |||||
| out_(out), | |||||
| limit_(limit) | |||||
| { | |||||
| } | |||||
| public: | |||||
| using iterator_category = std::output_iterator_tag; | |||||
| using value_type = typename std::iterator_traits<OutputIt>::value_type; | |||||
| using difference_type = std::ptrdiff_t; | |||||
| using pointer = void; | |||||
| using reference = void; | |||||
| FMT_UNCHECKED_ITERATOR(truncating_iterator_base); | |||||
| OutputIt base() const | |||||
| { | |||||
| return out_; | |||||
| } | |||||
| size_t count() const | |||||
| { | |||||
| return count_; | |||||
| } | |||||
| }; | |||||
| // An output iterator that truncates the output and counts the number of objects | |||||
| // written to it. | |||||
| template<typename OutputIt, typename Enable = typename std::is_void<typename std::iterator_traits<OutputIt>::value_type>::type> | |||||
| class truncating_iterator; | |||||
| template<typename OutputIt> | |||||
| class truncating_iterator<OutputIt, std::false_type> : public truncating_iterator_base<OutputIt> | |||||
| { | |||||
| mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_; | |||||
| public: | |||||
| using value_type = typename truncating_iterator_base<OutputIt>::value_type; | |||||
| truncating_iterator() = default; | |||||
| truncating_iterator(OutputIt out, size_t limit) : | |||||
| truncating_iterator_base<OutputIt>(out, limit) | |||||
| { | |||||
| } | |||||
| truncating_iterator& operator++() | |||||
| { | |||||
| if (this->count_++ < this->limit_) | |||||
| ++this->out_; | |||||
| return *this; | |||||
| } | |||||
| truncating_iterator operator++(int) | |||||
| { | |||||
| auto it = *this; | |||||
| ++*this; | |||||
| return it; | |||||
| } | |||||
| value_type& operator*() const | |||||
| { | |||||
| return this->count_ < this->limit_ ? *this->out_ : blackhole_; | |||||
| } | |||||
| }; | |||||
| template<typename OutputIt> | |||||
| class truncating_iterator<OutputIt, std::true_type> : public truncating_iterator_base<OutputIt> | |||||
| { | |||||
| public: | |||||
| truncating_iterator() = default; | |||||
| truncating_iterator(OutputIt out, size_t limit) : | |||||
| truncating_iterator_base<OutputIt>(out, limit) | |||||
| { | |||||
| } | |||||
| template<typename T> | |||||
| truncating_iterator& operator=(T val) | |||||
| { | |||||
| if (this->count_++ < this->limit_) | |||||
| *this->out_++ = val; | |||||
| return *this; | |||||
| } | |||||
| truncating_iterator& operator++() | |||||
| { | |||||
| return *this; | |||||
| } | |||||
| truncating_iterator& operator++(int) | |||||
| { | |||||
| return *this; | |||||
| } | |||||
| truncating_iterator& operator*() | |||||
| { | |||||
| return *this; | |||||
| } | |||||
| }; | |||||
| // A compile-time string which is compiled into fast formatting code. | |||||
| class compiled_string | |||||
| { | |||||
| }; | |||||
| template<typename S> | |||||
| struct is_compiled_string : std::is_base_of<compiled_string, S> | |||||
| { | |||||
| }; | |||||
| /** | |||||
| \rst | |||||
| Converts a string literal *s* into a format string that will be parsed at | |||||
| compile time and converted into efficient formatting code. Requires C++17 | |||||
| ``constexpr if`` compiler support. | |||||
| **Example**:: | |||||
| // Converts 42 into std::string using the most efficient method and no | |||||
| // runtime format string processing. | |||||
| std::string s = fmt::format(FMT_COMPILE("{}"), 42); | |||||
| \endrst | |||||
| */ | |||||
| #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) | |||||
| #define FMT_COMPILE(s) \ | |||||
| FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) | |||||
| #else | |||||
| #define FMT_COMPILE(s) FMT_STRING(s) | |||||
| #endif | |||||
| #if FMT_USE_NONTYPE_TEMPLATE_ARGS | |||||
| template<typename Char, size_t N, fmt::detail_exported::fixed_string<Char, N> Str> | |||||
| struct udl_compiled_string : compiled_string | |||||
| { | |||||
| using char_type = Char; | |||||
| explicit constexpr operator basic_string_view<char_type>() const | |||||
| { | |||||
| return {Str.data, N - 1}; | |||||
| } | |||||
| }; | |||||
| #endif | |||||
| template<typename T, typename... Tail> | |||||
| const T& first(const T& value, const Tail&...) | |||||
| { | |||||
| return value; | |||||
| } | |||||
| #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) | |||||
| template<typename... Args> | |||||
| struct type_list | |||||
| { | |||||
| }; | |||||
| // Returns a reference to the argument at index N from [first, rest...]. | |||||
| template<int N, typename T, typename... Args> | |||||
| constexpr const auto& get([[maybe_unused]] const T& first, [[maybe_unused]] const Args&... rest) | |||||
| { | |||||
| static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); | |||||
| if constexpr (N == 0) | |||||
| return first; | |||||
| else | |||||
| return detail::get<N - 1>(rest...); | |||||
| } | |||||
| template<typename Char, typename... Args> | |||||
| constexpr int get_arg_index_by_name(basic_string_view<Char> name, type_list<Args...>) | |||||
| { | |||||
| return get_arg_index_by_name<Args...>(name); | |||||
| } | |||||
| template<int N, typename> | |||||
| struct get_type_impl; | |||||
| template<int N, typename... Args> | |||||
| struct get_type_impl<N, type_list<Args...>> | |||||
| { | |||||
| using type = | |||||
| remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>; | |||||
| }; | |||||
| template<int N, typename T> | |||||
| using get_type = typename get_type_impl<N, T>::type; | |||||
| template<typename T> | |||||
| struct is_compiled_format : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename Char> | |||||
| struct text | |||||
| { | |||||
| basic_string_view<Char> data; | |||||
| using char_type = Char; | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr OutputIt format(OutputIt out, const Args&...) const | |||||
| { | |||||
| return write<Char>(out, data); | |||||
| } | |||||
| }; | |||||
| template<typename Char> | |||||
| struct is_compiled_format<text<Char>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename Char> | |||||
| constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, size_t size) | |||||
| { | |||||
| return {{&s[pos], size}}; | |||||
| } | |||||
| template<typename Char> | |||||
| struct code_unit | |||||
| { | |||||
| Char value; | |||||
| using char_type = Char; | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr OutputIt format(OutputIt out, const Args&...) const | |||||
| { | |||||
| return write<Char>(out, value); | |||||
| } | |||||
| }; | |||||
| // This ensures that the argument type is convertible to `const T&`. | |||||
| template<typename T, int N, typename... Args> | |||||
| constexpr const T& get_arg_checked(const Args&... args) | |||||
| { | |||||
| const auto& arg = detail::get<N>(args...); | |||||
| if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) | |||||
| { | |||||
| return arg.value; | |||||
| } | |||||
| else | |||||
| { | |||||
| return arg; | |||||
| } | |||||
| } | |||||
| template<typename Char> | |||||
| struct is_compiled_format<code_unit<Char>> : std::true_type | |||||
| { | |||||
| }; | |||||
| // A replacement field that refers to argument N. | |||||
| template<typename Char, typename T, int N> | |||||
| struct field | |||||
| { | |||||
| using char_type = Char; | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr OutputIt format(OutputIt out, const Args&... args) const | |||||
| { | |||||
| return write<Char>(out, get_arg_checked<T, N>(args...)); | |||||
| } | |||||
| }; | |||||
| template<typename Char, typename T, int N> | |||||
| struct is_compiled_format<field<Char, T, N>> : std::true_type | |||||
| { | |||||
| }; | |||||
| // A replacement field that refers to argument with name. | |||||
| template<typename Char> | |||||
| struct runtime_named_field | |||||
| { | |||||
| using char_type = Char; | |||||
| basic_string_view<Char> name; | |||||
| template<typename OutputIt, typename T> | |||||
| constexpr static bool try_format_argument( | |||||
| OutputIt& out, | |||||
| // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 | |||||
| [[maybe_unused]] basic_string_view<Char> arg_name, | |||||
| const T& arg | |||||
| ) | |||||
| { | |||||
| if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) | |||||
| { | |||||
| if (arg_name == arg.name) | |||||
| { | |||||
| out = write<Char>(out, arg.value); | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr OutputIt format(OutputIt out, const Args&... args) const | |||||
| { | |||||
| bool found = (try_format_argument(out, name, args) || ...); | |||||
| if (!found) | |||||
| { | |||||
| FMT_THROW(format_error("argument with specified name is not found")); | |||||
| } | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| template<typename Char> | |||||
| struct is_compiled_format<runtime_named_field<Char>> : std::true_type | |||||
| { | |||||
| }; | |||||
| // A replacement field that refers to argument N and has format specifiers. | |||||
| template<typename Char, typename T, int N> | |||||
| struct spec_field | |||||
| { | |||||
| using char_type = Char; | |||||
| formatter<T, Char> fmt; | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr FMT_INLINE OutputIt format(OutputIt out, const Args&... args) const | |||||
| { | |||||
| const auto& vargs = | |||||
| fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...); | |||||
| basic_format_context<OutputIt, Char> ctx(out, vargs); | |||||
| return fmt.format(get_arg_checked<T, N>(args...), ctx); | |||||
| } | |||||
| }; | |||||
| template<typename Char, typename T, int N> | |||||
| struct is_compiled_format<spec_field<Char, T, N>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename L, typename R> | |||||
| struct concat | |||||
| { | |||||
| L lhs; | |||||
| R rhs; | |||||
| using char_type = typename L::char_type; | |||||
| template<typename OutputIt, typename... Args> | |||||
| constexpr OutputIt format(OutputIt out, const Args&... args) const | |||||
| { | |||||
| out = lhs.format(out, args...); | |||||
| return rhs.format(out, args...); | |||||
| } | |||||
| }; | |||||
| template<typename L, typename R> | |||||
| struct is_compiled_format<concat<L, R>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename L, typename R> | |||||
| constexpr concat<L, R> make_concat(L lhs, R rhs) | |||||
| { | |||||
| return {lhs, rhs}; | |||||
| } | |||||
| struct unknown_format | |||||
| { | |||||
| }; | |||||
| template<typename Char> | |||||
| constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) | |||||
| { | |||||
| for (size_t size = str.size(); pos != size; ++pos) | |||||
| { | |||||
| if (str[pos] == '{' || str[pos] == '}') | |||||
| break; | |||||
| } | |||||
| return pos; | |||||
| } | |||||
| template<typename Args, size_t POS, int ID, typename S> | |||||
| constexpr auto compile_format_string(S format_str); | |||||
| template<typename Args, size_t POS, int ID, typename T, typename S> | |||||
| constexpr auto parse_tail(T head, S format_str) | |||||
| { | |||||
| if constexpr (POS != basic_string_view<typename S::char_type>(format_str).size()) | |||||
| { | |||||
| constexpr auto tail = compile_format_string<Args, POS, ID>(format_str); | |||||
| if constexpr (std::is_same<remove_cvref_t<decltype(tail)>, unknown_format>()) | |||||
| return tail; | |||||
| else | |||||
| return make_concat(head, tail); | |||||
| } | |||||
| else | |||||
| { | |||||
| return head; | |||||
| } | |||||
| } | |||||
| template<typename T, typename Char> | |||||
| struct parse_specs_result | |||||
| { | |||||
| formatter<T, Char> fmt; | |||||
| size_t end; | |||||
| int next_arg_id; | |||||
| }; | |||||
| constexpr int manual_indexing_id = -1; | |||||
| template<typename T, typename Char> | |||||
| constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, size_t pos, int next_arg_id) | |||||
| { | |||||
| str.remove_prefix(pos); | |||||
| auto ctx = compile_parse_context<Char>(str, max_value<int>(), nullptr, {}, next_arg_id); | |||||
| auto f = formatter<T, Char>(); | |||||
| auto end = f.parse(ctx); | |||||
| return {f, pos + fmt::detail::to_unsigned(end - str.data()), next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; | |||||
| } | |||||
| template<typename Char> | |||||
| struct arg_id_handler | |||||
| { | |||||
| arg_ref<Char> arg_id; | |||||
| constexpr int operator()() | |||||
| { | |||||
| FMT_ASSERT(false, "handler cannot be used with automatic indexing"); | |||||
| return 0; | |||||
| } | |||||
| constexpr int operator()(int id) | |||||
| { | |||||
| arg_id = arg_ref<Char>(id); | |||||
| return 0; | |||||
| } | |||||
| constexpr int operator()(basic_string_view<Char> id) | |||||
| { | |||||
| arg_id = arg_ref<Char>(id); | |||||
| return 0; | |||||
| } | |||||
| constexpr void on_error(const char* message) | |||||
| { | |||||
| FMT_THROW(format_error(message)); | |||||
| } | |||||
| }; | |||||
| template<typename Char> | |||||
| struct parse_arg_id_result | |||||
| { | |||||
| arg_ref<Char> arg_id; | |||||
| const Char* arg_id_end; | |||||
| }; | |||||
| template<int ID, typename Char> | |||||
| constexpr auto parse_arg_id(const Char* begin, const Char* end) | |||||
| { | |||||
| auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; | |||||
| auto arg_id_end = parse_arg_id(begin, end, handler); | |||||
| return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; | |||||
| } | |||||
| template<typename T, typename Enable = void> | |||||
| struct field_type | |||||
| { | |||||
| using type = remove_cvref_t<T>; | |||||
| }; | |||||
| template<typename T> | |||||
| struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> | |||||
| { | |||||
| using type = remove_cvref_t<decltype(T::value)>; | |||||
| }; | |||||
| template<typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID, typename S> | |||||
| constexpr auto parse_replacement_field_then_tail(S format_str) | |||||
| { | |||||
| using char_type = typename S::char_type; | |||||
| constexpr auto str = basic_string_view<char_type>(format_str); | |||||
| constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); | |||||
| if constexpr (c == '}') | |||||
| { | |||||
| return parse_tail<Args, END_POS + 1, NEXT_ID>( | |||||
| field<char_type, typename field_type<T>::type, ARG_INDEX>(), | |||||
| format_str | |||||
| ); | |||||
| } | |||||
| else if constexpr (c != ':') | |||||
| { | |||||
| FMT_THROW(format_error("expected ':'")); | |||||
| } | |||||
| else | |||||
| { | |||||
| constexpr auto result = parse_specs<typename field_type<T>::type>( | |||||
| str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID | |||||
| ); | |||||
| if constexpr (result.end >= str.size() || str[result.end] != '}') | |||||
| { | |||||
| FMT_THROW(format_error("expected '}'")); | |||||
| return 0; | |||||
| } | |||||
| else | |||||
| { | |||||
| return parse_tail<Args, result.end + 1, result.next_arg_id>( | |||||
| spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ | |||||
| result.fmt}, | |||||
| format_str | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| // Compiles a non-empty format string and returns the compiled representation | |||||
| // or unknown_format() on unrecognized input. | |||||
| template<typename Args, size_t POS, int ID, typename S> | |||||
| constexpr auto compile_format_string(S format_str) | |||||
| { | |||||
| using char_type = typename S::char_type; | |||||
| constexpr auto str = basic_string_view<char_type>(format_str); | |||||
| if constexpr (str[POS] == '{') | |||||
| { | |||||
| if constexpr (POS + 1 == str.size()) | |||||
| FMT_THROW(format_error("unmatched '{' in format string")); | |||||
| if constexpr (str[POS + 1] == '{') | |||||
| { | |||||
| return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); | |||||
| } | |||||
| else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') | |||||
| { | |||||
| static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); | |||||
| constexpr auto next_id = | |||||
| ID != manual_indexing_id ? ID + 1 : manual_indexing_id; | |||||
| return parse_replacement_field_then_tail<get_type<ID, Args>, Args, POS + 1, ID, next_id>( | |||||
| format_str | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| constexpr auto arg_id_result = | |||||
| parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size()); | |||||
| constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); | |||||
| constexpr char_type c = | |||||
| arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); | |||||
| static_assert(c == '}' || c == ':', "missing '}' in format string"); | |||||
| if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) | |||||
| { | |||||
| static_assert( | |||||
| ID == manual_indexing_id || ID == 0, | |||||
| "cannot switch from automatic to manual argument indexing" | |||||
| ); | |||||
| constexpr auto arg_index = arg_id_result.arg_id.val.index; | |||||
| return parse_replacement_field_then_tail<get_type<arg_index, Args>, Args, arg_id_end_pos, arg_index, manual_indexing_id>( | |||||
| format_str | |||||
| ); | |||||
| } | |||||
| else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) | |||||
| { | |||||
| constexpr auto arg_index = | |||||
| get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); | |||||
| if constexpr (arg_index != invalid_arg_index) | |||||
| { | |||||
| constexpr auto next_id = | |||||
| ID != manual_indexing_id ? ID + 1 : manual_indexing_id; | |||||
| return parse_replacement_field_then_tail< | |||||
| decltype(get_type<arg_index, Args>::value), | |||||
| Args, | |||||
| arg_id_end_pos, | |||||
| arg_index, | |||||
| next_id>(format_str); | |||||
| } | |||||
| else | |||||
| { | |||||
| if constexpr (c == '}') | |||||
| { | |||||
| return parse_tail<Args, arg_id_end_pos + 1, ID>( | |||||
| runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, | |||||
| format_str | |||||
| ); | |||||
| } | |||||
| else if constexpr (c == ':') | |||||
| { | |||||
| return unknown_format(); // no type info for specs parsing | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| else if constexpr (str[POS] == '}') | |||||
| { | |||||
| if constexpr (POS + 1 == str.size()) | |||||
| FMT_THROW(format_error("unmatched '}' in format string")); | |||||
| return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); | |||||
| } | |||||
| else | |||||
| { | |||||
| constexpr auto end = parse_text(str, POS + 1); | |||||
| if constexpr (end - POS > 1) | |||||
| { | |||||
| return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), format_str); | |||||
| } | |||||
| else | |||||
| { | |||||
| return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, format_str); | |||||
| } | |||||
| } | |||||
| } | |||||
| template<typename... Args, typename S, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| constexpr auto compile(S format_str) | |||||
| { | |||||
| constexpr auto str = basic_string_view<typename S::char_type>(format_str); | |||||
| if constexpr (str.size() == 0) | |||||
| { | |||||
| return detail::make_text(str, 0, 0); | |||||
| } | |||||
| else | |||||
| { | |||||
| constexpr auto result = | |||||
| detail::compile_format_string<detail::type_list<Args...>, 0, 0>( | |||||
| format_str | |||||
| ); | |||||
| return result; | |||||
| } | |||||
| } | |||||
| #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) | |||||
| } // namespace detail | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) | |||||
| template<typename CompiledFormat, typename... Args, typename Char = typename CompiledFormat::char_type, FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> | |||||
| FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) | |||||
| { | |||||
| auto s = std::basic_string<Char>(); | |||||
| cf.format(std::back_inserter(s), args...); | |||||
| return s; | |||||
| } | |||||
| template<typename OutputIt, typename CompiledFormat, typename... Args, FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> | |||||
| constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) | |||||
| { | |||||
| return cf.format(out, args...); | |||||
| } | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| FMT_INLINE std::basic_string<typename S::char_type> format(const S&, Args&&... args) | |||||
| { | |||||
| if constexpr (std::is_same<typename S::char_type, char>::value) | |||||
| { | |||||
| constexpr auto str = basic_string_view<typename S::char_type>(S()); | |||||
| if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') | |||||
| { | |||||
| const auto& first = detail::first(args...); | |||||
| if constexpr (detail::is_named_arg< | |||||
| remove_cvref_t<decltype(first)>>::value) | |||||
| { | |||||
| return fmt::to_string(first.value); | |||||
| } | |||||
| else | |||||
| { | |||||
| return fmt::to_string(first); | |||||
| } | |||||
| } | |||||
| } | |||||
| constexpr auto compiled = detail::compile<Args...>(S()); | |||||
| if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, detail::unknown_format>()) | |||||
| { | |||||
| return fmt::format( | |||||
| static_cast<basic_string_view<typename S::char_type>>(S()), | |||||
| std::forward<Args>(args)... | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| return fmt::format(compiled, std::forward<Args>(args)...); | |||||
| } | |||||
| } | |||||
| template<typename OutputIt, typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) | |||||
| { | |||||
| constexpr auto compiled = detail::compile<Args...>(S()); | |||||
| if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, detail::unknown_format>()) | |||||
| { | |||||
| return fmt::format_to( | |||||
| out, static_cast<basic_string_view<typename S::char_type>>(S()), std::forward<Args>(args)... | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| return fmt::format_to(out, compiled, std::forward<Args>(args)...); | |||||
| } | |||||
| } | |||||
| #endif | |||||
| template<typename OutputIt, typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, const S& format_str, Args&&... args) | |||||
| { | |||||
| auto it = fmt::format_to(detail::truncating_iterator<OutputIt>(out, n), format_str, std::forward<Args>(args)...); | |||||
| return {it.base(), it.count()}; | |||||
| } | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, const Args&... args) | |||||
| { | |||||
| return fmt::format_to(detail::counting_iterator(), format_str, args...) | |||||
| .count(); | |||||
| } | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| void print(std::FILE* f, const S& format_str, const Args&... args) | |||||
| { | |||||
| memory_buffer buffer; | |||||
| fmt::format_to(std::back_inserter(buffer), format_str, args...); | |||||
| detail::print(f, {buffer.data(), buffer.size()}); | |||||
| } | |||||
| template<typename S, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> | |||||
| void print(const S& format_str, const Args&... args) | |||||
| { | |||||
| print(stdout, format_str, args...); | |||||
| } | |||||
| #if FMT_USE_NONTYPE_TEMPLATE_ARGS | |||||
| inline namespace literals | |||||
| { | |||||
| template<detail_exported::fixed_string Str> | |||||
| constexpr auto operator""_cf() | |||||
| { | |||||
| using char_t = remove_cvref_t<decltype(Str.data[0])>; | |||||
| return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t), Str>(); | |||||
| } | |||||
| } // namespace literals | |||||
| #endif | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_COMPILE_H_ | |||||
| @@ -0,0 +1,27 @@ | |||||
| Copyright (c) 2012 - present, Victor Zverovich | |||||
| Permission is hereby granted, free of charge, to any person obtaining | |||||
| a copy of this software and associated documentation files (the | |||||
| "Software"), to deal in the Software without restriction, including | |||||
| without limitation the rights to use, copy, modify, merge, publish, | |||||
| distribute, sublicense, and/or sell copies of the Software, and to | |||||
| permit persons to whom the Software is furnished to do so, subject to | |||||
| the following conditions: | |||||
| The above copyright notice and this permission notice shall be | |||||
| included in all copies or substantial portions of the Software. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | |||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | |||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
| --- Optional exception to the license --- | |||||
| As an exception, if, as a result of your compiling your source code, portions | |||||
| of this Software are embedded into a machine-executable object form of such | |||||
| source code, you may redistribute such embedded portions in such object form | |||||
| without including the above copyright and permission notices. | |||||
| @@ -0,0 +1,2 @@ | |||||
| #include "xchar.h" | |||||
| #warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead | |||||
| @@ -0,0 +1,561 @@ | |||||
| // Formatting library for C++ - optional OS-specific functionality | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_OS_H_ | |||||
| #define FMT_OS_H_ | |||||
| #include <cerrno> | |||||
| #include <cstddef> | |||||
| #include <cstdio> | |||||
| #include <system_error> // std::system_error | |||||
| #if defined __APPLE__ || defined(__FreeBSD__) | |||||
| #include <xlocale.h> // for LC_NUMERIC_MASK on OS X | |||||
| #endif | |||||
| #include "format.h" | |||||
| #ifndef FMT_USE_FCNTL | |||||
| // UWP doesn't provide _pipe. | |||||
| #if FMT_HAS_INCLUDE("winapifamily.h") | |||||
| #include <winapifamily.h> | |||||
| #endif | |||||
| #if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || defined(__linux__)) && \ | |||||
| (!defined(WINAPI_FAMILY) || \ | |||||
| (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) | |||||
| #include <fcntl.h> // for O_RDONLY | |||||
| #define FMT_USE_FCNTL 1 | |||||
| #else | |||||
| #define FMT_USE_FCNTL 0 | |||||
| #endif | |||||
| #endif | |||||
| #ifndef FMT_POSIX | |||||
| #if defined(_WIN32) && !defined(__MINGW32__) | |||||
| // Fix warnings about deprecated symbols. | |||||
| #define FMT_POSIX(call) _##call | |||||
| #else | |||||
| #define FMT_POSIX(call) call | |||||
| #endif | |||||
| #endif | |||||
| // Calls to system functions are wrapped in FMT_SYSTEM for testability. | |||||
| #ifdef FMT_SYSTEM | |||||
| #define FMT_POSIX_CALL(call) FMT_SYSTEM(call) | |||||
| #else | |||||
| #define FMT_SYSTEM(call) ::call | |||||
| #ifdef _WIN32 | |||||
| // Fix warnings about deprecated symbols. | |||||
| #define FMT_POSIX_CALL(call) ::_##call | |||||
| #else | |||||
| #define FMT_POSIX_CALL(call) ::call | |||||
| #endif | |||||
| #endif | |||||
| // Retries the expression while it evaluates to error_result and errno | |||||
| // equals to EINTR. | |||||
| #ifndef _WIN32 | |||||
| #define FMT_RETRY_VAL(result, expression, error_result) \ | |||||
| do \ | |||||
| { \ | |||||
| (result) = (expression); \ | |||||
| } while ((result) == (error_result) && errno == EINTR) | |||||
| #else | |||||
| #define FMT_RETRY_VAL(result, expression, error_result) result = (expression) | |||||
| #endif | |||||
| #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) | |||||
| FMT_BEGIN_NAMESPACE | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| /** | |||||
| \rst | |||||
| A reference to a null-terminated string. It can be constructed from a C | |||||
| string or ``std::string``. | |||||
| You can use one of the following type aliases for common character types: | |||||
| +---------------+-----------------------------+ | |||||
| | Type | Definition | | |||||
| +===============+=============================+ | |||||
| | cstring_view | basic_cstring_view<char> | | |||||
| +---------------+-----------------------------+ | |||||
| | wcstring_view | basic_cstring_view<wchar_t> | | |||||
| +---------------+-----------------------------+ | |||||
| This class is most useful as a parameter type to allow passing | |||||
| different types of strings to a function, for example:: | |||||
| template <typename... Args> | |||||
| std::string format(cstring_view format_str, const Args & ... args); | |||||
| format("{}", 42); | |||||
| format(std::string("{}"), 42); | |||||
| \endrst | |||||
| */ | |||||
| template<typename Char> | |||||
| class basic_cstring_view | |||||
| { | |||||
| private: | |||||
| const Char* data_; | |||||
| public: | |||||
| /** Constructs a string reference object from a C string. */ | |||||
| basic_cstring_view(const Char* s) : | |||||
| data_(s) | |||||
| { | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Constructs a string reference from an ``std::string`` object. | |||||
| \endrst | |||||
| */ | |||||
| basic_cstring_view(const std::basic_string<Char>& s) : | |||||
| data_(s.c_str()) | |||||
| { | |||||
| } | |||||
| /** Returns the pointer to a C string. */ | |||||
| const Char* c_str() const | |||||
| { | |||||
| return data_; | |||||
| } | |||||
| }; | |||||
| using cstring_view = basic_cstring_view<char>; | |||||
| using wcstring_view = basic_cstring_view<wchar_t>; | |||||
| template<typename Char> | |||||
| struct formatter<std::error_code, Char> | |||||
| { | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return ctx.begin(); | |||||
| } | |||||
| template<typename FormatContext> | |||||
| FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| auto out = ctx.out(); | |||||
| out = detail::write_bytes(out, ec.category().name(), basic_format_specs<Char>()); | |||||
| out = detail::write<Char>(out, Char(':')); | |||||
| out = detail::write<Char>(out, ec.value()); | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| #ifdef _WIN32 | |||||
| FMT_API const std::error_category& system_category() noexcept; | |||||
| FMT_BEGIN_DETAIL_NAMESPACE | |||||
| // A converter from UTF-16 to UTF-8. | |||||
| // It is only provided for Windows since other systems support UTF-8 natively. | |||||
| class utf16_to_utf8 | |||||
| { | |||||
| private: | |||||
| memory_buffer buffer_; | |||||
| public: | |||||
| utf16_to_utf8() | |||||
| { | |||||
| } | |||||
| FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s); | |||||
| operator string_view() const | |||||
| { | |||||
| return string_view(&buffer_[0], size()); | |||||
| } | |||||
| size_t size() const | |||||
| { | |||||
| return buffer_.size() - 1; | |||||
| } | |||||
| const char* c_str() const | |||||
| { | |||||
| return &buffer_[0]; | |||||
| } | |||||
| std::string str() const | |||||
| { | |||||
| return std::string(&buffer_[0], size()); | |||||
| } | |||||
| // Performs conversion returning a system error code instead of | |||||
| // throwing exception on conversion error. This method may still throw | |||||
| // in case of memory allocation error. | |||||
| FMT_API int convert(basic_string_view<wchar_t> s); | |||||
| }; | |||||
| FMT_API void format_windows_error(buffer<char>& out, int error_code, const char* message) noexcept; | |||||
| FMT_END_DETAIL_NAMESPACE | |||||
| FMT_API std::system_error vwindows_error(int error_code, string_view format_str, format_args args); | |||||
| /** | |||||
| \rst | |||||
| Constructs a :class:`std::system_error` object with the description | |||||
| of the form | |||||
| .. parsed-literal:: | |||||
| *<message>*: *<system-message>* | |||||
| where *<message>* is the formatted message and *<system-message>* is the | |||||
| system message corresponding to the error code. | |||||
| *error_code* is a Windows error code as given by ``GetLastError``. | |||||
| If *error_code* is not a valid error code such as -1, the system message | |||||
| will look like "error -1". | |||||
| **Example**:: | |||||
| // This throws a system_error with the description | |||||
| // cannot open file 'madeup': The system cannot find the file specified. | |||||
| // or similar (system message may vary). | |||||
| const char *filename = "madeup"; | |||||
| LPOFSTRUCT of = LPOFSTRUCT(); | |||||
| HFILE file = OpenFile(filename, &of, OF_READ); | |||||
| if (file == HFILE_ERROR) { | |||||
| throw fmt::windows_error(GetLastError(), | |||||
| "cannot open file '{}'", filename); | |||||
| } | |||||
| \endrst | |||||
| */ | |||||
| template<typename... Args> | |||||
| std::system_error windows_error(int error_code, string_view message, const Args&... args) | |||||
| { | |||||
| return vwindows_error(error_code, message, fmt::make_format_args(args...)); | |||||
| } | |||||
| // Reports a Windows error without throwing an exception. | |||||
| // Can be used to report errors from destructors. | |||||
| FMT_API void report_windows_error(int error_code, const char* message) noexcept; | |||||
| #else | |||||
| inline const std::error_category& system_category() noexcept | |||||
| { | |||||
| return std::system_category(); | |||||
| } | |||||
| #endif // _WIN32 | |||||
| // std::system is not available on some platforms such as iOS (#2248). | |||||
| #ifdef __OSX__ | |||||
| template<typename S, typename... Args, typename Char = char_t<S>> | |||||
| void say(const S& format_str, Args&&... args) | |||||
| { | |||||
| std::system(format("say \"{}\"", format(format_str, args...)).c_str()); | |||||
| } | |||||
| #endif | |||||
| // A buffered file. | |||||
| class buffered_file | |||||
| { | |||||
| private: | |||||
| FILE* file_; | |||||
| friend class file; | |||||
| explicit buffered_file(FILE* f) : | |||||
| file_(f) | |||||
| { | |||||
| } | |||||
| public: | |||||
| buffered_file(const buffered_file&) = delete; | |||||
| void operator=(const buffered_file&) = delete; | |||||
| // Constructs a buffered_file object which doesn't represent any file. | |||||
| buffered_file() noexcept : | |||||
| file_(nullptr) | |||||
| { | |||||
| } | |||||
| // Destroys the object closing the file it represents if any. | |||||
| FMT_API ~buffered_file() noexcept; | |||||
| public: | |||||
| buffered_file(buffered_file&& other) noexcept : | |||||
| file_(other.file_) | |||||
| { | |||||
| other.file_ = nullptr; | |||||
| } | |||||
| buffered_file& operator=(buffered_file&& other) | |||||
| { | |||||
| close(); | |||||
| file_ = other.file_; | |||||
| other.file_ = nullptr; | |||||
| return *this; | |||||
| } | |||||
| // Opens a file. | |||||
| FMT_API buffered_file(cstring_view filename, cstring_view mode); | |||||
| // Closes the file. | |||||
| FMT_API void close(); | |||||
| // Returns the pointer to a FILE object representing this file. | |||||
| FILE* get() const noexcept | |||||
| { | |||||
| return file_; | |||||
| } | |||||
| FMT_API int descriptor() const; | |||||
| void vprint(string_view format_str, format_args args) | |||||
| { | |||||
| fmt::vprint(file_, format_str, args); | |||||
| } | |||||
| template<typename... Args> | |||||
| inline void print(string_view format_str, const Args&... args) | |||||
| { | |||||
| vprint(format_str, fmt::make_format_args(args...)); | |||||
| } | |||||
| }; | |||||
| #if FMT_USE_FCNTL | |||||
| // A file. Closed file is represented by a file object with descriptor -1. | |||||
| // Methods that are not declared with noexcept may throw | |||||
| // fmt::system_error in case of failure. Note that some errors such as | |||||
| // closing the file multiple times will cause a crash on Windows rather | |||||
| // than an exception. You can get standard behavior by overriding the | |||||
| // invalid parameter handler with _set_invalid_parameter_handler. | |||||
| class FMT_API file | |||||
| { | |||||
| private: | |||||
| int fd_; // File descriptor. | |||||
| // Constructs a file object with a given descriptor. | |||||
| explicit file(int fd) : | |||||
| fd_(fd) | |||||
| { | |||||
| } | |||||
| public: | |||||
| // Possible values for the oflag argument to the constructor. | |||||
| enum | |||||
| { | |||||
| RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. | |||||
| WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. | |||||
| RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. | |||||
| CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist. | |||||
| APPEND = FMT_POSIX(O_APPEND), // Open in append mode. | |||||
| TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file. | |||||
| }; | |||||
| // Constructs a file object which doesn't represent any file. | |||||
| file() noexcept : | |||||
| fd_(-1) | |||||
| { | |||||
| } | |||||
| // Opens a file and constructs a file object representing this file. | |||||
| file(cstring_view path, int oflag); | |||||
| public: | |||||
| file(const file&) = delete; | |||||
| void operator=(const file&) = delete; | |||||
| file(file&& other) noexcept : | |||||
| fd_(other.fd_) | |||||
| { | |||||
| other.fd_ = -1; | |||||
| } | |||||
| // Move assignment is not noexcept because close may throw. | |||||
| file& operator=(file&& other) | |||||
| { | |||||
| close(); | |||||
| fd_ = other.fd_; | |||||
| other.fd_ = -1; | |||||
| return *this; | |||||
| } | |||||
| // Destroys the object closing the file it represents if any. | |||||
| ~file() noexcept; | |||||
| // Returns the file descriptor. | |||||
| int descriptor() const noexcept | |||||
| { | |||||
| return fd_; | |||||
| } | |||||
| // Closes the file. | |||||
| void close(); | |||||
| // Returns the file size. The size has signed type for consistency with | |||||
| // stat::st_size. | |||||
| long long size() const; | |||||
| // Attempts to read count bytes from the file into the specified buffer. | |||||
| size_t read(void* buffer, size_t count); | |||||
| // Attempts to write count bytes from the specified buffer to the file. | |||||
| size_t write(const void* buffer, size_t count); | |||||
| // Duplicates a file descriptor with the dup function and returns | |||||
| // the duplicate as a file object. | |||||
| static file dup(int fd); | |||||
| // Makes fd be the copy of this file descriptor, closing fd first if | |||||
| // necessary. | |||||
| void dup2(int fd); | |||||
| // Makes fd be the copy of this file descriptor, closing fd first if | |||||
| // necessary. | |||||
| void dup2(int fd, std::error_code& ec) noexcept; | |||||
| // Creates a pipe setting up read_end and write_end file objects for reading | |||||
| // and writing respectively. | |||||
| static void pipe(file& read_end, file& write_end); | |||||
| // Creates a buffered_file object associated with this file and detaches | |||||
| // this file object from the file. | |||||
| buffered_file fdopen(const char* mode); | |||||
| }; | |||||
| // Returns the memory page size. | |||||
| long getpagesize(); | |||||
| FMT_BEGIN_DETAIL_NAMESPACE | |||||
| struct buffer_size | |||||
| { | |||||
| buffer_size() = default; | |||||
| size_t value = 0; | |||||
| buffer_size operator=(size_t val) const | |||||
| { | |||||
| auto bs = buffer_size(); | |||||
| bs.value = val; | |||||
| return bs; | |||||
| } | |||||
| }; | |||||
| struct ostream_params | |||||
| { | |||||
| int oflag = file::WRONLY | file::CREATE | file::TRUNC; | |||||
| size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; | |||||
| ostream_params() | |||||
| { | |||||
| } | |||||
| template<typename... T> | |||||
| ostream_params(T... params, int new_oflag) : | |||||
| ostream_params(params...) | |||||
| { | |||||
| oflag = new_oflag; | |||||
| } | |||||
| template<typename... T> | |||||
| ostream_params(T... params, detail::buffer_size bs) : | |||||
| ostream_params(params...) | |||||
| { | |||||
| this->buffer_size = bs.value; | |||||
| } | |||||
| // Intel has a bug that results in failure to deduce a constructor | |||||
| // for empty parameter packs. | |||||
| #if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000 | |||||
| ostream_params(int new_oflag) : | |||||
| oflag(new_oflag) | |||||
| { | |||||
| } | |||||
| ostream_params(detail::buffer_size bs) : | |||||
| buffer_size(bs.value) | |||||
| { | |||||
| } | |||||
| #endif | |||||
| }; | |||||
| FMT_END_DETAIL_NAMESPACE | |||||
| // Added {} below to work around default constructor error known to | |||||
| // occur in Xcode versions 7.2.1 and 8.2.1. | |||||
| constexpr detail::buffer_size buffer_size{}; | |||||
| /** A fast output stream which is not thread-safe. */ | |||||
| class FMT_API ostream final : private detail::buffer<char> | |||||
| { | |||||
| private: | |||||
| file file_; | |||||
| void grow(size_t) override; | |||||
| ostream(cstring_view path, const detail::ostream_params& params) : | |||||
| file_(path, params.oflag) | |||||
| { | |||||
| set(new char[params.buffer_size], params.buffer_size); | |||||
| } | |||||
| public: | |||||
| ostream(ostream&& other) : | |||||
| detail::buffer<char>(other.data(), other.size(), other.capacity()), | |||||
| file_(std::move(other.file_)) | |||||
| { | |||||
| other.clear(); | |||||
| other.set(nullptr, 0); | |||||
| } | |||||
| ~ostream() | |||||
| { | |||||
| flush(); | |||||
| delete[] data(); | |||||
| } | |||||
| void flush() | |||||
| { | |||||
| if (size() == 0) | |||||
| return; | |||||
| file_.write(data(), size()); | |||||
| clear(); | |||||
| } | |||||
| template<typename... T> | |||||
| friend ostream output_file(cstring_view path, T... params); | |||||
| void close() | |||||
| { | |||||
| flush(); | |||||
| file_.close(); | |||||
| } | |||||
| /** | |||||
| Formats ``args`` according to specifications in ``fmt`` and writes the | |||||
| output to the file. | |||||
| */ | |||||
| template<typename... T> | |||||
| void print(format_string<T...> fmt, T&&... args) | |||||
| { | |||||
| vformat_to(detail::buffer_appender<char>(*this), fmt, fmt::make_format_args(args...)); | |||||
| } | |||||
| }; | |||||
| /** | |||||
| \rst | |||||
| Opens a file for writing. Supported parameters passed in *params*: | |||||
| * ``<integer>``: Flags passed to `open | |||||
| <https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_ | |||||
| (``file::WRONLY | file::CREATE | file::TRUNC`` by default) | |||||
| * ``buffer_size=<integer>``: Output buffer size | |||||
| **Example**:: | |||||
| auto out = fmt::output_file("guide.txt"); | |||||
| out.print("Don't {}", "Panic"); | |||||
| \endrst | |||||
| */ | |||||
| template<typename... T> | |||||
| inline ostream output_file(cstring_view path, T... params) | |||||
| { | |||||
| return {path, detail::ostream_params(params...)}; | |||||
| } | |||||
| #endif // FMT_USE_FCNTL | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_OS_H_ | |||||
| @@ -0,0 +1,263 @@ | |||||
| // Formatting library for C++ - std::ostream support | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_OSTREAM_H_ | |||||
| #define FMT_OSTREAM_H_ | |||||
| #include <fstream> | |||||
| #include <ostream> | |||||
| #if defined(_WIN32) && defined(__GLIBCXX__) | |||||
| #include <ext/stdio_filebuf.h> | |||||
| #include <ext/stdio_sync_filebuf.h> | |||||
| #elif defined(_WIN32) && defined(_LIBCPP_VERSION) | |||||
| #include <__std_stream> | |||||
| #endif | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| template<typename OutputIt, typename Char> | |||||
| class basic_printf_context; | |||||
| namespace detail | |||||
| { | |||||
| // Checks if T has a user-defined operator<<. | |||||
| template<typename T, typename Char, typename Enable = void> | |||||
| class is_streamable | |||||
| { | |||||
| private: | |||||
| template<typename U> | |||||
| static auto test(int) | |||||
| -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>() << std::declval<U>()) != 0>; | |||||
| template<typename> | |||||
| static auto test(...) -> std::false_type; | |||||
| using result = decltype(test<T>(0)); | |||||
| public: | |||||
| is_streamable() = default; | |||||
| static const bool value = result::value; | |||||
| }; | |||||
| // Formatting of built-in types and arrays is intentionally disabled because | |||||
| // it's handled by standard (non-ostream) formatters. | |||||
| template<typename T, typename Char> | |||||
| struct is_streamable< | |||||
| T, | |||||
| Char, | |||||
| enable_if_t< | |||||
| std::is_arithmetic<T>::value || std::is_array<T>::value || | |||||
| std::is_pointer<T>::value || std::is_same<T, char8_type>::value || | |||||
| std::is_convertible<T, fmt::basic_string_view<Char>>::value || | |||||
| std::is_same<T, std_string_view<Char>>::value || | |||||
| (std::is_convertible<T, int>::value && !std::is_enum<T>::value)>> : std::false_type | |||||
| { | |||||
| }; | |||||
| // Generate a unique explicit instantion in every translation unit using a tag | |||||
| // type in an anonymous namespace. | |||||
| namespace | |||||
| { | |||||
| struct file_access_tag | |||||
| { | |||||
| }; | |||||
| } // namespace | |||||
| template<class Tag, class BufType, FILE* BufType::*FileMemberPtr> | |||||
| class file_access | |||||
| { | |||||
| friend auto get_file(BufType& obj) -> FILE* | |||||
| { | |||||
| return obj.*FileMemberPtr; | |||||
| } | |||||
| }; | |||||
| #if FMT_MSC_VERSION | |||||
| template class file_access<file_access_tag, std::filebuf, &std::filebuf::_Myfile>; | |||||
| auto get_file(std::filebuf&) -> FILE*; | |||||
| #elif defined(_WIN32) && defined(_LIBCPP_VERSION) | |||||
| template class file_access<file_access_tag, std::__stdoutbuf<char>, &std::__stdoutbuf<char>::__file_>; | |||||
| auto get_file(std::__stdoutbuf<char>&) -> FILE*; | |||||
| #endif | |||||
| inline bool write_ostream_unicode(std::ostream& os, fmt::string_view data) | |||||
| { | |||||
| #if FMT_MSC_VERSION | |||||
| if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf())) | |||||
| if (FILE* f = get_file(*buf)) | |||||
| return write_console(f, data); | |||||
| #elif defined(_WIN32) && defined(__GLIBCXX__) | |||||
| auto* rdbuf = os.rdbuf(); | |||||
| FILE* c_file; | |||||
| if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf)) | |||||
| c_file = fbuf->file(); | |||||
| else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf)) | |||||
| c_file = fbuf->file(); | |||||
| else | |||||
| return false; | |||||
| if (c_file) | |||||
| return write_console(c_file, data); | |||||
| #elif defined(_WIN32) && defined(_LIBCPP_VERSION) | |||||
| if (auto* buf = dynamic_cast<std::__stdoutbuf<char>*>(os.rdbuf())) | |||||
| if (FILE* f = get_file(*buf)) | |||||
| return write_console(f, data); | |||||
| #else | |||||
| ignore_unused(os, data); | |||||
| #endif | |||||
| return false; | |||||
| } | |||||
| inline bool write_ostream_unicode(std::wostream&, fmt::basic_string_view<wchar_t>) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| // Write the content of buf to os. | |||||
| // It is a separate function rather than a part of vprint to simplify testing. | |||||
| template<typename Char> | |||||
| void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) | |||||
| { | |||||
| const Char* buf_data = buf.data(); | |||||
| using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; | |||||
| unsigned_streamsize size = buf.size(); | |||||
| unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>()); | |||||
| do | |||||
| { | |||||
| unsigned_streamsize n = size <= max_size ? size : max_size; | |||||
| os.write(buf_data, static_cast<std::streamsize>(n)); | |||||
| buf_data += n; | |||||
| size -= n; | |||||
| } while (size != 0); | |||||
| } | |||||
| template<typename Char, typename T> | |||||
| void format_value(buffer<Char>& buf, const T& value, locale_ref loc = locale_ref()) | |||||
| { | |||||
| auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf); | |||||
| auto&& output = std::basic_ostream<Char>(&format_buf); | |||||
| #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) | |||||
| if (loc) | |||||
| output.imbue(loc.get<std::locale>()); | |||||
| #endif | |||||
| output << value; | |||||
| output.exceptions(std::ios_base::failbit | std::ios_base::badbit); | |||||
| } | |||||
| template<typename T> | |||||
| struct streamed_view | |||||
| { | |||||
| const T& value; | |||||
| }; | |||||
| } // namespace detail | |||||
| // Formats an object of type T that has an overloaded ostream operator<<. | |||||
| template<typename Char> | |||||
| struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> | |||||
| { | |||||
| void set_debug_format() = delete; | |||||
| template<typename T, typename OutputIt> | |||||
| auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) const | |||||
| -> OutputIt | |||||
| { | |||||
| auto buffer = basic_memory_buffer<Char>(); | |||||
| format_value(buffer, value, ctx.locale()); | |||||
| return formatter<basic_string_view<Char>, Char>::format( | |||||
| {buffer.data(), buffer.size()}, ctx | |||||
| ); | |||||
| } | |||||
| }; | |||||
| using ostream_formatter = basic_ostream_formatter<char>; | |||||
| template<typename T, typename Char> | |||||
| struct formatter<detail::streamed_view<T>, Char> : basic_ostream_formatter<Char> | |||||
| { | |||||
| template<typename OutputIt> | |||||
| auto format(detail::streamed_view<T> view, basic_format_context<OutputIt, Char>& ctx) const -> OutputIt | |||||
| { | |||||
| return basic_ostream_formatter<Char>::format(view.value, ctx); | |||||
| } | |||||
| }; | |||||
| /** | |||||
| \rst | |||||
| Returns a view that formats `value` via an ostream ``operator<<``. | |||||
| **Example**:: | |||||
| fmt::print("Current thread id: {}\n", | |||||
| fmt::streamed(std::this_thread::get_id())); | |||||
| \endrst | |||||
| */ | |||||
| template<typename T> | |||||
| auto streamed(const T& value) -> detail::streamed_view<T> | |||||
| { | |||||
| return {value}; | |||||
| } | |||||
| namespace detail | |||||
| { | |||||
| // Formats an object of type T that has an overloaded ostream operator<<. | |||||
| template<typename T, typename Char> | |||||
| struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> : basic_ostream_formatter<Char> | |||||
| { | |||||
| using basic_ostream_formatter<Char>::format; | |||||
| }; | |||||
| inline void vprint_directly(std::ostream& os, string_view format_str, format_args args) | |||||
| { | |||||
| auto buffer = memory_buffer(); | |||||
| detail::vformat_to(buffer, format_str, args); | |||||
| detail::write_buffer(os, buffer); | |||||
| } | |||||
| } // namespace detail | |||||
| FMT_MODULE_EXPORT template<typename Char> | |||||
| void vprint(std::basic_ostream<Char>& os, basic_string_view<type_identity_t<Char>> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) | |||||
| { | |||||
| auto buffer = basic_memory_buffer<Char>(); | |||||
| detail::vformat_to(buffer, format_str, args); | |||||
| if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) | |||||
| return; | |||||
| detail::write_buffer(os, buffer); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Prints formatted data to the stream *os*. | |||||
| **Example**:: | |||||
| fmt::print(cerr, "Don't {}!", "panic"); | |||||
| \endrst | |||||
| */ | |||||
| FMT_MODULE_EXPORT template<typename... T> | |||||
| void print(std::ostream& os, format_string<T...> fmt, T&&... args) | |||||
| { | |||||
| const auto& vargs = fmt::make_format_args(args...); | |||||
| if (detail::is_utf8()) | |||||
| vprint(os, fmt, vargs); | |||||
| else | |||||
| detail::vprint_directly(os, fmt, vargs); | |||||
| } | |||||
| FMT_MODULE_EXPORT | |||||
| template<typename... Args> | |||||
| void print(std::wostream& os, basic_format_string<wchar_t, type_identity_t<Args>...> fmt, Args&&... args) | |||||
| { | |||||
| vprint(os, fmt, fmt::make_format_args<buffer_context<wchar_t>>(args...)); | |||||
| } | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_OSTREAM_H_ | |||||
| @@ -0,0 +1,808 @@ | |||||
| // Formatting library for C++ - legacy printf implementation | |||||
| // | |||||
| // Copyright (c) 2012 - 2016, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_PRINTF_H_ | |||||
| #define FMT_PRINTF_H_ | |||||
| #include <algorithm> // std::max | |||||
| #include <limits> // std::numeric_limits | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| template<typename T> | |||||
| struct printf_formatter | |||||
| { | |||||
| printf_formatter() = delete; | |||||
| }; | |||||
| template<typename Char> | |||||
| class basic_printf_parse_context : public basic_format_parse_context<Char> | |||||
| { | |||||
| using basic_format_parse_context<Char>::basic_format_parse_context; | |||||
| }; | |||||
| template<typename OutputIt, typename Char> | |||||
| class basic_printf_context | |||||
| { | |||||
| private: | |||||
| OutputIt out_; | |||||
| basic_format_args<basic_printf_context> args_; | |||||
| public: | |||||
| using char_type = Char; | |||||
| using format_arg = basic_format_arg<basic_printf_context>; | |||||
| using parse_context_type = basic_printf_parse_context<Char>; | |||||
| template<typename T> | |||||
| using formatter_type = printf_formatter<T>; | |||||
| /** | |||||
| \rst | |||||
| Constructs a ``printf_context`` object. References to the arguments are | |||||
| stored in the context object so make sure they have appropriate lifetimes. | |||||
| \endrst | |||||
| */ | |||||
| basic_printf_context(OutputIt out, basic_format_args<basic_printf_context> args) : | |||||
| out_(out), | |||||
| args_(args) | |||||
| { | |||||
| } | |||||
| OutputIt out() | |||||
| { | |||||
| return out_; | |||||
| } | |||||
| void advance_to(OutputIt it) | |||||
| { | |||||
| out_ = it; | |||||
| } | |||||
| detail::locale_ref locale() | |||||
| { | |||||
| return {}; | |||||
| } | |||||
| format_arg arg(int id) const | |||||
| { | |||||
| return args_.get(id); | |||||
| } | |||||
| FMT_CONSTEXPR void on_error(const char* message) | |||||
| { | |||||
| detail::error_handler().on_error(message); | |||||
| } | |||||
| }; | |||||
| FMT_BEGIN_DETAIL_NAMESPACE | |||||
| // Checks if a value fits in int - used to avoid warnings about comparing | |||||
| // signed and unsigned integers. | |||||
| template<bool IsSigned> | |||||
| struct int_checker | |||||
| { | |||||
| template<typename T> | |||||
| static bool fits_in_int(T value) | |||||
| { | |||||
| unsigned max = max_value<int>(); | |||||
| return value <= max; | |||||
| } | |||||
| static bool fits_in_int(bool) | |||||
| { | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| template<> | |||||
| struct int_checker<true> | |||||
| { | |||||
| template<typename T> | |||||
| static bool fits_in_int(T value) | |||||
| { | |||||
| return value >= (std::numeric_limits<int>::min)() && | |||||
| value <= max_value<int>(); | |||||
| } | |||||
| static bool fits_in_int(int) | |||||
| { | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| class printf_precision_handler | |||||
| { | |||||
| public: | |||||
| template<typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
| int operator()(T value) | |||||
| { | |||||
| if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) | |||||
| FMT_THROW(format_error("number is too big")); | |||||
| return (std::max)(static_cast<int>(value), 0); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
| int operator()(T) | |||||
| { | |||||
| FMT_THROW(format_error("precision is not integer")); | |||||
| return 0; | |||||
| } | |||||
| }; | |||||
| // An argument visitor that returns true iff arg is a zero integer. | |||||
| class is_zero_int | |||||
| { | |||||
| public: | |||||
| template<typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
| bool operator()(T value) | |||||
| { | |||||
| return value == 0; | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
| bool operator()(T) | |||||
| { | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| template<typename T> | |||||
| struct make_unsigned_or_bool : std::make_unsigned<T> | |||||
| { | |||||
| }; | |||||
| template<> | |||||
| struct make_unsigned_or_bool<bool> | |||||
| { | |||||
| using type = bool; | |||||
| }; | |||||
| template<typename T, typename Context> | |||||
| class arg_converter | |||||
| { | |||||
| private: | |||||
| using char_type = typename Context::char_type; | |||||
| basic_format_arg<Context>& arg_; | |||||
| char_type type_; | |||||
| public: | |||||
| arg_converter(basic_format_arg<Context>& arg, char_type type) : | |||||
| arg_(arg), | |||||
| type_(type) | |||||
| { | |||||
| } | |||||
| void operator()(bool value) | |||||
| { | |||||
| if (type_ != 's') | |||||
| operator()<bool>(value); | |||||
| } | |||||
| template<typename U, FMT_ENABLE_IF(std::is_integral<U>::value)> | |||||
| void operator()(U value) | |||||
| { | |||||
| bool is_signed = type_ == 'd' || type_ == 'i'; | |||||
| using target_type = conditional_t<std::is_same<T, void>::value, U, T>; | |||||
| if (const_check(sizeof(target_type) <= sizeof(int))) | |||||
| { | |||||
| // Extra casts are used to silence warnings. | |||||
| if (is_signed) | |||||
| { | |||||
| arg_ = detail::make_arg<Context>( | |||||
| static_cast<int>(static_cast<target_type>(value)) | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| using unsigned_type = typename make_unsigned_or_bool<target_type>::type; | |||||
| arg_ = detail::make_arg<Context>( | |||||
| static_cast<unsigned>(static_cast<unsigned_type>(value)) | |||||
| ); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| if (is_signed) | |||||
| { | |||||
| // glibc's printf doesn't sign extend arguments of smaller types: | |||||
| // std::printf("%lld", -42); // prints "4294967254" | |||||
| // but we don't have to do the same because it's a UB. | |||||
| arg_ = detail::make_arg<Context>(static_cast<long long>(value)); | |||||
| } | |||||
| else | |||||
| { | |||||
| arg_ = detail::make_arg<Context>( | |||||
| static_cast<typename make_unsigned_or_bool<U>::type>(value) | |||||
| ); | |||||
| } | |||||
| } | |||||
| } | |||||
| template<typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)> | |||||
| void operator()(U) | |||||
| { | |||||
| } // No conversion needed for non-integral types. | |||||
| }; | |||||
| // Converts an integer argument to T for printf, if T is an integral type. | |||||
| // If T is void, the argument is converted to corresponding signed or unsigned | |||||
| // type depending on the type specifier: 'd' and 'i' - signed, other - | |||||
| // unsigned). | |||||
| template<typename T, typename Context, typename Char> | |||||
| void convert_arg(basic_format_arg<Context>& arg, Char type) | |||||
| { | |||||
| visit_format_arg(arg_converter<T, Context>(arg, type), arg); | |||||
| } | |||||
| // Converts an integer argument to char for printf. | |||||
| template<typename Context> | |||||
| class char_converter | |||||
| { | |||||
| private: | |||||
| basic_format_arg<Context>& arg_; | |||||
| public: | |||||
| explicit char_converter(basic_format_arg<Context>& arg) : | |||||
| arg_(arg) | |||||
| { | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
| void operator()(T value) | |||||
| { | |||||
| arg_ = detail::make_arg<Context>( | |||||
| static_cast<typename Context::char_type>(value) | |||||
| ); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
| void operator()(T) | |||||
| { | |||||
| } // No conversion needed for non-integral types. | |||||
| }; | |||||
| // An argument visitor that return a pointer to a C string if argument is a | |||||
| // string or null otherwise. | |||||
| template<typename Char> | |||||
| struct get_cstring | |||||
| { | |||||
| template<typename T> | |||||
| const Char* operator()(T) | |||||
| { | |||||
| return nullptr; | |||||
| } | |||||
| const Char* operator()(const Char* s) | |||||
| { | |||||
| return s; | |||||
| } | |||||
| }; | |||||
| // Checks if an argument is a valid printf width specifier and sets | |||||
| // left alignment if it is negative. | |||||
| template<typename Char> | |||||
| class printf_width_handler | |||||
| { | |||||
| private: | |||||
| using format_specs = basic_format_specs<Char>; | |||||
| format_specs& specs_; | |||||
| public: | |||||
| explicit printf_width_handler(format_specs& specs) : | |||||
| specs_(specs) | |||||
| { | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||||
| unsigned operator()(T value) | |||||
| { | |||||
| auto width = static_cast<uint32_or_64_or_128_t<T>>(value); | |||||
| if (detail::is_negative(value)) | |||||
| { | |||||
| specs_.align = align::left; | |||||
| width = 0 - width; | |||||
| } | |||||
| unsigned int_max = max_value<int>(); | |||||
| if (width > int_max) | |||||
| FMT_THROW(format_error("number is too big")); | |||||
| return static_cast<unsigned>(width); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||||
| unsigned operator()(T) | |||||
| { | |||||
| FMT_THROW(format_error("width is not integer")); | |||||
| return 0; | |||||
| } | |||||
| }; | |||||
| // The ``printf`` argument formatter. | |||||
| template<typename OutputIt, typename Char> | |||||
| class printf_arg_formatter : public arg_formatter<Char> | |||||
| { | |||||
| private: | |||||
| using base = arg_formatter<Char>; | |||||
| using context_type = basic_printf_context<OutputIt, Char>; | |||||
| using format_specs = basic_format_specs<Char>; | |||||
| context_type& context_; | |||||
| OutputIt write_null_pointer(bool is_string = false) | |||||
| { | |||||
| auto s = this->specs; | |||||
| s.type = presentation_type::none; | |||||
| return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); | |||||
| } | |||||
| public: | |||||
| printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx) : | |||||
| base{iter, s, locale_ref()}, | |||||
| context_(ctx) | |||||
| { | |||||
| } | |||||
| OutputIt operator()(monostate value) | |||||
| { | |||||
| return base::operator()(value); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> | |||||
| OutputIt operator()(T value) | |||||
| { | |||||
| // MSVC2013 fails to compile separate overloads for bool and Char so use | |||||
| // std::is_same instead. | |||||
| if (std::is_same<T, Char>::value) | |||||
| { | |||||
| format_specs fmt_specs = this->specs; | |||||
| if (fmt_specs.type != presentation_type::none && | |||||
| fmt_specs.type != presentation_type::chr) | |||||
| { | |||||
| return (*this)(static_cast<int>(value)); | |||||
| } | |||||
| fmt_specs.sign = sign::none; | |||||
| fmt_specs.alt = false; | |||||
| fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. | |||||
| // align::numeric needs to be overwritten here since the '0' flag is | |||||
| // ignored for non-numeric types | |||||
| if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) | |||||
| fmt_specs.align = align::right; | |||||
| return write<Char>(this->out, static_cast<Char>(value), fmt_specs); | |||||
| } | |||||
| return base::operator()(value); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||||
| OutputIt operator()(T value) | |||||
| { | |||||
| return base::operator()(value); | |||||
| } | |||||
| /** Formats a null-terminated C string. */ | |||||
| OutputIt operator()(const char* value) | |||||
| { | |||||
| if (value) | |||||
| return base::operator()(value); | |||||
| return write_null_pointer(this->specs.type != presentation_type::pointer); | |||||
| } | |||||
| /** Formats a null-terminated wide C string. */ | |||||
| OutputIt operator()(const wchar_t* value) | |||||
| { | |||||
| if (value) | |||||
| return base::operator()(value); | |||||
| return write_null_pointer(this->specs.type != presentation_type::pointer); | |||||
| } | |||||
| OutputIt operator()(basic_string_view<Char> value) | |||||
| { | |||||
| return base::operator()(value); | |||||
| } | |||||
| /** Formats a pointer. */ | |||||
| OutputIt operator()(const void* value) | |||||
| { | |||||
| return value ? base::operator()(value) : write_null_pointer(); | |||||
| } | |||||
| /** Formats an argument of a custom (user-defined) type. */ | |||||
| OutputIt operator()(typename basic_format_arg<context_type>::handle handle) | |||||
| { | |||||
| auto parse_ctx = | |||||
| basic_printf_parse_context<Char>(basic_string_view<Char>()); | |||||
| handle.format(parse_ctx, context_); | |||||
| return this->out; | |||||
| } | |||||
| }; | |||||
| template<typename Char> | |||||
| void parse_flags(basic_format_specs<Char>& specs, const Char*& it, const Char* end) | |||||
| { | |||||
| for (; it != end; ++it) | |||||
| { | |||||
| switch (*it) | |||||
| { | |||||
| case '-': | |||||
| specs.align = align::left; | |||||
| break; | |||||
| case '+': | |||||
| specs.sign = sign::plus; | |||||
| break; | |||||
| case '0': | |||||
| specs.fill[0] = '0'; | |||||
| break; | |||||
| case ' ': | |||||
| if (specs.sign != sign::plus) | |||||
| { | |||||
| specs.sign = sign::space; | |||||
| } | |||||
| break; | |||||
| case '#': | |||||
| specs.alt = true; | |||||
| break; | |||||
| default: | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| template<typename Char, typename GetArg> | |||||
| int parse_header(const Char*& it, const Char* end, basic_format_specs<Char>& specs, GetArg get_arg) | |||||
| { | |||||
| int arg_index = -1; | |||||
| Char c = *it; | |||||
| if (c >= '0' && c <= '9') | |||||
| { | |||||
| // Parse an argument index (if followed by '$') or a width possibly | |||||
| // preceded with '0' flag(s). | |||||
| int value = parse_nonnegative_int(it, end, -1); | |||||
| if (it != end && *it == '$') | |||||
| { // value is an argument index | |||||
| ++it; | |||||
| arg_index = value != -1 ? value : max_value<int>(); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (c == '0') | |||||
| specs.fill[0] = '0'; | |||||
| if (value != 0) | |||||
| { | |||||
| // Nonzero value means that we parsed width and don't need to | |||||
| // parse it or flags again, so return now. | |||||
| if (value == -1) | |||||
| FMT_THROW(format_error("number is too big")); | |||||
| specs.width = value; | |||||
| return arg_index; | |||||
| } | |||||
| } | |||||
| } | |||||
| parse_flags(specs, it, end); | |||||
| // Parse width. | |||||
| if (it != end) | |||||
| { | |||||
| if (*it >= '0' && *it <= '9') | |||||
| { | |||||
| specs.width = parse_nonnegative_int(it, end, -1); | |||||
| if (specs.width == -1) | |||||
| FMT_THROW(format_error("number is too big")); | |||||
| } | |||||
| else if (*it == '*') | |||||
| { | |||||
| ++it; | |||||
| specs.width = static_cast<int>(visit_format_arg( | |||||
| detail::printf_width_handler<Char>(specs), get_arg(-1) | |||||
| )); | |||||
| } | |||||
| } | |||||
| return arg_index; | |||||
| } | |||||
| template<typename Char, typename Context> | |||||
| void vprintf(buffer<Char>& buf, basic_string_view<Char> format, basic_format_args<Context> args) | |||||
| { | |||||
| using OutputIt = buffer_appender<Char>; | |||||
| auto out = OutputIt(buf); | |||||
| auto context = basic_printf_context<OutputIt, Char>(out, args); | |||||
| auto parse_ctx = basic_printf_parse_context<Char>(format); | |||||
| // Returns the argument with specified index or, if arg_index is -1, the next | |||||
| // argument. | |||||
| auto get_arg = [&](int arg_index) | |||||
| { | |||||
| if (arg_index < 0) | |||||
| arg_index = parse_ctx.next_arg_id(); | |||||
| else | |||||
| parse_ctx.check_arg_id(--arg_index); | |||||
| return detail::get_arg(context, arg_index); | |||||
| }; | |||||
| const Char* start = parse_ctx.begin(); | |||||
| const Char* end = parse_ctx.end(); | |||||
| auto it = start; | |||||
| while (it != end) | |||||
| { | |||||
| if (!detail::find<false, Char>(it, end, '%', it)) | |||||
| { | |||||
| it = end; // detail::find leaves it == nullptr if it doesn't find '%' | |||||
| break; | |||||
| } | |||||
| Char c = *it++; | |||||
| if (it != end && *it == c) | |||||
| { | |||||
| out = detail::write( | |||||
| out, basic_string_view<Char>(start, detail::to_unsigned(it - start)) | |||||
| ); | |||||
| start = ++it; | |||||
| continue; | |||||
| } | |||||
| out = detail::write(out, basic_string_view<Char>(start, detail::to_unsigned(it - 1 - start))); | |||||
| basic_format_specs<Char> specs; | |||||
| specs.align = align::right; | |||||
| // Parse argument index, flags and width. | |||||
| int arg_index = parse_header(it, end, specs, get_arg); | |||||
| if (arg_index == 0) | |||||
| parse_ctx.on_error("argument not found"); | |||||
| // Parse precision. | |||||
| if (it != end && *it == '.') | |||||
| { | |||||
| ++it; | |||||
| c = it != end ? *it : 0; | |||||
| if ('0' <= c && c <= '9') | |||||
| { | |||||
| specs.precision = parse_nonnegative_int(it, end, 0); | |||||
| } | |||||
| else if (c == '*') | |||||
| { | |||||
| ++it; | |||||
| specs.precision = static_cast<int>( | |||||
| visit_format_arg(detail::printf_precision_handler(), get_arg(-1)) | |||||
| ); | |||||
| } | |||||
| else | |||||
| { | |||||
| specs.precision = 0; | |||||
| } | |||||
| } | |||||
| auto arg = get_arg(arg_index); | |||||
| // For d, i, o, u, x, and X conversion specifiers, if a precision is | |||||
| // specified, the '0' flag is ignored | |||||
| if (specs.precision >= 0 && arg.is_integral()) | |||||
| specs.fill[0] = | |||||
| ' '; // Ignore '0' flag for non-numeric types or if '-' present. | |||||
| if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) | |||||
| { | |||||
| auto str = visit_format_arg(detail::get_cstring<Char>(), arg); | |||||
| auto str_end = str + specs.precision; | |||||
| auto nul = std::find(str, str_end, Char()); | |||||
| arg = detail::make_arg<basic_printf_context<OutputIt, Char>>( | |||||
| basic_string_view<Char>( | |||||
| str, detail::to_unsigned(nul != str_end ? nul - str : specs.precision) | |||||
| ) | |||||
| ); | |||||
| } | |||||
| if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) | |||||
| specs.alt = false; | |||||
| if (specs.fill[0] == '0') | |||||
| { | |||||
| if (arg.is_arithmetic() && specs.align != align::left) | |||||
| specs.align = align::numeric; | |||||
| else | |||||
| specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-' | |||||
| // flag is also present. | |||||
| } | |||||
| // Parse length and convert the argument to the required type. | |||||
| c = it != end ? *it++ : 0; | |||||
| Char t = it != end ? *it : 0; | |||||
| using detail::convert_arg; | |||||
| switch (c) | |||||
| { | |||||
| case 'h': | |||||
| if (t == 'h') | |||||
| { | |||||
| ++it; | |||||
| t = it != end ? *it : 0; | |||||
| convert_arg<signed char>(arg, t); | |||||
| } | |||||
| else | |||||
| { | |||||
| convert_arg<short>(arg, t); | |||||
| } | |||||
| break; | |||||
| case 'l': | |||||
| if (t == 'l') | |||||
| { | |||||
| ++it; | |||||
| t = it != end ? *it : 0; | |||||
| convert_arg<long long>(arg, t); | |||||
| } | |||||
| else | |||||
| { | |||||
| convert_arg<long>(arg, t); | |||||
| } | |||||
| break; | |||||
| case 'j': | |||||
| convert_arg<intmax_t>(arg, t); | |||||
| break; | |||||
| case 'z': | |||||
| convert_arg<size_t>(arg, t); | |||||
| break; | |||||
| case 't': | |||||
| convert_arg<std::ptrdiff_t>(arg, t); | |||||
| break; | |||||
| case 'L': | |||||
| // printf produces garbage when 'L' is omitted for long double, no | |||||
| // need to do the same. | |||||
| break; | |||||
| default: | |||||
| --it; | |||||
| convert_arg<void>(arg, c); | |||||
| } | |||||
| // Parse type. | |||||
| if (it == end) | |||||
| FMT_THROW(format_error("invalid format string")); | |||||
| char type = static_cast<char>(*it++); | |||||
| if (arg.is_integral()) | |||||
| { | |||||
| // Normalize type. | |||||
| switch (type) | |||||
| { | |||||
| case 'i': | |||||
| case 'u': | |||||
| type = 'd'; | |||||
| break; | |||||
| case 'c': | |||||
| visit_format_arg( | |||||
| detail::char_converter<basic_printf_context<OutputIt, Char>>(arg), | |||||
| arg | |||||
| ); | |||||
| break; | |||||
| } | |||||
| } | |||||
| specs.type = parse_presentation_type(type); | |||||
| if (specs.type == presentation_type::none) | |||||
| parse_ctx.on_error("invalid type specifier"); | |||||
| start = it; | |||||
| // Format argument. | |||||
| out = visit_format_arg( | |||||
| detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg | |||||
| ); | |||||
| } | |||||
| detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start))); | |||||
| } | |||||
| FMT_END_DETAIL_NAMESPACE | |||||
| template<typename Char> | |||||
| using basic_printf_context_t = | |||||
| basic_printf_context<detail::buffer_appender<Char>, Char>; | |||||
| using printf_context = basic_printf_context_t<char>; | |||||
| using wprintf_context = basic_printf_context_t<wchar_t>; | |||||
| using printf_args = basic_format_args<printf_context>; | |||||
| using wprintf_args = basic_format_args<wprintf_context>; | |||||
| /** | |||||
| \rst | |||||
| Constructs an `~fmt::format_arg_store` object that contains references to | |||||
| arguments and can be implicitly converted to `~fmt::printf_args`. | |||||
| \endrst | |||||
| */ | |||||
| template<typename... T> | |||||
| inline auto make_printf_args(const T&... args) | |||||
| -> format_arg_store<printf_context, T...> | |||||
| { | |||||
| return {args...}; | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Constructs an `~fmt::format_arg_store` object that contains references to | |||||
| arguments and can be implicitly converted to `~fmt::wprintf_args`. | |||||
| \endrst | |||||
| */ | |||||
| template<typename... T> | |||||
| inline auto make_wprintf_args(const T&... args) | |||||
| -> format_arg_store<wprintf_context, T...> | |||||
| { | |||||
| return {args...}; | |||||
| } | |||||
| template<typename S, typename Char = char_t<S>> | |||||
| inline auto vsprintf( | |||||
| const S& fmt, | |||||
| basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args | |||||
| ) | |||||
| -> std::basic_string<Char> | |||||
| { | |||||
| basic_memory_buffer<Char> buffer; | |||||
| vprintf(buffer, detail::to_string_view(fmt), args); | |||||
| return to_string(buffer); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Formats arguments and returns the result as a string. | |||||
| **Example**:: | |||||
| std::string message = fmt::sprintf("The answer is %d", 42); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... T, typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> | |||||
| inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> | |||||
| { | |||||
| using context = basic_printf_context_t<Char>; | |||||
| return vsprintf(detail::to_string_view(fmt), fmt::make_format_args<context>(args...)); | |||||
| } | |||||
| template<typename S, typename Char = char_t<S>> | |||||
| inline auto vfprintf( | |||||
| std::FILE* f, const S& fmt, basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args | |||||
| ) | |||||
| -> int | |||||
| { | |||||
| basic_memory_buffer<Char> buffer; | |||||
| vprintf(buffer, detail::to_string_view(fmt), args); | |||||
| size_t size = buffer.size(); | |||||
| return std::fwrite(buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Prints formatted data to the file *f*. | |||||
| **Example**:: | |||||
| fmt::fprintf(stderr, "Don't %s!", "panic"); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... T, typename Char = char_t<S>> | |||||
| inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int | |||||
| { | |||||
| using context = basic_printf_context_t<Char>; | |||||
| return vfprintf(f, detail::to_string_view(fmt), fmt::make_format_args<context>(args...)); | |||||
| } | |||||
| template<typename S, typename Char = char_t<S>> | |||||
| inline auto vprintf( | |||||
| const S& fmt, | |||||
| basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args | |||||
| ) | |||||
| -> int | |||||
| { | |||||
| return vfprintf(stdout, detail::to_string_view(fmt), args); | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Prints formatted data to ``stdout``. | |||||
| **Example**:: | |||||
| fmt::printf("Elapsed time: %.2f seconds", 1.23); | |||||
| \endrst | |||||
| */ | |||||
| template<typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)> | |||||
| inline auto printf(const S& fmt, const T&... args) -> int | |||||
| { | |||||
| return vprintf( | |||||
| detail::to_string_view(fmt), | |||||
| fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...) | |||||
| ); | |||||
| } | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_PRINTF_H_ | |||||
| @@ -0,0 +1,850 @@ | |||||
| // Formatting library for C++ - experimental range support | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| // | |||||
| // Copyright (c) 2018 - present, Remotion (Igor Schulz) | |||||
| // All Rights Reserved | |||||
| // {fmt} support for ranges, containers and types tuple interface. | |||||
| #ifndef FMT_RANGES_H_ | |||||
| #define FMT_RANGES_H_ | |||||
| #include <initializer_list> | |||||
| #include <tuple> | |||||
| #include <type_traits> | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| namespace detail | |||||
| { | |||||
| template<typename RangeT, typename OutputIterator> | |||||
| OutputIterator copy(const RangeT& range, OutputIterator out) | |||||
| { | |||||
| for (auto it = range.begin(), end = range.end(); it != end; ++it) | |||||
| *out++ = *it; | |||||
| return out; | |||||
| } | |||||
| template<typename OutputIterator> | |||||
| OutputIterator copy(const char* str, OutputIterator out) | |||||
| { | |||||
| while (*str) | |||||
| *out++ = *str++; | |||||
| return out; | |||||
| } | |||||
| template<typename OutputIterator> | |||||
| OutputIterator copy(char ch, OutputIterator out) | |||||
| { | |||||
| *out++ = ch; | |||||
| return out; | |||||
| } | |||||
| template<typename OutputIterator> | |||||
| OutputIterator copy(wchar_t ch, OutputIterator out) | |||||
| { | |||||
| *out++ = ch; | |||||
| return out; | |||||
| } | |||||
| // Returns true if T has a std::string-like interface, like std::string_view. | |||||
| template<typename T> | |||||
| class is_std_string_like | |||||
| { | |||||
| template<typename U> | |||||
| static auto check(U* p) | |||||
| -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); | |||||
| template<typename> | |||||
| static void check(...); | |||||
| public: | |||||
| static constexpr const bool value = | |||||
| is_string<T>::value || | |||||
| std::is_convertible<T, std_string_view<char>>::value || | |||||
| !std::is_void<decltype(check<T>(nullptr))>::value; | |||||
| }; | |||||
| template<typename Char> | |||||
| struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| class is_map | |||||
| { | |||||
| template<typename U> | |||||
| static auto check(U*) -> typename U::mapped_type; | |||||
| template<typename> | |||||
| static void check(...); | |||||
| public: | |||||
| #ifdef FMT_FORMAT_MAP_AS_LIST | |||||
| static constexpr const bool value = false; | |||||
| #else | |||||
| static constexpr const bool value = | |||||
| !std::is_void<decltype(check<T>(nullptr))>::value; | |||||
| #endif | |||||
| }; | |||||
| template<typename T> | |||||
| class is_set | |||||
| { | |||||
| template<typename U> | |||||
| static auto check(U*) -> typename U::key_type; | |||||
| template<typename> | |||||
| static void check(...); | |||||
| public: | |||||
| #ifdef FMT_FORMAT_SET_AS_LIST | |||||
| static constexpr const bool value = false; | |||||
| #else | |||||
| static constexpr const bool value = | |||||
| !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; | |||||
| #endif | |||||
| }; | |||||
| template<typename... Ts> | |||||
| struct conditional_helper | |||||
| { | |||||
| }; | |||||
| template<typename T, typename _ = void> | |||||
| struct is_range_ : std::false_type | |||||
| { | |||||
| }; | |||||
| #if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800 | |||||
| #define FMT_DECLTYPE_RETURN(val) \ | |||||
| ->decltype(val) \ | |||||
| { \ | |||||
| return val; \ | |||||
| } \ | |||||
| static_assert( \ | |||||
| true, "" \ | |||||
| ) // This makes it so that a semicolon is required after the | |||||
| // macro, which helps clang-format handle the formatting. | |||||
| // C array overload | |||||
| template<typename T, std::size_t N> | |||||
| auto range_begin(const T (&arr)[N]) -> const T* | |||||
| { | |||||
| return arr; | |||||
| } | |||||
| template<typename T, std::size_t N> | |||||
| auto range_end(const T (&arr)[N]) -> const T* | |||||
| { | |||||
| return arr + N; | |||||
| } | |||||
| template<typename T, typename Enable = void> | |||||
| struct has_member_fn_begin_end_t : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> : std::true_type | |||||
| { | |||||
| }; | |||||
| // Member function overload | |||||
| template<typename T> | |||||
| auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin()); | |||||
| template<typename T> | |||||
| auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end()); | |||||
| // ADL overload. Only participates in overload resolution if member functions | |||||
| // are not found. | |||||
| template<typename T> | |||||
| auto range_begin(T&& rng) | |||||
| -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, decltype(begin(static_cast<T&&>(rng)))> | |||||
| { | |||||
| return begin(static_cast<T&&>(rng)); | |||||
| } | |||||
| template<typename T> | |||||
| auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value, decltype(end(static_cast<T&&>(rng)))> | |||||
| { | |||||
| return end(static_cast<T&&>(rng)); | |||||
| } | |||||
| template<typename T, typename Enable = void> | |||||
| struct has_const_begin_end : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename T, typename Enable = void> | |||||
| struct has_mutable_begin_end : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct has_const_begin_end< | |||||
| T, | |||||
| void_t< | |||||
| decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())), | |||||
| decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct has_mutable_begin_end< | |||||
| T, | |||||
| void_t<decltype(detail::range_begin(std::declval<T>())), decltype(detail::range_end(std::declval<T>())), enable_if_t<std::is_copy_constructible<T>::value>>> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct is_range_<T, void> : std::integral_constant<bool, (has_const_begin_end<T>::value || has_mutable_begin_end<T>::value)> | |||||
| { | |||||
| }; | |||||
| #undef FMT_DECLTYPE_RETURN | |||||
| #endif | |||||
| // tuple_size and tuple_element check. | |||||
| template<typename T> | |||||
| class is_tuple_like_ | |||||
| { | |||||
| template<typename U> | |||||
| static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); | |||||
| template<typename> | |||||
| static void check(...); | |||||
| public: | |||||
| static constexpr const bool value = | |||||
| !std::is_void<decltype(check<T>(nullptr))>::value; | |||||
| }; | |||||
| // Check for integer_sequence | |||||
| #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 | |||||
| template<typename T, T... N> | |||||
| using integer_sequence = std::integer_sequence<T, N...>; | |||||
| template<size_t... N> | |||||
| using index_sequence = std::index_sequence<N...>; | |||||
| template<size_t N> | |||||
| using make_index_sequence = std::make_index_sequence<N>; | |||||
| #else | |||||
| template<typename T, T... N> | |||||
| struct integer_sequence | |||||
| { | |||||
| using value_type = T; | |||||
| static FMT_CONSTEXPR size_t size() | |||||
| { | |||||
| return sizeof...(N); | |||||
| } | |||||
| }; | |||||
| template<size_t... N> | |||||
| using index_sequence = integer_sequence<size_t, N...>; | |||||
| template<typename T, size_t N, T... Ns> | |||||
| struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> | |||||
| { | |||||
| }; | |||||
| template<typename T, T... Ns> | |||||
| struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> | |||||
| { | |||||
| }; | |||||
| template<size_t N> | |||||
| using make_index_sequence = make_integer_sequence<size_t, N>; | |||||
| #endif | |||||
| template<typename T> | |||||
| using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>; | |||||
| template<typename T, typename C, bool = is_tuple_like_<T>::value> | |||||
| class is_tuple_formattable_ | |||||
| { | |||||
| public: | |||||
| static constexpr const bool value = false; | |||||
| }; | |||||
| template<typename T, typename C> | |||||
| class is_tuple_formattable_<T, C, true> | |||||
| { | |||||
| template<std::size_t... I> | |||||
| static std::true_type check2(index_sequence<I...>, integer_sequence<bool, (I == I)...>); | |||||
| static std::false_type check2(...); | |||||
| template<std::size_t... I> | |||||
| static decltype(check2( | |||||
| index_sequence<I...>{}, | |||||
| integer_sequence< | |||||
| bool, | |||||
| (is_formattable<typename std::tuple_element<I, T>::type, C>::value)...>{} | |||||
| )) check(index_sequence<I...>); | |||||
| public: | |||||
| static constexpr const bool value = | |||||
| decltype(check(tuple_index_sequence<T>{}))::value; | |||||
| }; | |||||
| template<class Tuple, class F, size_t... Is> | |||||
| void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept | |||||
| { | |||||
| using std::get; | |||||
| // using free function get<I>(T) now. | |||||
| const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; | |||||
| (void)_; // blocks warnings | |||||
| } | |||||
| template<class T> | |||||
| FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( | |||||
| T const& | |||||
| ) | |||||
| { | |||||
| return {}; | |||||
| } | |||||
| template<class Tuple, class F> | |||||
| void for_each(Tuple&& tup, F&& f) | |||||
| { | |||||
| const auto indexes = get_indexes(tup); | |||||
| for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | |||||
| } | |||||
| #if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 | |||||
| // Older MSVC doesn't get the reference type correctly for arrays. | |||||
| template<typename R> | |||||
| struct range_reference_type_impl | |||||
| { | |||||
| using type = decltype(*detail::range_begin(std::declval<R&>())); | |||||
| }; | |||||
| template<typename T, std::size_t N> | |||||
| struct range_reference_type_impl<T[N]> | |||||
| { | |||||
| using type = T&; | |||||
| }; | |||||
| template<typename T> | |||||
| using range_reference_type = typename range_reference_type_impl<T>::type; | |||||
| #else | |||||
| template<typename Range> | |||||
| using range_reference_type = | |||||
| decltype(*detail::range_begin(std::declval<Range&>())); | |||||
| #endif | |||||
| // We don't use the Range's value_type for anything, but we do need the Range's | |||||
| // reference type, with cv-ref stripped. | |||||
| template<typename Range> | |||||
| using uncvref_type = remove_cvref_t<range_reference_type<Range>>; | |||||
| template<typename Range> | |||||
| using uncvref_first_type = | |||||
| remove_cvref_t<decltype(std::declval<range_reference_type<Range>>().first)>; | |||||
| template<typename Range> | |||||
| using uncvref_second_type = remove_cvref_t< | |||||
| decltype(std::declval<range_reference_type<Range>>().second)>; | |||||
| template<typename OutputIt> | |||||
| OutputIt write_delimiter(OutputIt out) | |||||
| { | |||||
| *out++ = ','; | |||||
| *out++ = ' '; | |||||
| return out; | |||||
| } | |||||
| template<typename Char, typename OutputIt> | |||||
| auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt | |||||
| { | |||||
| return write_escaped_string(out, str); | |||||
| } | |||||
| template<typename Char, typename OutputIt, typename T, FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)> | |||||
| inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt | |||||
| { | |||||
| auto sv = std_string_view<Char>(str); | |||||
| return write_range_entry<Char>(out, basic_string_view<Char>(sv)); | |||||
| } | |||||
| template<typename Char, typename OutputIt, typename Arg, FMT_ENABLE_IF(std::is_same<Arg, Char>::value)> | |||||
| OutputIt write_range_entry(OutputIt out, const Arg v) | |||||
| { | |||||
| return write_escaped_char(out, v); | |||||
| } | |||||
| template< | |||||
| typename Char, | |||||
| typename OutputIt, | |||||
| typename Arg, | |||||
| FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value && !std::is_same<Arg, Char>::value)> | |||||
| OutputIt write_range_entry(OutputIt out, const Arg& v) | |||||
| { | |||||
| return write<Char>(out, v); | |||||
| } | |||||
| } // namespace detail | |||||
| template<typename T> | |||||
| struct is_tuple_like | |||||
| { | |||||
| static constexpr const bool value = | |||||
| detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; | |||||
| }; | |||||
| template<typename T, typename C> | |||||
| struct is_tuple_formattable | |||||
| { | |||||
| static constexpr const bool value = | |||||
| detail::is_tuple_formattable_<T, C>::value; | |||||
| }; | |||||
| template<typename TupleT, typename Char> | |||||
| struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value && fmt::is_tuple_formattable<TupleT, Char>::value>> | |||||
| { | |||||
| private: | |||||
| basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; | |||||
| basic_string_view<Char> opening_bracket_ = | |||||
| detail::string_literal<Char, '('>{}; | |||||
| basic_string_view<Char> closing_bracket_ = | |||||
| detail::string_literal<Char, ')'>{}; | |||||
| // C++11 generic lambda for format(). | |||||
| template<typename FormatContext> | |||||
| struct format_each | |||||
| { | |||||
| template<typename T> | |||||
| void operator()(const T& v) | |||||
| { | |||||
| if (i > 0) | |||||
| out = detail::copy_str<Char>(separator, out); | |||||
| out = detail::write_range_entry<Char>(out, v); | |||||
| ++i; | |||||
| } | |||||
| int i; | |||||
| typename FormatContext::iterator& out; | |||||
| basic_string_view<Char> separator; | |||||
| }; | |||||
| public: | |||||
| FMT_CONSTEXPR formatter() | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) | |||||
| { | |||||
| separator_ = sep; | |||||
| } | |||||
| FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, basic_string_view<Char> close) | |||||
| { | |||||
| opening_bracket_ = open; | |||||
| closing_bracket_ = close; | |||||
| } | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return ctx.begin(); | |||||
| } | |||||
| template<typename FormatContext = format_context> | |||||
| auto format(const TupleT& values, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| auto out = ctx.out(); | |||||
| out = detail::copy_str<Char>(opening_bracket_, out); | |||||
| detail::for_each(values, format_each<FormatContext>{0, out, separator_}); | |||||
| out = detail::copy_str<Char>(closing_bracket_, out); | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| template<typename T, typename Char> | |||||
| struct is_range | |||||
| { | |||||
| static constexpr const bool value = | |||||
| detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && | |||||
| !std::is_convertible<T, std::basic_string<Char>>::value && | |||||
| !std::is_convertible<T, detail::std_string_view<Char>>::value; | |||||
| }; | |||||
| namespace detail | |||||
| { | |||||
| template<typename Context> | |||||
| struct range_mapper | |||||
| { | |||||
| using mapper = arg_mapper<Context>; | |||||
| template<typename T, FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)> | |||||
| static auto map(T&& value) -> T&& | |||||
| { | |||||
| return static_cast<T&&>(value); | |||||
| } | |||||
| template<typename T, FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)> | |||||
| static auto map(T&& value) | |||||
| -> decltype(mapper().map(static_cast<T&&>(value))) | |||||
| { | |||||
| return mapper().map(static_cast<T&&>(value)); | |||||
| } | |||||
| }; | |||||
| template<typename Char, typename Element> | |||||
| using range_formatter_type = conditional_t< | |||||
| is_formattable<Element, Char>::value, | |||||
| formatter<remove_cvref_t<decltype(range_mapper<buffer_context<Char>>{}.map(std::declval<Element>()))>, Char>, | |||||
| fallback_formatter<Element, Char>>; | |||||
| template<typename R> | |||||
| using maybe_const_range = | |||||
| conditional_t<has_const_begin_end<R>::value, const R, R>; | |||||
| // Workaround a bug in MSVC 2015 and earlier. | |||||
| #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 | |||||
| template<typename R, typename Char> | |||||
| struct is_formattable_delayed : disjunction<is_formattable<uncvref_type<maybe_const_range<R>>, Char>, has_fallback_formatter<uncvref_type<maybe_const_range<R>>, Char>> | |||||
| { | |||||
| }; | |||||
| #endif | |||||
| } // namespace detail | |||||
| template<typename T, typename Char, typename Enable = void> | |||||
| struct range_formatter; | |||||
| template<typename T, typename Char> | |||||
| struct range_formatter< | |||||
| T, | |||||
| Char, | |||||
| enable_if_t<conjunction< | |||||
| std::is_same<T, remove_cvref_t<T>>, | |||||
| disjunction<is_formattable<T, Char>, detail::has_fallback_formatter<T, Char>>>::value>> | |||||
| { | |||||
| private: | |||||
| detail::range_formatter_type<Char, T> underlying_; | |||||
| bool custom_specs_ = false; | |||||
| basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{}; | |||||
| basic_string_view<Char> opening_bracket_ = | |||||
| detail::string_literal<Char, '['>{}; | |||||
| basic_string_view<Char> closing_bracket_ = | |||||
| detail::string_literal<Char, ']'>{}; | |||||
| template<class U> | |||||
| FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, int) | |||||
| -> decltype(u.set_debug_format()) | |||||
| { | |||||
| u.set_debug_format(); | |||||
| } | |||||
| template<class U> | |||||
| FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR void maybe_set_debug_format() | |||||
| { | |||||
| maybe_set_debug_format(underlying_, 0); | |||||
| } | |||||
| public: | |||||
| FMT_CONSTEXPR range_formatter() | |||||
| { | |||||
| } | |||||
| FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& | |||||
| { | |||||
| return underlying_; | |||||
| } | |||||
| FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) | |||||
| { | |||||
| separator_ = sep; | |||||
| } | |||||
| FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open, basic_string_view<Char> close) | |||||
| { | |||||
| opening_bracket_ = open; | |||||
| closing_bracket_ = close; | |||||
| } | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| auto it = ctx.begin(); | |||||
| auto end = ctx.end(); | |||||
| if (it == end || *it == '}') | |||||
| { | |||||
| maybe_set_debug_format(); | |||||
| return it; | |||||
| } | |||||
| if (*it == 'n') | |||||
| { | |||||
| set_brackets({}, {}); | |||||
| ++it; | |||||
| } | |||||
| if (*it == '}') | |||||
| { | |||||
| maybe_set_debug_format(); | |||||
| return it; | |||||
| } | |||||
| if (*it != ':') | |||||
| FMT_THROW(format_error("no other top-level range formatters supported")); | |||||
| custom_specs_ = true; | |||||
| ++it; | |||||
| ctx.advance_to(it); | |||||
| return underlying_.parse(ctx); | |||||
| } | |||||
| template<typename R, class FormatContext> | |||||
| auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) | |||||
| { | |||||
| detail::range_mapper<buffer_context<Char>> mapper; | |||||
| auto out = ctx.out(); | |||||
| out = detail::copy_str<Char>(opening_bracket_, out); | |||||
| int i = 0; | |||||
| auto it = detail::range_begin(range); | |||||
| auto end = detail::range_end(range); | |||||
| for (; it != end; ++it) | |||||
| { | |||||
| if (i > 0) | |||||
| out = detail::copy_str<Char>(separator_, out); | |||||
| ; | |||||
| ctx.advance_to(out); | |||||
| out = underlying_.format(mapper.map(*it), ctx); | |||||
| ++i; | |||||
| } | |||||
| out = detail::copy_str<Char>(closing_bracket_, out); | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| enum class range_format | |||||
| { | |||||
| disabled, | |||||
| map, | |||||
| set, | |||||
| sequence, | |||||
| string, | |||||
| debug_string | |||||
| }; | |||||
| namespace detail | |||||
| { | |||||
| template<typename T> | |||||
| struct range_format_kind_ | |||||
| { | |||||
| static constexpr auto value = std::is_same<range_reference_type<T>, T>::value ? range_format::disabled : is_map<T>::value ? range_format::map : | |||||
| is_set<T>::value ? range_format::set : | |||||
| range_format::sequence; | |||||
| }; | |||||
| template<range_format K, typename R, typename Char, typename Enable = void> | |||||
| struct range_default_formatter; | |||||
| template<range_format K> | |||||
| using range_format_constant = std::integral_constant<range_format, K>; | |||||
| template<range_format K, typename R, typename Char> | |||||
| struct range_default_formatter< | |||||
| K, | |||||
| R, | |||||
| Char, | |||||
| enable_if_t<(K == range_format::sequence || K == range_format::map || K == range_format::set)>> | |||||
| { | |||||
| using range_type = detail::maybe_const_range<R>; | |||||
| range_formatter<detail::uncvref_type<range_type>, Char> underlying_; | |||||
| FMT_CONSTEXPR range_default_formatter() | |||||
| { | |||||
| init(range_format_constant<K>()); | |||||
| } | |||||
| FMT_CONSTEXPR void init(range_format_constant<range_format::set>) | |||||
| { | |||||
| underlying_.set_brackets(detail::string_literal<Char, '{'>{}, detail::string_literal<Char, '}'>{}); | |||||
| } | |||||
| FMT_CONSTEXPR void init(range_format_constant<range_format::map>) | |||||
| { | |||||
| underlying_.set_brackets(detail::string_literal<Char, '{'>{}, detail::string_literal<Char, '}'>{}); | |||||
| underlying_.underlying().set_brackets({}, {}); | |||||
| underlying_.underlying().set_separator( | |||||
| detail::string_literal<Char, ':', ' '>{} | |||||
| ); | |||||
| } | |||||
| FMT_CONSTEXPR void init(range_format_constant<range_format::sequence>) | |||||
| { | |||||
| } | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return underlying_.parse(ctx); | |||||
| } | |||||
| template<typename FormatContext> | |||||
| auto format(range_type& range, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| return underlying_.format(range, ctx); | |||||
| } | |||||
| }; | |||||
| } // namespace detail | |||||
| template<typename T, typename Char, typename Enable = void> | |||||
| struct range_format_kind : conditional_t<is_range<T, Char>::value, detail::range_format_kind_<T>, std::integral_constant<range_format, range_format::disabled>> | |||||
| { | |||||
| }; | |||||
| template<typename R, typename Char> | |||||
| struct formatter< | |||||
| R, | |||||
| Char, | |||||
| enable_if_t<conjunction<bool_constant<range_format_kind<R, Char>::value != range_format::disabled> | |||||
| // Workaround a bug in MSVC 2015 and earlier. | |||||
| #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 | |||||
| , | |||||
| detail::is_formattable_delayed<R, Char> | |||||
| #endif | |||||
| >::value>> : detail::range_default_formatter<range_format_kind<R, Char>::value, R, Char> | |||||
| { | |||||
| }; | |||||
| template<typename Char, typename... T> | |||||
| struct tuple_join_view : detail::view | |||||
| { | |||||
| const std::tuple<T...>& tuple; | |||||
| basic_string_view<Char> sep; | |||||
| tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s) : | |||||
| tuple(t), | |||||
| sep{s} | |||||
| { | |||||
| } | |||||
| }; | |||||
| template<typename Char, typename... T> | |||||
| using tuple_arg_join = tuple_join_view<Char, T...>; | |||||
| // Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers | |||||
| // support in tuple_join. It is disabled by default because of issues with | |||||
| // the dynamic width and precision. | |||||
| #ifndef FMT_TUPLE_JOIN_SPECIFIERS | |||||
| #define FMT_TUPLE_JOIN_SPECIFIERS 0 | |||||
| #endif | |||||
| template<typename Char, typename... T> | |||||
| struct formatter<tuple_join_view<Char, T...>, Char> | |||||
| { | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>()); | |||||
| } | |||||
| template<typename FormatContext> | |||||
| auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) const -> typename FormatContext::iterator | |||||
| { | |||||
| return do_format(value, ctx, std::integral_constant<size_t, sizeof...(T)>()); | |||||
| } | |||||
| private: | |||||
| std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_; | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto do_parse(ParseContext& ctx, std::integral_constant<size_t, 0>) | |||||
| -> decltype(ctx.begin()) | |||||
| { | |||||
| return ctx.begin(); | |||||
| } | |||||
| template<typename ParseContext, size_t N> | |||||
| FMT_CONSTEXPR auto do_parse(ParseContext& ctx, std::integral_constant<size_t, N>) | |||||
| -> decltype(ctx.begin()) | |||||
| { | |||||
| auto end = ctx.begin(); | |||||
| #if FMT_TUPLE_JOIN_SPECIFIERS | |||||
| end = std::get<sizeof...(T) - N>(formatters_).parse(ctx); | |||||
| if (N > 1) | |||||
| { | |||||
| auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>()); | |||||
| if (end != end1) | |||||
| FMT_THROW(format_error("incompatible format specs for tuple elements")); | |||||
| } | |||||
| #endif | |||||
| return end; | |||||
| } | |||||
| template<typename FormatContext> | |||||
| auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx, std::integral_constant<size_t, 0>) const -> | |||||
| typename FormatContext::iterator | |||||
| { | |||||
| return ctx.out(); | |||||
| } | |||||
| template<typename FormatContext, size_t N> | |||||
| auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx, std::integral_constant<size_t, N>) const -> | |||||
| typename FormatContext::iterator | |||||
| { | |||||
| auto out = std::get<sizeof...(T) - N>(formatters_) | |||||
| .format(std::get<sizeof...(T) - N>(value.tuple), ctx); | |||||
| if (N > 1) | |||||
| { | |||||
| out = std::copy(value.sep.begin(), value.sep.end(), out); | |||||
| ctx.advance_to(out); | |||||
| return do_format(value, ctx, std::integral_constant<size_t, N - 1>()); | |||||
| } | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| /** | |||||
| \rst | |||||
| Returns an object that formats `tuple` with elements separated by `sep`. | |||||
| **Example**:: | |||||
| std::tuple<int, char> t = {1, 'a'}; | |||||
| fmt::print("{}", fmt::join(t, ", ")); | |||||
| // Output: "1, a" | |||||
| \endrst | |||||
| */ | |||||
| template<typename... T> | |||||
| FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) | |||||
| -> tuple_join_view<char, T...> | |||||
| { | |||||
| return {tuple, sep}; | |||||
| } | |||||
| template<typename... T> | |||||
| FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep) | |||||
| -> tuple_join_view<wchar_t, T...> | |||||
| { | |||||
| return {tuple, sep}; | |||||
| } | |||||
| /** | |||||
| \rst | |||||
| Returns an object that formats `initializer_list` with elements separated by | |||||
| `sep`. | |||||
| **Example**:: | |||||
| fmt::print("{}", fmt::join({1, 2, 3}, ", ")); | |||||
| // Output: "1, 2, 3" | |||||
| \endrst | |||||
| */ | |||||
| template<typename T> | |||||
| auto join(std::initializer_list<T> list, string_view sep) | |||||
| -> join_view<const T*, const T*> | |||||
| { | |||||
| return join(std::begin(list), std::end(list), sep); | |||||
| } | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_RANGES_H_ | |||||
| @@ -0,0 +1,202 @@ | |||||
| // Formatting library for C++ - formatters for standard library types | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_STD_H_ | |||||
| #define FMT_STD_H_ | |||||
| #include <thread> | |||||
| #include <type_traits> | |||||
| #include <utility> | |||||
| #include "ostream.h" | |||||
| #if FMT_HAS_INCLUDE(<version>) | |||||
| #include <version> | |||||
| #endif | |||||
| // Checking FMT_CPLUSPLUS for warning suppression in MSVC. | |||||
| #if FMT_CPLUSPLUS >= 201703L | |||||
| #if FMT_HAS_INCLUDE(<filesystem>) | |||||
| #include <filesystem> | |||||
| #endif | |||||
| #if FMT_HAS_INCLUDE(<variant>) | |||||
| #include <variant> | |||||
| #endif | |||||
| #endif | |||||
| #ifdef __cpp_lib_filesystem | |||||
| FMT_BEGIN_NAMESPACE | |||||
| namespace detail | |||||
| { | |||||
| template<typename Char> | |||||
| void write_escaped_path(basic_memory_buffer<Char>& quoted, const std::filesystem::path& p) | |||||
| { | |||||
| write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>()); | |||||
| } | |||||
| #ifdef _WIN32 | |||||
| template<> | |||||
| inline void write_escaped_path<char>(basic_memory_buffer<char>& quoted, const std::filesystem::path& p) | |||||
| { | |||||
| auto s = p.u8string(); | |||||
| write_escaped_string<char>( | |||||
| std::back_inserter(quoted), | |||||
| string_view(reinterpret_cast<const char*>(s.c_str()), s.size()) | |||||
| ); | |||||
| } | |||||
| #endif | |||||
| template<> | |||||
| inline void write_escaped_path<std::filesystem::path::value_type>( | |||||
| basic_memory_buffer<std::filesystem::path::value_type>& quoted, | |||||
| const std::filesystem::path& p | |||||
| ) | |||||
| { | |||||
| write_escaped_string<std::filesystem::path::value_type>( | |||||
| std::back_inserter(quoted), p.native() | |||||
| ); | |||||
| } | |||||
| } // namespace detail | |||||
| template<typename Char> | |||||
| struct formatter<std::filesystem::path, Char> : formatter<basic_string_view<Char>> | |||||
| { | |||||
| template<typename FormatContext> | |||||
| auto format(const std::filesystem::path& p, FormatContext& ctx) const -> | |||||
| typename FormatContext::iterator | |||||
| { | |||||
| basic_memory_buffer<Char> quoted; | |||||
| detail::write_escaped_path(quoted, p); | |||||
| return formatter<basic_string_view<Char>>::format( | |||||
| basic_string_view<Char>(quoted.data(), quoted.size()), ctx | |||||
| ); | |||||
| } | |||||
| }; | |||||
| FMT_END_NAMESPACE | |||||
| #endif | |||||
| FMT_BEGIN_NAMESPACE | |||||
| template<typename Char> | |||||
| struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> | |||||
| { | |||||
| }; | |||||
| FMT_END_NAMESPACE | |||||
| #ifdef __cpp_lib_variant | |||||
| FMT_BEGIN_NAMESPACE | |||||
| template<typename Char> | |||||
| struct formatter<std::monostate, Char> | |||||
| { | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return ctx.begin(); | |||||
| } | |||||
| template<typename FormatContext> | |||||
| auto format(const std::monostate&, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| auto out = ctx.out(); | |||||
| out = detail::write<Char>(out, "monostate"); | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| namespace detail | |||||
| { | |||||
| template<typename T> | |||||
| using variant_index_sequence = | |||||
| std::make_index_sequence<std::variant_size<T>::value>; | |||||
| // variant_size and variant_alternative check. | |||||
| template<typename T, typename U = void> | |||||
| struct is_variant_like_ : std::false_type | |||||
| { | |||||
| }; | |||||
| template<typename T> | |||||
| struct is_variant_like_<T, std::void_t<decltype(std::variant_size<T>::value)>> : std::true_type | |||||
| { | |||||
| }; | |||||
| // formattable element check | |||||
| template<typename T, typename C> | |||||
| class is_variant_formattable_ | |||||
| { | |||||
| template<std::size_t... I> | |||||
| static std::conjunction< | |||||
| is_formattable<std::variant_alternative_t<I, T>, C>...> | |||||
| check(std::index_sequence<I...>); | |||||
| public: | |||||
| static constexpr const bool value = | |||||
| decltype(check(variant_index_sequence<T>{}))::value; | |||||
| }; | |||||
| template<typename Char, typename OutputIt, typename T> | |||||
| auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt | |||||
| { | |||||
| if constexpr (is_string<T>::value) | |||||
| return write_escaped_string<Char>(out, detail::to_string_view(v)); | |||||
| else if constexpr (std::is_same_v<T, Char>) | |||||
| return write_escaped_char(out, v); | |||||
| else | |||||
| return write<Char>(out, v); | |||||
| } | |||||
| } // namespace detail | |||||
| template<typename T> | |||||
| struct is_variant_like | |||||
| { | |||||
| static constexpr const bool value = detail::is_variant_like_<T>::value; | |||||
| }; | |||||
| template<typename T, typename C> | |||||
| struct is_variant_formattable | |||||
| { | |||||
| static constexpr const bool value = | |||||
| detail::is_variant_formattable_<T, C>::value; | |||||
| }; | |||||
| template<typename Variant, typename Char> | |||||
| struct formatter< | |||||
| Variant, | |||||
| Char, | |||||
| std::enable_if_t<std::conjunction_v< | |||||
| is_variant_like<Variant>, | |||||
| is_variant_formattable<Variant, Char>>>> | |||||
| { | |||||
| template<typename ParseContext> | |||||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) | |||||
| { | |||||
| return ctx.begin(); | |||||
| } | |||||
| template<typename FormatContext> | |||||
| auto format(const Variant& value, FormatContext& ctx) const | |||||
| -> decltype(ctx.out()) | |||||
| { | |||||
| auto out = ctx.out(); | |||||
| out = detail::write<Char>(out, "variant("); | |||||
| std::visit( | |||||
| [&](const auto& v) | |||||
| { | |||||
| out = detail::write_variant_alternative<Char>(out, v); | |||||
| }, | |||||
| value | |||||
| ); | |||||
| *out++ = ')'; | |||||
| return out; | |||||
| } | |||||
| }; | |||||
| FMT_END_NAMESPACE | |||||
| #endif | |||||
| #endif // FMT_STD_H_ | |||||
| @@ -0,0 +1,243 @@ | |||||
| // Formatting library for C++ - optional wchar_t and exotic character support | |||||
| // | |||||
| // Copyright (c) 2012 - present, Victor Zverovich | |||||
| // All rights reserved. | |||||
| // | |||||
| // For the license information refer to format.h. | |||||
| #ifndef FMT_XCHAR_H_ | |||||
| #define FMT_XCHAR_H_ | |||||
| #include <cwchar> | |||||
| #include "format.h" | |||||
| FMT_BEGIN_NAMESPACE | |||||
| namespace detail | |||||
| { | |||||
| template<typename T> | |||||
| using is_exotic_char = bool_constant<!std::is_same<T, char>::value>; | |||||
| } | |||||
| FMT_MODULE_EXPORT_BEGIN | |||||
| using wstring_view = basic_string_view<wchar_t>; | |||||
| using wformat_parse_context = basic_format_parse_context<wchar_t>; | |||||
| using wformat_context = buffer_context<wchar_t>; | |||||
| using wformat_args = basic_format_args<wformat_context>; | |||||
| using wmemory_buffer = basic_memory_buffer<wchar_t>; | |||||
| #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 | |||||
| // Workaround broken conversion on older gcc. | |||||
| template<typename... Args> | |||||
| using wformat_string = wstring_view; | |||||
| inline auto runtime(wstring_view s) -> wstring_view | |||||
| { | |||||
| return s; | |||||
| } | |||||
| #else | |||||
| template<typename... Args> | |||||
| using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>; | |||||
| inline auto runtime(wstring_view s) -> basic_runtime<wchar_t> | |||||
| { | |||||
| return {{s}}; | |||||
| } | |||||
| #endif | |||||
| template<> | |||||
| struct is_char<wchar_t> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<> | |||||
| struct is_char<detail::char8_type> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<> | |||||
| struct is_char<char16_t> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<> | |||||
| struct is_char<char32_t> : std::true_type | |||||
| { | |||||
| }; | |||||
| template<typename... Args> | |||||
| constexpr format_arg_store<wformat_context, Args...> make_wformat_args( | |||||
| const Args&... args | |||||
| ) | |||||
| { | |||||
| return {args...}; | |||||
| } | |||||
| inline namespace literals | |||||
| { | |||||
| #if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS | |||||
| constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) | |||||
| { | |||||
| return {s}; | |||||
| } | |||||
| #endif | |||||
| } // namespace literals | |||||
| template<typename It, typename Sentinel> | |||||
| auto join(It begin, Sentinel end, wstring_view sep) | |||||
| -> join_view<It, Sentinel, wchar_t> | |||||
| { | |||||
| return {begin, end, sep}; | |||||
| } | |||||
| template<typename Range> | |||||
| auto join(Range&& range, wstring_view sep) | |||||
| -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>, wchar_t> | |||||
| { | |||||
| return join(std::begin(range), std::end(range), sep); | |||||
| } | |||||
| template<typename T> | |||||
| auto join(std::initializer_list<T> list, wstring_view sep) | |||||
| -> join_view<const T*, const T*, wchar_t> | |||||
| { | |||||
| return join(std::begin(list), std::end(list), sep); | |||||
| } | |||||
| template<typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> | |||||
| auto vformat(basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) | |||||
| -> std::basic_string<Char> | |||||
| { | |||||
| basic_memory_buffer<Char> buffer; | |||||
| detail::vformat_to(buffer, format_str, args); | |||||
| return to_string(buffer); | |||||
| } | |||||
| template<typename... T> | |||||
| auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring | |||||
| { | |||||
| return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); | |||||
| } | |||||
| // Pass char_t as a default template parameter instead of using | |||||
| // std::basic_string<char_t<S>> to reduce the symbol size. | |||||
| template<typename S, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(!std::is_same<Char, char>::value && !std::is_same<Char, wchar_t>::value)> | |||||
| auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> | |||||
| { | |||||
| return vformat(detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| template<typename Locale, typename S, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto vformat( | |||||
| const Locale& loc, const S& format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args | |||||
| ) | |||||
| -> std::basic_string<Char> | |||||
| { | |||||
| return detail::vformat(loc, detail::to_string_view(format_str), args); | |||||
| } | |||||
| template<typename Locale, typename S, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto format(const Locale& loc, const S& format_str, Args&&... args) | |||||
| -> std::basic_string<Char> | |||||
| { | |||||
| return detail::vformat(loc, detail::to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| template<typename OutputIt, typename S, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> | |||||
| auto vformat_to(OutputIt out, const S& format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args) | |||||
| -> OutputIt | |||||
| { | |||||
| auto&& buf = detail::get_buffer<Char>(out); | |||||
| detail::vformat_to(buf, detail::to_string_view(format_str), args); | |||||
| return detail::get_iterator(buf); | |||||
| } | |||||
| template<typename OutputIt, typename S, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt | |||||
| { | |||||
| return vformat_to(out, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| template<typename Locale, typename S, typename OutputIt, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto vformat_to( | |||||
| OutputIt out, const Locale& loc, const S& format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args | |||||
| ) -> OutputIt | |||||
| { | |||||
| auto&& buf = detail::get_buffer<Char>(out); | |||||
| vformat_to(buf, detail::to_string_view(format_str), args, detail::locale_ref(loc)); | |||||
| return detail::get_iterator(buf); | |||||
| } | |||||
| template< | |||||
| typename OutputIt, | |||||
| typename Locale, | |||||
| typename S, | |||||
| typename... Args, | |||||
| typename Char = char_t<S>, | |||||
| bool enable = detail::is_output_iterator<OutputIt, Char>::value&& | |||||
| detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value> | |||||
| inline auto format_to(OutputIt out, const Locale& loc, const S& format_str, Args&&... args) -> | |||||
| typename std::enable_if<enable, OutputIt>::type | |||||
| { | |||||
| return vformat_to(out, loc, to_string_view(format_str), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| template<typename OutputIt, typename Char, typename... Args, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto vformat_to_n( | |||||
| OutputIt out, size_t n, basic_string_view<Char> format_str, basic_format_args<buffer_context<type_identity_t<Char>>> args | |||||
| ) | |||||
| -> format_to_n_result<OutputIt> | |||||
| { | |||||
| detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, n); | |||||
| detail::vformat_to(buf, format_str, args); | |||||
| return {buf.out(), buf.count()}; | |||||
| } | |||||
| template<typename OutputIt, typename S, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& detail::is_exotic_char<Char>::value)> | |||||
| inline auto format_to_n(OutputIt out, size_t n, const S& fmt, const Args&... args) -> format_to_n_result<OutputIt> | |||||
| { | |||||
| return vformat_to_n(out, n, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| } | |||||
| template<typename S, typename... Args, typename Char = char_t<S>, FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> | |||||
| inline auto formatted_size(const S& fmt, Args&&... args) -> size_t | |||||
| { | |||||
| detail::counting_buffer<Char> buf; | |||||
| detail::vformat_to(buf, detail::to_string_view(fmt), fmt::make_format_args<buffer_context<Char>>(args...)); | |||||
| return buf.count(); | |||||
| } | |||||
| inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) | |||||
| { | |||||
| wmemory_buffer buffer; | |||||
| detail::vformat_to(buffer, fmt, args); | |||||
| buffer.push_back(L'\0'); | |||||
| if (std::fputws(buffer.data(), f) == -1) | |||||
| FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); | |||||
| } | |||||
| inline void vprint(wstring_view fmt, wformat_args args) | |||||
| { | |||||
| vprint(stdout, fmt, args); | |||||
| } | |||||
| template<typename... T> | |||||
| void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) | |||||
| { | |||||
| return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); | |||||
| } | |||||
| template<typename... T> | |||||
| void print(wformat_string<T...> fmt, T&&... args) | |||||
| { | |||||
| return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); | |||||
| } | |||||
| /** | |||||
| Converts *value* to ``std::wstring`` using the default format for type *T*. | |||||
| */ | |||||
| template<typename T> | |||||
| inline auto to_wstring(const T& value) -> std::wstring | |||||
| { | |||||
| return format(FMT_STRING(L"{}"), value); | |||||
| } | |||||
| FMT_MODULE_EXPORT_END | |||||
| FMT_END_NAMESPACE | |||||
| #endif // FMT_XCHAR_H_ | |||||
| @@ -0,0 +1,22 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's chrono support | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/chrono.h> | |||||
| #else | |||||
| #include <fmt/chrono.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,22 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's compile-time support | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/compile.h> | |||||
| #else | |||||
| #include <fmt/compile.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,33 @@ | |||||
| // | |||||
| // Copyright(c) 2016-2018 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // Include a bundled header-only copy of fmtlib or an external one. | |||||
| // By default spdlog include its own copy. | |||||
| // | |||||
| #if defined(SPDLOG_USE_STD_FORMAT) // SPDLOG_USE_STD_FORMAT is defined - use std::format | |||||
| #include <format> | |||||
| #elif !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #if !defined(SPDLOG_COMPILED_LIB) && !defined(FMT_HEADER_ONLY) | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #ifndef FMT_USE_WINDOWS_H | |||||
| #define FMT_USE_WINDOWS_H 0 | |||||
| #endif | |||||
| // enable the 'n' flag in for backward compatibility with fmt 6.x | |||||
| #define FMT_DEPRECATED_N_SPECIFIER | |||||
| // enable ostream formatting for backward compatibility with fmt 8.x | |||||
| #define FMT_DEPRECATED_OSTREAM | |||||
| #include <spdlog/fmt/bundled/core.h> | |||||
| #include <spdlog/fmt/bundled/format.h> | |||||
| #else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib | |||||
| #include <fmt/core.h> | |||||
| #include <fmt/format.h> | |||||
| #endif | |||||
| @@ -0,0 +1,22 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's ostream support | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/ostream.h> | |||||
| #else | |||||
| #include <fmt/ostream.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,22 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's ranges support | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/ranges.h> | |||||
| #else | |||||
| #include <fmt/ranges.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,23 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's std support (for formatting e.g. std::filesystem::path, std::thread::id, std::monostate, | |||||
| // std::variant, ...) | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/std.h> | |||||
| #else | |||||
| #include <fmt/std.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,22 @@ | |||||
| // | |||||
| // Copyright(c) 2016 Gabi Melman. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // | |||||
| #pragma once | |||||
| // | |||||
| // include bundled or external copy of fmtlib's xchar support | |||||
| // | |||||
| #if !defined(SPDLOG_USE_STD_FORMAT) | |||||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #ifndef FMT_HEADER_ONLY | |||||
| #define FMT_HEADER_ONLY | |||||
| #endif | |||||
| #endif | |||||
| #include <spdlog/fmt/bundled/xchar.h> | |||||
| #else | |||||
| #include <fmt/xchar.h> | |||||
| #endif | |||||
| #endif | |||||
| @@ -0,0 +1,19 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <spdlog/details/log_msg.h> | |||||
| namespace spdlog | |||||
| { | |||||
| class formatter | |||||
| { | |||||
| public: | |||||
| virtual ~formatter() = default; | |||||
| virtual void format(const details::log_msg& msg, memory_buf_t& dest) = 0; | |||||
| virtual std::unique_ptr<formatter> clone() const = 0; | |||||
| }; | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,21 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| namespace spdlog | |||||
| { | |||||
| class logger; | |||||
| class formatter; | |||||
| namespace sinks | |||||
| { | |||||
| class sink; | |||||
| } | |||||
| namespace level | |||||
| { | |||||
| enum level_enum : int; | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,256 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/logger.h> | |||||
| #endif | |||||
| #include <spdlog/sinks/sink.h> | |||||
| #include <spdlog/details/backtracer.h> | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #include <cstdio> | |||||
| namespace spdlog | |||||
| { | |||||
| // public methods | |||||
| SPDLOG_INLINE logger::logger(const logger& other) : | |||||
| name_(other.name_), | |||||
| sinks_(other.sinks_), | |||||
| level_(other.level_.load(std::memory_order_relaxed)), | |||||
| flush_level_(other.flush_level_.load(std::memory_order_relaxed)), | |||||
| custom_err_handler_(other.custom_err_handler_), | |||||
| tracer_(other.tracer_) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE logger::logger(logger&& other) SPDLOG_NOEXCEPT : name_(std::move(other.name_)), sinks_(std::move(other.sinks_)), level_(other.level_.load(std::memory_order_relaxed)), flush_level_(other.flush_level_.load(std::memory_order_relaxed)), custom_err_handler_(std::move(other.custom_err_handler_)), tracer_(std::move(other.tracer_)) | |||||
| { | |||||
| } | |||||
| SPDLOG_INLINE logger& logger::operator=(logger other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| this->swap(other); | |||||
| return *this; | |||||
| } | |||||
| SPDLOG_INLINE void logger::swap(spdlog::logger& other) SPDLOG_NOEXCEPT | |||||
| { | |||||
| name_.swap(other.name_); | |||||
| sinks_.swap(other.sinks_); | |||||
| // swap level_ | |||||
| auto other_level = other.level_.load(); | |||||
| auto my_level = level_.exchange(other_level); | |||||
| other.level_.store(my_level); | |||||
| // swap flush level_ | |||||
| other_level = other.flush_level_.load(); | |||||
| my_level = flush_level_.exchange(other_level); | |||||
| other.flush_level_.store(my_level); | |||||
| custom_err_handler_.swap(other.custom_err_handler_); | |||||
| std::swap(tracer_, other.tracer_); | |||||
| } | |||||
| SPDLOG_INLINE void swap(logger& a, logger& b) | |||||
| { | |||||
| a.swap(b); | |||||
| } | |||||
| SPDLOG_INLINE void logger::set_level(level::level_enum log_level) | |||||
| { | |||||
| level_.store(log_level); | |||||
| } | |||||
| SPDLOG_INLINE level::level_enum logger::level() const | |||||
| { | |||||
| return static_cast<level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
| } | |||||
| SPDLOG_INLINE const std::string& logger::name() const | |||||
| { | |||||
| return name_; | |||||
| } | |||||
| // set formatting for the sinks in this logger. | |||||
| // each sink will get a separate instance of the formatter object. | |||||
| SPDLOG_INLINE void logger::set_formatter(std::unique_ptr<formatter> f) | |||||
| { | |||||
| for (auto it = sinks_.begin(); it != sinks_.end(); ++it) | |||||
| { | |||||
| if (std::next(it) == sinks_.end()) | |||||
| { | |||||
| // last element - we can be move it. | |||||
| (*it)->set_formatter(std::move(f)); | |||||
| break; // to prevent clang-tidy warning | |||||
| } | |||||
| else | |||||
| { | |||||
| (*it)->set_formatter(f->clone()); | |||||
| } | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type) | |||||
| { | |||||
| auto new_formatter = details::make_unique<pattern_formatter>(std::move(pattern), time_type); | |||||
| set_formatter(std::move(new_formatter)); | |||||
| } | |||||
| // create new backtrace sink and move to it all our child sinks | |||||
| SPDLOG_INLINE void logger::enable_backtrace(size_t n_messages) | |||||
| { | |||||
| tracer_.enable(n_messages); | |||||
| } | |||||
| // restore orig sinks and level and delete the backtrace sink | |||||
| SPDLOG_INLINE void logger::disable_backtrace() | |||||
| { | |||||
| tracer_.disable(); | |||||
| } | |||||
| SPDLOG_INLINE void logger::dump_backtrace() | |||||
| { | |||||
| dump_backtrace_(); | |||||
| } | |||||
| // flush functions | |||||
| SPDLOG_INLINE void logger::flush() | |||||
| { | |||||
| flush_(); | |||||
| } | |||||
| SPDLOG_INLINE void logger::flush_on(level::level_enum log_level) | |||||
| { | |||||
| flush_level_.store(log_level); | |||||
| } | |||||
| SPDLOG_INLINE level::level_enum logger::flush_level() const | |||||
| { | |||||
| return static_cast<level::level_enum>(flush_level_.load(std::memory_order_relaxed)); | |||||
| } | |||||
| // sinks | |||||
| SPDLOG_INLINE const std::vector<sink_ptr>& logger::sinks() const | |||||
| { | |||||
| return sinks_; | |||||
| } | |||||
| SPDLOG_INLINE std::vector<sink_ptr>& logger::sinks() | |||||
| { | |||||
| return sinks_; | |||||
| } | |||||
| // error handler | |||||
| SPDLOG_INLINE void logger::set_error_handler(err_handler handler) | |||||
| { | |||||
| custom_err_handler_ = std::move(handler); | |||||
| } | |||||
| // create new logger with same sinks and configuration. | |||||
| SPDLOG_INLINE std::shared_ptr<logger> logger::clone(std::string logger_name) | |||||
| { | |||||
| auto cloned = std::make_shared<logger>(*this); | |||||
| cloned->name_ = std::move(logger_name); | |||||
| return cloned; | |||||
| } | |||||
| // protected methods | |||||
| SPDLOG_INLINE void logger::log_it_(const spdlog::details::log_msg& log_msg, bool log_enabled, bool traceback_enabled) | |||||
| { | |||||
| if (log_enabled) | |||||
| { | |||||
| sink_it_(log_msg); | |||||
| } | |||||
| if (traceback_enabled) | |||||
| { | |||||
| tracer_.push_back(log_msg); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void logger::sink_it_(const details::log_msg& msg) | |||||
| { | |||||
| for (auto& sink : sinks_) | |||||
| { | |||||
| if (sink->should_log(msg.level)) | |||||
| { | |||||
| SPDLOG_TRY | |||||
| { | |||||
| sink->log(msg); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(msg.source) | |||||
| } | |||||
| } | |||||
| if (should_flush_(msg)) | |||||
| { | |||||
| flush_(); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void logger::flush_() | |||||
| { | |||||
| for (auto& sink : sinks_) | |||||
| { | |||||
| SPDLOG_TRY | |||||
| { | |||||
| sink->flush(); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(source_loc()) | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE void logger::dump_backtrace_() | |||||
| { | |||||
| using details::log_msg; | |||||
| if (tracer_.enabled() && !tracer_.empty()) | |||||
| { | |||||
| sink_it_(log_msg{name(), level::info, "****************** Backtrace Start ******************"}); | |||||
| tracer_.foreach_pop([this](const log_msg& msg) | |||||
| { this->sink_it_(msg); }); | |||||
| sink_it_(log_msg{name(), level::info, "****************** Backtrace End ********************"}); | |||||
| } | |||||
| } | |||||
| SPDLOG_INLINE bool logger::should_flush_(const details::log_msg& msg) | |||||
| { | |||||
| auto flush_level = flush_level_.load(std::memory_order_relaxed); | |||||
| return (msg.level >= flush_level) && (msg.level != level::off); | |||||
| } | |||||
| SPDLOG_INLINE void logger::err_handler_(const std::string& msg) | |||||
| { | |||||
| if (custom_err_handler_) | |||||
| { | |||||
| custom_err_handler_(msg); | |||||
| } | |||||
| else | |||||
| { | |||||
| using std::chrono::system_clock; | |||||
| static std::mutex mutex; | |||||
| static std::chrono::system_clock::time_point last_report_time; | |||||
| static size_t err_counter = 0; | |||||
| std::lock_guard<std::mutex> lk{mutex}; | |||||
| auto now = system_clock::now(); | |||||
| err_counter++; | |||||
| if (now - last_report_time < std::chrono::seconds(1)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| last_report_time = now; | |||||
| auto tm_time = details::os::localtime(system_clock::to_time_t(now)); | |||||
| char date_buf[64]; | |||||
| std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); | |||||
| #if defined(USING_R) && defined(R_R_H) // if in R environment | |||||
| REprintf("[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str()); | |||||
| #else | |||||
| std::fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str()); | |||||
| #endif | |||||
| } | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,434 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // Thread safe logger (except for set_error_handler()) | |||||
| // Has name, log level, vector of std::shared sink pointers and formatter | |||||
| // Upon each log write the logger: | |||||
| // 1. Checks if its log level is enough to log the message and if yes: | |||||
| // 2. Call the underlying sinks to do the job. | |||||
| // 3. Each sink use its own private copy of a formatter to format the message | |||||
| // and send to its destination. | |||||
| // | |||||
| // The use of private formatter per sink provides the opportunity to cache some | |||||
| // formatted data, and support for different format per sink. | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <spdlog/details/backtracer.h> | |||||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| #ifndef _WIN32 | |||||
| #error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||||
| #endif | |||||
| #include <spdlog/details/os.h> | |||||
| #endif | |||||
| #include <vector> | |||||
| #ifndef SPDLOG_NO_EXCEPTIONS | |||||
| #define SPDLOG_LOGGER_CATCH(location) \ | |||||
| catch (const std::exception& ex) \ | |||||
| { \ | |||||
| if (location.filename) \ | |||||
| { \ | |||||
| err_handler_(fmt_lib::format(SPDLOG_FMT_STRING("{} [{}({})]"), ex.what(), location.filename, location.line)); \ | |||||
| } \ | |||||
| else \ | |||||
| { \ | |||||
| err_handler_(ex.what()); \ | |||||
| } \ | |||||
| } \ | |||||
| catch (...) \ | |||||
| { \ | |||||
| err_handler_("Rethrowing unknown exception in logger"); \ | |||||
| throw; \ | |||||
| } | |||||
| #else | |||||
| #define SPDLOG_LOGGER_CATCH(location) | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| class SPDLOG_API logger | |||||
| { | |||||
| public: | |||||
| // Empty logger | |||||
| explicit logger(std::string name) : | |||||
| name_(std::move(name)), | |||||
| sinks_() | |||||
| { | |||||
| } | |||||
| // Logger with range on sinks | |||||
| template<typename It> | |||||
| logger(std::string name, It begin, It end) : | |||||
| name_(std::move(name)), | |||||
| sinks_(begin, end) | |||||
| { | |||||
| } | |||||
| // Logger with single sink | |||||
| logger(std::string name, sink_ptr single_sink) : | |||||
| logger(std::move(name), {std::move(single_sink)}) | |||||
| { | |||||
| } | |||||
| // Logger with sinks init list | |||||
| logger(std::string name, sinks_init_list sinks) : | |||||
| logger(std::move(name), sinks.begin(), sinks.end()) | |||||
| { | |||||
| } | |||||
| virtual ~logger() = default; | |||||
| logger(const logger& other); | |||||
| logger(logger&& other) SPDLOG_NOEXCEPT; | |||||
| logger& operator=(logger other) SPDLOG_NOEXCEPT; | |||||
| void swap(spdlog::logger& other) SPDLOG_NOEXCEPT; | |||||
| template<typename... Args> | |||||
| void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log_(loc, lvl, details::to_string_view(fmt), std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void log(level::level_enum lvl, format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename T> | |||||
| void log(level::level_enum lvl, const T& msg) | |||||
| { | |||||
| log(source_loc{}, lvl, msg); | |||||
| } | |||||
| // T cannot be statically converted to format string (including string_view/wstring_view) | |||||
| template<class T, typename std::enable_if<!is_convertible_to_any_format_string<const T&>::value, int>::type = 0> | |||||
| void log(source_loc loc, level::level_enum lvl, const T& msg) | |||||
| { | |||||
| log(loc, lvl, "{}", msg); | |||||
| } | |||||
| void log(log_clock::time_point log_time, source_loc loc, level::level_enum lvl, string_view_t msg) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| details::log_msg log_msg(log_time, loc, name_, lvl, msg); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| void log(source_loc loc, level::level_enum lvl, string_view_t msg) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| details::log_msg log_msg(loc, name_, lvl, msg); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| void log(level::level_enum lvl, string_view_t msg) | |||||
| { | |||||
| log(source_loc{}, lvl, msg); | |||||
| } | |||||
| template<typename... Args> | |||||
| void trace(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::trace, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void debug(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::debug, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void info(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::info, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void warn(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::warn, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void error(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::err, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void critical(format_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::critical, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| template<typename... Args> | |||||
| void log(source_loc loc, level::level_enum lvl, wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log_(loc, lvl, details::to_string_view(fmt), std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void log(level::level_enum lvl, wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| void log(log_clock::time_point log_time, source_loc loc, level::level_enum lvl, wstring_view_t msg) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| memory_buf_t buf; | |||||
| details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.size()), buf); | |||||
| details::log_msg log_msg(log_time, loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| void log(source_loc loc, level::level_enum lvl, wstring_view_t msg) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| memory_buf_t buf; | |||||
| details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.size()), buf); | |||||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| void log(level::level_enum lvl, wstring_view_t msg) | |||||
| { | |||||
| log(source_loc{}, lvl, msg); | |||||
| } | |||||
| template<typename... Args> | |||||
| void trace(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::trace, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void debug(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::debug, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void info(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::info, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void warn(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::warn, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void error(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::err, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| template<typename... Args> | |||||
| void critical(wformat_string_t<Args...> fmt, Args&&... args) | |||||
| { | |||||
| log(level::critical, fmt, std::forward<Args>(args)...); | |||||
| } | |||||
| #endif | |||||
| template<typename T> | |||||
| void trace(const T& msg) | |||||
| { | |||||
| log(level::trace, msg); | |||||
| } | |||||
| template<typename T> | |||||
| void debug(const T& msg) | |||||
| { | |||||
| log(level::debug, msg); | |||||
| } | |||||
| template<typename T> | |||||
| void info(const T& msg) | |||||
| { | |||||
| log(level::info, msg); | |||||
| } | |||||
| template<typename T> | |||||
| void warn(const T& msg) | |||||
| { | |||||
| log(level::warn, msg); | |||||
| } | |||||
| template<typename T> | |||||
| void error(const T& msg) | |||||
| { | |||||
| log(level::err, msg); | |||||
| } | |||||
| template<typename T> | |||||
| void critical(const T& msg) | |||||
| { | |||||
| log(level::critical, msg); | |||||
| } | |||||
| // return true logging is enabled for the given level. | |||||
| bool should_log(level::level_enum msg_level) const | |||||
| { | |||||
| return msg_level >= level_.load(std::memory_order_relaxed); | |||||
| } | |||||
| // return true if backtrace logging is enabled. | |||||
| bool should_backtrace() const | |||||
| { | |||||
| return tracer_.enabled(); | |||||
| } | |||||
| void set_level(level::level_enum log_level); | |||||
| level::level_enum level() const; | |||||
| const std::string& name() const; | |||||
| // set formatting for the sinks in this logger. | |||||
| // each sink will get a separate instance of the formatter object. | |||||
| void set_formatter(std::unique_ptr<formatter> f); | |||||
| // set formatting for the sinks in this logger. | |||||
| // equivalent to | |||||
| // set_formatter(make_unique<pattern_formatter>(pattern, time_type)) | |||||
| // Note: each sink will get a new instance of a formatter object, replacing the old one. | |||||
| void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||||
| // backtrace support. | |||||
| // efficiently store all debug/trace messages in a circular buffer until needed for debugging. | |||||
| void enable_backtrace(size_t n_messages); | |||||
| void disable_backtrace(); | |||||
| void dump_backtrace(); | |||||
| // flush functions | |||||
| void flush(); | |||||
| void flush_on(level::level_enum log_level); | |||||
| level::level_enum flush_level() const; | |||||
| // sinks | |||||
| const std::vector<sink_ptr>& sinks() const; | |||||
| std::vector<sink_ptr>& sinks(); | |||||
| // error handler | |||||
| void set_error_handler(err_handler); | |||||
| // create new logger with same sinks and configuration. | |||||
| virtual std::shared_ptr<logger> clone(std::string logger_name); | |||||
| protected: | |||||
| std::string name_; | |||||
| std::vector<sink_ptr> sinks_; | |||||
| spdlog::level_t level_{level::info}; | |||||
| spdlog::level_t flush_level_{level::off}; | |||||
| err_handler custom_err_handler_{nullptr}; | |||||
| details::backtracer tracer_; | |||||
| // common implementation for after templated public api has been resolved | |||||
| template<typename... Args> | |||||
| void log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args&&... args) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| SPDLOG_TRY | |||||
| { | |||||
| memory_buf_t buf; | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| fmt_lib::vformat_to(std::back_inserter(buf), fmt, fmt_lib::make_format_args(std::forward<Args>(args)...)); | |||||
| #else | |||||
| fmt::vformat_to(fmt::appender(buf), fmt, fmt::make_format_args(std::forward<Args>(args)...)); | |||||
| #endif | |||||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(loc) | |||||
| } | |||||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| template<typename... Args> | |||||
| void log_(source_loc loc, level::level_enum lvl, wstring_view_t fmt, Args&&... args) | |||||
| { | |||||
| bool log_enabled = should_log(lvl); | |||||
| bool traceback_enabled = tracer_.enabled(); | |||||
| if (!log_enabled && !traceback_enabled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| SPDLOG_TRY | |||||
| { | |||||
| // format to wmemory_buffer and convert to utf8 | |||||
| wmemory_buf_t wbuf; | |||||
| fmt_lib::vformat_to( | |||||
| std::back_inserter(wbuf), fmt, fmt_lib::make_format_args<fmt_lib::wformat_context>(std::forward<Args>(args)...) | |||||
| ); | |||||
| memory_buf_t buf; | |||||
| details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); | |||||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||||
| log_it_(log_msg, log_enabled, traceback_enabled); | |||||
| } | |||||
| SPDLOG_LOGGER_CATCH(loc) | |||||
| } | |||||
| #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| // log the given message (if the given log level is high enough), | |||||
| // and save backtrace (if backtrace is enabled). | |||||
| void log_it_(const details::log_msg& log_msg, bool log_enabled, bool traceback_enabled); | |||||
| virtual void sink_it_(const details::log_msg& msg); | |||||
| virtual void flush_(); | |||||
| void dump_backtrace_(); | |||||
| bool should_flush_(const details::log_msg& msg); | |||||
| // handle errors during logging. | |||||
| // default handler prints the error to stderr at max rate of 1 message/sec. | |||||
| void err_handler_(const std::string& msg); | |||||
| }; | |||||
| void swap(logger& a, logger& b); | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "logger-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,131 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/formatter.h> | |||||
| #include <chrono> | |||||
| #include <ctime> | |||||
| #include <memory> | |||||
| #include <string> | |||||
| #include <vector> | |||||
| #include <unordered_map> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace details | |||||
| { | |||||
| // padding information. | |||||
| struct padding_info | |||||
| { | |||||
| enum class pad_side | |||||
| { | |||||
| left, | |||||
| right, | |||||
| center | |||||
| }; | |||||
| padding_info() = default; | |||||
| padding_info(size_t width, padding_info::pad_side side, bool truncate) : | |||||
| width_(width), | |||||
| side_(side), | |||||
| truncate_(truncate), | |||||
| enabled_(true) | |||||
| { | |||||
| } | |||||
| bool enabled() const | |||||
| { | |||||
| return enabled_; | |||||
| } | |||||
| size_t width_ = 0; | |||||
| pad_side side_ = pad_side::left; | |||||
| bool truncate_ = false; | |||||
| bool enabled_ = false; | |||||
| }; | |||||
| class SPDLOG_API flag_formatter | |||||
| { | |||||
| public: | |||||
| explicit flag_formatter(padding_info padinfo) : | |||||
| padinfo_(padinfo) | |||||
| { | |||||
| } | |||||
| flag_formatter() = default; | |||||
| virtual ~flag_formatter() = default; | |||||
| virtual void format(const details::log_msg& msg, const std::tm& tm_time, memory_buf_t& dest) = 0; | |||||
| protected: | |||||
| padding_info padinfo_; | |||||
| }; | |||||
| } // namespace details | |||||
| class SPDLOG_API custom_flag_formatter : public details::flag_formatter | |||||
| { | |||||
| public: | |||||
| virtual std::unique_ptr<custom_flag_formatter> clone() const = 0; | |||||
| void set_padding_info(const details::padding_info& padding) | |||||
| { | |||||
| flag_formatter::padinfo_ = padding; | |||||
| } | |||||
| }; | |||||
| class SPDLOG_API pattern_formatter final : public formatter | |||||
| { | |||||
| public: | |||||
| using custom_flags = std::unordered_map<char, std::unique_ptr<custom_flag_formatter>>; | |||||
| explicit pattern_formatter(std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol, custom_flags custom_user_flags = custom_flags()); | |||||
| // use default pattern is not given | |||||
| explicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||||
| pattern_formatter(const pattern_formatter& other) = delete; | |||||
| pattern_formatter& operator=(const pattern_formatter& other) = delete; | |||||
| std::unique_ptr<formatter> clone() const override; | |||||
| void format(const details::log_msg& msg, memory_buf_t& dest) override; | |||||
| template<typename T, typename... Args> | |||||
| pattern_formatter& add_flag(char flag, Args&&... args) | |||||
| { | |||||
| custom_handlers_[flag] = details::make_unique<T>(std::forward<Args>(args)...); | |||||
| return *this; | |||||
| } | |||||
| void set_pattern(std::string pattern); | |||||
| void need_localtime(bool need = true); | |||||
| private: | |||||
| std::string pattern_; | |||||
| std::string eol_; | |||||
| pattern_time_type pattern_time_type_; | |||||
| bool need_localtime_; | |||||
| std::tm cached_tm_; | |||||
| std::chrono::seconds last_log_secs_; | |||||
| std::vector<std::unique_ptr<details::flag_formatter>> formatters_; | |||||
| custom_flags custom_handlers_; | |||||
| std::tm get_time_(const details::log_msg& msg); | |||||
| template<typename Padder> | |||||
| void handle_flag_(char flag, details::padding_info padding); | |||||
| // Extract given pad spec (e.g. %8X) | |||||
| // Advance the given it pass the end of the padding spec found (if any) | |||||
| // Return padding. | |||||
| static details::padding_info handle_padspec_(std::string::const_iterator& it, std::string::const_iterator end); | |||||
| void compile_pattern_(const std::string& pattern); | |||||
| }; | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "pattern_formatter-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,147 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifdef __ANDROID__ | |||||
| #include <spdlog/details/fmt_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <android/log.h> | |||||
| #include <chrono> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <thread> | |||||
| #include <type_traits> | |||||
| #if !defined(SPDLOG_ANDROID_RETRIES) | |||||
| #define SPDLOG_ANDROID_RETRIES 2 | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * Android sink | |||||
| * (logging using __android_log_write or __android_log_buf_write depending on the specified BufferID) | |||||
| */ | |||||
| template<typename Mutex, int BufferID = log_id::LOG_ID_MAIN> | |||||
| class android_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) : | |||||
| tag_(std::move(tag)), | |||||
| use_raw_msg_(use_raw_msg) | |||||
| { | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| const android_LogPriority priority = convert_to_android_(msg.level); | |||||
| memory_buf_t formatted; | |||||
| if (use_raw_msg_) | |||||
| { | |||||
| details::fmt_helper::append_string_view(msg.payload, formatted); | |||||
| } | |||||
| else | |||||
| { | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| } | |||||
| formatted.push_back('\0'); | |||||
| const char* msg_output = formatted.data(); | |||||
| // See system/core/liblog/logger_write.c for explanation of return value | |||||
| int ret = android_log(priority, tag_.c_str(), msg_output); | |||||
| int retry_count = 0; | |||||
| while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) | |||||
| { | |||||
| details::os::sleep_for_millis(5); | |||||
| ret = android_log(priority, tag_.c_str(), msg_output); | |||||
| retry_count++; | |||||
| } | |||||
| if (ret < 0) | |||||
| { | |||||
| throw_spdlog_ex("logging to Android failed", ret); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| private: | |||||
| // There might be liblog versions used, that do not support __android_log_buf_write. So we only compile and link against | |||||
| // __android_log_buf_write, if user explicitely provides a non-default log buffer. Otherwise, when using the default log buffer, always | |||||
| // log via __android_log_write. | |||||
| template<int ID = BufferID> | |||||
| typename std::enable_if<ID == static_cast<int>(log_id::LOG_ID_MAIN), int>::type android_log(int prio, const char* tag, const char* text) | |||||
| { | |||||
| return __android_log_write(prio, tag, text); | |||||
| } | |||||
| template<int ID = BufferID> | |||||
| typename std::enable_if<ID != static_cast<int>(log_id::LOG_ID_MAIN), int>::type android_log(int prio, const char* tag, const char* text) | |||||
| { | |||||
| return __android_log_buf_write(ID, prio, tag, text); | |||||
| } | |||||
| static android_LogPriority convert_to_android_(spdlog::level::level_enum level) | |||||
| { | |||||
| switch (level) | |||||
| { | |||||
| case spdlog::level::trace: | |||||
| return ANDROID_LOG_VERBOSE; | |||||
| case spdlog::level::debug: | |||||
| return ANDROID_LOG_DEBUG; | |||||
| case spdlog::level::info: | |||||
| return ANDROID_LOG_INFO; | |||||
| case spdlog::level::warn: | |||||
| return ANDROID_LOG_WARN; | |||||
| case spdlog::level::err: | |||||
| return ANDROID_LOG_ERROR; | |||||
| case spdlog::level::critical: | |||||
| return ANDROID_LOG_FATAL; | |||||
| default: | |||||
| return ANDROID_LOG_DEFAULT; | |||||
| } | |||||
| } | |||||
| std::string tag_; | |||||
| bool use_raw_msg_; | |||||
| }; | |||||
| using android_sink_mt = android_sink<std::mutex>; | |||||
| using android_sink_st = android_sink<details::null_mutex>; | |||||
| template<int BufferId = log_id::LOG_ID_MAIN> | |||||
| using android_sink_buf_mt = android_sink<std::mutex, BufferId>; | |||||
| template<int BufferId = log_id::LOG_ID_MAIN> | |||||
| using android_sink_buf_st = android_sink<details::null_mutex, BufferId>; | |||||
| } // namespace sinks | |||||
| // Create and register android syslog logger | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> android_logger_mt(const std::string& logger_name, const std::string& tag = "spdlog") | |||||
| { | |||||
| return Factory::template create<sinks::android_sink_mt>(logger_name, tag); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> android_logger_st(const std::string& logger_name, const std::string& tag = "spdlog") | |||||
| { | |||||
| return Factory::template create<sinks::android_sink_st>(logger_name, tag); | |||||
| } | |||||
| } // namespace spdlog | |||||
| #endif // __ANDROID__ | |||||
| @@ -0,0 +1,149 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/ansicolor_sink.h> | |||||
| #endif | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #include <spdlog/details/os.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE ansicolor_sink<ConsoleMutex>::ansicolor_sink(FILE* target_file, color_mode mode) : | |||||
| target_file_(target_file), | |||||
| mutex_(ConsoleMutex::mutex()), | |||||
| formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
| { | |||||
| set_color_mode(mode); | |||||
| colors_[level::trace] = to_string_(white); | |||||
| colors_[level::debug] = to_string_(cyan); | |||||
| colors_[level::info] = to_string_(green); | |||||
| colors_[level::warn] = to_string_(yellow_bold); | |||||
| colors_[level::err] = to_string_(red_bold); | |||||
| colors_[level::critical] = to_string_(bold_on_red); | |||||
| colors_[level::off] = to_string_(reset); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color) | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| colors_[static_cast<size_t>(color_level)] = to_string_(color); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::log(const details::log_msg& msg) | |||||
| { | |||||
| // Wrap the originally formatted message in color codes. | |||||
| // If color is not supported in the terminal, log as is instead. | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| msg.color_range_start = 0; | |||||
| msg.color_range_end = 0; | |||||
| memory_buf_t formatted; | |||||
| formatter_->format(msg, formatted); | |||||
| if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||||
| { | |||||
| // before color range | |||||
| print_range_(formatted, 0, msg.color_range_start); | |||||
| // in color range | |||||
| print_ccode_(colors_[static_cast<size_t>(msg.level)]); | |||||
| print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||||
| print_ccode_(reset); | |||||
| // after color range | |||||
| print_range_(formatted, msg.color_range_end, formatted.size()); | |||||
| } | |||||
| else // no color | |||||
| { | |||||
| print_range_(formatted, 0, formatted.size()); | |||||
| } | |||||
| fflush(target_file_); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::flush() | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| fflush(target_file_); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_pattern(const std::string& pattern) | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| formatter_ = std::move(sink_formatter); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() | |||||
| { | |||||
| return should_do_colors_; | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||||
| { | |||||
| switch (mode) | |||||
| { | |||||
| case color_mode::always: | |||||
| should_do_colors_ = true; | |||||
| return; | |||||
| case color_mode::automatic: | |||||
| should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); | |||||
| return; | |||||
| case color_mode::never: | |||||
| should_do_colors_ = false; | |||||
| return; | |||||
| default: | |||||
| should_do_colors_ = false; | |||||
| } | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t& color_code) | |||||
| { | |||||
| fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t& formatted, size_t start, size_t end) | |||||
| { | |||||
| fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE std::string ansicolor_sink<ConsoleMutex>::to_string_(const string_view_t& sv) | |||||
| { | |||||
| return std::string(sv.data(), sv.size()); | |||||
| } | |||||
| // ansicolor_stdout_sink | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE ansicolor_stdout_sink<ConsoleMutex>::ansicolor_stdout_sink(color_mode mode) : | |||||
| ansicolor_sink<ConsoleMutex>(stdout, mode) | |||||
| { | |||||
| } | |||||
| // ansicolor_stderr_sink | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE ansicolor_stderr_sink<ConsoleMutex>::ansicolor_stderr_sink(color_mode mode) : | |||||
| ansicolor_sink<ConsoleMutex>(stderr, mode) | |||||
| { | |||||
| } | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,120 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/console_globals.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/sink.h> | |||||
| #include <memory> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <array> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /** | |||||
| * This sink prefixes the output with an ANSI escape sequence color code | |||||
| * depending on the severity | |||||
| * of the message. | |||||
| * If no color terminal detected, omit the escape codes. | |||||
| */ | |||||
| template<typename ConsoleMutex> | |||||
| class ansicolor_sink : public sink | |||||
| { | |||||
| public: | |||||
| using mutex_t = typename ConsoleMutex::mutex_t; | |||||
| ansicolor_sink(FILE* target_file, color_mode mode); | |||||
| ~ansicolor_sink() override = default; | |||||
| ansicolor_sink(const ansicolor_sink& other) = delete; | |||||
| ansicolor_sink(ansicolor_sink&& other) = delete; | |||||
| ansicolor_sink& operator=(const ansicolor_sink& other) = delete; | |||||
| ansicolor_sink& operator=(ansicolor_sink&& other) = delete; | |||||
| void set_color(level::level_enum color_level, string_view_t color); | |||||
| void set_color_mode(color_mode mode); | |||||
| bool should_color(); | |||||
| void log(const details::log_msg& msg) override; | |||||
| void flush() override; | |||||
| void set_pattern(const std::string& pattern) final; | |||||
| void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
| // Formatting codes | |||||
| const string_view_t reset = "\033[m"; | |||||
| const string_view_t bold = "\033[1m"; | |||||
| const string_view_t dark = "\033[2m"; | |||||
| const string_view_t underline = "\033[4m"; | |||||
| const string_view_t blink = "\033[5m"; | |||||
| const string_view_t reverse = "\033[7m"; | |||||
| const string_view_t concealed = "\033[8m"; | |||||
| const string_view_t clear_line = "\033[K"; | |||||
| // Foreground colors | |||||
| const string_view_t black = "\033[30m"; | |||||
| const string_view_t red = "\033[31m"; | |||||
| const string_view_t green = "\033[32m"; | |||||
| const string_view_t yellow = "\033[33m"; | |||||
| const string_view_t blue = "\033[34m"; | |||||
| const string_view_t magenta = "\033[35m"; | |||||
| const string_view_t cyan = "\033[36m"; | |||||
| const string_view_t white = "\033[37m"; | |||||
| /// Background colors | |||||
| const string_view_t on_black = "\033[40m"; | |||||
| const string_view_t on_red = "\033[41m"; | |||||
| const string_view_t on_green = "\033[42m"; | |||||
| const string_view_t on_yellow = "\033[43m"; | |||||
| const string_view_t on_blue = "\033[44m"; | |||||
| const string_view_t on_magenta = "\033[45m"; | |||||
| const string_view_t on_cyan = "\033[46m"; | |||||
| const string_view_t on_white = "\033[47m"; | |||||
| /// Bold colors | |||||
| const string_view_t yellow_bold = "\033[33m\033[1m"; | |||||
| const string_view_t red_bold = "\033[31m\033[1m"; | |||||
| const string_view_t bold_on_red = "\033[1m\033[41m"; | |||||
| private: | |||||
| FILE* target_file_; | |||||
| mutex_t& mutex_; | |||||
| bool should_do_colors_; | |||||
| std::unique_ptr<spdlog::formatter> formatter_; | |||||
| std::array<std::string, level::n_levels> colors_; | |||||
| void print_ccode_(const string_view_t& color_code); | |||||
| void print_range_(const memory_buf_t& formatted, size_t start, size_t end); | |||||
| static std::string to_string_(const string_view_t& sv); | |||||
| }; | |||||
| template<typename ConsoleMutex> | |||||
| class ansicolor_stdout_sink : public ansicolor_sink<ConsoleMutex> | |||||
| { | |||||
| public: | |||||
| explicit ansicolor_stdout_sink(color_mode mode = color_mode::automatic); | |||||
| }; | |||||
| template<typename ConsoleMutex> | |||||
| class ansicolor_stderr_sink : public ansicolor_sink<ConsoleMutex> | |||||
| { | |||||
| public: | |||||
| explicit ansicolor_stderr_sink(color_mode mode = color_mode::automatic); | |||||
| }; | |||||
| using ansicolor_stdout_sink_mt = ansicolor_stdout_sink<details::console_mutex>; | |||||
| using ansicolor_stdout_sink_st = ansicolor_stdout_sink<details::console_nullmutex>; | |||||
| using ansicolor_stderr_sink_mt = ansicolor_stderr_sink<details::console_mutex>; | |||||
| using ansicolor_stderr_sink_st = ansicolor_stderr_sink<details::console_nullmutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "ansicolor_sink-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,65 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #include <memory> | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink() : | |||||
| formatter_{details::make_unique<spdlog::pattern_formatter>()} | |||||
| { | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink(std::unique_ptr<spdlog::formatter> formatter) : | |||||
| formatter_{std::move(formatter)} | |||||
| { | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg& msg) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(mutex_); | |||||
| sink_it_(msg); | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::flush() | |||||
| { | |||||
| std::lock_guard<Mutex> lock(mutex_); | |||||
| flush_(); | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern(const std::string& pattern) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(mutex_); | |||||
| set_pattern_(pattern); | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(mutex_); | |||||
| set_formatter_(std::move(sink_formatter)); | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern_(const std::string& pattern) | |||||
| { | |||||
| set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
| } | |||||
| template<typename Mutex> | |||||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
| { | |||||
| formatter_ = std::move(sink_formatter); | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // | |||||
| // base sink templated over a mutex (either dummy or real) | |||||
| // concrete implementation should override the sink_it_() and flush_() methods. | |||||
| // locking is taken care of in this class - no locking needed by the | |||||
| // implementers.. | |||||
| // | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <spdlog/sinks/sink.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class SPDLOG_API base_sink : public sink | |||||
| { | |||||
| public: | |||||
| base_sink(); | |||||
| explicit base_sink(std::unique_ptr<spdlog::formatter> formatter); | |||||
| ~base_sink() override = default; | |||||
| base_sink(const base_sink&) = delete; | |||||
| base_sink(base_sink&&) = delete; | |||||
| base_sink& operator=(const base_sink&) = delete; | |||||
| base_sink& operator=(base_sink&&) = delete; | |||||
| void log(const details::log_msg& msg) final; | |||||
| void flush() final; | |||||
| void set_pattern(const std::string& pattern) final; | |||||
| void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; | |||||
| protected: | |||||
| // sink formatter | |||||
| std::unique_ptr<spdlog::formatter> formatter_; | |||||
| Mutex mutex_; | |||||
| virtual void sink_it_(const details::log_msg& msg) = 0; | |||||
| virtual void flush_() = 0; | |||||
| virtual void set_pattern_(const std::string& pattern); | |||||
| virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter); | |||||
| }; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "base_sink-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,46 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/basic_file_sink.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/os.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t& filename, bool truncate, const file_event_handlers& event_handlers) : | |||||
| file_helper_{event_handlers} | |||||
| { | |||||
| file_helper_.open(filename, truncate); | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE const filename_t& basic_file_sink<Mutex>::filename() const | |||||
| { | |||||
| return file_helper_.filename(); | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg& msg) | |||||
| { | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| file_helper_.write(formatted); | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE void basic_file_sink<Mutex>::flush_() | |||||
| { | |||||
| file_helper_.flush(); | |||||
| } | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,64 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * Trivial file sink with single file as target | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class basic_file_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| explicit basic_file_sink(const filename_t& filename, bool truncate = false, const file_event_handlers& event_handlers = {}); | |||||
| const filename_t& filename() const; | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override; | |||||
| void flush_() override; | |||||
| private: | |||||
| details::file_helper file_helper_; | |||||
| }; | |||||
| using basic_file_sink_mt = basic_file_sink<std::mutex>; | |||||
| using basic_file_sink_st = basic_file_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> basic_logger_mt( | |||||
| const std::string& logger_name, const filename_t& filename, bool truncate = false, const file_event_handlers& event_handlers = {} | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate, event_handlers); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> basic_logger_st( | |||||
| const std::string& logger_name, const filename_t& filename, bool truncate = false, const file_event_handlers& event_handlers = {} | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate, event_handlers); | |||||
| } | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "basic_file_sink-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,295 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <spdlog/fmt/chrono.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/details/circular_q.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <chrono> | |||||
| #include <cstdio> | |||||
| #include <ctime> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * Generator of daily log file names in format basename.YYYY-MM-DD.ext | |||||
| */ | |||||
| struct daily_filename_calculator | |||||
| { | |||||
| // Create filename for the form basename.YYYY-MM-DD | |||||
| static filename_t calc_filename(const filename_t& filename, const tm& now_tm) | |||||
| { | |||||
| filename_t basename, ext; | |||||
| std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
| return fmt_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}")), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); | |||||
| } | |||||
| }; | |||||
| /* | |||||
| * Generator of daily log file names with strftime format. | |||||
| * Usages: | |||||
| * auto sink = std::make_shared<spdlog::sinks::daily_file_format_sink_mt>("myapp-%Y-%m-%d:%H:%M:%S.log", hour, minute);" | |||||
| * auto logger = spdlog::daily_logger_format_mt("loggername, "myapp-%Y-%m-%d:%X.log", hour, minute)" | |||||
| * | |||||
| */ | |||||
| struct daily_filename_format_calculator | |||||
| { | |||||
| static filename_t calc_filename(const filename_t& filename, const tm& now_tm) | |||||
| { | |||||
| #ifdef SPDLOG_USE_STD_FORMAT | |||||
| // adapted from fmtlib: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/chrono.h#L522-L546 | |||||
| filename_t tm_format; | |||||
| tm_format.append(filename); | |||||
| // By appending an extra space we can distinguish an empty result that | |||||
| // indicates insufficient buffer size from a guaranteed non-empty result | |||||
| // https://github.com/fmtlib/fmt/issues/2238 | |||||
| tm_format.push_back(' '); | |||||
| const size_t MIN_SIZE = 10; | |||||
| filename_t buf; | |||||
| buf.resize(MIN_SIZE); | |||||
| for (;;) | |||||
| { | |||||
| size_t count = strftime(buf.data(), buf.size(), tm_format.c_str(), &now_tm); | |||||
| if (count != 0) | |||||
| { | |||||
| // Remove the extra space. | |||||
| buf.resize(count - 1); | |||||
| break; | |||||
| } | |||||
| buf.resize(buf.size() * 2); | |||||
| } | |||||
| return buf; | |||||
| #else | |||||
| // generate fmt datetime format string, e.g. {:%Y-%m-%d}. | |||||
| filename_t fmt_filename = fmt::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{{:{}}}")), filename); | |||||
| // MSVC doesn't allow fmt::runtime(..) with wchar, with fmtlib versions < 9.1.x | |||||
| #if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) && FMT_VERSION < 90101 | |||||
| return fmt::format(fmt_filename, now_tm); | |||||
| #else | |||||
| return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm); | |||||
| #endif | |||||
| #endif | |||||
| } | |||||
| private: | |||||
| #if defined __GNUC__ | |||||
| #pragma GCC diagnostic push | |||||
| #pragma GCC diagnostic ignored "-Wformat-nonliteral" | |||||
| #endif | |||||
| static size_t strftime(char* str, size_t count, const char* format, const std::tm* time) | |||||
| { | |||||
| return std::strftime(str, count, format, time); | |||||
| } | |||||
| static size_t strftime(wchar_t* str, size_t count, const wchar_t* format, const std::tm* time) | |||||
| { | |||||
| return std::wcsftime(str, count, format, time); | |||||
| } | |||||
| #if defined(__GNUC__) | |||||
| #pragma GCC diagnostic pop | |||||
| #endif | |||||
| }; | |||||
| /* | |||||
| * Rotating file sink based on date. | |||||
| * If truncate != false , the created file will be truncated. | |||||
| * If max_files > 0, retain only the last max_files and delete previous. | |||||
| */ | |||||
| template<typename Mutex, typename FileNameCalc = daily_filename_calculator> | |||||
| class daily_file_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| // create daily file sink which rotates on given time | |||||
| daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) : | |||||
| base_filename_(std::move(base_filename)), | |||||
| rotation_h_(rotation_hour), | |||||
| rotation_m_(rotation_minute), | |||||
| file_helper_{event_handlers}, | |||||
| truncate_(truncate), | |||||
| max_files_(max_files), | |||||
| filenames_q_() | |||||
| { | |||||
| if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) | |||||
| { | |||||
| throw_spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); | |||||
| } | |||||
| auto now = log_clock::now(); | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
| file_helper_.open(filename, truncate_); | |||||
| rotation_tp_ = next_rotation_tp_(); | |||||
| if (max_files_ > 0) | |||||
| { | |||||
| init_filenames_q_(); | |||||
| } | |||||
| } | |||||
| filename_t filename() | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| return file_helper_.filename(); | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| auto time = msg.time; | |||||
| bool should_rotate = time >= rotation_tp_; | |||||
| if (should_rotate) | |||||
| { | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); | |||||
| file_helper_.open(filename, truncate_); | |||||
| rotation_tp_ = next_rotation_tp_(); | |||||
| } | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| file_helper_.write(formatted); | |||||
| // Do the cleaning only at the end because it might throw on failure. | |||||
| if (should_rotate && max_files_ > 0) | |||||
| { | |||||
| delete_old_(); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| file_helper_.flush(); | |||||
| } | |||||
| private: | |||||
| void init_filenames_q_() | |||||
| { | |||||
| using details::os::path_exists; | |||||
| filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); | |||||
| std::vector<filename_t> filenames; | |||||
| auto now = log_clock::now(); | |||||
| while (filenames.size() < max_files_) | |||||
| { | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
| if (!path_exists(filename)) | |||||
| { | |||||
| break; | |||||
| } | |||||
| filenames.emplace_back(filename); | |||||
| now -= std::chrono::hours(24); | |||||
| } | |||||
| for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter) | |||||
| { | |||||
| filenames_q_.push_back(std::move(*iter)); | |||||
| } | |||||
| } | |||||
| tm now_tm(log_clock::time_point tp) | |||||
| { | |||||
| time_t tnow = log_clock::to_time_t(tp); | |||||
| return spdlog::details::os::localtime(tnow); | |||||
| } | |||||
| log_clock::time_point next_rotation_tp_() | |||||
| { | |||||
| auto now = log_clock::now(); | |||||
| tm date = now_tm(now); | |||||
| date.tm_hour = rotation_h_; | |||||
| date.tm_min = rotation_m_; | |||||
| date.tm_sec = 0; | |||||
| auto rotation_time = log_clock::from_time_t(std::mktime(&date)); | |||||
| if (rotation_time > now) | |||||
| { | |||||
| return rotation_time; | |||||
| } | |||||
| return {rotation_time + std::chrono::hours(24)}; | |||||
| } | |||||
| // Delete the file N rotations ago. | |||||
| // Throw spdlog_ex on failure to delete the old file. | |||||
| void delete_old_() | |||||
| { | |||||
| using details::os::filename_to_str; | |||||
| using details::os::remove_if_exists; | |||||
| filename_t current_file = file_helper_.filename(); | |||||
| if (filenames_q_.full()) | |||||
| { | |||||
| auto old_filename = std::move(filenames_q_.front()); | |||||
| filenames_q_.pop_front(); | |||||
| bool ok = remove_if_exists(old_filename) == 0; | |||||
| if (!ok) | |||||
| { | |||||
| filenames_q_.push_back(std::move(current_file)); | |||||
| throw_spdlog_ex("Failed removing daily file " + filename_to_str(old_filename), errno); | |||||
| } | |||||
| } | |||||
| filenames_q_.push_back(std::move(current_file)); | |||||
| } | |||||
| filename_t base_filename_; | |||||
| int rotation_h_; | |||||
| int rotation_m_; | |||||
| log_clock::time_point rotation_tp_; | |||||
| details::file_helper file_helper_; | |||||
| bool truncate_; | |||||
| uint16_t max_files_; | |||||
| details::circular_q<filename_t> filenames_q_; | |||||
| }; | |||||
| using daily_file_sink_mt = daily_file_sink<std::mutex>; | |||||
| using daily_file_sink_st = daily_file_sink<details::null_mutex>; | |||||
| using daily_file_format_sink_mt = daily_file_sink<std::mutex, daily_filename_format_calculator>; | |||||
| using daily_file_format_sink_st = daily_file_sink<details::null_mutex, daily_filename_format_calculator>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> daily_logger_format_mt(const std::string& logger_name, const filename_t& filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::daily_file_format_sink_mt>( | |||||
| logger_name, filename, hour, minute, truncate, max_files, event_handlers | |||||
| ); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files, event_handlers); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> daily_logger_format_st(const std::string& logger_name, const filename_t& filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::daily_file_format_sink_st>( | |||||
| logger_name, filename, hour, minute, truncate, max_files, event_handlers | |||||
| ); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,100 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include "base_sink.h" | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #include <algorithm> | |||||
| #include <memory> | |||||
| #include <mutex> | |||||
| #include <vector> | |||||
| // Distribution sink (mux). Stores a vector of sinks which get called when log | |||||
| // is called | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class dist_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| dist_sink() = default; | |||||
| explicit dist_sink(std::vector<std::shared_ptr<sink>> sinks) : | |||||
| sinks_(sinks) | |||||
| { | |||||
| } | |||||
| dist_sink(const dist_sink&) = delete; | |||||
| dist_sink& operator=(const dist_sink&) = delete; | |||||
| void add_sink(std::shared_ptr<sink> sub_sink) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| sinks_.push_back(sub_sink); | |||||
| } | |||||
| void remove_sink(std::shared_ptr<sink> sub_sink) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sub_sink), sinks_.end()); | |||||
| } | |||||
| void set_sinks(std::vector<std::shared_ptr<sink>> sinks) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| sinks_ = std::move(sinks); | |||||
| } | |||||
| std::vector<std::shared_ptr<sink>>& sinks() | |||||
| { | |||||
| return sinks_; | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| for (auto& sub_sink : sinks_) | |||||
| { | |||||
| if (sub_sink->should_log(msg.level)) | |||||
| { | |||||
| sub_sink->log(msg); | |||||
| } | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| for (auto& sub_sink : sinks_) | |||||
| { | |||||
| sub_sink->flush(); | |||||
| } | |||||
| } | |||||
| void set_pattern_(const std::string& pattern) override | |||||
| { | |||||
| set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||||
| } | |||||
| void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override | |||||
| { | |||||
| base_sink<Mutex>::formatter_ = std::move(sink_formatter); | |||||
| for (auto& sub_sink : sinks_) | |||||
| { | |||||
| sub_sink->set_formatter(base_sink<Mutex>::formatter_->clone()); | |||||
| } | |||||
| } | |||||
| std::vector<std::shared_ptr<sink>> sinks_; | |||||
| }; | |||||
| using dist_sink_mt = dist_sink<std::mutex>; | |||||
| using dist_sink_st = dist_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,99 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include "dist_sink.h" | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <cstdio> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <chrono> | |||||
| // Duplicate message removal sink. | |||||
| // Skip the message if previous one is identical and less than "max_skip_duration" have passed | |||||
| // | |||||
| // Example: | |||||
| // | |||||
| // #include <spdlog/sinks/dup_filter_sink.h> | |||||
| // | |||||
| // int main() { | |||||
| // auto dup_filter = std::make_shared<dup_filter_sink_st>(std::chrono::seconds(5), level::info); | |||||
| // dup_filter->add_sink(std::make_shared<stdout_color_sink_mt>()); | |||||
| // spdlog::logger l("logger", dup_filter); | |||||
| // l.info("Hello"); | |||||
| // l.info("Hello"); | |||||
| // l.info("Hello"); | |||||
| // l.info("Different Hello"); | |||||
| // } | |||||
| // | |||||
| // Will produce: | |||||
| // [2019-06-25 17:50:56.511] [logger] [info] Hello | |||||
| // [2019-06-25 17:50:56.512] [logger] [info] Skipped 3 duplicate messages.. | |||||
| // [2019-06-25 17:50:56.512] [logger] [info] Different Hello | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class dup_filter_sink : public dist_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| template<class Rep, class Period> | |||||
| explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration, level::level_enum notification_level = level::info) : | |||||
| max_skip_duration_{max_skip_duration}, | |||||
| log_level_{notification_level} | |||||
| { | |||||
| } | |||||
| protected: | |||||
| std::chrono::microseconds max_skip_duration_; | |||||
| log_clock::time_point last_msg_time_; | |||||
| std::string last_msg_payload_; | |||||
| size_t skip_counter_ = 0; | |||||
| level::level_enum log_level_; | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| bool filtered = filter_(msg); | |||||
| if (!filtered) | |||||
| { | |||||
| skip_counter_ += 1; | |||||
| return; | |||||
| } | |||||
| // log the "skipped.." message | |||||
| if (skip_counter_ > 0) | |||||
| { | |||||
| char buf[64]; | |||||
| auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", static_cast<unsigned>(skip_counter_)); | |||||
| if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf)) | |||||
| { | |||||
| details::log_msg skipped_msg{msg.source, msg.logger_name, log_level_, string_view_t{buf, static_cast<size_t>(msg_size)}}; | |||||
| dist_sink<Mutex>::sink_it_(skipped_msg); | |||||
| } | |||||
| } | |||||
| // log current message | |||||
| dist_sink<Mutex>::sink_it_(msg); | |||||
| last_msg_time_ = msg.time; | |||||
| skip_counter_ = 0; | |||||
| last_msg_payload_.assign(msg.payload.data(), msg.payload.data() + msg.payload.size()); | |||||
| } | |||||
| // return whether the log msg should be displayed (true) or skipped (false) | |||||
| bool filter_(const details::log_msg& msg) | |||||
| { | |||||
| auto filter_duration = msg.time - last_msg_time_; | |||||
| return (filter_duration > max_skip_duration_) || (msg.payload != last_msg_payload_); | |||||
| } | |||||
| }; | |||||
| using dup_filter_sink_mt = dup_filter_sink<std::mutex>; | |||||
| using dup_filter_sink_st = dup_filter_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,204 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/os.h> | |||||
| #include <spdlog/details/circular_q.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <chrono> | |||||
| #include <cstdio> | |||||
| #include <ctime> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * Generator of Hourly log file names in format basename.YYYY-MM-DD-HH.ext | |||||
| */ | |||||
| struct hourly_filename_calculator | |||||
| { | |||||
| // Create filename for the form basename.YYYY-MM-DD-H | |||||
| static filename_t calc_filename(const filename_t& filename, const tm& now_tm) | |||||
| { | |||||
| filename_t basename, ext; | |||||
| std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
| return fmt_lib::format(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, now_tm.tm_hour, ext); | |||||
| } | |||||
| }; | |||||
| /* | |||||
| * Rotating file sink based on time. | |||||
| * If truncate != false , the created file will be truncated. | |||||
| * If max_files > 0, retain only the last max_files and delete previous. | |||||
| */ | |||||
| template<typename Mutex, typename FileNameCalc = hourly_filename_calculator> | |||||
| class hourly_file_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| // create hourly file sink which rotates on given time | |||||
| hourly_file_sink( | |||||
| filename_t base_filename, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {} | |||||
| ) : | |||||
| base_filename_(std::move(base_filename)), | |||||
| file_helper_{event_handlers}, | |||||
| truncate_(truncate), | |||||
| max_files_(max_files), | |||||
| filenames_q_() | |||||
| { | |||||
| auto now = log_clock::now(); | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
| file_helper_.open(filename, truncate_); | |||||
| remove_init_file_ = file_helper_.size() == 0; | |||||
| rotation_tp_ = next_rotation_tp_(); | |||||
| if (max_files_ > 0) | |||||
| { | |||||
| init_filenames_q_(); | |||||
| } | |||||
| } | |||||
| filename_t filename() | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| return file_helper_.filename(); | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| auto time = msg.time; | |||||
| bool should_rotate = time >= rotation_tp_; | |||||
| if (should_rotate) | |||||
| { | |||||
| if (remove_init_file_) | |||||
| { | |||||
| file_helper_.close(); | |||||
| details::os::remove(file_helper_.filename()); | |||||
| } | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); | |||||
| file_helper_.open(filename, truncate_); | |||||
| rotation_tp_ = next_rotation_tp_(); | |||||
| } | |||||
| remove_init_file_ = false; | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| file_helper_.write(formatted); | |||||
| // Do the cleaning only at the end because it might throw on failure. | |||||
| if (should_rotate && max_files_ > 0) | |||||
| { | |||||
| delete_old_(); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| file_helper_.flush(); | |||||
| } | |||||
| private: | |||||
| void init_filenames_q_() | |||||
| { | |||||
| using details::os::path_exists; | |||||
| filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); | |||||
| std::vector<filename_t> filenames; | |||||
| auto now = log_clock::now(); | |||||
| while (filenames.size() < max_files_) | |||||
| { | |||||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||||
| if (!path_exists(filename)) | |||||
| { | |||||
| break; | |||||
| } | |||||
| filenames.emplace_back(filename); | |||||
| now -= std::chrono::hours(1); | |||||
| } | |||||
| for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter) | |||||
| { | |||||
| filenames_q_.push_back(std::move(*iter)); | |||||
| } | |||||
| } | |||||
| tm now_tm(log_clock::time_point tp) | |||||
| { | |||||
| time_t tnow = log_clock::to_time_t(tp); | |||||
| return spdlog::details::os::localtime(tnow); | |||||
| } | |||||
| log_clock::time_point next_rotation_tp_() | |||||
| { | |||||
| auto now = log_clock::now(); | |||||
| tm date = now_tm(now); | |||||
| date.tm_min = 0; | |||||
| date.tm_sec = 0; | |||||
| auto rotation_time = log_clock::from_time_t(std::mktime(&date)); | |||||
| if (rotation_time > now) | |||||
| { | |||||
| return rotation_time; | |||||
| } | |||||
| return {rotation_time + std::chrono::hours(1)}; | |||||
| } | |||||
| // Delete the file N rotations ago. | |||||
| // Throw spdlog_ex on failure to delete the old file. | |||||
| void delete_old_() | |||||
| { | |||||
| using details::os::filename_to_str; | |||||
| using details::os::remove_if_exists; | |||||
| filename_t current_file = file_helper_.filename(); | |||||
| if (filenames_q_.full()) | |||||
| { | |||||
| auto old_filename = std::move(filenames_q_.front()); | |||||
| filenames_q_.pop_front(); | |||||
| bool ok = remove_if_exists(old_filename) == 0; | |||||
| if (!ok) | |||||
| { | |||||
| filenames_q_.push_back(std::move(current_file)); | |||||
| SPDLOG_THROW(spdlog_ex("Failed removing hourly file " + filename_to_str(old_filename), errno)); | |||||
| } | |||||
| } | |||||
| filenames_q_.push_back(std::move(current_file)); | |||||
| } | |||||
| filename_t base_filename_; | |||||
| log_clock::time_point rotation_tp_; | |||||
| details::file_helper file_helper_; | |||||
| bool truncate_; | |||||
| uint16_t max_files_; | |||||
| details::circular_q<filename_t> filenames_q_; | |||||
| bool remove_init_file_; | |||||
| }; | |||||
| using hourly_file_sink_mt = hourly_file_sink<std::mutex>; | |||||
| using hourly_file_sink_st = hourly_file_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> hourly_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files, event_handlers); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> hourly_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate = false, uint16_t max_files = 0, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files, event_handlers); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,109 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // | |||||
| // Custom sink for mongodb | |||||
| // Building and using requires mongocxx library. | |||||
| // For building mongocxx library check the url below | |||||
| // http://mongocxx.org/mongocxx-v3/installation/ | |||||
| // | |||||
| #include "spdlog/common.h" | |||||
| #include "spdlog/details/log_msg.h" | |||||
| #include "spdlog/sinks/base_sink.h" | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <bsoncxx/builder/stream/document.hpp> | |||||
| #include <bsoncxx/types.hpp> | |||||
| #include <bsoncxx/view_or_value.hpp> | |||||
| #include <mongocxx/client.hpp> | |||||
| #include <mongocxx/instance.hpp> | |||||
| #include <mongocxx/uri.hpp> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class mongo_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| mongo_sink(const std::string& db_name, const std::string& collection_name, const std::string& uri = "mongodb://localhost:27017") | |||||
| try : mongo_sink(std::make_shared<mongocxx::instance>(), db_name, collection_name, uri) | |||||
| { | |||||
| } | |||||
| catch (const std::exception& e) | |||||
| { | |||||
| throw_spdlog_ex(fmt_lib::format("Error opening database: {}", e.what())); | |||||
| } | |||||
| mongo_sink(std::shared_ptr<mongocxx::instance> instance, const std::string& db_name, const std::string& collection_name, const std::string& uri = "mongodb://localhost:27017") : | |||||
| instance_(std::move(instance)), | |||||
| db_name_(db_name), | |||||
| coll_name_(collection_name) | |||||
| { | |||||
| try | |||||
| { | |||||
| client_ = spdlog::details::make_unique<mongocxx::client>(mongocxx::uri{uri}); | |||||
| } | |||||
| catch (const std::exception& e) | |||||
| { | |||||
| throw_spdlog_ex(fmt_lib::format("Error opening database: {}", e.what())); | |||||
| } | |||||
| } | |||||
| ~mongo_sink() | |||||
| { | |||||
| flush_(); | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| using bsoncxx::builder::stream::document; | |||||
| using bsoncxx::builder::stream::finalize; | |||||
| if (client_ != nullptr) | |||||
| { | |||||
| auto doc = document{} << "timestamp" << bsoncxx::types::b_date(msg.time) << "level" << level::to_string_view(msg.level).data() | |||||
| << "level_num" << msg.level << "message" << std::string(msg.payload.begin(), msg.payload.end()) | |||||
| << "logger_name" << std::string(msg.logger_name.begin(), msg.logger_name.end()) << "thread_id" | |||||
| << static_cast<int>(msg.thread_id) << finalize; | |||||
| client_->database(db_name_).collection(coll_name_).insert_one(doc.view()); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| private: | |||||
| std::shared_ptr<mongocxx::instance> instance_; | |||||
| std::string db_name_; | |||||
| std::string coll_name_; | |||||
| std::unique_ptr<mongocxx::client> client_ = nullptr; | |||||
| }; | |||||
| #include "spdlog/details/null_mutex.h" | |||||
| #include <mutex> | |||||
| using mongo_sink_mt = mongo_sink<std::mutex>; | |||||
| using mongo_sink_st = mongo_sink<spdlog::details::null_mutex>; | |||||
| } // namespace sinks | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> mongo_logger_mt(const std::string& logger_name, const std::string& db_name, const std::string& collection_name, const std::string& uri = "mongodb://localhost:27017") | |||||
| { | |||||
| return Factory::template create<sinks::mongo_sink_mt>(logger_name, db_name, collection_name, uri); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> mongo_logger_st(const std::string& logger_name, const std::string& db_name, const std::string& collection_name, const std::string& uri = "mongodb://localhost:27017") | |||||
| { | |||||
| return Factory::template create<sinks::mongo_sink_st>(logger_name, db_name, collection_name, uri); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,62 @@ | |||||
| // Copyright(c) 2016 Alexander Dalshov & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #if defined(_WIN32) | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| // Avoid including windows.h (https://stackoverflow.com/a/30741042) | |||||
| extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); | |||||
| extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * MSVC sink (logging using OutputDebugStringA) | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class msvc_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| msvc_sink() = default; | |||||
| msvc_sink(bool check_debugger_present) : | |||||
| check_debugger_present_{check_debugger_present} {}; | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| if (check_debugger_present_ && !IsDebuggerPresent()) | |||||
| { | |||||
| return; | |||||
| } | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| formatted.push_back('\0'); // add a null terminator for OutputDebugStringA | |||||
| OutputDebugStringA(formatted.data()); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| bool check_debugger_present_ = true; | |||||
| }; | |||||
| using msvc_sink_mt = msvc_sink<std::mutex>; | |||||
| using msvc_sink_st = msvc_sink<details::null_mutex>; | |||||
| using windebug_sink_mt = msvc_sink_mt; | |||||
| using windebug_sink_st = msvc_sink_st; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| #endif | |||||
| @@ -0,0 +1,50 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <mutex> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class null_sink : public base_sink<Mutex> | |||||
| { | |||||
| protected: | |||||
| void sink_it_(const details::log_msg&) override | |||||
| { | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| }; | |||||
| using null_sink_mt = null_sink<details::null_mutex>; | |||||
| using null_sink_st = null_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> null_logger_mt(const std::string& logger_name) | |||||
| { | |||||
| auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); | |||||
| null_logger->set_level(level::off); | |||||
| return null_logger; | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> null_logger_st(const std::string& logger_name) | |||||
| { | |||||
| auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); | |||||
| null_logger->set_level(level::off); | |||||
| return null_logger; | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,53 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <mutex> | |||||
| #include <ostream> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class ostream_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| explicit ostream_sink(std::ostream& os, bool force_flush = false) : | |||||
| ostream_(os), | |||||
| force_flush_(force_flush) | |||||
| { | |||||
| } | |||||
| ostream_sink(const ostream_sink&) = delete; | |||||
| ostream_sink& operator=(const ostream_sink&) = delete; | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); | |||||
| if (force_flush_) | |||||
| { | |||||
| ostream_.flush(); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| ostream_.flush(); | |||||
| } | |||||
| std::ostream& ostream_; | |||||
| bool force_flush_; | |||||
| }; | |||||
| using ostream_sink_mt = ostream_sink<std::mutex>; | |||||
| using ostream_sink_st = ostream_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,107 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman, mguludag and spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| // | |||||
| // Custom sink for QPlainTextEdit or QTextEdit and its childs(QTextBrowser... | |||||
| // etc) Building and using requires Qt library. | |||||
| // | |||||
| #include "spdlog/common.h" | |||||
| #include "spdlog/details/log_msg.h" | |||||
| #include "spdlog/details/synchronous_factory.h" | |||||
| #include "spdlog/sinks/base_sink.h" | |||||
| #include <QTextEdit> | |||||
| #include <QPlainTextEdit> | |||||
| // | |||||
| // qt_sink class | |||||
| // | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| class qt_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| qt_sink(QObject* qt_object, const std::string& meta_method) | |||||
| { | |||||
| qt_object_ = qt_object; | |||||
| meta_method_ = meta_method; | |||||
| } | |||||
| ~qt_sink() | |||||
| { | |||||
| flush_(); | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| string_view_t str = string_view_t(formatted.data(), formatted.size()); | |||||
| QMetaObject::invokeMethod(qt_object_, meta_method_.c_str(), Qt::AutoConnection, Q_ARG(QString, QString::fromUtf8(str.data(), static_cast<int>(str.size())).trimmed())); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| private: | |||||
| QObject* qt_object_ = nullptr; | |||||
| std::string meta_method_; | |||||
| }; | |||||
| #include "spdlog/details/null_mutex.h" | |||||
| #include <mutex> | |||||
| using qt_sink_mt = qt_sink<std::mutex>; | |||||
| using qt_sink_st = qt_sink<spdlog::details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // Factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_mt(const std::string& logger_name, QTextEdit* qt_object, const std::string& meta_method = "append") | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_st(const std::string& logger_name, QTextEdit* qt_object, const std::string& meta_method = "append") | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_mt( | |||||
| const std::string& logger_name, QPlainTextEdit* qt_object, const std::string& meta_method = "appendPlainText" | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_st( | |||||
| const std::string& logger_name, QPlainTextEdit* qt_object, const std::string& meta_method = "appendPlainText" | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_mt(const std::string& logger_name, QObject* qt_object, const std::string& meta_method) | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> qt_logger_st(const std::string& logger_name, QObject* qt_object, const std::string& meta_method) | |||||
| { | |||||
| return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,79 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include "spdlog/sinks/base_sink.h" | |||||
| #include "spdlog/details/circular_q.h" | |||||
| #include "spdlog/details/log_msg_buffer.h" | |||||
| #include "spdlog/details/null_mutex.h" | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <vector> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /* | |||||
| * Ring buffer sink | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class ringbuffer_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| explicit ringbuffer_sink(size_t n_items) : | |||||
| q_{n_items} | |||||
| { | |||||
| } | |||||
| std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| auto items_available = q_.size(); | |||||
| auto n_items = lim > 0 ? (std::min)(lim, items_available) : items_available; | |||||
| std::vector<details::log_msg_buffer> ret; | |||||
| ret.reserve(n_items); | |||||
| for (size_t i = (items_available - n_items); i < items_available; i++) | |||||
| { | |||||
| ret.push_back(q_.at(i)); | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| std::vector<std::string> last_formatted(size_t lim = 0) | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| auto items_available = q_.size(); | |||||
| auto n_items = lim > 0 ? (std::min)(lim, items_available) : items_available; | |||||
| std::vector<std::string> ret; | |||||
| ret.reserve(n_items); | |||||
| for (size_t i = (items_available - n_items); i < items_available; i++) | |||||
| { | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(q_.at(i), formatted); | |||||
| ret.push_back(std::move(SPDLOG_BUF_TO_STRING(formatted))); | |||||
| } | |||||
| return ret; | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| q_.push_back(details::log_msg_buffer{msg}); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| private: | |||||
| details::circular_q<details::log_msg_buffer> q_; | |||||
| }; | |||||
| using ringbuffer_sink_mt = ringbuffer_sink<std::mutex>; | |||||
| using ringbuffer_sink_st = ringbuffer_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,155 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/rotating_file_sink.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/fmt/fmt.h> | |||||
| #include <cerrno> | |||||
| #include <chrono> | |||||
| #include <ctime> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <tuple> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( | |||||
| filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open, const file_event_handlers& event_handlers | |||||
| ) : | |||||
| base_filename_(std::move(base_filename)), | |||||
| max_size_(max_size), | |||||
| max_files_(max_files), | |||||
| file_helper_{event_handlers} | |||||
| { | |||||
| if (max_size == 0) | |||||
| { | |||||
| throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); | |||||
| } | |||||
| if (max_files > 200000) | |||||
| { | |||||
| throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000"); | |||||
| } | |||||
| file_helper_.open(calc_filename(base_filename_, 0)); | |||||
| current_size_ = file_helper_.size(); // expensive. called only once | |||||
| if (rotate_on_open && current_size_ > 0) | |||||
| { | |||||
| rotate_(); | |||||
| current_size_ = 0; | |||||
| } | |||||
| } | |||||
| // calc filename according to index and file extension if exists. | |||||
| // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t& filename, std::size_t index) | |||||
| { | |||||
| if (index == 0u) | |||||
| { | |||||
| return filename; | |||||
| } | |||||
| filename_t basename, ext; | |||||
| std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||||
| return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::filename() | |||||
| { | |||||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||||
| return file_helper_.filename(); | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg& msg) | |||||
| { | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| auto new_size = current_size_ + formatted.size(); | |||||
| // rotate if the new estimated file size exceeds max size. | |||||
| // rotate only if the real size > 0 to better deal with full disk (see issue #2261). | |||||
| // we only check the real size when new_size > max_size_ because it is relatively expensive. | |||||
| if (new_size > max_size_) | |||||
| { | |||||
| file_helper_.flush(); | |||||
| if (file_helper_.size() > 0) | |||||
| { | |||||
| rotate_(); | |||||
| new_size = formatted.size(); | |||||
| } | |||||
| } | |||||
| file_helper_.write(formatted); | |||||
| current_size_ = new_size; | |||||
| } | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::flush_() | |||||
| { | |||||
| file_helper_.flush(); | |||||
| } | |||||
| // Rotate files: | |||||
| // log.txt -> log.1.txt | |||||
| // log.1.txt -> log.2.txt | |||||
| // log.2.txt -> log.3.txt | |||||
| // log.3.txt -> delete | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() | |||||
| { | |||||
| using details::os::filename_to_str; | |||||
| using details::os::path_exists; | |||||
| file_helper_.close(); | |||||
| for (auto i = max_files_; i > 0; --i) | |||||
| { | |||||
| filename_t src = calc_filename(base_filename_, i - 1); | |||||
| if (!path_exists(src)) | |||||
| { | |||||
| continue; | |||||
| } | |||||
| filename_t target = calc_filename(base_filename_, i); | |||||
| if (!rename_file_(src, target)) | |||||
| { | |||||
| // if failed try again after a small delay. | |||||
| // this is a workaround to a windows issue, where very high rotation | |||||
| // rates can cause the rename to fail with permission denied (because of antivirus?). | |||||
| details::os::sleep_for_millis(100); | |||||
| if (!rename_file_(src, target)) | |||||
| { | |||||
| file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! | |||||
| current_size_ = 0; | |||||
| throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); | |||||
| } | |||||
| } | |||||
| } | |||||
| file_helper_.reopen(true); | |||||
| } | |||||
| // delete the target if exists, and rename the src file to target | |||||
| // return true on success, false otherwise. | |||||
| template<typename Mutex> | |||||
| SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file_(const filename_t& src_filename, const filename_t& target_filename) | |||||
| { | |||||
| // try to delete the target file in case it already exists. | |||||
| (void)details::os::remove(target_filename); | |||||
| return details::os::rename(src_filename, target_filename) == 0; | |||||
| } | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,82 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/file_helper.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <chrono> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| // | |||||
| // Rotating file sink based on size | |||||
| // | |||||
| template<typename Mutex> | |||||
| class rotating_file_sink final : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false, const file_event_handlers& event_handlers = {}); | |||||
| static filename_t calc_filename(const filename_t& filename, std::size_t index); | |||||
| filename_t filename(); | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override; | |||||
| void flush_() override; | |||||
| private: | |||||
| // Rotate files: | |||||
| // log.txt -> log.1.txt | |||||
| // log.1.txt -> log.2.txt | |||||
| // log.2.txt -> log.3.txt | |||||
| // log.3.txt -> delete | |||||
| void rotate_(); | |||||
| // delete the target if exists, and rename the src file to target | |||||
| // return true on success, false otherwise. | |||||
| bool rename_file_(const filename_t& src_filename, const filename_t& target_filename); | |||||
| filename_t base_filename_; | |||||
| std::size_t max_size_; | |||||
| std::size_t max_files_; | |||||
| std::size_t current_size_; | |||||
| details::file_helper file_helper_; | |||||
| }; | |||||
| using rotating_file_sink_mt = rotating_file_sink<std::mutex>; | |||||
| using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::rotating_file_sink_mt>( | |||||
| logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers | |||||
| ); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false, const file_event_handlers& event_handlers = {}) | |||||
| { | |||||
| return Factory::template create<sinks::rotating_file_sink_st>( | |||||
| logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers | |||||
| ); | |||||
| } | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "rotating_file_sink-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,25 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/sink.h> | |||||
| #endif | |||||
| #include <spdlog/common.h> | |||||
| SPDLOG_INLINE bool spdlog::sinks::sink::should_log(spdlog::level::level_enum msg_level) const | |||||
| { | |||||
| return msg_level >= level_.load(std::memory_order_relaxed); | |||||
| } | |||||
| SPDLOG_INLINE void spdlog::sinks::sink::set_level(level::level_enum log_level) | |||||
| { | |||||
| level_.store(log_level, std::memory_order_relaxed); | |||||
| } | |||||
| SPDLOG_INLINE spdlog::level::level_enum spdlog::sinks::sink::level() const | |||||
| { | |||||
| return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/log_msg.h> | |||||
| #include <spdlog/formatter.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| class SPDLOG_API sink | |||||
| { | |||||
| public: | |||||
| virtual ~sink() = default; | |||||
| virtual void log(const details::log_msg& msg) = 0; | |||||
| virtual void flush() = 0; | |||||
| virtual void set_pattern(const std::string& pattern) = 0; | |||||
| virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; | |||||
| void set_level(level::level_enum log_level); | |||||
| level::level_enum level() const; | |||||
| bool should_log(level::level_enum msg_level) const; | |||||
| protected: | |||||
| // sink log level - default is all | |||||
| level_t level_{level::trace}; | |||||
| }; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "sink-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,39 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/stdout_color_sinks.h> | |||||
| #endif | |||||
| #include <spdlog/logger.h> | |||||
| #include <spdlog/common.h> | |||||
| namespace spdlog | |||||
| { | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string& logger_name, color_mode mode) | |||||
| { | |||||
| return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string& logger_name, color_mode mode) | |||||
| { | |||||
| return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string& logger_name, color_mode mode) | |||||
| { | |||||
| return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string& logger_name, color_mode mode) | |||||
| { | |||||
| return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,47 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifdef _WIN32 | |||||
| #include <spdlog/sinks/wincolor_sink.h> | |||||
| #else | |||||
| #include <spdlog/sinks/ansicolor_sink.h> | |||||
| #endif | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| using stdout_color_sink_mt = wincolor_stdout_sink_mt; | |||||
| using stdout_color_sink_st = wincolor_stdout_sink_st; | |||||
| using stderr_color_sink_mt = wincolor_stderr_sink_mt; | |||||
| using stderr_color_sink_st = wincolor_stderr_sink_st; | |||||
| #else | |||||
| using stdout_color_sink_mt = ansicolor_stdout_sink_mt; | |||||
| using stdout_color_sink_st = ansicolor_stdout_sink_st; | |||||
| using stderr_color_sink_mt = ansicolor_stderr_sink_mt; | |||||
| using stderr_color_sink_st = ansicolor_stderr_sink_st; | |||||
| #endif | |||||
| } // namespace sinks | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stdout_color_mt(const std::string& logger_name, color_mode mode = color_mode::automatic); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stdout_color_st(const std::string& logger_name, color_mode mode = color_mode::automatic); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stderr_color_mt(const std::string& logger_name, color_mode mode = color_mode::automatic); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stderr_color_st(const std::string& logger_name, color_mode mode = color_mode::automatic); | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "stdout_color_sinks-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,143 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #ifndef SPDLOG_HEADER_ONLY | |||||
| #include <spdlog/sinks/stdout_sinks.h> | |||||
| #endif | |||||
| #include <spdlog/details/console_globals.h> | |||||
| #include <spdlog/pattern_formatter.h> | |||||
| #include <memory> | |||||
| #ifdef _WIN32 | |||||
| // under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675) | |||||
| // so instead we use ::FileWrite | |||||
| #include <spdlog/details/windows_include.h> | |||||
| #ifndef _USING_V110_SDK71_ // fileapi.h doesn't exist in winxp | |||||
| #include <fileapi.h> // WriteFile (..) | |||||
| #endif | |||||
| #include <io.h> // _get_osfhandle(..) | |||||
| #include <stdio.h> // _fileno(..) | |||||
| #endif // WIN32 | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE* file) : | |||||
| mutex_(ConsoleMutex::mutex()), | |||||
| file_(file), | |||||
| formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| // get windows handle from the FILE* object | |||||
| handle_ = reinterpret_cast<HANDLE>(::_get_osfhandle(::_fileno(file_))); | |||||
| // don't throw to support cases where no console is attached, | |||||
| // and let the log method to do nothing if (handle_ == INVALID_HANDLE_VALUE). | |||||
| // throw only if non stdout/stderr target is requested (probably regular file and not console). | |||||
| if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr) | |||||
| { | |||||
| throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() failed", errno); | |||||
| } | |||||
| #endif // WIN32 | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg& msg) | |||||
| { | |||||
| #ifdef _WIN32 | |||||
| if (handle_ == INVALID_HANDLE_VALUE) | |||||
| { | |||||
| return; | |||||
| } | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| memory_buf_t formatted; | |||||
| formatter_->format(msg, formatted); | |||||
| ::fflush(file_); // flush in case there is something in this file_ already | |||||
| auto size = static_cast<DWORD>(formatted.size()); | |||||
| DWORD bytes_written = 0; | |||||
| bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0; | |||||
| if (!ok) | |||||
| { | |||||
| throw_spdlog_ex("stdout_sink_base: WriteFile() failed. GetLastError(): " + std::to_string(::GetLastError())); | |||||
| } | |||||
| #else | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| memory_buf_t formatted; | |||||
| formatter_->format(msg, formatted); | |||||
| ::fwrite(formatted.data(), sizeof(char), formatted.size(), file_); | |||||
| ::fflush(file_); // flush every line to terminal | |||||
| #endif // WIN32 | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::flush() | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| fflush(file_); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_pattern(const std::string& pattern) | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||||
| } | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||||
| { | |||||
| std::lock_guard<mutex_t> lock(mutex_); | |||||
| formatter_ = std::move(sink_formatter); | |||||
| } | |||||
| // stdout sink | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE stdout_sink<ConsoleMutex>::stdout_sink() : | |||||
| stdout_sink_base<ConsoleMutex>(stdout) | |||||
| { | |||||
| } | |||||
| // stderr sink | |||||
| template<typename ConsoleMutex> | |||||
| SPDLOG_INLINE stderr_sink<ConsoleMutex>::stderr_sink() : | |||||
| stdout_sink_base<ConsoleMutex>(stderr) | |||||
| { | |||||
| } | |||||
| } // namespace sinks | |||||
| // factory methods | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_mt(const std::string& logger_name) | |||||
| { | |||||
| return Factory::template create<sinks::stdout_sink_mt>(logger_name); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_st(const std::string& logger_name) | |||||
| { | |||||
| return Factory::template create<sinks::stdout_sink_st>(logger_name); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_mt(const std::string& logger_name) | |||||
| { | |||||
| return Factory::template create<sinks::stderr_sink_mt>(logger_name); | |||||
| } | |||||
| template<typename Factory> | |||||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_st(const std::string& logger_name) | |||||
| { | |||||
| return Factory::template create<sinks::stderr_sink_st>(logger_name); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,89 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/details/console_globals.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <spdlog/sinks/sink.h> | |||||
| #include <cstdio> | |||||
| #ifdef _WIN32 | |||||
| #include <spdlog/details/windows_include.h> | |||||
| #endif | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| template<typename ConsoleMutex> | |||||
| class stdout_sink_base : public sink | |||||
| { | |||||
| public: | |||||
| using mutex_t = typename ConsoleMutex::mutex_t; | |||||
| explicit stdout_sink_base(FILE* file); | |||||
| ~stdout_sink_base() override = default; | |||||
| stdout_sink_base(const stdout_sink_base& other) = delete; | |||||
| stdout_sink_base(stdout_sink_base&& other) = delete; | |||||
| stdout_sink_base& operator=(const stdout_sink_base& other) = delete; | |||||
| stdout_sink_base& operator=(stdout_sink_base&& other) = delete; | |||||
| void log(const details::log_msg& msg) override; | |||||
| void flush() override; | |||||
| void set_pattern(const std::string& pattern) override; | |||||
| void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; | |||||
| protected: | |||||
| mutex_t& mutex_; | |||||
| FILE* file_; | |||||
| std::unique_ptr<spdlog::formatter> formatter_; | |||||
| #ifdef _WIN32 | |||||
| HANDLE handle_; | |||||
| #endif // WIN32 | |||||
| }; | |||||
| template<typename ConsoleMutex> | |||||
| class stdout_sink : public stdout_sink_base<ConsoleMutex> | |||||
| { | |||||
| public: | |||||
| stdout_sink(); | |||||
| }; | |||||
| template<typename ConsoleMutex> | |||||
| class stderr_sink : public stdout_sink_base<ConsoleMutex> | |||||
| { | |||||
| public: | |||||
| stderr_sink(); | |||||
| }; | |||||
| using stdout_sink_mt = stdout_sink<details::console_mutex>; | |||||
| using stdout_sink_st = stdout_sink<details::console_nullmutex>; | |||||
| using stderr_sink_mt = stderr_sink<details::console_mutex>; | |||||
| using stderr_sink_st = stderr_sink<details::console_nullmutex>; | |||||
| } // namespace sinks | |||||
| // factory methods | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stdout_logger_mt(const std::string& logger_name); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stdout_logger_st(const std::string& logger_name); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stderr_logger_mt(const std::string& logger_name); | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| std::shared_ptr<logger> stderr_logger_st(const std::string& logger_name); | |||||
| } // namespace spdlog | |||||
| #ifdef SPDLOG_HEADER_ONLY | |||||
| #include "stdout_sinks-inl.h" | |||||
| #endif | |||||
| @@ -0,0 +1,110 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <array> | |||||
| #include <string> | |||||
| #include <syslog.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /** | |||||
| * Sink that write to syslog using the `syscall()` library call. | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class syslog_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| syslog_sink(std::string ident, int syslog_option, int syslog_facility, bool enable_formatting) : | |||||
| enable_formatting_{enable_formatting}, | |||||
| syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
| /* spdlog::level::debug */ LOG_DEBUG, | |||||
| /* spdlog::level::info */ LOG_INFO, | |||||
| /* spdlog::level::warn */ LOG_WARNING, | |||||
| /* spdlog::level::err */ LOG_ERR, | |||||
| /* spdlog::level::critical */ LOG_CRIT, | |||||
| /* spdlog::level::off */ LOG_INFO}}, | |||||
| ident_{std::move(ident)} | |||||
| { | |||||
| // set ident to be program name if empty | |||||
| ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); | |||||
| } | |||||
| ~syslog_sink() override | |||||
| { | |||||
| ::closelog(); | |||||
| } | |||||
| syslog_sink(const syslog_sink&) = delete; | |||||
| syslog_sink& operator=(const syslog_sink&) = delete; | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| string_view_t payload; | |||||
| memory_buf_t formatted; | |||||
| if (enable_formatting_) | |||||
| { | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| payload = string_view_t(formatted.data(), formatted.size()); | |||||
| } | |||||
| else | |||||
| { | |||||
| payload = msg.payload; | |||||
| } | |||||
| size_t length = payload.size(); | |||||
| // limit to max int | |||||
| if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
| { | |||||
| length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
| } | |||||
| ::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data()); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| bool enable_formatting_ = false; | |||||
| private: | |||||
| using levels_array = std::array<int, 7>; | |||||
| levels_array syslog_levels_; | |||||
| // must store the ident because the man says openlog might use the pointer as | |||||
| // is and not a string copy | |||||
| const std::string ident_; | |||||
| // | |||||
| // Simply maps spdlog's log level to syslog priority level. | |||||
| // | |||||
| int syslog_prio_from_level(const details::log_msg& msg) const | |||||
| { | |||||
| return syslog_levels_.at(static_cast<levels_array::size_type>(msg.level)); | |||||
| } | |||||
| }; | |||||
| using syslog_sink_mt = syslog_sink<std::mutex>; | |||||
| using syslog_sink_st = syslog_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // Create and register a syslog logger | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> syslog_logger_mt(const std::string& logger_name, const std::string& syslog_ident = "", int syslog_option = 0, int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
| { | |||||
| return Factory::template create<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> syslog_logger_st(const std::string& logger_name, const std::string& syslog_ident = "", int syslog_option = 0, int syslog_facility = LOG_USER, bool enable_formatting = false) | |||||
| { | |||||
| return Factory::template create<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,125 @@ | |||||
| // Copyright(c) 2019 ZVYAGIN.Alexander@gmail.com | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/details/synchronous_factory.h> | |||||
| #include <array> | |||||
| #ifndef SD_JOURNAL_SUPPRESS_LOCATION | |||||
| #define SD_JOURNAL_SUPPRESS_LOCATION | |||||
| #endif | |||||
| #include <systemd/sd-journal.h> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| /** | |||||
| * Sink that write to systemd journal using the `sd_journal_send()` library call. | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class systemd_sink : public base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| systemd_sink(std::string ident = "", bool enable_formatting = false) : | |||||
| ident_{std::move(ident)}, | |||||
| enable_formatting_{enable_formatting}, | |||||
| syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, | |||||
| /* spdlog::level::debug */ LOG_DEBUG, | |||||
| /* spdlog::level::info */ LOG_INFO, | |||||
| /* spdlog::level::warn */ LOG_WARNING, | |||||
| /* spdlog::level::err */ LOG_ERR, | |||||
| /* spdlog::level::critical */ LOG_CRIT, | |||||
| /* spdlog::level::off */ LOG_INFO}} | |||||
| { | |||||
| } | |||||
| ~systemd_sink() override | |||||
| { | |||||
| } | |||||
| systemd_sink(const systemd_sink&) = delete; | |||||
| systemd_sink& operator=(const systemd_sink&) = delete; | |||||
| protected: | |||||
| const std::string ident_; | |||||
| bool enable_formatting_ = false; | |||||
| using levels_array = std::array<int, 7>; | |||||
| levels_array syslog_levels_; | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| int err; | |||||
| string_view_t payload; | |||||
| memory_buf_t formatted; | |||||
| if (enable_formatting_) | |||||
| { | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| payload = string_view_t(formatted.data(), formatted.size()); | |||||
| } | |||||
| else | |||||
| { | |||||
| payload = msg.payload; | |||||
| } | |||||
| size_t length = payload.size(); | |||||
| // limit to max int | |||||
| if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||||
| { | |||||
| length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||||
| } | |||||
| const string_view_t syslog_identifier = ident_.empty() ? msg.logger_name : ident_; | |||||
| // Do not send source location if not available | |||||
| if (msg.source.empty()) | |||||
| { | |||||
| // Note: function call inside '()' to avoid macro expansion | |||||
| err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), payload.data(), "PRIORITY=%d", syslog_level(msg.level), "SYSLOG_IDENTIFIER=%.*s", static_cast<int>(syslog_identifier.size()), syslog_identifier.data(), nullptr); | |||||
| } | |||||
| else | |||||
| { | |||||
| err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), payload.data(), "PRIORITY=%d", syslog_level(msg.level), "SYSLOG_IDENTIFIER=%.*s", static_cast<int>(syslog_identifier.size()), syslog_identifier.data(), "CODE_FILE=%s", msg.source.filename, "CODE_LINE=%d", msg.source.line, "CODE_FUNC=%s", msg.source.funcname, nullptr); | |||||
| } | |||||
| if (err) | |||||
| { | |||||
| throw_spdlog_ex("Failed writing to systemd", errno); | |||||
| } | |||||
| } | |||||
| int syslog_level(level::level_enum l) | |||||
| { | |||||
| return syslog_levels_.at(static_cast<levels_array::size_type>(l)); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| }; | |||||
| using systemd_sink_mt = systemd_sink<std::mutex>; | |||||
| using systemd_sink_st = systemd_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // Create and register a syslog logger | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> systemd_logger_mt( | |||||
| const std::string& logger_name, const std::string& ident = "", bool enable_formatting = false | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::systemd_sink_mt>(logger_name, ident, enable_formatting); | |||||
| } | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> systemd_logger_st( | |||||
| const std::string& logger_name, const std::string& ident = "", bool enable_formatting = false | |||||
| ) | |||||
| { | |||||
| return Factory::template create<sinks::systemd_sink_st>(logger_name, ident, enable_formatting); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,86 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #ifdef _WIN32 | |||||
| #include <spdlog/details/tcp_client-windows.h> | |||||
| #else | |||||
| #include <spdlog/details/tcp_client.h> | |||||
| #endif | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <chrono> | |||||
| #include <functional> | |||||
| #pragma once | |||||
| // Simple tcp client sink | |||||
| // Connects to remote address and send the formatted log. | |||||
| // Will attempt to reconnect if connection drops. | |||||
| // If more complicated behaviour is needed (i.e get responses), you can inherit it and override the sink_it_ method. | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| struct tcp_sink_config | |||||
| { | |||||
| std::string server_host; | |||||
| int server_port; | |||||
| bool lazy_connect = false; // if true connect on first log call instead of on construction | |||||
| tcp_sink_config(std::string host, int port) : | |||||
| server_host{std::move(host)}, | |||||
| server_port{port} | |||||
| { | |||||
| } | |||||
| }; | |||||
| template<typename Mutex> | |||||
| class tcp_sink : public spdlog::sinks::base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| // connect to tcp host/port or throw if failed | |||||
| // host can be hostname or ip address | |||||
| explicit tcp_sink(tcp_sink_config sink_config) : | |||||
| config_{std::move(sink_config)} | |||||
| { | |||||
| if (!config_.lazy_connect) | |||||
| { | |||||
| this->client_.connect(config_.server_host, config_.server_port); | |||||
| } | |||||
| } | |||||
| ~tcp_sink() override = default; | |||||
| protected: | |||||
| void sink_it_(const spdlog::details::log_msg& msg) override | |||||
| { | |||||
| spdlog::memory_buf_t formatted; | |||||
| spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| if (!client_.is_connected()) | |||||
| { | |||||
| client_.connect(config_.server_host, config_.server_port); | |||||
| } | |||||
| client_.send(formatted.data(), formatted.size()); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| tcp_sink_config config_; | |||||
| details::tcp_client client_; | |||||
| }; | |||||
| using tcp_sink_mt = tcp_sink<std::mutex>; | |||||
| using tcp_sink_st = tcp_sink<spdlog::details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,80 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| #pragma once | |||||
| #include <spdlog/common.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #ifdef _WIN32 | |||||
| #include <spdlog/details/udp_client-windows.h> | |||||
| #else | |||||
| #include <spdlog/details/udp_client.h> | |||||
| #endif | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <chrono> | |||||
| #include <functional> | |||||
| // Simple udp client sink | |||||
| // Sends formatted log via udp | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| struct udp_sink_config | |||||
| { | |||||
| std::string server_host; | |||||
| uint16_t server_port; | |||||
| udp_sink_config(std::string host, uint16_t port) : | |||||
| server_host{std::move(host)}, | |||||
| server_port{port} | |||||
| { | |||||
| } | |||||
| }; | |||||
| template<typename Mutex> | |||||
| class udp_sink : public spdlog::sinks::base_sink<Mutex> | |||||
| { | |||||
| public: | |||||
| // host can be hostname or ip address | |||||
| explicit udp_sink(udp_sink_config sink_config) : | |||||
| client_{sink_config.server_host, sink_config.server_port} | |||||
| { | |||||
| } | |||||
| ~udp_sink() override = default; | |||||
| protected: | |||||
| void sink_it_(const spdlog::details::log_msg& msg) override | |||||
| { | |||||
| spdlog::memory_buf_t formatted; | |||||
| spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| client_.send(formatted.data(), formatted.size()); | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| details::udp_client client_; | |||||
| }; | |||||
| using udp_sink_mt = udp_sink<std::mutex>; | |||||
| using udp_sink_st = udp_sink<spdlog::details::null_mutex>; | |||||
| } // namespace sinks | |||||
| // | |||||
| // factory functions | |||||
| // | |||||
| template<typename Factory = spdlog::synchronous_factory> | |||||
| inline std::shared_ptr<logger> udp_logger_mt(const std::string& logger_name, sinks::udp_sink_config skin_config) | |||||
| { | |||||
| return Factory::template create<sinks::udp_sink_mt>(logger_name, skin_config); | |||||
| } | |||||
| } // namespace spdlog | |||||
| @@ -0,0 +1,297 @@ | |||||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||||
| // Writing to Windows Event Log requires the registry entries below to be present, with the following modifications: | |||||
| // 1. <log_name> should be replaced with your log name (e.g. your application name) | |||||
| // 2. <source_name> should be replaced with the specific source name and the key should be duplicated for | |||||
| // each source used in the application | |||||
| // | |||||
| // Since typically modifications of this kind require elevation, it's better to do it as a part of setup procedure. | |||||
| // The snippet below uses mscoree.dll as the message file as it exists on most of the Windows systems anyway and | |||||
| // happens to contain the needed resource. | |||||
| // | |||||
| // You can also specify a custom message file if needed. | |||||
| // Please refer to Event Log functions descriptions in MSDN for more details on custom message files. | |||||
| /*--------------------------------------------------------------------------------------- | |||||
| Windows Registry Editor Version 5.00 | |||||
| [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>] | |||||
| [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\<log_name>\<source_name>] | |||||
| "TypesSupported"=dword:00000007 | |||||
| "EventMessageFile"=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,\ | |||||
| 00,6f,00,74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,\ | |||||
| 5c,00,6d,00,73,00,63,00,6f,00,72,00,65,00,65,00,2e,00,64,00,6c,00,6c,00,00,\ | |||||
| 00 | |||||
| -----------------------------------------------------------------------------------------*/ | |||||
| #pragma once | |||||
| #include <spdlog/details/null_mutex.h> | |||||
| #include <spdlog/sinks/base_sink.h> | |||||
| #include <spdlog/details/windows_include.h> | |||||
| #include <winbase.h> | |||||
| #include <mutex> | |||||
| #include <string> | |||||
| #include <vector> | |||||
| namespace spdlog | |||||
| { | |||||
| namespace sinks | |||||
| { | |||||
| namespace win_eventlog | |||||
| { | |||||
| namespace internal | |||||
| { | |||||
| struct local_alloc_t | |||||
| { | |||||
| HLOCAL hlocal_; | |||||
| SPDLOG_CONSTEXPR local_alloc_t() SPDLOG_NOEXCEPT : hlocal_(nullptr) | |||||
| { | |||||
| } | |||||
| local_alloc_t(local_alloc_t const&) = delete; | |||||
| local_alloc_t& operator=(local_alloc_t const&) = delete; | |||||
| ~local_alloc_t() SPDLOG_NOEXCEPT | |||||
| { | |||||
| if (hlocal_) | |||||
| { | |||||
| LocalFree(hlocal_); | |||||
| } | |||||
| } | |||||
| }; | |||||
| /** Windows error */ | |||||
| struct win32_error : public spdlog_ex | |||||
| { | |||||
| /** Formats an error report line: "user-message: error-code (system message)" */ | |||||
| static std::string format(std::string const& user_message, DWORD error_code = GetLastError()) | |||||
| { | |||||
| std::string system_message; | |||||
| local_alloc_t format_message_result{}; | |||||
| auto format_message_succeeded = | |||||
| ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result.hlocal_, 0, nullptr); | |||||
| if (format_message_succeeded && format_message_result.hlocal_) | |||||
| { | |||||
| system_message = fmt_lib::format(" ({})", (LPSTR)format_message_result.hlocal_); | |||||
| } | |||||
| return fmt_lib::format("{}: {}{}", user_message, error_code, system_message); | |||||
| } | |||||
| explicit win32_error(std::string const& func_name, DWORD error = GetLastError()) : | |||||
| spdlog_ex(format(func_name, error)) | |||||
| { | |||||
| } | |||||
| }; | |||||
| /** Wrapper for security identifiers (SID) on Windows */ | |||||
| struct sid_t | |||||
| { | |||||
| std::vector<char> buffer_; | |||||
| public: | |||||
| sid_t() | |||||
| { | |||||
| } | |||||
| /** creates a wrapped SID copy */ | |||||
| static sid_t duplicate_sid(PSID psid) | |||||
| { | |||||
| if (!::IsValidSid(psid)) | |||||
| { | |||||
| throw_spdlog_ex("sid_t::sid_t(): invalid SID received"); | |||||
| } | |||||
| auto const sid_length{::GetLengthSid(psid)}; | |||||
| sid_t result; | |||||
| result.buffer_.resize(sid_length); | |||||
| if (!::CopySid(sid_length, (PSID)result.as_sid(), psid)) | |||||
| { | |||||
| SPDLOG_THROW(win32_error("CopySid")); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| /** Retrieves pointer to the internal buffer contents as SID* */ | |||||
| SID* as_sid() const | |||||
| { | |||||
| return buffer_.empty() ? nullptr : (SID*)buffer_.data(); | |||||
| } | |||||
| /** Get SID for the current user */ | |||||
| static sid_t get_current_user_sid() | |||||
| { | |||||
| /* create and init RAII holder for process token */ | |||||
| struct process_token_t | |||||
| { | |||||
| HANDLE token_handle_ = INVALID_HANDLE_VALUE; | |||||
| explicit process_token_t(HANDLE process) | |||||
| { | |||||
| if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle_)) | |||||
| { | |||||
| SPDLOG_THROW(win32_error("OpenProcessToken")); | |||||
| } | |||||
| } | |||||
| ~process_token_t() | |||||
| { | |||||
| ::CloseHandle(token_handle_); | |||||
| } | |||||
| } current_process_token(::GetCurrentProcess()); // GetCurrentProcess returns pseudohandle, no leak here! | |||||
| // Get the required size, this is expected to fail with ERROR_INSUFFICIENT_BUFFER and return the token size | |||||
| DWORD tusize = 0; | |||||
| if (::GetTokenInformation(current_process_token.token_handle_, TokenUser, NULL, 0, &tusize)) | |||||
| { | |||||
| SPDLOG_THROW(win32_error("GetTokenInformation should fail")); | |||||
| } | |||||
| // get user token | |||||
| std::vector<unsigned char> buffer(static_cast<size_t>(tusize)); | |||||
| if (!::GetTokenInformation(current_process_token.token_handle_, TokenUser, (LPVOID)buffer.data(), tusize, &tusize)) | |||||
| { | |||||
| SPDLOG_THROW(win32_error("GetTokenInformation")); | |||||
| } | |||||
| // create a wrapper of the SID data as stored in the user token | |||||
| return sid_t::duplicate_sid(((TOKEN_USER*)buffer.data())->User.Sid); | |||||
| } | |||||
| }; | |||||
| struct eventlog | |||||
| { | |||||
| static WORD get_event_type(details::log_msg const& msg) | |||||
| { | |||||
| switch (msg.level) | |||||
| { | |||||
| case level::trace: | |||||
| case level::debug: | |||||
| return EVENTLOG_SUCCESS; | |||||
| case level::info: | |||||
| return EVENTLOG_INFORMATION_TYPE; | |||||
| case level::warn: | |||||
| return EVENTLOG_WARNING_TYPE; | |||||
| case level::err: | |||||
| case level::critical: | |||||
| case level::off: | |||||
| return EVENTLOG_ERROR_TYPE; | |||||
| default: | |||||
| return EVENTLOG_INFORMATION_TYPE; | |||||
| } | |||||
| } | |||||
| static WORD get_event_category(details::log_msg const& msg) | |||||
| { | |||||
| return (WORD)msg.level; | |||||
| } | |||||
| }; | |||||
| } // namespace internal | |||||
| /* | |||||
| * Windows Event Log sink | |||||
| */ | |||||
| template<typename Mutex> | |||||
| class win_eventlog_sink : public base_sink<Mutex> | |||||
| { | |||||
| private: | |||||
| HANDLE hEventLog_{NULL}; | |||||
| internal::sid_t current_user_sid_; | |||||
| std::string source_; | |||||
| DWORD event_id_; | |||||
| HANDLE event_log_handle() | |||||
| { | |||||
| if (!hEventLog_) | |||||
| { | |||||
| hEventLog_ = ::RegisterEventSourceA(nullptr, source_.c_str()); | |||||
| if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED) | |||||
| { | |||||
| SPDLOG_THROW(internal::win32_error("RegisterEventSource")); | |||||
| } | |||||
| } | |||||
| return hEventLog_; | |||||
| } | |||||
| protected: | |||||
| void sink_it_(const details::log_msg& msg) override | |||||
| { | |||||
| using namespace internal; | |||||
| bool succeeded; | |||||
| memory_buf_t formatted; | |||||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||||
| formatted.push_back('\0'); | |||||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||||
| wmemory_buf_t buf; | |||||
| details::os::utf8_to_wstrbuf(string_view_t(formatted.data(), formatted.size()), buf); | |||||
| LPCWSTR lp_wstr = buf.data(); | |||||
| succeeded = static_cast<bool>(::ReportEventW(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_, current_user_sid_.as_sid(), 1, 0, &lp_wstr, nullptr)); | |||||
| #else | |||||
| LPCSTR lp_str = formatted.data(); | |||||
| succeeded = static_cast<bool>(::ReportEventA(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_, current_user_sid_.as_sid(), 1, 0, &lp_str, nullptr)); | |||||
| #endif | |||||
| if (!succeeded) | |||||
| { | |||||
| SPDLOG_THROW(win32_error("ReportEvent")); | |||||
| } | |||||
| } | |||||
| void flush_() override | |||||
| { | |||||
| } | |||||
| public: | |||||
| win_eventlog_sink(std::string const& source, DWORD event_id = 1000 /* according to mscoree.dll */) : | |||||
| source_(source), | |||||
| event_id_(event_id) | |||||
| { | |||||
| try | |||||
| { | |||||
| current_user_sid_ = internal::sid_t::get_current_user_sid(); | |||||
| } | |||||
| catch (...) | |||||
| { | |||||
| // get_current_user_sid() is unlikely to fail and if it does, we can still proceed without | |||||
| // current_user_sid but in the event log the record will have no user name | |||||
| } | |||||
| } | |||||
| ~win_eventlog_sink() | |||||
| { | |||||
| if (hEventLog_) | |||||
| DeregisterEventSource(hEventLog_); | |||||
| } | |||||
| }; | |||||
| } // namespace win_eventlog | |||||
| using win_eventlog_sink_mt = win_eventlog::win_eventlog_sink<std::mutex>; | |||||
| using win_eventlog_sink_st = win_eventlog::win_eventlog_sink<details::null_mutex>; | |||||
| } // namespace sinks | |||||
| } // namespace spdlog | |||||