diff --git a/CMakeLists.txt b/CMakeLists.txt index d250beab35ff096ad83a79bd2661a244278e7956..e3fff2c9843beeafc69ee6402cf6d3719bc464a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,11 @@ find_package( URIParser REQUIRED ) find_package( MsgPack REQUIRED ) find_package( Eigen3 REQUIRED ) +find_package( LibArchive ) +if (LibArchive_FOUND) + set(HAVE_LIBARCHIVE true) +endif() + if (WITH_FIXSTARS) find_package( LibSGM ) if (LibSGM_FOUND) @@ -142,11 +147,13 @@ if (WIN32) # TODO(nick) Should do based upon compiler (VS) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++17") set(CMAKE_CXX_FLAGS_DEBUG "-DFTL_DEBUG -Wall") set(CMAKE_CXX_FLAGS_RELEASE "/O2") + set(OS_LIBS "") else() add_definitions(-DUNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -msse3") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG -pg -Wall") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -mfpmath=sse") + set(OS_LIBS "dl") endif() SET(CMAKE_USE_RELATIVE_PATHS ON) @@ -155,6 +162,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) add_subdirectory(components/common/cpp) add_subdirectory(components/net) add_subdirectory(components/rgbd-sources) +add_subdirectory(components/control/cpp) if (BUILD_RENDERER) add_subdirectory(components/renderers) diff --git a/applications/reconstruct/include/ftl/ray_cast_sdf.hpp b/applications/reconstruct/include/ftl/ray_cast_sdf.hpp index 6e0796bfc40d7c7854a251caadb507c5bd861fe8..206e37c5d2fcd6044f1924af3d20491833f69ef1 100644 --- a/applications/reconstruct/include/ftl/ray_cast_sdf.hpp +++ b/applications/reconstruct/include/ftl/ray_cast_sdf.hpp @@ -13,8 +13,9 @@ class CUDARayCastSDF : public ftl::Configurable { public: CUDARayCastSDF(nlohmann::json& config) : ftl::Configurable(config) { - create(parametersFromConfig(config)); - hash_render_ = config.value("hash_renderer", false); + auto &cfg = ftl::config::resolve(config); + create(parametersFromConfig(cfg)); + hash_render_ = cfg.value("hash_renderer", false); } ~CUDARayCastSDF(void) { diff --git a/applications/reconstruct/include/ftl/scene_rep_hash_sdf.hpp b/applications/reconstruct/include/ftl/scene_rep_hash_sdf.hpp index 20072e7f3317ae2ddecea625d08a56be9f1bd977..a9f32b4c3489ab8c91fc0f69dc02c8a87d8eb6fa 100644 --- a/applications/reconstruct/include/ftl/scene_rep_hash_sdf.hpp +++ b/applications/reconstruct/include/ftl/scene_rep_hash_sdf.hpp @@ -54,22 +54,23 @@ class SceneRep : public ftl::Configurable { } static HashParams parametersFromConfig(nlohmann::json &config) { + auto &cfg = ftl::config::resolve(config); HashParams params; // First camera view is set to identity pose to be at the centre of // the virtual coordinate space. params.m_rigidTransform.setIdentity(); params.m_rigidTransformInverse.setIdentity(); - params.m_hashNumBuckets = config["hashNumBuckets"]; + params.m_hashNumBuckets = cfg["hashNumBuckets"]; params.m_hashBucketSize = HASH_BUCKET_SIZE; - params.m_hashMaxCollisionLinkedListSize = config["hashMaxCollisionLinkedListSize"]; + params.m_hashMaxCollisionLinkedListSize = cfg["hashMaxCollisionLinkedListSize"]; params.m_SDFBlockSize = SDF_BLOCK_SIZE; - params.m_numSDFBlocks = config["hashNumSDFBlocks"]; - params.m_virtualVoxelSize = config["SDFVoxelSize"]; - params.m_maxIntegrationDistance = config["SDFMaxIntegrationDistance"]; - params.m_truncation = config["SDFTruncation"]; - params.m_truncScale = config["SDFTruncationScale"]; - params.m_integrationWeightSample = config["SDFIntegrationWeightSample"]; - params.m_integrationWeightMax = config["SDFIntegrationWeightMax"]; + params.m_numSDFBlocks = cfg["hashNumSDFBlocks"]; + params.m_virtualVoxelSize = cfg["SDFVoxelSize"]; + params.m_maxIntegrationDistance = cfg["SDFMaxIntegrationDistance"]; + params.m_truncation = cfg["SDFTruncation"]; + params.m_truncScale = cfg["SDFTruncationScale"]; + params.m_integrationWeightSample = cfg["SDFIntegrationWeightSample"]; + params.m_integrationWeightMax = cfg["SDFIntegrationWeightMax"]; // Note (Nick): We are not streaming voxels in/out of GPU //params.m_streamingVoxelExtents = MatrixConversion::toCUDA(gas.s_streamingVoxelExtents); //params.m_streamingGridDimensions = MatrixConversion::toCUDA(gas.s_streamingGridDimensions); diff --git a/applications/reconstruct/src/main.cpp b/applications/reconstruct/src/main.cpp index 8b66399d1a8e38b80337a8aa90b8c4c31b8ff2df..7c8a58e4cc9afe1d267cf88ab049ba5d4c938917 100644 --- a/applications/reconstruct/src/main.cpp +++ b/applications/reconstruct/src/main.cpp @@ -4,7 +4,8 @@ * See LICENSE. */ -#include <glog/logging.h> +#define LOGURU_WITH_STREAMS 1 +#include <loguru.hpp> #include <ftl/config.h> #include <ftl/configuration.hpp> #include <ftl/depth_camera.hpp> diff --git a/applications/reconstruct/src/registration.cpp b/applications/reconstruct/src/registration.cpp index 5bc5b6e75395ca41e5a8fff5e6f8fbb8281730aa..37be030b9cdca8aa22bb008f75e3e54cc428660b 100644 --- a/applications/reconstruct/src/registration.cpp +++ b/applications/reconstruct/src/registration.cpp @@ -2,7 +2,8 @@ #ifdef HAVE_PCL -#include <glog/logging.h> +#define LOGURU_WITH_STREAMS 1 +#include <loguru.hpp> #include <pcl/common/transforms.h> #include <pcl/registration/transformation_estimation_svd.h> @@ -66,7 +67,7 @@ float fitPlaneError(PointCloud<PointXYZ>::Ptr cloud_in, float distance_threshold proj.setModelCoefficients(coefficients); proj.filter(cloud_proj); - LOG_ASSERT(cloud_in->size() == cloud_proj.size()); + CHECK(cloud_in->size() == cloud_proj.size()); // todo: which error score is suitable? (using MSE) float score = 0.0; diff --git a/applications/reconstruct/src/virtual_source.cpp b/applications/reconstruct/src/virtual_source.cpp index 8e4955a320bc2847f2ca5b358661208c1c5cc7fc..7e7317b68ee9e4e11545b27569e94303002a68c7 100644 --- a/applications/reconstruct/src/virtual_source.cpp +++ b/applications/reconstruct/src/virtual_source.cpp @@ -3,7 +3,8 @@ #include <ftl/scene_rep_hash_sdf.hpp> #include <ftl/ray_cast_sdf.hpp> -#include <glog/logging.h> +#define LOGURU_WITH_STREAMS 1 +#include <loguru.hpp> using ftl::rgbd::VirtualSource; using std::mutex; diff --git a/applications/vision/CMakeLists.txt b/applications/vision/CMakeLists.txt index 00cf2abafb0bbe91327823ffb81bf73b39e5d7cc..5ce6c5240cd9fc81de29eb688984dccb01c7a5be 100644 --- a/applications/vision/CMakeLists.txt +++ b/applications/vision/CMakeLists.txt @@ -22,6 +22,6 @@ set_property(TARGET ftl-vision PROPERTY CUDA_SEPARABLE_COMPILATION OFF) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlrender Threads::Threads ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} glog::glog ftlnet) +target_link_libraries(ftl-vision ftlrgbd ftlcommon ftlctrl ftlrender ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} ftlnet) diff --git a/applications/vision/src/main.cpp b/applications/vision/src/main.cpp index e9bcc690f11440596727a53f9b6f97e02e86261e..bb77b0810c810cded5c1a74eec432b1ee47fa8f2 100644 --- a/applications/vision/src/main.cpp +++ b/applications/vision/src/main.cpp @@ -4,7 +4,8 @@ * See LICENSE. */ -#include <glog/logging.h> +#define LOGURU_WITH_STREAMS 1 +#include <loguru.hpp> #include <ftl/configuration.hpp> #include <ctpl_stl.h> // #include <zlib.h> @@ -23,6 +24,7 @@ #include <ftl/display.hpp> #include <ftl/rgbd_streamer.hpp> #include <ftl/net/universe.hpp> +#include <ftl/slave.hpp> #include <nlohmann/json.hpp> #include "opencv2/imgproc.hpp" @@ -79,7 +81,7 @@ void disparityToDepth(const cv::Mat &disparity, cv::Mat &depth, const cv::Mat &q static void run(ftl::Configurable *root) { Universe *net = ftl::create<Universe>(root, "net"); - LOG(INFO) << "Net started."; + ftl::ctrl::Slave slave(net, root); auto paths = root->get<vector<string>>("paths"); string file = ""; diff --git a/applications/vision/src/middlebury.cpp b/applications/vision/src/middlebury.cpp index dfe9c8c88919845569c23f0606ee0e275a4ca274..79d1b2f0a18937105e4ca74747a57eacc1b52fe8 100644 --- a/applications/vision/src/middlebury.cpp +++ b/applications/vision/src/middlebury.cpp @@ -1,5 +1,5 @@ #include <ftl/middlebury.hpp> -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/rgbd.hpp> #include <string> diff --git a/applications/vision/src/streamer.cpp b/applications/vision/src/streamer.cpp index 0a05351f7de5cd2a72e08846d2cee03305a1dc71..29b84568cbba3f94d54ef16a6599fbb398394085 100644 --- a/applications/vision/src/streamer.cpp +++ b/applications/vision/src/streamer.cpp @@ -1,4 +1,4 @@ -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/streamer.hpp> #include <vector> // #include <zlib.h> diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt index 56a74f2664127f3ad18390d1fbfc47d581d4e95b..911b7d1d69225d55e84cf6ab2fd87e0d1fc81a79 100644 --- a/components/common/cpp/CMakeLists.txt +++ b/components/common/cpp/CMakeLists.txt @@ -3,6 +3,7 @@ set(COMMONSRC src/uri.cpp src/configuration.cpp src/configurable.cpp + src/loguru.cpp src/opencv_to_pcl.cpp ) @@ -17,7 +18,7 @@ target_include_directories(ftlcommon PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PRIVATE src) -target_link_libraries(ftlcommon glog::glog ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES}) +target_link_libraries(ftlcommon Threads::Threads ${OS_LIBS} ${OpenCV_LIBS} ${PCL_LIBRARIES} ${URIPARSER_LIBRARIES}) add_subdirectory(test) diff --git a/components/common/cpp/include/ftl/configurable.hpp b/components/common/cpp/include/ftl/configurable.hpp index c1f903b61df4004e458c54f243fd0014c69ff96f..3fb497c1d3bb9ccc17d543db5716670ca79c55f6 100644 --- a/components/common/cpp/include/ftl/configurable.hpp +++ b/components/common/cpp/include/ftl/configurable.hpp @@ -2,7 +2,8 @@ #ifndef _FTL_CONFIGURABLE_HPP_ #define _FTL_CONFIGURABLE_HPP_ -#include <glog/logging.h> +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> #include <nlohmann/json.hpp> #include <string> #include <tuple> @@ -15,6 +16,15 @@ namespace ftl { +class Configurable; + +namespace config { +struct Event { + Configurable *entity; + std::string name; +}; +} + /** * The Configurable class should be inherited by any entity that is to be * configured using json objects. Additionally, any such object can then be @@ -39,6 +49,9 @@ class Configurable { */ void required(const char *f, const std::vector<std::tuple<std::string, std::string, std::string>> &r); + /** + * Return raw JSON entity for this Configurable. + */ nlohmann::json &getConfig() { return config_; } /** @@ -68,18 +81,26 @@ class Configurable { _trigger(name); } + /** + * Create or find existing configurable object of given type from the + * given property name. Additional constructor arguments can also be + * provided. Any kind of failure results in a nullptr being returned. + */ + template <typename T, typename... ARGS> + T *create(const std::string &name, ARGS ...args); + /** * Add callback for whenever a specified property changes value. * @param prop Name of property to watch * @param callback A function object that will be called on change. */ - void on(const std::string &prop, std::function<void(Configurable*, const std::string&)>); + void on(const std::string &prop, std::function<void(const config::Event&)>); protected: - nlohmann::json config_; + nlohmann::json &config_; private: - std::map<std::string, std::list<std::function<void(Configurable*, const std::string&)>>> observers_; + std::map<std::string, std::list<std::function<void(const config::Event&)>>> observers_; void _trigger(const std::string &name); @@ -111,7 +132,7 @@ std::optional<T> ftl::Configurable::get(const std::string &name) { std::string res_uri = config_["$ref"].get<std::string>()+"/"+name; auto &r = ftl::config::resolve(res_uri); - LOG(INFO) << "GET Resolve: " << res_uri << " = " << r; + DLOG(2) << "GET: " << res_uri << " = " << r; try { return r.get<T>(); @@ -124,4 +145,9 @@ std::optional<T> ftl::Configurable::get(const std::string &name) { } } +template <typename T, typename... ARGS> +T *ftl::Configurable::create(const std::string &name, ARGS ...args) { + return ftl::config::create<T>(this, name, args...); +} + #endif // _FTL_CONFIGURABLE_HPP_ diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp index 9f8c6e0fda3033f0102b2ac1de95e7cb3afe3790..6a91c9b05781a6442844a53b73828027b1fcf9e0 100644 --- a/components/common/cpp/include/ftl/configuration.hpp +++ b/components/common/cpp/include/ftl/configuration.hpp @@ -2,6 +2,8 @@ #ifndef _FTL_COMMON_CONFIGURATION_HPP_ #define _FTL_COMMON_CONFIGURATION_HPP_ +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> #include <nlohmann/json.hpp> //#include <ftl/configurable.hpp> #include <string> @@ -25,11 +27,17 @@ std::optional<std::string> locateFile(const std::string &name); Configurable *configure(int argc, char **argv, const std::string &root); +/** + * Change a configuration value based upon a URI. Return true if changed, + * false if it was not able to change. + */ +bool update(const std::string &puri, const json_t &value); + /** * Resolve a JSON schema reference, but do not wait for a remote reference * if it is not available. A null entity is returned if not resolved. */ -json_t &resolve(const std::string &); +json_t &resolve(const std::string &, bool eager=true); /** * Resolve a reference object, or if not a reference object it simply returns @@ -77,17 +85,17 @@ using config::configure; template <typename T, typename... ARGS> T *ftl::config::create(json_t &link, ARGS ...args) { - auto &r = link; // = ftl::config::resolve(link); + //auto &r = link; // = ftl::config::resolve(link); - if (!r["$id"].is_string()) { - LOG(FATAL) << "Entity does not have $id or parent: " << r; + if (!link["$id"].is_string()) { + LOG(FATAL) << "Entity does not have $id or parent: " << link; return nullptr; } - ftl::Configurable *cfg = ftl::config::find(r["$id"].get<std::string>()); + ftl::Configurable *cfg = ftl::config::find(link["$id"].get<std::string>()); if (!cfg) { // try { - cfg = new T(r, args...); + cfg = new T(link, args...); //} catch(...) { // LOG(FATAL) << "Could not construct " << link; //} @@ -103,12 +111,18 @@ T *ftl::config::create(json_t &link, ARGS ...args) { template <typename T, typename... ARGS> T *ftl::config::create(ftl::Configurable *parent, const std::string &name, ARGS ...args) { - nlohmann::json &entity = ftl::config::resolve(parent->getConfig()[name]); + //nlohmann::json &entity = ftl::config::resolve(parent->getConfig()[name]); + nlohmann::json &entity = (!parent->getConfig()[name].is_null()) ? parent->getConfig()[name] : ftl::config::resolve(parent->getConfig())[name]; if (entity.is_object()) { if (!entity["$id"].is_string()) { // TODO(Nick) Check for # in URI - entity["$id"] = *parent->get<std::string>("$id") + std::string("/") + name; + std::string id_str = *parent->get<std::string>("$id"); + if (id_str.find('#') != std::string::npos) { + entity["$id"] = id_str + std::string("/") + name; + } else { + entity["$id"] = id_str + std::string("#") + name; + } } } /*else { nlohmann::json &res = resolve(entity); diff --git a/components/common/cpp/include/loguru.hpp b/components/common/cpp/include/loguru.hpp new file mode 100644 index 0000000000000000000000000000000000000000..2bfe8e0e2e174b107fea79dd286be956adc00a82 --- /dev/null +++ b/components/common/cpp/include/loguru.hpp @@ -0,0 +1,1327 @@ +/* +Loguru logging library for C++, by Emil Ernerfeldt. +www.github.com/emilk/loguru +If you find Loguru useful, please let me know on twitter or in a mail! +Twitter: @ernerfeldt +Mail: emil.ernerfeldt@gmail.com +Website: www.ilikebigbits.com + +# License + This software is in the public domain. Where that dedication is not + recognized, you are granted a perpetual, irrevocable license to copy + and modify this file as you see fit. + +# Inspiration + Much of Loguru was inspired by GLOG, https://code.google.com/p/google-glog/. + The choice of public domain is fully due Sean T. Barrett + and his wonderful stb libraries at https://github.com/nothings/stb. + +# Version history + * Version 0.1.0 - 2015-03-22 - Works great on Mac. + * Version 0.2.0 - 2015-09-17 - Removed the only dependency. + * Version 0.3.0 - 2015-10-02 - Drop-in replacement for most of GLOG + * Version 0.4.0 - 2015-10-07 - Single-file! + * Version 0.5.0 - 2015-10-17 - Improved file logging + * Version 0.6.0 - 2015-10-24 - Add stack traces + * Version 0.7.0 - 2015-10-27 - Signals + * Version 0.8.0 - 2015-10-30 - Color logging. + * Version 0.9.0 - 2015-11-26 - ABORT_S and proper handling of FATAL + * Version 1.0.0 - 2016-02-14 - ERROR_CONTEXT + * Version 1.1.0 - 2016-02-19 - -v OFF, -v INFO etc + * Version 1.1.1 - 2016-02-20 - textprintf vs strprintf + * Version 1.1.2 - 2016-02-22 - Remove g_alsologtostderr + * Version 1.1.3 - 2016-02-29 - ERROR_CONTEXT as linked list + * Version 1.2.0 - 2016-03-19 - Add get_thread_name() + * Version 1.2.1 - 2016-03-20 - Minor fixes + * Version 1.2.2 - 2016-03-29 - Fix issues with set_fatal_handler throwing an exception + * Version 1.2.3 - 2016-05-16 - Log current working directory in loguru::init(). + * Version 1.2.4 - 2016-05-18 - Custom replacement for -v in loguru::init() by bjoernpollex + * Version 1.2.5 - 2016-05-18 - Add ability to print ERROR_CONTEXT of parent thread. + * Version 1.2.6 - 2016-05-19 - Bug fix regarding VLOG verbosity argument lacking (). + * Version 1.2.7 - 2016-05-23 - Fix PATH_MAX problem. + * Version 1.2.8 - 2016-05-26 - Add shutdown() and remove_all_callbacks() + * Version 1.2.9 - 2016-06-09 - Use a monotonic clock for uptime. + * Version 1.3.0 - 2016-07-20 - Fix issues with callback flush/close not being called. + * Version 1.3.1 - 2016-07-20 - Add LOGURU_UNSAFE_SIGNAL_HANDLER to toggle stacktrace on signals. + * Version 1.3.2 - 2016-07-20 - Add loguru::arguments() + * Version 1.4.0 - 2016-09-15 - Semantic versioning + add loguru::create_directories + * Version 1.4.1 - 2016-09-29 - Customize formating with LOGURU_FILENAME_WIDTH + * Version 1.5.0 - 2016-12-22 - LOGURU_USE_FMTLIB by kolis and LOGURU_WITH_FILEABS by scinart + * Version 1.5.1 - 2017-08-08 - Terminal colors on Windows 10 thanks to looki + * Version 1.6.0 - 2018-01-03 - Add LOGURU_RTTI and LOGURU_STACKTRACES settings + * Version 1.7.0 - 2018-01-03 - Add ability to turn off the preamble with loguru::g_preamble + * Version 1.7.1 - 2018-04-05 - Add function get_fatal_handler + * Version 1.7.2 - 2018-04-22 - Fix a bug where large file names could cause stack corruption (thanks @ccamporesi) + * Version 1.8.0 - 2018-04-23 - Shorten long file names to keep preamble fixed width + * Version 1.9.0 - 2018-09-22 - Adjust terminal colors, add LOGURU_VERBOSE_SCOPE_ENDINGS, add LOGURU_SCOPE_TIME_PRECISION, add named log levels + * Version 2.0.0 - 2018-09-22 - Split loguru.hpp into loguru.hpp and loguru.cpp + +# Compiling + Just include <loguru.hpp> where you want to use Loguru. + Then, in one .cpp file #include <loguru.cpp> + Make sure you compile with -std=c++11 -lstdc++ -lpthread -ldl + +# Usage + For details, please see the official documentation at emilk.github.io/loguru + + #include <loguru.hpp> + + int main(int argc, char* argv[]) { + loguru::init(argc, argv); + + // Put every log message in "everything.log": + loguru::add_file("everything.log", loguru::Append, loguru::Verbosity_MAX); + + LOG_F(INFO, "The magic number is %d", 42); + } + +*/ + +#pragma once +#if defined(LOGURU_IMPLEMENTATION) + #warning "You are defining LOGURU_IMPLEMENTATION. This is for older versions of Loguru. You should now instead include loguru.cpp (or build it and link with it)" +#endif + +// Disable all warnings from gcc/clang: +#if defined(__clang__) + #pragma clang system_header +#elif defined(__GNUC__) + #pragma GCC system_header +#endif + +#ifndef LOGURU_HAS_DECLARED_FORMAT_HEADER +#define LOGURU_HAS_DECLARED_FORMAT_HEADER + +// Semantic versioning. Loguru version can be printed with printf("%d.%d.%d", LOGURU_VERSION_MAJOR, LOGURU_VERSION_MINOR, LOGURU_VERSION_PATCH); +#define LOGURU_VERSION_MAJOR 2 +#define LOGURU_VERSION_MINOR 0 +#define LOGURU_VERSION_PATCH 0 + +#define LOGURU_REPLACE_GLOG 1 + +#if defined(_MSC_VER) +#include <sal.h> // Needed for _In_z_ etc annotations +#endif + +// ---------------------------------------------------------------------------- + +#ifndef LOGURU_EXPORT + // Define to your project's export declaration if needed for use in a shared library. + #define LOGURU_EXPORT +#endif + +#ifndef LOGURU_SCOPE_TEXT_SIZE + // Maximum length of text that can be printed by a LOG_SCOPE. + // This should be long enough to get most things, but short enough not to clutter the stack. + #define LOGURU_SCOPE_TEXT_SIZE 196 +#endif + +#ifndef LOGURU_FILENAME_WIDTH + // Width of the column containing the file name + #define LOGURU_FILENAME_WIDTH 23 +#endif + +#ifndef LOGURU_THREADNAME_WIDTH + // Width of the column containing the thread name + #define LOGURU_THREADNAME_WIDTH 16 +#endif + +#ifndef LOGURU_SCOPE_TIME_PRECISION + // Resolution of scope timers. 3=ms, 6=us, 9=ns + #define LOGURU_SCOPE_TIME_PRECISION 3 +#endif + +#ifndef LOGURU_CATCH_SIGABRT + // Should Loguru catch SIGABRT to print stack trace etc? + #define LOGURU_CATCH_SIGABRT 1 +#endif + +#ifndef LOGURU_VERBOSE_SCOPE_ENDINGS + // Show milliseconds and scope name at end of scope. + #define LOGURU_VERBOSE_SCOPE_ENDINGS 1 +#endif + +#ifndef LOGURU_REDEFINE_ASSERT + #define LOGURU_REDEFINE_ASSERT 0 +#endif + +#ifndef LOGURU_WITH_STREAMS + #define LOGURU_WITH_STREAMS 0 +#endif + +#ifndef LOGURU_REPLACE_GLOG + #define LOGURU_REPLACE_GLOG 0 +#endif + +#if LOGURU_REPLACE_GLOG + #undef LOGURU_WITH_STREAMS + #define LOGURU_WITH_STREAMS 1 +#endif + +#ifndef LOGURU_UNSAFE_SIGNAL_HANDLER + #define LOGURU_UNSAFE_SIGNAL_HANDLER 1 +#endif + +#if LOGURU_IMPLEMENTATION + #undef LOGURU_WITH_STREAMS + #define LOGURU_WITH_STREAMS 1 +#endif + +#ifndef LOGURU_USE_FMTLIB + #define LOGURU_USE_FMTLIB 0 +#endif + +#ifndef LOGURU_WITH_FILEABS + #define LOGURU_WITH_FILEABS 0 +#endif + +#ifndef LOGURU_RTTI +#if defined(__clang__) + #if __has_feature(cxx_rtti) + #define LOGURU_RTTI 1 + #endif +#elif defined(__GNUG__) + #if defined(__GXX_RTTI) + #define LOGURU_RTTI 1 + #endif +#elif defined(_MSC_VER) + #if defined(_CPPRTTI) + #define LOGURU_RTTI 1 + #endif +#endif +#endif + +// -------------------------------------------------------------------- +// Utility macros + +#define LOGURU_CONCATENATE_IMPL(s1, s2) s1 ## s2 +#define LOGURU_CONCATENATE(s1, s2) LOGURU_CONCATENATE_IMPL(s1, s2) + +#ifdef __COUNTER__ +# define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __COUNTER__) +#else +# define LOGURU_ANONYMOUS_VARIABLE(str) LOGURU_CONCATENATE(str, __LINE__) +#endif + +#if defined(__clang__) || defined(__GNUC__) + // Helper macro for declaring functions as having similar signature to printf. + // This allows the compiler to catch format errors at compile-time. + #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) __attribute__((__format__ (__printf__, fmtarg, firstvararg))) + #define LOGURU_FORMAT_STRING_TYPE const char* +#elif defined(_MSC_VER) + #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) + #define LOGURU_FORMAT_STRING_TYPE _In_z_ _Printf_format_string_ const char* +#else + #define LOGURU_PRINTF_LIKE(fmtarg, firstvararg) + #define LOGURU_FORMAT_STRING_TYPE const char* +#endif + +// Used to mark log_and_abort for the benefit of the static analyzer and optimizer. +#if defined(_MSC_VER) +#define LOGURU_NORETURN __declspec(noreturn) +#else +#define LOGURU_NORETURN __attribute__((noreturn)) +#endif + +#if defined(_MSC_VER) +#define LOGURU_PREDICT_FALSE(x) (x) +#define LOGURU_PREDICT_TRUE(x) (x) +#else +#define LOGURU_PREDICT_FALSE(x) (__builtin_expect(x, 0)) +#define LOGURU_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) +#endif + +#if LOGURU_USE_FMTLIB + #include <fmt/format.h> +#endif + +// -------------------------------------------------------------------- + +namespace loguru +{ + // Simple RAII ownership of a char*. + class LOGURU_EXPORT Text + { + public: + explicit Text(char* owned_str) : _str(owned_str) {} + ~Text(); + Text(Text&& t) + { + _str = t._str; + t._str = nullptr; + } + Text(Text& t) = delete; + Text& operator=(Text& t) = delete; + void operator=(Text&& t) = delete; + + const char* c_str() const { return _str; } + bool empty() const { return _str == nullptr || *_str == '\0'; } + + char* release() + { + auto result = _str; + _str = nullptr; + return result; + } + + private: + char* _str; + }; + + // Like printf, but returns the formated text. + LOGURU_EXPORT + Text textprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); + + // Overloaded for variadic template matching. + LOGURU_EXPORT + Text textprintf(); + + using Verbosity = int; + +#undef FATAL +#undef ERROR +#undef WARNING +#undef INFO +#undef MAX + + enum NamedVerbosity : Verbosity + { + // Used to mark an invalid verbosity. Do not log to this level. + Verbosity_INVALID = -10, // Never do LOG_F(INVALID) + + // You may use Verbosity_OFF on g_stderr_verbosity, but for nothing else! + Verbosity_OFF = -9, // Never do LOG_F(OFF) + + // Prefer to use ABORT_F or ABORT_S over LOG_F(FATAL) or LOG_S(FATAL). + Verbosity_FATAL = -3, + Verbosity_ERROR = -2, + Verbosity_WARNING = -1, + + // Normal messages. By default written to stderr. + Verbosity_INFO = 0, + + // Same as Verbosity_INFO in every way. + Verbosity_0 = 0, + + // Verbosity levels 1-9 are generally not written to stderr, but are written to file. + Verbosity_1 = +1, + Verbosity_2 = +2, + Verbosity_3 = +3, + Verbosity_4 = +4, + Verbosity_5 = +5, + Verbosity_6 = +6, + Verbosity_7 = +7, + Verbosity_8 = +8, + Verbosity_9 = +9, + + // Don not use higher verbosity levels, as that will make grepping log files harder. + Verbosity_MAX = +9, + }; + + struct Message + { + // You would generally print a Message by just concating the buffers without spacing. + // Optionally, ignore preamble and indentation. + Verbosity verbosity; // Already part of preamble + const char* filename; // Already part of preamble + unsigned line; // Already part of preamble + const char* preamble; // Date, time, uptime, thread, file:line, verbosity. + const char* indentation; // Just a bunch of spacing. + const char* prefix; // Assertion failure info goes here (or ""). + const char* message; // User message goes here. + }; + + /* Everything with a verbosity equal or greater than g_stderr_verbosity will be + written to stderr. You can set this in code or via the -v argument. + Set to loguru::Verbosity_OFF to write nothing to stderr. + Default is 0, i.e. only log ERROR, WARNING and INFO are written to stderr. + */ + LOGURU_EXPORT extern Verbosity g_stderr_verbosity; + LOGURU_EXPORT extern bool g_colorlogtostderr; // True by default. + LOGURU_EXPORT extern unsigned g_flush_interval_ms; // 0 (unbuffered) by default. + LOGURU_EXPORT extern bool g_preamble; // Prefix each log line with date, time etc? True by default. + + // Turn off individual parts of the preamble + LOGURU_EXPORT extern bool g_preamble_date; // The date field + LOGURU_EXPORT extern bool g_preamble_time; // The time of the current day + LOGURU_EXPORT extern bool g_preamble_uptime; // The time since init call + LOGURU_EXPORT extern bool g_preamble_thread; // The logging thread + LOGURU_EXPORT extern bool g_preamble_file; // The file from which the log originates from + LOGURU_EXPORT extern bool g_preamble_verbose; // The verbosity field + LOGURU_EXPORT extern bool g_preamble_pipe; // The pipe symbol right before the message + + // May not throw! + typedef void (*log_handler_t)(void* user_data, const Message& message); + typedef void (*close_handler_t)(void* user_data); + typedef void (*flush_handler_t)(void* user_data); + + // May throw if that's how you'd like to handle your errors. + typedef void (*fatal_handler_t)(const Message& message); + + // Given a verbosity level, return the level's name or nullptr. + typedef const char* (*verbosity_to_name_t)(Verbosity verbosity); + + // Given a verbosity level name, return the verbosity level or + // Verbosity_INVALID if name is not recognized. + typedef Verbosity (*name_to_verbosity_t)(const char* name); + + /* Should be called from the main thread. + You don't *need* to call this, but if you do you get: + * Signal handlers installed + * Program arguments logged + * Working dir logged + * Optional -v verbosity flag parsed + * Main thread name set to "main thread" + * Explanation of the preamble (date, threanmae etc) logged + + loguru::init() will look for arguments meant for loguru and remove them. + Arguments meant for loguru are: + -v n Set loguru::g_stderr_verbosity level. Examples: + -v 3 Show verbosity level 3 and lower. + -v 0 Only show INFO, WARNING, ERROR, FATAL (default). + -v INFO Only show INFO, WARNING, ERROR, FATAL (default). + -v WARNING Only show WARNING, ERROR, FATAL. + -v ERROR Only show ERROR, FATAL. + -v FATAL Only show FATAL. + -v OFF Turn off logging to stderr. + + Tip: You can set g_stderr_verbosity before calling loguru::init. + That way you can set the default but have the user override it with the -v flag. + Note that -v does not affect file logging (see loguru::add_file). + + You can use something else instead of "-v" via verbosity_flag. + You can also set verbosity_flag to nullptr. + */ + LOGURU_EXPORT + void init(int& argc, char* argv[], const char* verbosity_flag = "-v"); + + // Will call remove_all_callbacks(). After calling this, logging will still go to stderr. + // You generally don't need to call this. + LOGURU_EXPORT + void shutdown(); + + // What ~ will be replaced with, e.g. "/home/your_user_name/" + LOGURU_EXPORT + const char* home_dir(); + + /* Returns the name of the app as given in argv[0] but without leading path. + That is, if argv[0] is "../foo/app" this will return "app". + */ + LOGURU_EXPORT + const char* argv0_filename(); + + // Returns all arguments given to loguru::init(), but escaped with a single space as separator. + LOGURU_EXPORT + const char* arguments(); + + // Returns the path to the current working dir when loguru::init() was called. + LOGURU_EXPORT + const char* current_dir(); + + // Returns the part of the path after the last / or \ (if any). + LOGURU_EXPORT + const char* filename(const char* path); + + // e.g. "foo/bar/baz.ext" will create the directories "foo/" and "foo/bar/" + LOGURU_EXPORT + bool create_directories(const char* file_path_const); + + // Writes date and time with millisecond precision, e.g. "20151017_161503.123" + LOGURU_EXPORT + void write_date_time(char* buff, unsigned buff_size); + + // Helper: thread-safe version strerror + LOGURU_EXPORT + Text errno_as_text(); + + /* Given a prefix of e.g. "~/loguru/" this might return + "/home/your_username/loguru/app_name/20151017_161503.123.log" + + where "app_name" is a sanitized version of argv[0]. + */ + LOGURU_EXPORT + void suggest_log_path(const char* prefix, char* buff, unsigned buff_size); + + enum FileMode { Truncate, Append }; + + /* Will log to a file at the given path. + Any logging message with a verbosity lower or equal to + the given verbosity will be included. + The function will create all directories in 'path' if needed. + If path starts with a ~, it will be replaced with loguru::home_dir() + To stop the file logging, just call loguru::remove_callback(path) with the same path. + */ + LOGURU_EXPORT + bool add_file(const char* path, FileMode mode, Verbosity verbosity); + + /* Will be called right before abort(). + You can for instance use this to print custom error messages, or throw an exception. + Feel free to call LOG:ing function from this, but not FATAL ones! */ + LOGURU_EXPORT + void set_fatal_handler(fatal_handler_t handler); + + // Get the current fatal handler, if any. Default value is nullptr. + LOGURU_EXPORT + fatal_handler_t get_fatal_handler(); + + /* Will be called on each log messages with a verbosity less or equal to the given one. + Useful for displaying messages on-screen in a game, for example. + The given on_close is also expected to flush (if desired). + */ + LOGURU_EXPORT + void add_callback( + const char* id, + log_handler_t callback, + void* user_data, + Verbosity verbosity, + close_handler_t on_close = nullptr, + flush_handler_t on_flush = nullptr); + + /* Set a callback that returns custom verbosity level names. If callback + is nullptr or returns nullptr, default log names will be used. + */ + LOGURU_EXPORT + void set_verbosity_to_name_callback(verbosity_to_name_t callback); + + /* Set a callback that returns the verbosity level matching a name. The + callback should return Verbosity_INVALID if the name is not + recognized. + */ + LOGURU_EXPORT + void set_name_to_verbosity_callback(name_to_verbosity_t callback); + + /* Get a custom name for a specific verbosity, if one exists, or nullptr. */ + LOGURU_EXPORT + const char* get_verbosity_name(Verbosity verbosity); + + /* Get the verbosity enum value from a custom 4-character level name, if one exists. + If the name does not match a custom level name, Verbosity_INVALID is returned. + */ + LOGURU_EXPORT + Verbosity get_verbosity_from_name(const char* name); + + // Returns true iff the callback was found (and removed). + LOGURU_EXPORT + bool remove_callback(const char* id); + + // Shut down all file logging and any other callback hooks installed. + LOGURU_EXPORT + void remove_all_callbacks(); + + // Returns the maximum of g_stderr_verbosity and all file/custom outputs. + LOGURU_EXPORT + Verbosity current_verbosity_cutoff(); + +#if LOGURU_USE_FMTLIB + // Actual logging function. Use the LOG macro instead of calling this directly. + LOGURU_EXPORT + void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::ArgList args); + FMT_VARIADIC(void, log, Verbosity, const char*, unsigned, LOGURU_FORMAT_STRING_TYPE) + + // Log without any preamble or indentation. + LOGURU_EXPORT + void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, fmt::ArgList args); + FMT_VARIADIC(void, raw_log, Verbosity, const char*, unsigned, LOGURU_FORMAT_STRING_TYPE) +#else // LOGURU_USE_FMTLIB? + // Actual logging function. Use the LOG macro instead of calling this directly. + LOGURU_EXPORT + void log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); + + // Log without any preamble or indentation. + LOGURU_EXPORT + void raw_log(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(4, 5); +#endif // !LOGURU_USE_FMTLIB + + // Helper class for LOG_SCOPE_F + class LOGURU_EXPORT LogScopeRAII + { + public: + LogScopeRAII() : _file(nullptr) {} // No logging + LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); + ~LogScopeRAII(); + +#if defined(_MSC_VER) && _MSC_VER > 1800 + // older MSVC default move ctors close the scope on move. See + // issue #43 + LogScopeRAII(LogScopeRAII&& other) + : _verbosity(other._verbosity) + , _file(other._file) + , _line(other._line) + , _indent_stderr(other._indent_stderr) + , _start_time_ns(other._start_time_ns) + { + // Make sure the tmp object's destruction doesn't close the scope: + other._file = nullptr; + + for (unsigned int i = 0; i < LOGURU_SCOPE_TEXT_SIZE; ++i) { + _name[i] = other._name[i]; + } + } +#else + LogScopeRAII(LogScopeRAII&&) = default; +#endif + + private: + LogScopeRAII(const LogScopeRAII&) = delete; + LogScopeRAII& operator=(const LogScopeRAII&) = delete; + void operator=(LogScopeRAII&&) = delete; + + Verbosity _verbosity; + const char* _file; // Set to null if we are disabled due to verbosity + unsigned _line; + bool _indent_stderr; // Did we? + long long _start_time_ns; + char _name[LOGURU_SCOPE_TEXT_SIZE]; + }; + + // Marked as 'noreturn' for the benefit of the static analyzer and optimizer. + // stack_trace_skip is the number of extrace stack frames to skip above log_and_abort. + LOGURU_EXPORT + LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(5, 6); + LOGURU_EXPORT + LOGURU_NORETURN void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line); + + // Flush output to stderr and files. + // If g_flush_interval_ms is set to non-zero, this will be called automatically this often. + // If not set, you do not need to call this at all. + LOGURU_EXPORT + void flush(); + + template<class T> inline Text format_value(const T&) { return textprintf("N/A"); } + template<> inline Text format_value(const char& v) { return textprintf("%c", v); } + template<> inline Text format_value(const int& v) { return textprintf("%d", v); } + template<> inline Text format_value(const unsigned int& v) { return textprintf("%u", v); } + template<> inline Text format_value(const long& v) { return textprintf("%lu", v); } + template<> inline Text format_value(const unsigned long& v) { return textprintf("%ld", v); } + template<> inline Text format_value(const long long& v) { return textprintf("%llu", v); } + template<> inline Text format_value(const unsigned long long& v) { return textprintf("%lld", v); } + template<> inline Text format_value(const float& v) { return textprintf("%f", v); } + template<> inline Text format_value(const double& v) { return textprintf("%f", v); } + + /* Thread names can be set for the benefit of readable logs. + If you do not set the thread name, a hex id will be shown instead. + These thread names may or may not be the same as the system thread names, + depending on the system. + Try to limit the thread name to 15 characters or less. */ + LOGURU_EXPORT + void set_thread_name(const char* name); + + /* Returns the thread name for this thread. + On OSX this will return the system thread name (settable from both within and without Loguru). + On other systems it will return whatever you set in set_thread_name(); + If no thread name is set, this will return a hexadecimal thread id. + length should be the number of bytes available in the buffer. + 17 is a good number for length. + right_align_hext_id means any hexadecimal thread id will be written to the end of buffer. + */ + LOGURU_EXPORT + void get_thread_name(char* buffer, unsigned long long length, bool right_align_hext_id); + + /* Generates a readable stacktrace as a string. + 'skip' specifies how many stack frames to skip. + For instance, the default skip (1) means: + don't include the call to loguru::stacktrace in the stack trace. */ + LOGURU_EXPORT + Text stacktrace(int skip = 1); + + /* Add a string to be replaced with something else in the stack output. + + For instance, instead of having a stack trace look like this: + 0x41f541 some_function(std::basic_ofstream<char, std::char_traits<char> >&) + You can clean it up with: + auto verbose_type_name = loguru::demangle(typeid(std::ofstream).name()); + loguru::add_stack_cleanup(verbose_type_name.c_str(); "std::ofstream"); + So the next time you will instead see: + 0x41f541 some_function(std::ofstream&) + + `replace_with_this` must be shorter than `find_this`. + */ + LOGURU_EXPORT + void add_stack_cleanup(const char* find_this, const char* replace_with_this); + + // Example: demangle(typeid(std::ofstream).name()) -> "std::basic_ofstream<char, std::char_traits<char> >" + LOGURU_EXPORT + Text demangle(const char* name); + + // ------------------------------------------------------------------------ + /* + Not all terminals support colors, but if they do, and g_colorlogtostderr + is set, Loguru will write them to stderr to make errors in red, etc. + + You also have the option to manually use them, via the function below. + + Note, however, that if you do, the color codes could end up in your logfile! + + This means if you intend to use them functions you should either: + * Use them on the stderr/stdout directly (bypass Loguru). + * Don't add file outputs to Loguru. + * Expect some \e[1m things in your logfile. + + Usage: + printf("%sRed%sGreen%sBold green%sClear again\n", + loguru::terminal_red(), loguru::terminal_green(), + loguru::terminal_bold(), loguru::terminal_reset()); + + If the terminal at hand does not support colors the above output + will just not have funky \e[1m things showing. + */ + + // Do the output terminal support colors? + LOGURU_EXPORT + bool terminal_has_color(); + + // Colors + LOGURU_EXPORT const char* terminal_black(); + LOGURU_EXPORT const char* terminal_red(); + LOGURU_EXPORT const char* terminal_green(); + LOGURU_EXPORT const char* terminal_yellow(); + LOGURU_EXPORT const char* terminal_blue(); + LOGURU_EXPORT const char* terminal_purple(); + LOGURU_EXPORT const char* terminal_cyan(); + LOGURU_EXPORT const char* terminal_light_gray(); + LOGURU_EXPORT const char* terminal_light_red(); + LOGURU_EXPORT const char* terminal_white(); + + // Formating + LOGURU_EXPORT const char* terminal_bold(); + LOGURU_EXPORT const char* terminal_underline(); + + // You should end each line with this! + LOGURU_EXPORT const char* terminal_reset(); + + // -------------------------------------------------------------------- + // Error context related: + + struct StringStream; + + // Use this in your EcEntryBase::print_value overload. + LOGURU_EXPORT + void stream_print(StringStream& out_string_stream, const char* text); + + class LOGURU_EXPORT EcEntryBase + { + public: + EcEntryBase(const char* file, unsigned line, const char* descr); + ~EcEntryBase(); + EcEntryBase(const EcEntryBase&) = delete; + EcEntryBase(EcEntryBase&&) = delete; + EcEntryBase& operator=(const EcEntryBase&) = delete; + EcEntryBase& operator=(EcEntryBase&&) = delete; + + virtual void print_value(StringStream& out_string_stream) const = 0; + + EcEntryBase* previous() const { return _previous; } + + // private: + const char* _file; + unsigned _line; + const char* _descr; + EcEntryBase* _previous; + }; + + template<typename T> + class EcEntryData : public EcEntryBase + { + public: + using Printer = Text(*)(T data); + + EcEntryData(const char* file, unsigned line, const char* descr, T data, Printer&& printer) + : EcEntryBase(file, line, descr), _data(data), _printer(printer) {} + + virtual void print_value(StringStream& out_string_stream) const override + { + const auto str = _printer(_data); + stream_print(out_string_stream, str.c_str()); + } + + private: + T _data; + Printer _printer; + }; + + // template<typename Printer> + // class EcEntryLambda : public EcEntryBase + // { + // public: + // EcEntryLambda(const char* file, unsigned line, const char* descr, Printer&& printer) + // : EcEntryBase(file, line, descr), _printer(std::move(printer)) {} + + // virtual void print_value(StringStream& out_string_stream) const override + // { + // const auto str = _printer(); + // stream_print(out_string_stream, str.c_str()); + // } + + // private: + // Printer _printer; + // }; + + // template<typename Printer> + // EcEntryLambda<Printer> make_ec_entry_lambda(const char* file, unsigned line, const char* descr, Printer&& printer) + // { + // return {file, line, descr, std::move(printer)}; + // } + + template <class T> + struct decay_char_array { using type = T; }; + + template <unsigned long long N> + struct decay_char_array<const char(&)[N]> { using type = const char*; }; + + template <class T> + struct make_const_ptr { using type = T; }; + + template <class T> + struct make_const_ptr<T*> { using type = const T*; }; + + template <class T> + struct make_ec_type { using type = typename make_const_ptr<typename decay_char_array<T>::type>::type; }; + + /* A stack trace gives you the names of the function at the point of a crash. + With ERROR_CONTEXT, you can also get the values of select local variables. + Usage: + + void process_customers(const std::string& filename) + { + ERROR_CONTEXT("Processing file", filename.c_str()); + for (int customer_index : ...) + { + ERROR_CONTEXT("Customer index", customer_index); + ... + } + } + + The context is in effect during the scope of the ERROR_CONTEXT. + Use loguru::get_error_context() to get the contents of the active error contexts. + + Example result: + + ------------------------------------------------ + [ErrorContext] main.cpp:416 Processing file: "customers.json" + [ErrorContext] main.cpp:417 Customer index: 42 + ------------------------------------------------ + + Error contexts are printed automatically on crashes, and only on crashes. + This makes them much faster than logging the value of a variable. + */ + #define ERROR_CONTEXT(descr, data) \ + const loguru::EcEntryData<loguru::make_ec_type<decltype(data)>::type> \ + LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ + __FILE__, __LINE__, descr, data, \ + static_cast<loguru::EcEntryData<loguru::make_ec_type<decltype(data)>::type>::Printer>(loguru::ec_to_text) ) // For better error messages + +/* + #define ERROR_CONTEXT(descr, data) \ + const auto LOGURU_ANONYMOUS_VARIABLE(error_context_scope_)( \ + loguru::make_ec_entry_lambda(__FILE__, __LINE__, descr, \ + [=](){ return loguru::ec_to_text(data); })) +*/ + + using EcHandle = const EcEntryBase*; + + /* + Get a light-weight handle to the error context stack on this thread. + The handle is valid as long as the current thread has no changes to its error context stack. + You can pass the handle to loguru::get_error_context on another thread. + This can be very useful for when you have a parent thread spawning several working threads, + and you want the error context of the parent thread to get printed (too) when there is an + error on the child thread. You can accomplish this thusly: + + void foo(const char* parameter) + { + ERROR_CONTEXT("parameter", parameter) + const auto parent_ec_handle = loguru::get_thread_ec_handle(); + + std::thread([=]{ + loguru::set_thread_name("child thread"); + ERROR_CONTEXT("parent context", parent_ec_handle); + dangerous_code(); + }.join(); + } + + */ + LOGURU_EXPORT + EcHandle get_thread_ec_handle(); + + // Get a string describing the current stack of error context. Empty string if there is none. + LOGURU_EXPORT + Text get_error_context(); + + // Get a string describing the error context of the given thread handle. + LOGURU_EXPORT + Text get_error_context_for(EcHandle ec_handle); + + // ------------------------------------------------------------------------ + + LOGURU_EXPORT Text ec_to_text(const char* data); + LOGURU_EXPORT Text ec_to_text(char data); + LOGURU_EXPORT Text ec_to_text(int data); + LOGURU_EXPORT Text ec_to_text(unsigned int data); + LOGURU_EXPORT Text ec_to_text(long data); + LOGURU_EXPORT Text ec_to_text(unsigned long data); + LOGURU_EXPORT Text ec_to_text(long long data); + LOGURU_EXPORT Text ec_to_text(unsigned long long data); + LOGURU_EXPORT Text ec_to_text(float data); + LOGURU_EXPORT Text ec_to_text(double data); + LOGURU_EXPORT Text ec_to_text(long double data); + LOGURU_EXPORT Text ec_to_text(EcHandle); + + /* + You can add ERROR_CONTEXT support for your own types by overloading ec_to_text. Here's how: + + some.hpp: + namespace loguru { + Text ec_to_text(MySmallType data) + Text ec_to_text(const MyBigType* data) + } // namespace loguru + + some.cpp: + namespace loguru { + Text ec_to_text(MySmallType small_value) + { + // Called only when needed, i.e. on a crash. + std::string str = small_value.as_string(); // Format 'small_value' here somehow. + return Text{strdup(str.c_str())}; + } + + Text ec_to_text(const MyBigType* big_value) + { + // Called only when needed, i.e. on a crash. + std::string str = big_value->as_string(); // Format 'big_value' here somehow. + return Text{strdup(str.c_str())}; + } + } // namespace loguru + + Any file that include some.hpp: + void foo(MySmallType small, const MyBigType& big) + { + ERROR_CONTEXT("Small", small); // Copy ´small` by value. + ERROR_CONTEXT("Big", &big); // `big` should not change during this scope! + .... + } + */ +} // namespace loguru + +// -------------------------------------------------------------------- +// Logging macros + +// LOG_F(2, "Only logged if verbosity is 2 or higher: %d", some_number); +#define VLOG_F(verbosity, ...) \ + ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ + : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) + +// LOG_F(INFO, "Foo: %d", some_number); +#define LOG_F(verbosity_name, ...) VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) + +#define VLOG_IF_F(verbosity, cond, ...) \ + ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ + ? (void)0 \ + : loguru::log(verbosity, __FILE__, __LINE__, __VA_ARGS__) + +#define LOG_IF_F(verbosity_name, cond, ...) \ + VLOG_IF_F(loguru::Verbosity_ ## verbosity_name, cond, __VA_ARGS__) + +#define VLOG_SCOPE_F(verbosity, ...) \ + loguru::LogScopeRAII LOGURU_ANONYMOUS_VARIABLE(error_context_RAII_) = \ + ((verbosity) > loguru::current_verbosity_cutoff()) ? loguru::LogScopeRAII() : \ + loguru::LogScopeRAII(verbosity, __FILE__, __LINE__, __VA_ARGS__) + +// Raw logging - no preamble, no indentation. Slightly faster than full logging. +#define RAW_VLOG_F(verbosity, ...) \ + ((verbosity) > loguru::current_verbosity_cutoff()) ? (void)0 \ + : loguru::raw_log(verbosity, __FILE__, __LINE__, __VA_ARGS__) + +#define RAW_LOG_F(verbosity_name, ...) RAW_VLOG_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) + +// Use to book-end a scope. Affects logging on all threads. +#define LOG_SCOPE_F(verbosity_name, ...) \ + VLOG_SCOPE_F(loguru::Verbosity_ ## verbosity_name, __VA_ARGS__) + +#define LOG_SCOPE_FUNCTION(verbosity_name) LOG_SCOPE_F(verbosity_name, __func__) + +// ----------------------------------------------- +// ABORT_F macro. Usage: ABORT_F("Cause of error: %s", error_str); + +// Message is optional +#define ABORT_F(...) loguru::log_and_abort(0, "ABORT: ", __FILE__, __LINE__, __VA_ARGS__) + +// -------------------------------------------------------------------- +// CHECK_F macros: + +#define CHECK_WITH_INFO_F(test, info, ...) \ + LOGURU_PREDICT_TRUE((test) == true) ? (void)0 : loguru::log_and_abort(0, "CHECK FAILED: " info " ", __FILE__, \ + __LINE__, ##__VA_ARGS__) + +/* Checked at runtime too. Will print error, then call fatal_handler (if any), then 'abort'. + Note that the test must be boolean. + CHECK_F(ptr); will not compile, but CHECK_F(ptr != nullptr); will. */ +#define CHECK_F(test, ...) CHECK_WITH_INFO_F(test, #test, ##__VA_ARGS__) + +#define CHECK_NOTNULL_F(x, ...) CHECK_WITH_INFO_F((x) != nullptr, #x " != nullptr", ##__VA_ARGS__) + +#define CHECK_OP_F(expr_left, expr_right, op, ...) \ + do \ + { \ + auto val_left = expr_left; \ + auto val_right = expr_right; \ + if (! LOGURU_PREDICT_TRUE(val_left op val_right)) \ + { \ + auto str_left = loguru::format_value(val_left); \ + auto str_right = loguru::format_value(val_right); \ + auto fail_info = loguru::textprintf("CHECK FAILED: %s %s %s (%s %s %s) ", \ + #expr_left, #op, #expr_right, str_left.c_str(), #op, str_right.c_str()); \ + auto user_msg = loguru::textprintf(__VA_ARGS__); \ + loguru::log_and_abort(0, fail_info.c_str(), __FILE__, __LINE__, \ + "%s", user_msg.c_str()); \ + } \ + } while (false) + +#ifndef LOGURU_DEBUG_LOGGING + #ifndef NDEBUG + #define LOGURU_DEBUG_LOGGING 1 + #else + #define LOGURU_DEBUG_LOGGING 0 + #endif +#endif + +#if LOGURU_DEBUG_LOGGING + // Debug logging enabled: + #define DLOG_F(verbosity_name, ...) LOG_F(verbosity_name, __VA_ARGS__) + #define DVLOG_F(verbosity, ...) VLOG_F(verbosity, __VA_ARGS__) + #define DLOG_IF_F(verbosity_name, ...) LOG_IF_F(verbosity_name, __VA_ARGS__) + #define DVLOG_IF_F(verbosity, ...) VLOG_IF_F(verbosity, __VA_ARGS__) + #define DRAW_LOG_F(verbosity_name, ...) RAW_LOG_F(verbosity_name, __VA_ARGS__) + #define DRAW_VLOG_F(verbosity, ...) RAW_VLOG_F(verbosity, __VA_ARGS__) +#else + // Debug logging disabled: + #define DLOG_F(verbosity_name, ...) + #define DVLOG_F(verbosity, ...) + #define DLOG_IF_F(verbosity_name, ...) + #define DVLOG_IF_F(verbosity, ...) + #define DRAW_LOG_F(verbosity_name, ...) + #define DRAW_VLOG_F(verbosity, ...) +#endif + +#define CHECK_EQ_F(a, b, ...) CHECK_OP_F(a, b, ==, ##__VA_ARGS__) +#define CHECK_NE_F(a, b, ...) CHECK_OP_F(a, b, !=, ##__VA_ARGS__) +#define CHECK_LT_F(a, b, ...) CHECK_OP_F(a, b, < , ##__VA_ARGS__) +#define CHECK_GT_F(a, b, ...) CHECK_OP_F(a, b, > , ##__VA_ARGS__) +#define CHECK_LE_F(a, b, ...) CHECK_OP_F(a, b, <=, ##__VA_ARGS__) +#define CHECK_GE_F(a, b, ...) CHECK_OP_F(a, b, >=, ##__VA_ARGS__) + +#ifndef LOGURU_DEBUG_CHECKS + #ifndef NDEBUG + #define LOGURU_DEBUG_CHECKS 1 + #else + #define LOGURU_DEBUG_CHECKS 0 + #endif +#endif + +#if LOGURU_DEBUG_CHECKS + // Debug checks enabled: + #define DCHECK_F(test, ...) CHECK_F(test, ##__VA_ARGS__) + #define DCHECK_NOTNULL_F(x, ...) CHECK_NOTNULL_F(x, ##__VA_ARGS__) + #define DCHECK_EQ_F(a, b, ...) CHECK_EQ_F(a, b, ##__VA_ARGS__) + #define DCHECK_NE_F(a, b, ...) CHECK_NE_F(a, b, ##__VA_ARGS__) + #define DCHECK_LT_F(a, b, ...) CHECK_LT_F(a, b, ##__VA_ARGS__) + #define DCHECK_LE_F(a, b, ...) CHECK_LE_F(a, b, ##__VA_ARGS__) + #define DCHECK_GT_F(a, b, ...) CHECK_GT_F(a, b, ##__VA_ARGS__) + #define DCHECK_GE_F(a, b, ...) CHECK_GE_F(a, b, ##__VA_ARGS__) +#else + // Debug checks disabled: + #define DCHECK_F(test, ...) + #define DCHECK_NOTNULL_F(x, ...) + #define DCHECK_EQ_F(a, b, ...) + #define DCHECK_NE_F(a, b, ...) + #define DCHECK_LT_F(a, b, ...) + #define DCHECK_LE_F(a, b, ...) + #define DCHECK_GT_F(a, b, ...) + #define DCHECK_GE_F(a, b, ...) +#endif // NDEBUG + + +#if LOGURU_REDEFINE_ASSERT + #undef assert + #ifndef NDEBUG + // Debug: + #define assert(test) CHECK_WITH_INFO_F(!!(test), #test) // HACK + #else + #define assert(test) + #endif +#endif // LOGURU_REDEFINE_ASSERT + +#endif // LOGURU_HAS_DECLARED_FORMAT_HEADER + +// ---------------------------------------------------------------------------- +// .dP"Y8 888888 88""Yb 888888 db 8b d8 .dP"Y8 +// `Ybo." 88 88__dP 88__ dPYb 88b d88 `Ybo." +// o.`Y8b 88 88"Yb 88"" dP__Yb 88YbdP88 o.`Y8b +// 8bodP' 88 88 Yb 888888 dP""""Yb 88 YY 88 8bodP' + +#if LOGURU_WITH_STREAMS +#ifndef LOGURU_HAS_DECLARED_STREAMS_HEADER +#define LOGURU_HAS_DECLARED_STREAMS_HEADER + +/* This file extends loguru to enable std::stream-style logging, a la Glog. + It's an optional feature behind the LOGURU_WITH_STREAMS settings + because including it everywhere will slow down compilation times. +*/ + +#include <cstdarg> +#include <sstream> // Adds about 38 kLoC on clang. +#include <string> + +namespace loguru +{ + // Like sprintf, but returns the formated text. + LOGURU_EXPORT + std::string strprintf(LOGURU_FORMAT_STRING_TYPE format, ...) LOGURU_PRINTF_LIKE(1, 2); + + // Like vsprintf, but returns the formated text. + LOGURU_EXPORT + std::string vstrprintf(LOGURU_FORMAT_STRING_TYPE format, va_list) LOGURU_PRINTF_LIKE(1, 0); + + class LOGURU_EXPORT StreamLogger + { + public: + StreamLogger(Verbosity verbosity, const char* file, unsigned line) : _verbosity(verbosity), _file(file), _line(line) {} + ~StreamLogger() noexcept(false); + + template<typename T> + StreamLogger& operator<<(const T& t) + { + _ss << t; + return *this; + } + + // std::endl and other iomanip:s. + StreamLogger& operator<<(std::ostream&(*f)(std::ostream&)) + { + f(_ss); + return *this; + } + + private: + Verbosity _verbosity; + const char* _file; + unsigned _line; + std::ostringstream _ss; + }; + + class LOGURU_EXPORT AbortLogger + { + public: + AbortLogger(const char* expr, const char* file, unsigned line) : _expr(expr), _file(file), _line(line) { } + LOGURU_NORETURN ~AbortLogger() noexcept(false); + + template<typename T> + AbortLogger& operator<<(const T& t) + { + _ss << t; + return *this; + } + + // std::endl and other iomanip:s. + AbortLogger& operator<<(std::ostream&(*f)(std::ostream&)) + { + f(_ss); + return *this; + } + + private: + const char* _expr; + const char* _file; + unsigned _line; + std::ostringstream _ss; + }; + + class LOGURU_EXPORT Voidify + { + public: + Voidify() {} + // This has to be an operator with a precedence lower than << but higher than ?: + void operator&(const StreamLogger&) { } + void operator&(const AbortLogger&) { } + }; + + /* Helper functions for CHECK_OP_S macro. + GLOG trick: The (int, int) specialization works around the issue that the compiler + will not instantiate the template version of the function on values of unnamed enum type. */ + #define DEFINE_CHECK_OP_IMPL(name, op) \ + template <typename T1, typename T2> \ + inline std::string* name(const char* expr, const T1& v1, const char* op_str, const T2& v2) \ + { \ + if (LOGURU_PREDICT_TRUE(v1 op v2)) { return NULL; } \ + std::ostringstream ss; \ + ss << "CHECK FAILED: " << expr << " (" << v1 << " " << op_str << " " << v2 << ") "; \ + return new std::string(ss.str()); \ + } \ + inline std::string* name(const char* expr, int v1, const char* op_str, int v2) \ + { \ + return name<int, int>(expr, v1, op_str, v2); \ + } + + DEFINE_CHECK_OP_IMPL(check_EQ_impl, ==) + DEFINE_CHECK_OP_IMPL(check_NE_impl, !=) + DEFINE_CHECK_OP_IMPL(check_LE_impl, <=) + DEFINE_CHECK_OP_IMPL(check_LT_impl, < ) + DEFINE_CHECK_OP_IMPL(check_GE_impl, >=) + DEFINE_CHECK_OP_IMPL(check_GT_impl, > ) + #undef DEFINE_CHECK_OP_IMPL + + /* GLOG trick: Function is overloaded for integral types to allow static const integrals + declared in classes and not defined to be used as arguments to CHECK* macros. */ + template <class T> + inline const T& referenceable_value(const T& t) { return t; } + inline char referenceable_value(char t) { return t; } + inline unsigned char referenceable_value(unsigned char t) { return t; } + inline signed char referenceable_value(signed char t) { return t; } + inline short referenceable_value(short t) { return t; } + inline unsigned short referenceable_value(unsigned short t) { return t; } + inline int referenceable_value(int t) { return t; } + inline unsigned int referenceable_value(unsigned int t) { return t; } + inline long referenceable_value(long t) { return t; } + inline unsigned long referenceable_value(unsigned long t) { return t; } + inline long long referenceable_value(long long t) { return t; } + inline unsigned long long referenceable_value(unsigned long long t) { return t; } +} // namespace loguru + +// ----------------------------------------------- +// Logging macros: + +// usage: LOG_STREAM(INFO) << "Foo " << std::setprecision(10) << some_value; +#define VLOG_IF_S(verbosity, cond) \ + ((verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ + ? (void)0 \ + : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) +#define LOG_IF_S(verbosity_name, cond) VLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) +#define VLOG_S(verbosity) VLOG_IF_S(verbosity, true) +#define LOG_S(verbosity_name) VLOG_S(loguru::Verbosity_ ## verbosity_name) + +// ----------------------------------------------- +// ABORT_S macro. Usage: ABORT_S() << "Causo of error: " << details; + +#define ABORT_S() loguru::Voidify() & loguru::AbortLogger("ABORT: ", __FILE__, __LINE__) + +// ----------------------------------------------- +// CHECK_S macros: + +#define CHECK_WITH_INFO_S(cond, info) \ + LOGURU_PREDICT_TRUE((cond) == true) \ + ? (void)0 \ + : loguru::Voidify() & loguru::AbortLogger("CHECK FAILED: " info " ", __FILE__, __LINE__) + +#define CHECK_S(cond) CHECK_WITH_INFO_S(cond, #cond) +#define CHECK_NOTNULL_S(x) CHECK_WITH_INFO_S((x) != nullptr, #x " != nullptr") + +#define CHECK_OP_S(function_name, expr1, op, expr2) \ + while (auto error_string = loguru::function_name(#expr1 " " #op " " #expr2, \ + loguru::referenceable_value(expr1), #op, \ + loguru::referenceable_value(expr2))) \ + loguru::AbortLogger(error_string->c_str(), __FILE__, __LINE__) + +#define CHECK_EQ_S(expr1, expr2) CHECK_OP_S(check_EQ_impl, expr1, ==, expr2) +#define CHECK_NE_S(expr1, expr2) CHECK_OP_S(check_NE_impl, expr1, !=, expr2) +#define CHECK_LE_S(expr1, expr2) CHECK_OP_S(check_LE_impl, expr1, <=, expr2) +#define CHECK_LT_S(expr1, expr2) CHECK_OP_S(check_LT_impl, expr1, < , expr2) +#define CHECK_GE_S(expr1, expr2) CHECK_OP_S(check_GE_impl, expr1, >=, expr2) +#define CHECK_GT_S(expr1, expr2) CHECK_OP_S(check_GT_impl, expr1, > , expr2) + +#if LOGURU_DEBUG_LOGGING + // Debug logging enabled: + #define DVLOG_IF_S(verbosity, cond) VLOG_IF_S(verbosity, cond) + #define DLOG_IF_S(verbosity_name, cond) LOG_IF_S(verbosity_name, cond) + #define DVLOG_S(verbosity) VLOG_S(verbosity) + #define DLOG_S(verbosity_name) LOG_S(verbosity_name) +#else + // Debug logging disabled: + #define DVLOG_IF_S(verbosity, cond) \ + (true || (verbosity) > loguru::current_verbosity_cutoff() || (cond) == false) \ + ? (void)0 \ + : loguru::Voidify() & loguru::StreamLogger(verbosity, __FILE__, __LINE__) + + #define DLOG_IF_S(verbosity_name, cond) DVLOG_IF_S(loguru::Verbosity_ ## verbosity_name, cond) + #define DVLOG_S(verbosity) DVLOG_IF_S(verbosity, true) + #define DLOG_S(verbosity_name) DVLOG_S(loguru::Verbosity_ ## verbosity_name) +#endif + +#if LOGURU_DEBUG_CHECKS + // Debug checks enabled: + #define DCHECK_S(cond) CHECK_S(cond) + #define DCHECK_NOTNULL_S(x) CHECK_NOTNULL_S(x) + #define DCHECK_EQ_S(a, b) CHECK_EQ_S(a, b) + #define DCHECK_NE_S(a, b) CHECK_NE_S(a, b) + #define DCHECK_LT_S(a, b) CHECK_LT_S(a, b) + #define DCHECK_LE_S(a, b) CHECK_LE_S(a, b) + #define DCHECK_GT_S(a, b) CHECK_GT_S(a, b) + #define DCHECK_GE_S(a, b) CHECK_GE_S(a, b) +#else +// Debug checks disabled: + #define DCHECK_S(cond) CHECK_S(true || (cond)) + #define DCHECK_NOTNULL_S(x) CHECK_S(true || (x) != nullptr) + #define DCHECK_EQ_S(a, b) CHECK_S(true || (a) == (b)) + #define DCHECK_NE_S(a, b) CHECK_S(true || (a) != (b)) + #define DCHECK_LT_S(a, b) CHECK_S(true || (a) < (b)) + #define DCHECK_LE_S(a, b) CHECK_S(true || (a) <= (b)) + #define DCHECK_GT_S(a, b) CHECK_S(true || (a) > (b)) + #define DCHECK_GE_S(a, b) CHECK_S(true || (a) >= (b)) +#endif + +#if LOGURU_REPLACE_GLOG + #undef LOG + #undef VLOG + #undef LOG_IF + #undef VLOG_IF + #undef CHECK + #undef CHECK_NOTNULL + #undef CHECK_EQ + #undef CHECK_NE + #undef CHECK_LT + #undef CHECK_LE + #undef CHECK_GT + #undef CHECK_GE + #undef DLOG + #undef DVLOG + #undef DLOG_IF + #undef DVLOG_IF + #undef DCHECK + #undef DCHECK_NOTNULL + #undef DCHECK_EQ + #undef DCHECK_NE + #undef DCHECK_LT + #undef DCHECK_LE + #undef DCHECK_GT + #undef DCHECK_GE + #undef VLOG_IS_ON + + #define LOG LOG_S + #define VLOG VLOG_S + #define LOG_IF LOG_IF_S + #define VLOG_IF VLOG_IF_S + #define CHECK(cond) CHECK_S(!!(cond)) + #define CHECK_NOTNULL CHECK_NOTNULL_S + #define CHECK_EQ CHECK_EQ_S + #define CHECK_NE CHECK_NE_S + #define CHECK_LT CHECK_LT_S + #define CHECK_LE CHECK_LE_S + #define CHECK_GT CHECK_GT_S + #define CHECK_GE CHECK_GE_S + #define DLOG DLOG_S + #define DVLOG DVLOG_S + #define DLOG_IF DLOG_IF_S + #define DVLOG_IF DVLOG_IF_S + #define DCHECK DCHECK_S + #define DCHECK_NOTNULL DCHECK_NOTNULL_S + #define DCHECK_EQ DCHECK_EQ_S + #define DCHECK_NE DCHECK_NE_S + #define DCHECK_LT DCHECK_LT_S + #define DCHECK_LE DCHECK_LE_S + #define DCHECK_GT DCHECK_GT_S + #define DCHECK_GE DCHECK_GE_S + #define VLOG_IS_ON(verbosity) ((verbosity) <= loguru::current_verbosity_cutoff()) + +#endif // LOGURU_REPLACE_GLOG + +#endif // LOGURU_WITH_STREAMS + +#endif // LOGURU_HAS_DECLARED_STREAMS_HEADER diff --git a/components/common/cpp/src/configurable.cpp b/components/common/cpp/src/configurable.cpp index d770ae6e7003a21bc4a9d15d9c523cb0f0339f84..81f9092b8e316af4fb4dc13f104b8d3b0b77af30 100644 --- a/components/common/cpp/src/configurable.cpp +++ b/components/common/cpp/src/configurable.cpp @@ -30,7 +30,7 @@ void Configurable::_trigger(const string &name) { if (ix != observers_.end()) { for (auto &f : (*ix).second) { try { - f(this, name); + f({this, name}); } catch(...) { LOG(ERROR) << "Exception in event handler for '" << name << "'"; } @@ -38,7 +38,7 @@ void Configurable::_trigger(const string &name) { } } -void Configurable::on(const string &prop, function<void(Configurable*, const string&)> f) { +void Configurable::on(const string &prop, function<void(const ftl::config::Event&)> f) { auto ix = observers_.find(prop); if (ix == observers_.end()) { observers_[prop] = {f}; diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp index 07fad52d1021b8146d63722f36b157262b199148..b2e8c23c723f5255aa4728ef9820c2123b15f03d 100644 --- a/components/common/cpp/src/configuration.cpp +++ b/components/common/cpp/src/configuration.cpp @@ -1,4 +1,5 @@ -#include <glog/logging.h> +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> #include <ftl/config.h> #ifdef WIN32 @@ -9,6 +10,10 @@ #include <unistd.h> #endif +#ifndef WIN32 +#include <signal.h> +#endif + #include <nlohmann/json.hpp> #include <ftl/configuration.hpp> #include <ftl/configurable.hpp> @@ -187,18 +192,60 @@ void ftl::config::registerConfigurable(ftl::Configurable *cfg) { json_t null_json; -json_t &ftl::config::resolve(const std::string &puri) { +bool ftl::config::update(const std::string &puri, const json_t &value) { + // Remove last component of URI + string tail = ""; + string head = ""; + size_t last_hash = puri.find_last_of('#'); + if (last_hash != string::npos) { + size_t last = puri.find_last_of('/'); + if (last != string::npos && last > last_hash) { + tail = puri.substr(last+1); + head = puri.substr(0, last); + } else { + tail = puri.substr(last_hash+1); + head = puri.substr(0, last_hash); + } + } else { + LOG(WARNING) << "Expected a # in an update URI: " << puri; + return false; + } + + Configurable *cfg = find(head); + + if (cfg) { + DLOG(1) << "Updating CFG: " << head << "[" << tail << "] = " << value; + cfg->set<json_t>(tail, value); + } else { + DLOG(1) << "Updating: " << head << "[" << tail << "] = " << value; + auto &r = resolve(head, false); + + if (!r.is_structured()) { + LOG(ERROR) << "Cannot update property '" << tail << "' of '" << head << "'"; + return false; + } + + r[tail] = value; + return true; + } +} + +json_t &ftl::config::resolve(const std::string &puri, bool eager) { string uri_str = puri; // TODO(Nick) Must support alternative root (last $id) if (uri_str.at(0) == '#') { - uri_str = config["$id"].get<string>() + uri_str; + string id_str = config["$id"].get<string>(); + if (id_str.find('#') != string::npos) { + uri_str[0] = '/'; + } // else { + uri_str = id_str + uri_str; + //} } ftl::URI uri(uri_str); if (uri.isValid()) { std::string u = uri.getBaseURI(); - LOG(INFO) << "Resolve URI: " << u; auto ix = config_index.find(u); if (ix == config_index.end()) { LOG(FATAL) << "Cannot find resource: " << u; @@ -206,49 +253,13 @@ json_t &ftl::config::resolve(const std::string &puri) { auto ptr = nlohmann::json::json_pointer("/"+uri.getFragment()); try { - return resolve((*ix).second->at(ptr)); + return (eager) ? resolve((*ix).second->at(ptr)) : (*ix).second->at(ptr); } catch(...) { + LOG(WARNING) << "Resolve failed for " << puri; return null_json; } - //} -/* - - int n = 0; - while (u.size() != 0) { - LOG(INFO) << "Resolve URI: " << u; - auto ix = config_index.find(u); - if (ix == config_index.end()) { - u = uri.getBaseURI(--n); - continue; - } - //LOG(INFO) << "FOUND URI " << *(*ix).second; - - if (n == 0) { - return *(*ix).second; - } else { - // Must apply path segments again... - nlohmann::json *c = (*ix).second; - while (n < 0) { - auto seg = uri.getPathSegment(n++); - c = &(*c)[seg]; - - // Recursive resolve... - if ((*c).is_string()) { - c = &resolve(*c); - } - } - - // Give it the correct URI - if (!(*c)["$id"].is_string()) { - (*c)["$id"] = uri.getBaseURI(); - } - - return *c; - } - } - LOG(FATAL) << "Unresolvable configuration URI: " << uri.getBaseURI(); - return null_json;*/ } else { + LOG(WARNING) << "Resolve failed for " << puri; return null_json; } } @@ -281,18 +292,36 @@ static bool findConfiguration(const string &file, const vector<string> &paths) { f = mergeConfig(FTL_GLOBAL_CONFIG_ROOT "/config.json"); found |= f; if (f) LOG(INFO) << "Loaded config: " << FTL_GLOBAL_CONFIG_ROOT "/config.json"; + + f = mergeConfig(FTL_GLOBAL_CONFIG_ROOT "/config.jsonc"); + found |= f; + if (f) LOG(INFO) << "Loaded config: " << FTL_GLOBAL_CONFIG_ROOT "/config.jsonc"; + f = mergeConfig(FTL_LOCAL_CONFIG_ROOT "/config.json"); found |= f; if (f) LOG(INFO) << "Loaded config: " << FTL_LOCAL_CONFIG_ROOT "/config.json"; + + f = mergeConfig(FTL_LOCAL_CONFIG_ROOT "/config.jsonc"); + found |= f; + if (f) LOG(INFO) << "Loaded config: " << FTL_LOCAL_CONFIG_ROOT "/config.jsonc"; + f = mergeConfig("./config.json"); found |= f; if (f) LOG(INFO) << "Loaded config: " << "./config.json"; + + f = mergeConfig("./config.jsonc"); + found |= f; + if (f) LOG(INFO) << "Loaded config: " << "./config.jsonc"; for (auto p : paths) { if (is_directory(p)) { f = mergeConfig(p+"/config.json"); found |= f; if (f) LOG(INFO) << "Loaded config: " << p << "/config.json"; + + f = mergeConfig(p+"/config.jsonc"); + found |= f; + if (f) LOG(INFO) << "Loaded config: " << p << "/config.jsonc"; } } } @@ -355,25 +384,48 @@ static void process_options(Configurable *root, const map<string, string> &opts) } try { - auto ptr = nlohmann::json::json_pointer("/"+opt.first); - // TODO(nick) Allow strings without quotes + /* //auto ptr = nlohmann::json::json_pointer("/"+opt.first); + auto ptr = ftl::config::resolve(*root->get<string>("$id") + string("/") + opt.first); + + LOG(INFO) << "PARAM RES TO " << (*root->get<string>("$id") + string("/") + opt.first); + auto v = nlohmann::json::parse(opt.second); - std::string type = root->getConfig().at(ptr).type_name(); + std::string type = ptr.type_name(); if (type != "null" && v.type_name() != type) { LOG(ERROR) << "Incorrect type for argument " << opt.first << " - expected '" << type << "', got '" << v.type_name() << "'"; continue; } - root->getConfig().at(ptr) = v; + ptr.swap(v);*/ + + auto v = nlohmann::json::parse(opt.second); + ftl::config::update(*root->get<string>("$id") + string("/") + opt.first, v); } catch(...) { LOG(ERROR) << "Unrecognised option: " << *root->get<string>("$id") << "#" << opt.first; } } } +static void signalIntHandler( int signum ) { + std::cout << "Closing...\n"; + + // cleanup and close up stuff here + // terminate program + + exit(0); +} + Configurable *ftl::config::configure(int argc, char **argv, const std::string &root) { + loguru::g_preamble_date = false; + loguru::g_preamble_uptime = false; + loguru::g_preamble_thread = false; + loguru::init(argc, argv, "--verbosity"); argc--; argv++; +#ifndef WIN32 + signal(SIGINT,signalIntHandler); +#endif // WIN32 + // Process Arguments auto options = read_options(&argv, &argc); @@ -398,6 +450,10 @@ Configurable *ftl::config::configure(int argc, char **argv, const std::string &r rootCFG = rootcfg; rootcfg->set("paths", paths); process_options(rootcfg, options); + + //LOG(INFO) << "CONFIG: " << config["vision_default"]; + //CHECK_EQ( &config, config_index["ftl://utu.fi"] ); + return rootcfg; } diff --git a/components/common/cpp/src/loguru.cpp b/components/common/cpp/src/loguru.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ccbc864b47d8c92b2883348f77e6b5972e4fa0b8 --- /dev/null +++ b/components/common/cpp/src/loguru.cpp @@ -0,0 +1,1788 @@ +// Disable all warnings from gcc/clang: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" + +#pragma GCC diagnostic ignored "-Wc++98-compat" +#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#pragma GCC diagnostic ignored "-Wexit-time-destructors" +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#pragma GCC diagnostic ignored "-Wglobal-constructors" +#pragma GCC diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#pragma GCC diagnostic ignored "-Wpadded" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wunused-macros" +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" + +#define LOGURU_REPLACE_GLOG 1 +#include "loguru.hpp" + +#ifndef LOGURU_HAS_BEEN_IMPLEMENTED +#define LOGURU_HAS_BEEN_IMPLEMENTED + +#define LOGURU_PREAMBLE_WIDTH (53 + LOGURU_THREADNAME_WIDTH + LOGURU_FILENAME_WIDTH) + +#undef min +#undef max + +#include <algorithm> +#include <atomic> +#include <chrono> +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <mutex> +#include <regex> +#include <string> +#include <thread> +#include <vector> + +#ifdef _WIN32 + #include <direct.h> + + #define localtime_r(a, b) localtime_s(b, a) // No localtime_r with MSVC, but arguments are swapped for localtime_s +#else + #include <signal.h> + #include <sys/stat.h> // mkdir + #include <unistd.h> // STDERR_FILENO +#endif + +#ifdef __linux__ + #include <linux/limits.h> // PATH_MAX +#elif !defined(_WIN32) + #include <limits.h> // PATH_MAX +#endif + +#ifndef PATH_MAX + #define PATH_MAX 1024 +#endif + +#ifdef __APPLE__ + #include "TargetConditionals.h" +#endif + +// TODO: use defined(_POSIX_VERSION) for some of these things? + +#if defined(_WIN32) || defined(__CYGWIN__) + #define LOGURU_PTHREADS 0 + #define LOGURU_WINTHREADS 1 + #ifndef LOGURU_STACKTRACES + #define LOGURU_STACKTRACES 0 + #endif +#elif defined(__rtems__) + #define LOGURU_PTHREADS 1 + #define LOGURU_WINTHREADS 0 + #ifndef LOGURU_STACKTRACES + #define LOGURU_STACKTRACES 0 + #endif +#else + #define LOGURU_PTHREADS 1 + #define LOGURU_WINTHREADS 0 + #ifndef LOGURU_STACKTRACES + #define LOGURU_STACKTRACES 1 + #endif +#endif + +#if LOGURU_STACKTRACES + #include <cxxabi.h> // for __cxa_demangle + #include <dlfcn.h> // for dladdr + #include <execinfo.h> // for backtrace +#endif // LOGURU_STACKTRACES + +#if LOGURU_PTHREADS + #include <pthread.h> + #if defined(__FreeBSD__) + #include <pthread_np.h> + #include <sys/thr.h> + #elif defined(__OpenBSD__) + #include <pthread_np.h> + #endif + + #ifdef __linux__ + /* On Linux, the default thread name is the same as the name of the binary. + Additionally, all new threads inherit the name of the thread it got forked from. + For this reason, Loguru use the pthread Thread Local Storage + for storing thread names on Linux. */ + #define LOGURU_PTLS_NAMES 1 + #endif +#endif + +#if LOGURU_WINTHREADS + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x0502 + #endif + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include <windows.h> +#endif + +#ifndef LOGURU_PTLS_NAMES + #define LOGURU_PTLS_NAMES 0 +#endif + +namespace loguru +{ + using namespace std::chrono; + +#if LOGURU_WITH_FILEABS + struct FileAbs + { + char path[PATH_MAX]; + char mode_str[4]; + Verbosity verbosity; + struct stat st; + FILE* fp; + bool is_reopening = false; // to prevent recursive call in file_reopen. + decltype(steady_clock::now()) last_check_time = steady_clock::now(); + }; +#else + typedef FILE* FileAbs; +#endif + + struct Callback + { + std::string id; + log_handler_t callback; + void* user_data; + Verbosity verbosity; // Does not change! + close_handler_t close; + flush_handler_t flush; + unsigned indentation; + }; + + using CallbackVec = std::vector<Callback>; + + using StringPair = std::pair<std::string, std::string>; + using StringPairList = std::vector<StringPair>; + + const auto s_start_time = steady_clock::now(); + + Verbosity g_stderr_verbosity = Verbosity_0; + bool g_colorlogtostderr = true; + unsigned g_flush_interval_ms = 0; + bool g_preamble = true; + + // Preamble details + bool g_preamble_date = true; + bool g_preamble_time = true; + bool g_preamble_uptime = true; + bool g_preamble_thread = true; + bool g_preamble_file = true; + bool g_preamble_verbose = true; + bool g_preamble_pipe = true; + + static std::recursive_mutex s_mutex; + static Verbosity s_max_out_verbosity = Verbosity_OFF; + static std::string s_argv0_filename; + static std::string s_arguments; + static char s_current_dir[PATH_MAX]; + static CallbackVec s_callbacks; + static fatal_handler_t s_fatal_handler = nullptr; + static verbosity_to_name_t s_verbosity_to_name_callback = nullptr; + static name_to_verbosity_t s_name_to_verbosity_callback = nullptr; + static StringPairList s_user_stack_cleanups; + static bool s_strip_file_path = true; + static std::atomic<unsigned> s_stderr_indentation { 0 }; + + // For periodic flushing: + static std::thread* s_flush_thread = nullptr; + static bool s_needs_flushing = false; + + static const bool s_terminal_has_color = [](){ + #ifdef _WIN32 + #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING + #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 + #endif + + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut != INVALID_HANDLE_VALUE) { + DWORD dwMode = 0; + GetConsoleMode(hOut, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + return SetConsoleMode(hOut, dwMode) != 0; + } + return false; + #else + if (const char* term = getenv("TERM")) { + return 0 == strcmp(term, "cygwin") + || 0 == strcmp(term, "linux") + || 0 == strcmp(term, "rxvt-unicode-256color") + || 0 == strcmp(term, "screen") + || 0 == strcmp(term, "screen-256color") + || 0 == strcmp(term, "tmux-256color") + || 0 == strcmp(term, "xterm") + || 0 == strcmp(term, "xterm-256color") + || 0 == strcmp(term, "xterm-termite") + || 0 == strcmp(term, "xterm-color"); + } else { + return false; + } + #endif + }(); + + static void print_preamble_header(char* out_buff, size_t out_buff_size); + + #if LOGURU_PTLS_NAMES + static pthread_once_t s_pthread_key_once = PTHREAD_ONCE_INIT; + static pthread_key_t s_pthread_key_name; + + void make_pthread_key_name() + { + (void)pthread_key_create(&s_pthread_key_name, free); + } + #endif + + // ------------------------------------------------------------------------------ + // Colors + + bool terminal_has_color() { return s_terminal_has_color; } + + // Colors + +#ifdef _WIN32 +#define VTSEQ(ID) ("\x1b[1;" #ID "m") +#else +#define VTSEQ(ID) ("\x1b[" #ID "m") +#endif + + const char* terminal_black() { return s_terminal_has_color ? VTSEQ(30) : ""; } + const char* terminal_red() { return s_terminal_has_color ? VTSEQ(31) : ""; } + const char* terminal_green() { return s_terminal_has_color ? VTSEQ(32) : ""; } + const char* terminal_yellow() { return s_terminal_has_color ? VTSEQ(33) : ""; } + const char* terminal_blue() { return s_terminal_has_color ? VTSEQ(34) : ""; } + const char* terminal_purple() { return s_terminal_has_color ? VTSEQ(35) : ""; } + const char* terminal_cyan() { return s_terminal_has_color ? VTSEQ(36) : ""; } + const char* terminal_light_gray() { return s_terminal_has_color ? VTSEQ(37) : ""; } + const char* terminal_white() { return s_terminal_has_color ? VTSEQ(37) : ""; } + const char* terminal_light_red() { return s_terminal_has_color ? VTSEQ(91) : ""; } + const char* terminal_dim() { return s_terminal_has_color ? VTSEQ(2) : ""; } + + // Formating + const char* terminal_bold() { return s_terminal_has_color ? VTSEQ(1) : ""; } + const char* terminal_underline() { return s_terminal_has_color ? VTSEQ(4) : ""; } + + // You should end each line with this! + const char* terminal_reset() { return s_terminal_has_color ? VTSEQ(0) : ""; } + + // ------------------------------------------------------------------------------ +#if LOGURU_WITH_FILEABS + void file_reopen(void* user_data); + inline FILE* to_file(void* user_data) { return reinterpret_cast<FileAbs*>(user_data)->fp; } +#else + inline FILE* to_file(void* user_data) { return reinterpret_cast<FILE*>(user_data); } +#endif + + void file_log(void* user_data, const Message& message) + { +#if LOGURU_WITH_FILEABS + FileAbs* file_abs = reinterpret_cast<FileAbs*>(user_data); + if (file_abs->is_reopening) { + return; + } + // It is better checking file change every minute/hour/day, + // instead of doing this every time we log. + // Here check_interval is set to zero to enable checking every time; + const auto check_interval = seconds(0); + if (duration_cast<seconds>(steady_clock::now() - file_abs->last_check_time) > check_interval) { + file_abs->last_check_time = steady_clock::now(); + file_reopen(user_data); + } + FILE* file = to_file(user_data); + if (!file) { + return; + } +#else + FILE* file = to_file(user_data); +#endif + fprintf(file, "%s%s%s%s\n", + message.preamble, message.indentation, message.prefix, message.message); + if (g_flush_interval_ms == 0) { + fflush(file); + } + } + + void file_close(void* user_data) + { + FILE* file = to_file(user_data); + if (file) { + fclose(file); + } +#if LOGURU_WITH_FILEABS + delete reinterpret_cast<FileAbs*>(user_data); +#endif + } + + void file_flush(void* user_data) + { + FILE* file = to_file(user_data); + fflush(file); + } + +#if LOGURU_WITH_FILEABS + void file_reopen(void* user_data) + { + FileAbs * file_abs = reinterpret_cast<FileAbs*>(user_data); + struct stat st; + int ret; + if (!file_abs->fp || (ret = stat(file_abs->path, &st)) == -1 || (st.st_ino != file_abs->st.st_ino)) { + file_abs->is_reopening = true; + if (file_abs->fp) { + fclose(file_abs->fp); + } + if (!file_abs->fp) { + LOG_F(INFO, "Reopening file '%s' due to previous error", file_abs->path); + } + else if (ret < 0) { + const auto why = errno_as_text(); + LOG_F(INFO, "Reopening file '%s' due to '%s'", file_abs->path, why.c_str()); + } else { + LOG_F(INFO, "Reopening file '%s' due to file changed", file_abs->path); + } + // try reopen current file. + if (!create_directories(file_abs->path)) { + LOG_F(ERROR, "Failed to create directories to '%s'", file_abs->path); + } + file_abs->fp = fopen(file_abs->path, file_abs->mode_str); + if (!file_abs->fp) { + LOG_F(ERROR, "Failed to open '%s'", file_abs->path); + } else { + stat(file_abs->path, &file_abs->st); + } + file_abs->is_reopening = false; + } + } +#endif + // ------------------------------------------------------------------------------ + + // Helpers: + + Text::~Text() { free(_str); } + + LOGURU_PRINTF_LIKE(1, 0) + static Text vtextprintf(const char* format, va_list vlist) + { +#ifdef _WIN32 + int bytes_needed = _vscprintf(format, vlist); + CHECK_F(bytes_needed >= 0, "Bad string format: '%s'", format); + char* buff = (char*)malloc(bytes_needed+1); + vsnprintf(buff, bytes_needed+1, format, vlist); + return Text(buff); +#else + char* buff = nullptr; + int result = vasprintf(&buff, format, vlist); + CHECK_F(result >= 0, "Bad string format: '%s'", format); + return Text(buff); +#endif + } + + Text textprintf(const char* format, ...) + { + va_list vlist; + va_start(vlist, format); + auto result = vtextprintf(format, vlist); + va_end(vlist); + return result; + } + + // Overloaded for variadic template matching. + Text textprintf() + { + return Text(static_cast<char*>(calloc(1, 1))); + } + + static const char* indentation(unsigned depth) + { + static const char buff[] = + ". . . . . . . . . . " ". . . . . . . . . . " + ". . . . . . . . . . " ". . . . . . . . . . " + ". . . . . . . . . . " ". . . . . . . . . . " + ". . . . . . . . . . " ". . . . . . . . . . " + ". . . . . . . . . . " ". . . . . . . . . . "; + static const size_t INDENTATION_WIDTH = 4; + static const size_t NUM_INDENTATIONS = (sizeof(buff) - 1) / INDENTATION_WIDTH; + depth = std::min<unsigned>(depth, NUM_INDENTATIONS); + return buff + INDENTATION_WIDTH * (NUM_INDENTATIONS - depth); + } + + static void parse_args(int& argc, char* argv[], const char* verbosity_flag) + { + int arg_dest = 1; + int out_argc = argc; + + for (int arg_it = 1; arg_it < argc; ++arg_it) { + auto cmd = argv[arg_it]; + auto arg_len = strlen(verbosity_flag); + if (strncmp(cmd, verbosity_flag, arg_len) == 0 && !std::isalpha(cmd[arg_len], std::locale(""))) { + out_argc -= 1; + auto value_str = cmd + arg_len; + if (value_str[0] == '\0') { + // Value in separate argument + arg_it += 1; + CHECK_LT_F(arg_it, argc, "Missing verbosiy level after %s", verbosity_flag); + value_str = argv[arg_it]; + out_argc -= 1; + } + if (*value_str == '=') { value_str += 1; } + + auto req_verbosity = get_verbosity_from_name(value_str); + if (req_verbosity != Verbosity_INVALID) { + g_stderr_verbosity = req_verbosity; + } else { + char* end = 0; + g_stderr_verbosity = static_cast<int>(strtol(value_str, &end, 10)); + CHECK_F(end && *end == '\0', + "Invalid verbosity. Expected integer, INFO, WARNING, ERROR or OFF, got '%s'", value_str); + } + } else { + argv[arg_dest++] = argv[arg_it]; + } + } + + argc = out_argc; + argv[argc] = nullptr; + } + + static long long now_ns() + { + return duration_cast<nanoseconds>(high_resolution_clock::now().time_since_epoch()).count(); + } + + // Returns the part of the path after the last / or \ (if any). + const char* filename(const char* path) + { + for (auto ptr = path; *ptr; ++ptr) { + if (*ptr == '/' || *ptr == '\\') { + path = ptr + 1; + } + } + return path; + } + + // ------------------------------------------------------------------------------ + + static void on_atexit() + { + LOG_F(INFO, "atexit"); + flush(); + } + + static void install_signal_handlers(); + + static void write_hex_digit(std::string& out, unsigned num) + { + DCHECK_LT_F(num, 16u); + if (num < 10u) { out.push_back(char('0' + num)); } + else { out.push_back(char('A' + num - 10)); } + } + + static void write_hex_byte(std::string& out, uint8_t n) + { + write_hex_digit(out, n >> 4u); + write_hex_digit(out, n & 0x0f); + } + + static void escape(std::string& out, const std::string& str) + { + for (char c : str) { + /**/ if (c == '\a') { out += "\\a"; } + else if (c == '\b') { out += "\\b"; } + else if (c == '\f') { out += "\\f"; } + else if (c == '\n') { out += "\\n"; } + else if (c == '\r') { out += "\\r"; } + else if (c == '\t') { out += "\\t"; } + else if (c == '\v') { out += "\\v"; } + else if (c == '\\') { out += "\\\\"; } + else if (c == '\'') { out += "\\\'"; } + else if (c == '\"') { out += "\\\""; } + else if (c == ' ') { out += "\\ "; } + else if (0 <= c && c < 0x20) { // ASCI control character: + // else if (c < 0x20 || c != (c & 127)) { // ASCII control character or UTF-8: + out += "\\x"; + write_hex_byte(out, static_cast<uint8_t>(c)); + } else { out += c; } + } + } + + Text errno_as_text() + { + char buff[256]; + #if defined(__GLIBC__) && defined(_GNU_SOURCE) + // GNU Version + return Text(strdup(strerror_r(errno, buff, sizeof(buff)))); + #elif defined(__APPLE__) || _POSIX_C_SOURCE >= 200112L + // XSI Version + strerror_r(errno, buff, sizeof(buff)); + return Text(strdup(buff)); + #elif defined(_WIN32) + strerror_s(buff, sizeof(buff), errno); + return Text(strdup(buff)); + #else + // Not thread-safe. + return Text(strdup(strerror(errno))); + #endif + } + + void init(int& argc, char* argv[], const char* verbosity_flag) + { + CHECK_GT_F(argc, 0, "Expected proper argc/argv"); + CHECK_EQ_F(argv[argc], nullptr, "Expected proper argc/argv"); + + s_argv0_filename = filename(argv[0]); + + #ifdef _WIN32 + #define getcwd _getcwd + #endif + + if (!getcwd(s_current_dir, sizeof(s_current_dir))) + { + const auto error_text = errno_as_text(); + LOG_F(WARNING, "Failed to get current working directory: %s", error_text.c_str()); + } + + s_arguments = ""; + for (int i = 0; i < argc; ++i) { + escape(s_arguments, argv[i]); + if (i + 1 < argc) { + s_arguments += " "; + } + } + + if (verbosity_flag) { + parse_args(argc, argv, verbosity_flag); + } + + #if LOGURU_PTLS_NAMES || LOGURU_WINTHREADS + set_thread_name("main thread"); + #elif LOGURU_PTHREADS + char old_thread_name[16] = {0}; + auto this_thread = pthread_self(); + #if defined(__APPLE__) || defined(__linux__) + pthread_getname_np(this_thread, old_thread_name, sizeof(old_thread_name)); + #endif + if (old_thread_name[0] == 0) { + #ifdef __APPLE__ + pthread_setname_np("main thread"); + #elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(this_thread, "main thread"); + #elif defined(__linux__) + pthread_setname_np(this_thread, "main thread"); + #endif + } + #endif // LOGURU_PTHREADS + + if (g_stderr_verbosity >= Verbosity_INFO) { + if (g_preamble) { + char preamble_explain[LOGURU_PREAMBLE_WIDTH]; + print_preamble_header(preamble_explain, sizeof(preamble_explain)); + if (g_colorlogtostderr && s_terminal_has_color) { + fprintf(stderr, "%s%s%s\n", terminal_reset(), terminal_dim(), preamble_explain); + } else { + fprintf(stderr, "%s\n", preamble_explain); + } + } + fflush(stderr); + } + LOG_F(INFO, "arguments: %s", s_arguments.c_str()); + if (strlen(s_current_dir) != 0) + { + LOG_F(INFO, "Current dir: %s", s_current_dir); + } + LOG_F(INFO, "stderr verbosity: %d", g_stderr_verbosity); + LOG_F(INFO, "-----------------------------------"); + + install_signal_handlers(); + + atexit(on_atexit); + } + + void shutdown() + { + LOG_F(INFO, "loguru::shutdown()"); + remove_all_callbacks(); + set_fatal_handler(nullptr); + set_verbosity_to_name_callback(nullptr); + set_name_to_verbosity_callback(nullptr); + } + + void write_date_time(char* buff, size_t buff_size) + { + auto now = system_clock::now(); + long long ms_since_epoch = duration_cast<milliseconds>(now.time_since_epoch()).count(); + time_t sec_since_epoch = time_t(ms_since_epoch / 1000); + tm time_info; + localtime_r(&sec_since_epoch, &time_info); + snprintf(buff, buff_size, "%04d%02d%02d_%02d%02d%02d.%03lld", + 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday, + time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); + } + + const char* argv0_filename() + { + return s_argv0_filename.c_str(); + } + + const char* arguments() + { + return s_arguments.c_str(); + } + + const char* current_dir() + { + return s_current_dir; + } + + const char* home_dir() + { + #ifdef _WIN32 + auto user_profile = getenv("USERPROFILE"); + CHECK_F(user_profile != nullptr, "Missing USERPROFILE"); + return user_profile; + #else // _WIN32 + auto home = getenv("HOME"); + CHECK_F(home != nullptr, "Missing HOME"); + return home; + #endif // _WIN32 + } + + void suggest_log_path(const char* prefix, char* buff, unsigned buff_size) + { + if (prefix[0] == '~') { + snprintf(buff, buff_size - 1, "%s%s", home_dir(), prefix + 1); + } else { + snprintf(buff, buff_size - 1, "%s", prefix); + } + + // Check for terminating / + size_t n = strlen(buff); + if (n != 0) { + if (buff[n - 1] != '/') { + CHECK_F(n + 2 < buff_size, "Filename buffer too small"); + buff[n] = '/'; + buff[n + 1] = '\0'; + } + } + + strncat(buff, s_argv0_filename.c_str(), buff_size - strlen(buff) - 1); + strncat(buff, "/", buff_size - strlen(buff) - 1); + write_date_time(buff + strlen(buff), buff_size - strlen(buff)); + strncat(buff, ".log", buff_size - strlen(buff) - 1); + } + + bool create_directories(const char* file_path_const) + { + CHECK_F(file_path_const && *file_path_const); + char* file_path = strdup(file_path_const); + for (char* p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { + *p = '\0'; + + #ifdef _WIN32 + if (_mkdir(file_path) == -1) { + #else + if (mkdir(file_path, 0755) == -1) { + #endif + if (errno != EEXIST) { + LOG_F(ERROR, "Failed to create directory '%s'", file_path); + LOG_IF_F(ERROR, errno == EACCES, "EACCES"); + LOG_IF_F(ERROR, errno == ENAMETOOLONG, "ENAMETOOLONG"); + LOG_IF_F(ERROR, errno == ENOENT, "ENOENT"); + LOG_IF_F(ERROR, errno == ENOTDIR, "ENOTDIR"); + LOG_IF_F(ERROR, errno == ELOOP, "ELOOP"); + + *p = '/'; + free(file_path); + return false; + } + } + *p = '/'; + } + free(file_path); + return true; + } + bool add_file(const char* path_in, FileMode mode, Verbosity verbosity) + { + char path[PATH_MAX]; + if (path_in[0] == '~') { + snprintf(path, sizeof(path) - 1, "%s%s", home_dir(), path_in + 1); + } else { + snprintf(path, sizeof(path) - 1, "%s", path_in); + } + + if (!create_directories(path)) { + LOG_F(ERROR, "Failed to create directories to '%s'", path); + } + + const char* mode_str = (mode == FileMode::Truncate ? "w" : "a"); + auto file = fopen(path, mode_str); + if (!file) { + LOG_F(ERROR, "Failed to open '%s'", path); + return false; + } +#if LOGURU_WITH_FILEABS + FileAbs* file_abs = new FileAbs(); // this is deleted in file_close; + snprintf(file_abs->path, sizeof(file_abs->path) - 1, "%s", path); + snprintf(file_abs->mode_str, sizeof(file_abs->mode_str) - 1, "%s", mode_str); + stat(file_abs->path, &file_abs->st); + file_abs->fp = file; + file_abs->verbosity = verbosity; + add_callback(path_in, file_log, file_abs, verbosity, file_close, file_flush); +#else + add_callback(path_in, file_log, file, verbosity, file_close, file_flush); +#endif + + if (mode == FileMode::Append) { + fprintf(file, "\n\n\n\n\n"); + } + if (!s_arguments.empty()) { + fprintf(file, "arguments: %s\n", s_arguments.c_str()); + } + if (strlen(s_current_dir) != 0) { + fprintf(file, "Current dir: %s\n", s_current_dir); + } + fprintf(file, "File verbosity level: %d\n", verbosity); + if (g_preamble) { + char preamble_explain[LOGURU_PREAMBLE_WIDTH]; + print_preamble_header(preamble_explain, sizeof(preamble_explain)); + fprintf(file, "%s\n", preamble_explain); + } + fflush(file); + + LOG_F(INFO, "Logging to '%s', mode: '%s', verbosity: %d", path, mode_str, verbosity); + return true; + } + + // Will be called right before abort(). + void set_fatal_handler(fatal_handler_t handler) + { + s_fatal_handler = handler; + } + + fatal_handler_t get_fatal_handler() + { + return s_fatal_handler; + } + + void set_verbosity_to_name_callback(verbosity_to_name_t callback) + { + s_verbosity_to_name_callback = callback; + } + + void set_name_to_verbosity_callback(name_to_verbosity_t callback) + { + s_name_to_verbosity_callback = callback; + } + + void add_stack_cleanup(const char* find_this, const char* replace_with_this) + { + if (strlen(find_this) <= strlen(replace_with_this)) { + LOG_F(WARNING, "add_stack_cleanup: the replacement should be shorter than the pattern!"); + return; + } + + s_user_stack_cleanups.push_back(StringPair(find_this, replace_with_this)); + } + + static void on_callback_change() + { + s_max_out_verbosity = Verbosity_OFF; + for (const auto& callback : s_callbacks) { + s_max_out_verbosity = std::max(s_max_out_verbosity, callback.verbosity); + } + } + + void add_callback( + const char* id, + log_handler_t callback, + void* user_data, + Verbosity verbosity, + close_handler_t on_close, + flush_handler_t on_flush) + { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + s_callbacks.push_back(Callback{id, callback, user_data, verbosity, on_close, on_flush, 0}); + on_callback_change(); + } + + // Returns a custom verbosity name if one is available, or nullptr. + // See also set_verbosity_to_name_callback. + const char* get_verbosity_name(Verbosity verbosity) + { + auto name = s_verbosity_to_name_callback + ? (*s_verbosity_to_name_callback)(verbosity) + : nullptr; + + // Use standard replacements if callback fails: + if (!name) + { + if (verbosity <= Verbosity_FATAL) { + name = "FATL"; + } else if (verbosity == Verbosity_ERROR) { + name = "ERR"; + } else if (verbosity == Verbosity_WARNING) { + name = "WARN"; + } else if (verbosity == Verbosity_INFO) { + name = "INFO"; + } + } + + return name; + } + + // Returns Verbosity_INVALID if the name is not found. + // See also set_name_to_verbosity_callback. + Verbosity get_verbosity_from_name(const char* name) + { + auto verbosity = s_name_to_verbosity_callback + ? (*s_name_to_verbosity_callback)(name) + : Verbosity_INVALID; + + // Use standard replacements if callback fails: + if (verbosity == Verbosity_INVALID) { + if (strcmp(name, "OFF") == 0) { + verbosity = Verbosity_OFF; + } else if (strcmp(name, "INFO") == 0) { + verbosity = Verbosity_INFO; + } else if (strcmp(name, "WARNING") == 0) { + verbosity = Verbosity_WARNING; + } else if (strcmp(name, "ERROR") == 0) { + verbosity = Verbosity_ERROR; + } else if (strcmp(name, "FATAL") == 0) { + verbosity = Verbosity_FATAL; + } + } + + return verbosity; + } + + bool remove_callback(const char* id) + { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + auto it = std::find_if(begin(s_callbacks), end(s_callbacks), [&](const Callback& c) { return c.id == id; }); + if (it != s_callbacks.end()) { + if (it->close) { it->close(it->user_data); } + s_callbacks.erase(it); + on_callback_change(); + return true; + } else { + LOG_F(ERROR, "Failed to locate callback with id '%s'", id); + return false; + } + } + + void remove_all_callbacks() + { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + for (auto& callback : s_callbacks) { + if (callback.close) { + callback.close(callback.user_data); + } + } + s_callbacks.clear(); + on_callback_change(); + } + + // Returns the maximum of g_stderr_verbosity and all file/custom outputs. + Verbosity current_verbosity_cutoff() + { + return g_stderr_verbosity > s_max_out_verbosity ? + g_stderr_verbosity : s_max_out_verbosity; + } + +#if LOGURU_WINTHREADS + char* get_thread_name_win32() + { + __declspec( thread ) static char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; + return &thread_name[0]; + } +#endif // LOGURU_WINTHREADS + + void set_thread_name(const char* name) + { + #if LOGURU_PTLS_NAMES + (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); + (void)pthread_setspecific(s_pthread_key_name, strdup(name)); + + #elif LOGURU_PTHREADS + #ifdef __APPLE__ + pthread_setname_np(name); + #elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(pthread_self(), name); + #elif defined(__linux__) + pthread_setname_np(pthread_self(), name); + #endif + #elif LOGURU_WINTHREADS + strncpy_s(get_thread_name_win32(), LOGURU_THREADNAME_WIDTH + 1, name, _TRUNCATE); + #else // LOGURU_PTHREADS + (void)name; + #endif // LOGURU_PTHREADS + } + +#if LOGURU_PTLS_NAMES + const char* get_thread_name_ptls() + { + (void)pthread_once(&s_pthread_key_once, make_pthread_key_name); + return static_cast<const char*>(pthread_getspecific(s_pthread_key_name)); + } +#endif // LOGURU_PTLS_NAMES + + void get_thread_name(char* buffer, unsigned long long length, bool right_align_hext_id) + { + CHECK_NE_F(length, 0u, "Zero length buffer in get_thread_name"); + CHECK_NOTNULL_F(buffer, "nullptr in get_thread_name"); +#if LOGURU_PTHREADS + auto thread = pthread_self(); + #if LOGURU_PTLS_NAMES + if (const char* name = get_thread_name_ptls()) { + snprintf(buffer, length, "%s", name); + } else { + buffer[0] = 0; + } + #elif defined(__APPLE__) || defined(__linux__) + pthread_getname_np(thread, buffer, length); + #else + buffer[0] = 0; + #endif + + if (buffer[0] == 0) { + #ifdef __APPLE__ + uint64_t thread_id; + pthread_threadid_np(thread, &thread_id); + #elif defined(__FreeBSD__) + long thread_id; + (void)thr_self(&thread_id); + #elif defined(__OpenBSD__) + unsigned thread_id = -1; + #else + uint64_t thread_id = thread; + #endif + if (right_align_hext_id) { + snprintf(buffer, length, "%*X", static_cast<int>(length - 1), static_cast<unsigned>(thread_id)); + } else { + snprintf(buffer, length, "%X", static_cast<unsigned>(thread_id)); + } + } +#elif LOGURU_WINTHREADS + if (const char* name = get_thread_name_win32()) { + snprintf(buffer, (size_t)length, "%s", name); + } else { + buffer[0] = 0; + } +#else // !LOGURU_WINTHREADS && !LOGURU_WINTHREADS + buffer[0] = 0; +#endif + + } + + // ------------------------------------------------------------------------ + // Stack traces + +#if LOGURU_STACKTRACES + Text demangle(const char* name) + { + int status = -1; + char* demangled = abi::__cxa_demangle(name, 0, 0, &status); + Text result{status == 0 ? demangled : strdup(name)}; + return result; + } + + #if LOGURU_RTTI + template <class T> + std::string type_name() + { + auto demangled = demangle(typeid(T).name()); + return demangled.c_str(); + } + #endif // LOGURU_RTTI + + static const StringPairList REPLACE_LIST = { + #if LOGURU_RTTI + { type_name<std::string>(), "std::string" }, + { type_name<std::wstring>(), "std::wstring" }, + { type_name<std::u16string>(), "std::u16string" }, + { type_name<std::u32string>(), "std::u32string" }, + #endif // LOGURU_RTTI + { "std::__1::", "std::" }, + { "__thiscall ", "" }, + { "__cdecl ", "" }, + }; + + void do_replacements(const StringPairList& replacements, std::string& str) + { + for (auto&& p : replacements) { + if (p.first.size() <= p.second.size()) { + // On gcc, "type_name<std::string>()" is "std::string" + continue; + } + + size_t it; + while ((it=str.find(p.first)) != std::string::npos) { + str.replace(it, p.first.size(), p.second); + } + } + } + + std::string prettify_stacktrace(const std::string& input) + { + std::string output = input; + + do_replacements(s_user_stack_cleanups, output); + do_replacements(REPLACE_LIST, output); + + try { + std::regex std_allocator_re(R"(,\s*std::allocator<[^<>]+>)"); + output = std::regex_replace(output, std_allocator_re, std::string("")); + + std::regex template_spaces_re(R"(<\s*([^<> ]+)\s*>)"); + output = std::regex_replace(output, template_spaces_re, std::string("<$1>")); + } catch (std::regex_error&) { + // Probably old GCC. + } + + return output; + } + + std::string stacktrace_as_stdstring(int skip) + { + // From https://gist.github.com/fmela/591333 + void* callstack[128]; + const auto max_frames = sizeof(callstack) / sizeof(callstack[0]); + int num_frames = backtrace(callstack, max_frames); + char** symbols = backtrace_symbols(callstack, num_frames); + + std::string result; + // Print stack traces so the most relevant ones are written last + // Rationale: http://yellerapp.com/posts/2015-01-22-upside-down-stacktraces.html + for (int i = num_frames - 1; i >= skip; --i) { + char buf[1024]; + Dl_info info; + if (dladdr(callstack[i], &info) && info.dli_sname) { + char* demangled = NULL; + int status = -1; + if (info.dli_sname[0] == '_') { + demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, &status); + } + snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", + i - skip, int(2 + sizeof(void*) * 2), callstack[i], + status == 0 ? demangled : + info.dli_sname == 0 ? symbols[i] : info.dli_sname, + static_cast<char*>(callstack[i]) - static_cast<char*>(info.dli_saddr)); + free(demangled); + } else { + snprintf(buf, sizeof(buf), "%-3d %*p %s\n", + i - skip, int(2 + sizeof(void*) * 2), callstack[i], symbols[i]); + } + result += buf; + } + free(symbols); + + if (num_frames == max_frames) { + result = "[truncated]\n" + result; + } + + if (!result.empty() && result[result.size() - 1] == '\n') { + result.resize(result.size() - 1); + } + + return prettify_stacktrace(result); + } + +#else // LOGURU_STACKTRACES + Text demangle(const char* name) + { + return Text(strdup(name)); + } + + std::string stacktrace_as_stdstring(int) + { + // No stacktraces available on this platform" + return ""; + } + +#endif // LOGURU_STACKTRACES + + Text stacktrace(int skip) + { + auto str = stacktrace_as_stdstring(skip + 1); + return Text(strdup(str.c_str())); + } + + // ------------------------------------------------------------------------ + + static void print_preamble_header(char* out_buff, size_t out_buff_size) + { + if (out_buff_size == 0) { return; } + out_buff[0] = '\0'; + long pos = 0; + if (g_preamble_date && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "date "); + } + if (g_preamble_time && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "time "); + } + if (g_preamble_uptime && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "( uptime ) "); + } + if (g_preamble_thread && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", LOGURU_THREADNAME_WIDTH, " thread name/id"); + } + if (g_preamble_file && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:line ", LOGURU_FILENAME_WIDTH, "file"); + } + if (g_preamble_verbose && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, " v"); + } + if (g_preamble_pipe && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); + } + } + + static void print_preamble(char* out_buff, size_t out_buff_size, Verbosity verbosity, const char* file, unsigned line) + { + if (out_buff_size == 0) { return; } + out_buff[0] = '\0'; + if (!g_preamble) { return; } + long long ms_since_epoch = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); + time_t sec_since_epoch = time_t(ms_since_epoch / 1000); + tm time_info; + localtime_r(&sec_since_epoch, &time_info); + + auto uptime_ms = duration_cast<milliseconds>(steady_clock::now() - s_start_time).count(); + auto uptime_sec = uptime_ms / 1000.0; + + char thread_name[LOGURU_THREADNAME_WIDTH + 1] = {0}; + get_thread_name(thread_name, LOGURU_THREADNAME_WIDTH + 1, true); + + if (s_strip_file_path) { + file = filename(file); + } + + char level_buff[6]; + const char* custom_level_name = get_verbosity_name(verbosity); + if (custom_level_name) { + snprintf(level_buff, sizeof(level_buff) - 1, "%s", custom_level_name); + } else { + snprintf(level_buff, sizeof(level_buff) - 1, "% 4d", verbosity); + } + + long pos = 0; + + if (g_preamble_date && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "%04d-%02d-%02d ", + 1900 + time_info.tm_year, 1 + time_info.tm_mon, time_info.tm_mday); + } + if (g_preamble_time && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "%02d:%02d:%02d.%03lld ", + time_info.tm_hour, time_info.tm_min, time_info.tm_sec, ms_since_epoch % 1000); + } + if (g_preamble_uptime && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "(%8.3fs) ", + uptime_sec); + } + if (g_preamble_thread && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "[%-*s]", + LOGURU_THREADNAME_WIDTH, thread_name); + } + if (g_preamble_file && pos < out_buff_size) { + char shortened_filename[LOGURU_FILENAME_WIDTH + 1]; + snprintf(shortened_filename, LOGURU_FILENAME_WIDTH + 1, "%s", file); + pos += snprintf(out_buff + pos, out_buff_size - pos, "%*s:%-5u ", + LOGURU_FILENAME_WIDTH, shortened_filename, line); + } + if (g_preamble_verbose && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "%4s", + level_buff); + } + if (g_preamble_pipe && pos < out_buff_size) { + pos += snprintf(out_buff + pos, out_buff_size - pos, "| "); + } + } + + // stack_trace_skip is just if verbosity == FATAL. + static void log_message(int stack_trace_skip, Message& message, bool with_indentation, bool abort_if_fatal) + { + const auto verbosity = message.verbosity; + std::lock_guard<std::recursive_mutex> lock(s_mutex); + + if (message.verbosity == Verbosity_FATAL) { + auto st = loguru::stacktrace(stack_trace_skip + 2); + if (!st.empty()) { + RAW_LOG_F(ERROR, "Stack trace:\n%s", st.c_str()); + } + + auto ec = loguru::get_error_context(); + if (!ec.empty()) { + RAW_LOG_F(ERROR, "%s", ec.c_str()); + } + } + + if (with_indentation) { + message.indentation = indentation(s_stderr_indentation); + } + + if (verbosity <= g_stderr_verbosity) { + if (g_colorlogtostderr && s_terminal_has_color) { + if (verbosity > Verbosity_WARNING) { + fprintf(stderr, "%s%s%s%s%s%s%s%s\n", + terminal_reset(), + terminal_dim(), + message.preamble, + message.indentation, + verbosity == Verbosity_INFO ? terminal_reset() : "", // un-dim for info + message.prefix, + message.message, + terminal_reset()); + } else { + fprintf(stderr, "%s%s%s%s%s%s%s\n", + terminal_reset(), + verbosity == Verbosity_WARNING ? terminal_yellow() : terminal_red(), + message.preamble, + message.indentation, + message.prefix, + message.message, + terminal_reset()); + } + } else { + fprintf(stderr, "%s%s%s%s\n", + message.preamble, message.indentation, message.prefix, message.message); + } + + if (g_flush_interval_ms == 0) { + fflush(stderr); + } else { + s_needs_flushing = true; + } + } + + for (auto& p : s_callbacks) { + if (verbosity <= p.verbosity) { + if (with_indentation) { + message.indentation = indentation(p.indentation); + } + p.callback(p.user_data, message); + if (g_flush_interval_ms == 0) { + if (p.flush) { p.flush(p.user_data); } + } else { + s_needs_flushing = true; + } + } + } + + if (g_flush_interval_ms > 0 && !s_flush_thread) { + s_flush_thread = new std::thread([](){ + for (;;) { + if (s_needs_flushing) { + flush(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(g_flush_interval_ms)); + } + }); + } + + if (message.verbosity == Verbosity_FATAL) { + flush(); + + if (s_fatal_handler) { + s_fatal_handler(message); + flush(); + } + + if (abort_if_fatal) { +#if LOGURU_CATCH_SIGABRT && !defined(_WIN32) + // Make sure we don't catch our own abort: + signal(SIGABRT, SIG_DFL); +#endif + abort(); + } + } + } + + // stack_trace_skip is just if verbosity == FATAL. + void log_to_everywhere(int stack_trace_skip, Verbosity verbosity, + const char* file, unsigned line, + const char* prefix, const char* buff) + { + char preamble_buff[LOGURU_PREAMBLE_WIDTH]; + print_preamble(preamble_buff, sizeof(preamble_buff), verbosity, file, line); + auto message = Message{verbosity, file, line, preamble_buff, "", prefix, buff}; + log_message(stack_trace_skip + 1, message, true, true); + } + +#if LOGURU_USE_FMTLIB + void log(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::ArgList args) + { + auto formatted = fmt::format(format, args); + log_to_everywhere(1, verbosity, file, line, "", formatted.c_str()); + } + + void raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, fmt::ArgList args) + { + auto formatted = fmt::format(format, args); + auto message = Message{verbosity, file, line, "", "", "", formatted.c_str()}; + log_message(1, message, false, true); + } + +#else + void log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) + { + va_list vlist; + va_start(vlist, format); + auto buff = vtextprintf(format, vlist); + log_to_everywhere(1, verbosity, file, line, "", buff.c_str()); + va_end(vlist); + } + + void raw_log(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) + { + va_list vlist; + va_start(vlist, format); + auto buff = vtextprintf(format, vlist); + auto message = Message{verbosity, file, line, "", "", "", buff.c_str()}; + log_message(1, message, false, true); + va_end(vlist); + } +#endif + + void flush() + { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + fflush(stderr); + for (const auto& callback : s_callbacks) + { + if (callback.flush) { + callback.flush(callback.user_data); + } + } + s_needs_flushing = false; + } + + LogScopeRAII::LogScopeRAII(Verbosity verbosity, const char* file, unsigned line, const char* format, ...) + : _verbosity(verbosity), _file(file), _line(line) + { + if (verbosity <= current_verbosity_cutoff()) { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + _indent_stderr = (verbosity <= g_stderr_verbosity); + _start_time_ns = now_ns(); + va_list vlist; + va_start(vlist, format); + vsnprintf(_name, sizeof(_name), format, vlist); + log_to_everywhere(1, _verbosity, file, line, "{ ", _name); + va_end(vlist); + + if (_indent_stderr) { + ++s_stderr_indentation; + } + + for (auto& p : s_callbacks) { + if (verbosity <= p.verbosity) { + ++p.indentation; + } + } + } else { + _file = nullptr; + } + } + + LogScopeRAII::~LogScopeRAII() + { + if (_file) { + std::lock_guard<std::recursive_mutex> lock(s_mutex); + if (_indent_stderr && s_stderr_indentation > 0) { + --s_stderr_indentation; + } + for (auto& p : s_callbacks) { + // Note: Callback indentation cannot change! + if (_verbosity <= p.verbosity) { + // in unlikely case this callback is new + if (p.indentation > 0) { + --p.indentation; + } + } + } +#if LOGURU_VERBOSE_SCOPE_ENDINGS + auto duration_sec = (now_ns() - _start_time_ns) / 1e9; + auto buff = textprintf("%.*f s: %s", LOGURU_SCOPE_TIME_PRECISION, duration_sec, _name); + log_to_everywhere(1, _verbosity, _file, _line, "} ", buff.c_str()); +#else + log_to_everywhere(1, _verbosity, _file, _line, "}", ""); +#endif + } + } + + void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line, const char* format, ...) + { + va_list vlist; + va_start(vlist, format); + auto buff = vtextprintf(format, vlist); + log_to_everywhere(stack_trace_skip + 1, Verbosity_FATAL, file, line, expr, buff.c_str()); + va_end(vlist); + abort(); // log_to_everywhere already does this, but this makes the analyzer happy. + } + + void log_and_abort(int stack_trace_skip, const char* expr, const char* file, unsigned line) + { + log_and_abort(stack_trace_skip + 1, expr, file, line, " "); + } + + // ---------------------------------------------------------------------------- + // Streams: + + std::string vstrprintf(const char* format, va_list vlist) + { + auto text = vtextprintf(format, vlist); + std::string result = text.c_str(); + return result; + } + + std::string strprintf(const char* format, ...) + { + va_list vlist; + va_start(vlist, format); + auto result = vstrprintf(format, vlist); + va_end(vlist); + return result; + } + + #if LOGURU_WITH_STREAMS + + StreamLogger::~StreamLogger() noexcept(false) + { + auto message = _ss.str(); + log(_verbosity, _file, _line, "%s", message.c_str()); + } + + AbortLogger::~AbortLogger() noexcept(false) + { + auto message = _ss.str(); + loguru::log_and_abort(1, _expr, _file, _line, "%s", message.c_str()); + } + + #endif // LOGURU_WITH_STREAMS + + // ---------------------------------------------------------------------------- + // 888888 88""Yb 88""Yb dP"Yb 88""Yb dP""b8 dP"Yb 88b 88 888888 888888 Yb dP 888888 + // 88__ 88__dP 88__dP dP Yb 88__dP dP `" dP Yb 88Yb88 88 88__ YbdP 88 + // 88"" 88"Yb 88"Yb Yb dP 88"Yb Yb Yb dP 88 Y88 88 88"" dPYb 88 + // 888888 88 Yb 88 Yb YbodP 88 Yb YboodP YbodP 88 Y8 88 888888 dP Yb 88 + // ---------------------------------------------------------------------------- + + struct StringStream + { + std::string str; + }; + + // Use this in your EcPrinter implementations. + void stream_print(StringStream& out_string_stream, const char* text) + { + out_string_stream.str += text; + } + + // ---------------------------------------------------------------------------- + + using ECPtr = EcEntryBase*; + +#if defined(_WIN32) || (defined(__APPLE__) && !TARGET_OS_IPHONE) + #ifdef __APPLE__ + #define LOGURU_THREAD_LOCAL __thread + #else + #define LOGURU_THREAD_LOCAL thread_local + #endif + static LOGURU_THREAD_LOCAL ECPtr thread_ec_ptr = nullptr; + + ECPtr& get_thread_ec_head_ref() + { + return thread_ec_ptr; + } +#else // !thread_local + static pthread_once_t s_ec_pthread_once = PTHREAD_ONCE_INIT; + static pthread_key_t s_ec_pthread_key; + + void free_ec_head_ref(void* io_error_context) + { + delete reinterpret_cast<ECPtr*>(io_error_context); + } + + void ec_make_pthread_key() + { + (void)pthread_key_create(&s_ec_pthread_key, free_ec_head_ref); + } + + ECPtr& get_thread_ec_head_ref() + { + (void)pthread_once(&s_ec_pthread_once, ec_make_pthread_key); + auto ec = reinterpret_cast<ECPtr*>(pthread_getspecific(s_ec_pthread_key)); + if (ec == nullptr) { + ec = new ECPtr(nullptr); + (void)pthread_setspecific(s_ec_pthread_key, ec); + } + return *ec; + } +#endif // !thread_local + + // ---------------------------------------------------------------------------- + + EcHandle get_thread_ec_handle() + { + return get_thread_ec_head_ref(); + } + + Text get_error_context() + { + return get_error_context_for(get_thread_ec_head_ref()); + } + + Text get_error_context_for(const EcEntryBase* ec_head) + { + std::vector<const EcEntryBase*> stack; + while (ec_head) { + stack.push_back(ec_head); + ec_head = ec_head->_previous; + } + std::reverse(stack.begin(), stack.end()); + + StringStream result; + if (!stack.empty()) { + result.str += "------------------------------------------------\n"; + for (auto entry : stack) { + const auto description = std::string(entry->_descr) + ":"; + auto prefix = textprintf("[ErrorContext] %*s:%-5u %-20s ", + LOGURU_FILENAME_WIDTH, filename(entry->_file), entry->_line, description.c_str()); + result.str += prefix.c_str(); + entry->print_value(result); + result.str += "\n"; + } + result.str += "------------------------------------------------"; + } + return Text(strdup(result.str.c_str())); + } + + EcEntryBase::EcEntryBase(const char* file, unsigned line, const char* descr) + : _file(file), _line(line), _descr(descr) + { + EcEntryBase*& ec_head = get_thread_ec_head_ref(); + _previous = ec_head; + ec_head = this; + } + + EcEntryBase::~EcEntryBase() + { + get_thread_ec_head_ref() = _previous; + } + + // ------------------------------------------------------------------------ + + Text ec_to_text(const char* value) + { + // Add quotes around the string to make it obvious where it begin and ends. + // This is great for detecting erroneous leading or trailing spaces in e.g. an identifier. + auto str = "\"" + std::string(value) + "\""; + return Text{strdup(str.c_str())}; + } + + Text ec_to_text(char c) + { + // Add quotes around the character to make it obvious where it begin and ends. + std::string str = "'"; + + auto write_hex_digit = [&](unsigned num) + { + if (num < 10u) { str += char('0' + num); } + else { str += char('a' + num - 10); } + }; + + auto write_hex_16 = [&](uint16_t n) + { + write_hex_digit((n >> 12u) & 0x0f); + write_hex_digit((n >> 8u) & 0x0f); + write_hex_digit((n >> 4u) & 0x0f); + write_hex_digit((n >> 0u) & 0x0f); + }; + + if (c == '\\') { str += "\\\\"; } + else if (c == '\"') { str += "\\\""; } + else if (c == '\'') { str += "\\\'"; } + else if (c == '\0') { str += "\\0"; } + else if (c == '\b') { str += "\\b"; } + else if (c == '\f') { str += "\\f"; } + else if (c == '\n') { str += "\\n"; } + else if (c == '\r') { str += "\\r"; } + else if (c == '\t') { str += "\\t"; } + else if (0 <= c && c < 0x20) { + str += "\\u"; + write_hex_16(static_cast<uint16_t>(c)); + } else { str += c; } + + str += "'"; + + return Text{strdup(str.c_str())}; + } + + #define DEFINE_EC(Type) \ + Text ec_to_text(Type value) \ + { \ + auto str = std::to_string(value); \ + return Text{strdup(str.c_str())}; \ + } + + DEFINE_EC(int) + DEFINE_EC(unsigned int) + DEFINE_EC(long) + DEFINE_EC(unsigned long) + DEFINE_EC(long long) + DEFINE_EC(unsigned long long) + DEFINE_EC(float) + DEFINE_EC(double) + DEFINE_EC(long double) + + #undef DEFINE_EC + + Text ec_to_text(EcHandle ec_handle) + { + Text parent_ec = get_error_context_for(ec_handle); + char* with_newline = reinterpret_cast<char*>(malloc(strlen(parent_ec.c_str()) + 2)); + with_newline[0] = '\n'; + strcpy(with_newline + 1, parent_ec.c_str()); + return Text(with_newline); + } + + // ---------------------------------------------------------------------------- + +} // namespace loguru + +// ---------------------------------------------------------------------------- +// .dP"Y8 88 dP""b8 88b 88 db 88 .dP"Y8 +// `Ybo." 88 dP `" 88Yb88 dPYb 88 `Ybo." +// o.`Y8b 88 Yb "88 88 Y88 dP__Yb 88 .o o.`Y8b +// 8bodP' 88 YboodP 88 Y8 dP""""Yb 88ood8 8bodP' +// ---------------------------------------------------------------------------- + +#ifdef _WIN32 +namespace loguru { + void install_signal_handlers() + { + #if defined(_MSC_VER) + #pragma message ( "No signal handlers on Win32" ) + #else + #warning "No signal handlers on Win32" + #endif + } +} // namespace loguru + +#else // _WIN32 + +namespace loguru +{ + struct Signal + { + int number; + const char* name; + }; + const Signal ALL_SIGNALS[] = { +#if LOGURU_CATCH_SIGABRT + { SIGABRT, "SIGABRT" }, +#endif + { SIGBUS, "SIGBUS" }, + { SIGFPE, "SIGFPE" }, + { SIGILL, "SIGILL" }, + { SIGINT, "SIGINT" }, + { SIGSEGV, "SIGSEGV" }, + { SIGTERM, "SIGTERM" }, + }; + + void write_to_stderr(const char* data, size_t size) + { + auto result = write(STDERR_FILENO, data, size); + (void)result; // Ignore errors. + } + + void write_to_stderr(const char* data) + { + write_to_stderr(data, strlen(data)); + } + + void call_default_signal_handler(int signal_number) + { + struct sigaction sig_action; + memset(&sig_action, 0, sizeof(sig_action)); + sigemptyset(&sig_action.sa_mask); + sig_action.sa_handler = SIG_DFL; + sigaction(signal_number, &sig_action, NULL); + kill(getpid(), signal_number); + } + + void signal_handler(int signal_number, siginfo_t*, void*) + { + const char* signal_name = "UNKNOWN SIGNAL"; + + for (const auto& s : ALL_SIGNALS) { + if (s.number == signal_number) { + signal_name = s.name; + break; + } + } + + // -------------------------------------------------------------------- + /* There are few things that are safe to do in a signal handler, + but writing to stderr is one of them. + So we first print out what happened to stderr so we're sure that gets out, + then we do the unsafe things, like logging the stack trace. + */ + + if (g_colorlogtostderr && s_terminal_has_color) { + write_to_stderr(terminal_reset()); + write_to_stderr(terminal_bold()); + write_to_stderr(terminal_light_red()); + } + write_to_stderr("\n"); + write_to_stderr("Loguru caught a signal: "); + write_to_stderr(signal_name); + write_to_stderr("\n"); + if (g_colorlogtostderr && s_terminal_has_color) { + write_to_stderr(terminal_reset()); + } + + // -------------------------------------------------------------------- + +#if LOGURU_UNSAFE_SIGNAL_HANDLER + // -------------------------------------------------------------------- + /* Now we do unsafe things. This can for example lead to deadlocks if + the signal was triggered from the system's memory management functions + and the code below tries to do allocations. + */ + + flush(); + char preamble_buff[LOGURU_PREAMBLE_WIDTH]; + print_preamble(preamble_buff, sizeof(preamble_buff), Verbosity_FATAL, "", 0); + auto message = Message{Verbosity_FATAL, "", 0, preamble_buff, "", "Signal: ", signal_name}; + try { + log_message(1, message, false, false); + } catch (...) { + // This can happed due to s_fatal_handler. + write_to_stderr("Exception caught and ignored by Loguru signal handler.\n"); + } + flush(); + + // -------------------------------------------------------------------- +#endif // LOGURU_UNSAFE_SIGNAL_HANDLER + + call_default_signal_handler(signal_number); + } + + void install_signal_handlers() + { + struct sigaction sig_action; + memset(&sig_action, 0, sizeof(sig_action)); + sigemptyset(&sig_action.sa_mask); + sig_action.sa_flags |= SA_SIGINFO; + sig_action.sa_sigaction = &signal_handler; + for (const auto& s : ALL_SIGNALS) { + CHECK_F(sigaction(s.number, &sig_action, NULL) != -1, + "Failed to install handler for %s", s.name); + } + } +} // namespace loguru + +#endif // _WIN32 + +#endif // LOGURU_IMPLEMENTATION diff --git a/components/common/cpp/test/CMakeLists.txt b/components/common/cpp/test/CMakeLists.txt index c49d192bacfe66df7034612713dbc7e752291cb7..e2a54abbf11716aa0077e21ba25e9ce58dfa521a 100644 --- a/components/common/cpp/test/CMakeLists.txt +++ b/components/common/cpp/test/CMakeLists.txt @@ -5,20 +5,23 @@ add_executable(configurable_unit ../src/uri.cpp ../src/config.cpp ../src/configuration.cpp + ../src/loguru.cpp ./configurable_unit.cpp ) target_include_directories(configurable_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") target_link_libraries(configurable_unit ${URIPARSER_LIBRARIES} - glog::glog) + Threads::Threads ${OS_LIBS}) ### URI ######################################################################## add_executable(uri_unit ./tests.cpp ../src/uri.cpp + ../src/loguru.cpp ./uri_unit.cpp) target_include_directories(uri_unit PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../include") target_link_libraries(uri_unit + Threads::Threads ${OS_LIBS} ${URIPARSER_LIBRARIES}) diff --git a/components/common/cpp/test/configurable_unit.cpp b/components/common/cpp/test/configurable_unit.cpp index 7855438a64114825ec54c54e8bb1a9848066de08..ba3557d253371d0b8ef11841706cb1cade03d5db 100644 --- a/components/common/cpp/test/configurable_unit.cpp +++ b/components/common/cpp/test/configurable_unit.cpp @@ -1,4 +1,6 @@ #include "catch.hpp" +#define LOGURU_REPLACE_GLOG 1 +#include <loguru.hpp> #include <ftl/configurable.hpp> using ftl::Configurable; @@ -38,7 +40,7 @@ SCENARIO( "Configurable::on()" ) { Configurable cfg(json); bool trig = false; - cfg.on("test", [&trig](Configurable *c, const string &n) { + cfg.on("test", [&trig](const ftl::config::Event &e) { trig = true; }); @@ -53,10 +55,10 @@ SCENARIO( "Configurable::on()" ) { bool trig1 = false; bool trig2 = false; - cfg.on("test", [&trig1](Configurable *c, const string &n) { + cfg.on("test", [&trig1](const ftl::config::Event &e) { trig1 = true; }); - cfg.on("test", [&trig2](Configurable *c, const string &n) { + cfg.on("test", [&trig2](const ftl::config::Event &e) { trig2 = true; }); diff --git a/components/control/cpp/CMakeLists.txt b/components/control/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0705bc6e094c68b04ed0d9ad75a18504009eac18 --- /dev/null +++ b/components/control/cpp/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(ftlctrl + src/slave.cpp + src/master.cpp +) + +target_include_directories(ftlctrl PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include> + PRIVATE src) +target_link_libraries(ftlctrl ftlcommon ftlnet) + +install(TARGETS ftlctrl EXPORT ftlctrl-config + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +#ADD_SUBDIRECTORY(test) diff --git a/components/control/cpp/include/ftl/master.hpp b/components/control/cpp/include/ftl/master.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e069f12fcdfc813c27db76dbb75795b23f2570b0 --- /dev/null +++ b/components/control/cpp/include/ftl/master.hpp @@ -0,0 +1,65 @@ +#ifndef _FTL_CTRL_MASTER_HPP_ +#define _FTL_CTRL_MASTER_HPP_ + +#include <ftl/net/universe.hpp> +#include <ftl/configurable.hpp> +#include <ftl/uuid.hpp> +#include <functional> +#include <string> +#include <vector> + +namespace ftl { +namespace ctrl { + +struct LogEvent { + std::string preamble; + std::string message; +}; + +class Master { + public: + Master(ftl::Configurable *root, ftl::net::Universe *net); + ~Master(); + + void restart(); + + void restart(const ftl::UUID &peer); + + void shutdown(); + + void shutdown(const ftl::UUID &peer); + + void set(const std::string &uri, ftl::config::json_t &value); + + void set(const ftl::UUID &peer, const std::string &uri, ftl::config::json_t &value); + + std::vector<std::string> getConfigurables(); + + std::vector<std::string> getConfigurables(const ftl::UUID &peer); + + std::vector<ftl::config::json_t> get(const std::string &uri); + + ftl::config::json_t getOne(const std::string &uri); + + ftl::config::json_t get(const ftl::UUID &peer, const std::string &uri); + + void watch(const std::string &uri, std::function<void()> f); + + // Events + + //void onError(); + void onLog(std::function<void(const LogEvent &)>); + //void onFailure(); + //void onStatus(); + // void onChange(); + + private: + std::vector<std::function<void(const LogEvent&)>> log_handlers_; + ftl::Configurable *root_; + ftl::net::Universe *net_; +}; + +} +} + +#endif // _FTL_CTRL_MASTER_HPP_ diff --git a/components/control/cpp/include/ftl/slave.hpp b/components/control/cpp/include/ftl/slave.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e61290d630c1f10bd38335b228d9657b17ab6de7 --- /dev/null +++ b/components/control/cpp/include/ftl/slave.hpp @@ -0,0 +1,19 @@ +#ifndef _FTL_CTRL_SLAVE_HPP_ +#define _FTL_CTRL_SLAVE_HPP_ + +#include <ftl/net/universe.hpp> +#include <ftl/configurable.hpp> + +namespace ftl { +namespace ctrl { + +class Slave { + public: + Slave(ftl::net::Universe *, ftl::Configurable *); + ~Slave(); +}; + +} +} + +#endif // _FTL_CTRL_SLAVE_HPP_ diff --git a/components/control/cpp/src/master.cpp b/components/control/cpp/src/master.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cba2234c2bed6dccb2572781a2eea34f6e4c6893 --- /dev/null +++ b/components/control/cpp/src/master.cpp @@ -0,0 +1,78 @@ +#include <ftl/master.hpp> + +using ftl::ctrl::Master; +using ftl::net::Universe; +using ftl::Configurable; +using std::string; +using ftl::config::json_t; +using std::vector; +using std::function; +using ftl::ctrl::LogEvent; + +Master::Master(Configurable *root, Universe *net) + : root_(root), net_(net) { + net_->bind("log", [this](const std::string &pre, const std::string &msg) { + for (auto f : log_handlers_) { + f({pre,msg}); + } + }); +} + +Master::~Master() { + +} + +void Master::restart() { + net_->broadcast("restart"); +} + +void Master::restart(const ftl::UUID &peer) { + net_->send(peer, "restart"); +} + +void Master::shutdown() { + net_->broadcast("shutdown"); +} + +void Master::shutdown(const ftl::UUID &peer) { + net_->send(peer, "shutdown"); +} + +void Master::set(const string &uri, json_t &value) { + net_->broadcast("update_cfg", uri, (string)value); +} + +void Master::set(const ftl::UUID &peer, const string &uri, json_t &value) { + net_->send(peer, "update_cfg", uri, (string)value); +} + +vector<string> Master::getConfigurables() { + +} + +vector<string> Master::getConfigurables(const ftl::UUID &peer) { + +} + +vector<json_t> Master::get(const string &uri) { + +} + +json_t Master::getOne(const string &uri) { + +} + +json_t Master::get(const ftl::UUID &peer, const string &uri) { + +} + +void Master::watch(const string &uri, function<void()> f) { + +} + +// Events + +//void onError(); +void Master::onLog(function<void(const LogEvent &)> h) { + log_handlers_.push_back(h); +} \ No newline at end of file diff --git a/components/control/cpp/src/slave.cpp b/components/control/cpp/src/slave.cpp new file mode 100644 index 0000000000000000000000000000000000000000..700c1da56fb6de49bf45c36e24972a0d82be58cb --- /dev/null +++ b/components/control/cpp/src/slave.cpp @@ -0,0 +1,37 @@ +#include <ftl/slave.hpp> +#include <loguru.hpp> + +using ftl::Configurable; +using ftl::net::Universe; +using ftl::ctrl::Slave; + +static void netLog(void* user_data, const loguru::Message& message) { + Universe *net = (Universe*)user_data; + net->publish("log", message.preamble, message.message); +} + +Slave::Slave(Universe *net, ftl::Configurable *root) { + net->bind("restart", []() { + LOG(WARNING) << "Remote restart..."; + exit(1); + }); + + net->bind("shutdown", []() { + LOG(WARNING) << "Remote shutdown..."; + exit(0); + }); + + net->bind("update_cfg", [](const std::string &uri, const std::string &value) { + ftl::config::update(uri, nlohmann::json::parse(value)); + }); + + net->bind("get_cfg", [](const std::string &uri) -> std::string { + return ftl::config::resolve(uri); + }); + + loguru::add_callback("net_log", netLog, net, loguru::Verbosity_INFO); +} + +Slave::~Slave() { + +} diff --git a/components/net/cpp/include/ftl/net/peer.hpp b/components/net/cpp/include/ftl/net/peer.hpp index 9f0d25d2beafa63b2562e6ea752e3b30e31bcfc1..0a16dd27170268b1c098d8a235d4447b7d4354b2 100644 --- a/components/net/cpp/include/ftl/net/peer.hpp +++ b/components/net/cpp/include/ftl/net/peer.hpp @@ -12,8 +12,8 @@ #include <winsock2.h> #endif -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include <glog/logging.h> +//#define GLOG_NO_ABBREVIATED_SEVERITIES +#include <loguru.hpp> #include <ftl/net/protocol.hpp> #include <ftl/net/dispatcher.hpp> #include <ftl/uri.hpp> diff --git a/components/net/cpp/src/dispatcher.cpp b/components/net/cpp/src/dispatcher.cpp index a2dfd5ad64ddc9f84b719f633fcd3ef3ab54d5f6..7bfd7352c27010b80ac13005ffb63c4a1a2add80 100644 --- a/components/net/cpp/src/dispatcher.cpp +++ b/components/net/cpp/src/dispatcher.cpp @@ -1,5 +1,5 @@ -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include <glog/logging.h> +//#define GLOG_NO_ABBREVIATED_SEVERITIES +#include <loguru.hpp> #include <ftl/net/dispatcher.hpp> #include <ftl/net/peer.hpp> #include <iostream> diff --git a/components/net/cpp/src/listener.cpp b/components/net/cpp/src/listener.cpp index 3b1c4af0eac14102721cfd4f458a37e0f89fde45..8e0f1267f29c09c87aaea16692e08d3f93692cf9 100644 --- a/components/net/cpp/src/listener.cpp +++ b/components/net/cpp/src/listener.cpp @@ -1,5 +1,5 @@ -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include <glog/logging.h> +//#define GLOG_NO_ABBREVIATED_SEVERITIES +#include <loguru.hpp> #include <ftl/uri.hpp> #include <ftl/net/listener.hpp> diff --git a/components/net/cpp/src/peer.cpp b/components/net/cpp/src/peer.cpp index 186b4ba7853947bf49de96c96325e565ddd3e675..4f76a98ff2fadb9f8f7ee669668854e8c7f9bce0 100644 --- a/components/net/cpp/src/peer.cpp +++ b/components/net/cpp/src/peer.cpp @@ -1,5 +1,5 @@ -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include <glog/logging.h> +//#define GLOG_NO_ABBREVIATED_SEVERITIES +#include <loguru.hpp> #include <ctpl_stl.h> #ifndef NOMINMAX @@ -160,6 +160,7 @@ Peer::Peer(int s, Dispatcher *d) : sock_(s) { status_ = kConnected; version_ = version; peerid_ = pid; + if (version != ftl::net::kVersion) LOG(WARNING) << "Net protocol using different versions!"; _trigger(open_handlers_); } @@ -212,6 +213,7 @@ Peer::Peer(const char *pUri, Dispatcher *d) : uri_(pUri) { status_ = kConnected; version_ = version; peerid_ = pid; + if (version != ftl::net::kVersion) LOG(WARNING) << "Net protocol using different versions!"; send("__handshake__", ftl::net::kMagic, ftl::net::kVersion, ftl::net::this_peer); _trigger(open_handlers_); diff --git a/components/net/cpp/src/ws_internal.cpp b/components/net/cpp/src/ws_internal.cpp index 4c3e72a1bb2bec776616a6e859eb4e863157c7e8..50e9f8b5e3d029956b170c380b45815335b7f931 100644 --- a/components/net/cpp/src/ws_internal.cpp +++ b/components/net/cpp/src/ws_internal.cpp @@ -2,8 +2,8 @@ * Copyright 2019 Nicolas Pope. */ -#define GLOG_NO_ABBREVIATED_SEVERITIES -#include <glog/logging.h> +//#define GLOG_NO_ABBREVIATED_SEVERITIES +#include <loguru.hpp> #include <cstring> #include <ftl/net/ws_internal.hpp> diff --git a/components/renderers/cpp/src/display.cpp b/components/renderers/cpp/src/display.cpp index 23288289b01ef181f262f56d6d5cda501cfe146b..65c8b9eabfd770c39055fb73197dd61c3fc01c4f 100644 --- a/components/renderers/cpp/src/display.cpp +++ b/components/renderers/cpp/src/display.cpp @@ -2,7 +2,7 @@ * Copyright 2019 Nicolas Pope */ -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/display.hpp> #include <ftl/utility/opencv_to_pcl.hpp> @@ -132,7 +132,7 @@ static pcl::PointCloud<pcl::PointXYZRGB>::Ptr rgbdToPointXYZ(const cv::Mat &rgb, bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd::CameraParameters &p) { Mat idepth; - if (config_["points"] && rgb.rows != 0) { + if (value("points", false) && rgb.rows != 0) { #if defined HAVE_PCL auto pc = rgbdToPointXYZ(rgb, depth, p); @@ -166,15 +166,15 @@ bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd:: #endif // HAVE_VIZ } - if (config_["left"]) { - if (config_["crosshair"]) { + if (value("left", false)) { + if (value("crosshair", false)) { cv::line(rgb, cv::Point(0, rgb.rows/2), cv::Point(rgb.cols-1, rgb.rows/2), cv::Scalar(0,0,255), 1); cv::line(rgb, cv::Point(rgb.cols/2, 0), cv::Point(rgb.cols/2, rgb.rows-1), cv::Scalar(0,0,255), 1); } cv::namedWindow("Left: " + name_, cv::WINDOW_KEEPRATIO); cv::imshow("Left: " + name_, rgb); } - if (config_["right"]) { + if (value("right", false)) { /*if (config_["crosshair"]) { cv::line(rgbr, cv::Point(0, rgbr.rows/2), cv::Point(rgbr.cols-1, rgbr.rows/2), cv::Scalar(0,0,255), 1); cv::line(rgbr, cv::Point(rgbr.cols/2, 0), cv::Point(rgbr.cols/2, rgbr.rows-1), cv::Scalar(0,0,255), 1); @@ -183,7 +183,7 @@ bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd:: cv::imshow("Right: " + name_, rgbr);*/ } - if (config_["disparity"]) { + if (value("disparity", false)) { /*Mat depth32F = (focal * (float)l.cols * base_line) / depth; normalize(depth32F, depth32F, 0, 255, NORM_MINMAX, CV_8U); cv::imshow("Depth", depth32F); @@ -191,8 +191,8 @@ bool Display::render(const cv::Mat &rgb, const cv::Mat &depth, const ftl::rgbd:: //exit if ESC is pressed active_ = false; }*/ - } else if (config_["depth"]) { - if ((bool)config_["flip_vert"]) { + } else if (value("depth", false)) { + if (value("flip_vert", false)) { cv::flip(depth, idepth, 0); } else { idepth = depth; @@ -228,7 +228,7 @@ bool Display::render(const cv::Mat &img, style_t s) { } else if (s = STYLE_DISPARITY) { Mat idepth; - if ((bool)config_["flip_vert"]) { + if (value("flip_vert", false)) { cv::flip(img, idepth, 0); } else { idepth = img; @@ -244,7 +244,7 @@ bool Display::render(const cv::Mat &img, style_t s) { } void Display::wait(int ms) { - if (config_["points"]) { + if (value("points", false)) { #if defined HAVE_PCL if (pclviz_) pclviz_->spinOnce(20); #elif defined HAVE_VIZ @@ -252,7 +252,7 @@ void Display::wait(int ms) { #endif // HAVE_VIZ } - if (config_["depth"] || config_["left"] || config_["right"]) { + if (value("depth", false) || value("left", false) || value("right", false)) { while (true) { int key = cv::waitKey(ms); diff --git a/components/rgbd-sources/CMakeLists.txt b/components/rgbd-sources/CMakeLists.txt index 637255efd1109cb04112c6e867c0fcf88ac8db2b..fe828144030c81c66e754bae65c0bde1fcb20951 100644 --- a/components/rgbd-sources/CMakeLists.txt +++ b/components/rgbd-sources/CMakeLists.txt @@ -12,6 +12,13 @@ set(RGBDSRC src/algorithms/opencv_bm.cpp ) +if (LIBARCHIVE_FOUND) + list(APPEND RGBDSRC + "src/snapshot.cpp" + "src/snapshot_source.cpp" + ) +endif (LIBARCHIVE_FOUND) + if (LIBSGM_FOUND) list(APPEND RGBDSRC "src/algorithms/fixstars_sgm.cpp") endif (LIBSGM_FOUND) @@ -44,6 +51,6 @@ set_property(TARGET ftlrgbd PROPERTY CUDA_SEPARABLE_COMPILATION OFF) endif() #target_include_directories(cv-node PUBLIC ${PROJECT_SOURCE_DIR}/include) -target_link_libraries(ftlrgbd ftlcommon Threads::Threads ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen glog::glog ftlnet) +target_link_libraries(ftlrgbd ftlcommon Threads::Threads ${OpenCV_LIBS} ${LIBSGM_LIBRARIES} ${CUDA_LIBRARIES} Eigen3::Eigen glog::glog ftlnet ${LibArchive_LIBRARIES}) diff --git a/components/rgbd-sources/include/ftl/rgbd.hpp b/components/rgbd-sources/include/ftl/rgbd.hpp index c85524e9c344fe9e885f707804d814d737fb93e3..774ebfe67bdc96d5ef245b9a0cfa6bc6f5e462d7 100644 --- a/components/rgbd-sources/include/ftl/rgbd.hpp +++ b/components/rgbd-sources/include/ftl/rgbd.hpp @@ -8,4 +8,8 @@ #include <ftl/stereovideo_source.hpp> #include <ftl/net_source.hpp> +#ifdef HAVE_LIBARCHIVE +#include <ftl/snapshot_source.hpp> +#endif // HAVE_LIBARCHIVE + #endif // _FTL_RGBD_HPP_ diff --git a/components/rgbd-sources/include/ftl/rgbd_streamer.hpp b/components/rgbd-sources/include/ftl/rgbd_streamer.hpp index 8218518edfed0efd68db8c13f8b4ac579d7aff75..3d62721896c563711af7e1c2531900de246a504e 100644 --- a/components/rgbd-sources/include/ftl/rgbd_streamer.hpp +++ b/components/rgbd-sources/include/ftl/rgbd_streamer.hpp @@ -2,7 +2,7 @@ #define _FTL_RGBD_STREAMER_HPP_ #include <ctpl_stl.h> -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/configuration.hpp> #include <ftl/configurable.hpp> #include <ftl/rgbd_source.hpp> diff --git a/components/rgbd-sources/include/ftl/snapshot.hpp b/components/rgbd-sources/include/ftl/snapshot.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3f0f6d1a403e625e42686a576a68828d51a95c1d --- /dev/null +++ b/components/rgbd-sources/include/ftl/snapshot.hpp @@ -0,0 +1,69 @@ +#pragma once +#ifndef _FTL_RGBD_SNAPSHOT_HPP_ +#define _FTL_RGBD_SNAPSHOT_HPP_ + +#include <loguru.hpp> + +#include <opencv2/opencv.hpp> + +#include <Eigen/Eigen> +#include <opencv2/core/eigen.hpp> + +#include <ftl/camera_params.hpp> + +#include <archive.h> +#include <archive_entry.h> + +namespace ftl { +namespace rgbd { + +// FIXME: NOT thread safe + +class SnapshotWriter { +public: + SnapshotWriter(const std::string &filename); + ~SnapshotWriter(); + + bool addCameraRGBD(const std::string &name, const cv::Mat &rgb, const cv::Mat &depth, const Eigen::Matrix4f &pose, const ftl::rgbd::CameraParameters ¶ms); + bool addMat(const std::string &name, const cv::Mat &mat, const std::string &format="tiff"); + bool addEigenMatrix4f(const std::string &name, const Eigen::Matrix4f &m, const std::string &format="pfm"); + bool addFile(const std::string &name, const std::vector<uchar> &buf); + bool addFile(const std::string &name, const uchar *buf, const size_t len); + +private: + struct archive *archive_; + struct archive_entry *entry_; +}; + +struct SnapshotEntry { + cv::Mat rgb; + cv::Mat depth; + Eigen::Matrix4f pose; + ftl::rgbd::CameraParameters params; + uint status; + SnapshotEntry() : status(1+2+4+8) {}; +}; + +class SnapshotReader { +public: + SnapshotReader(const std::string &filename); + ~SnapshotReader(); + + bool getCameraRGBD(const std::string &id, cv::Mat &rgb, cv::Mat &depth, Eigen::Matrix4f &pose, ftl::rgbd::CameraParameters ¶ms); + std::vector<std::string> getIds(); + +private: + SnapshotEntry& getEntry(const std::string &id); + bool readEntry(std::vector<uchar> &data); + bool readArchive(); + + std::map<std::string, SnapshotEntry> data_; + struct archive *archive_; + struct archive_entry *entry_; +}; + + +}; +}; + +#endif // _FTL_RGBD_SNAPSHOT_HPP_ diff --git a/components/rgbd-sources/include/ftl/snapshot_source.hpp b/components/rgbd-sources/include/ftl/snapshot_source.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d4900f76a3079ab4b3cf6f70fa179b59bdd48c22 --- /dev/null +++ b/components/rgbd-sources/include/ftl/snapshot_source.hpp @@ -0,0 +1,23 @@ +#pragma once +#ifndef _FTL_RGBD_SNAPSHOT_SOURCE_HPP_ +#define _FTL_RGBD_SNAPSHOT_SOURCE_HPP_ + +#include <loguru.hpp> + +#include "ftl/rgbd_source.hpp" +#include "ftl/snapshot.hpp" + +namespace ftl { +namespace rgbd { + +class SnapshotSource : public RGBDSource { + public: + SnapshotSource(nlohmann::json &config, ftl::rgbd::SnapshotReader &reader, const std::string &id); + ~SnapshotSource() {}; + void grab() override {}; +}; + +}; +}; + +#endif // _FTL_RGBD_SNAPSHOT_SOURCE_HPP_ diff --git a/components/rgbd-sources/src/algorithms/elas.cpp b/components/rgbd-sources/src/algorithms/elas.cpp index b99c309c96717739327a488efcdfd2e510c9bbe7..07d0de9780092908bb87bc2b643d037fc5fca169 100644 --- a/components/rgbd-sources/src/algorithms/elas.cpp +++ b/components/rgbd-sources/src/algorithms/elas.cpp @@ -1,7 +1,7 @@ /* Copyright 2019 Nicolas Pope */ #include <ftl/algorithms/elas.hpp> -#include <glog/logging.h> +#include <loguru.hpp> #include <chrono> diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp index b2c2f110c72945f38e4ae005eed5b52f4a41bf46..7efb04b374e61215e9a86888dd4c830ea317ad3f 100644 --- a/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp +++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.cpp @@ -1,7 +1,7 @@ /* Copyright 2019 Nicolas Pope */ #include "fixstars_sgm.hpp" -#include <glog/logging.h> +#include <loguru.hpp> #include <opencv2/cudastereo.hpp> using ftl::algorithms::FixstarsSGM; @@ -17,7 +17,7 @@ FixstarsSGM::FixstarsSGM(nlohmann::json &config) : Disparity(config) { filter_ = cv::cuda::createDisparityBilateralFilter(max_disp_ << 4, config.value("filter_radius", 25), config.value("filter_iter", 1)); } -void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, const cv::Mat &mask_l) { +void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp) { Mat left_disp; Mat right_disp; @@ -25,7 +25,7 @@ void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, con cv::cvtColor(l, lbw, cv::COLOR_BGR2GRAY); cv::cvtColor(r, rbw, cv::COLOR_BGR2GRAY); - if (!ssgm_) { + if (!ssgm_) { // todo: move to constructor ssgm_ = new sgm::StereoSGM(l.cols, l.rows, max_disp_, 8, 16, sgm::EXECUTE_INOUT_HOST2HOST, sgm::StereoSGM::Parameters(10,120,0.95f,true)); @@ -43,7 +43,6 @@ void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, con // disparity values set to (256 << 5) in libSGM consistency check Mat bad_pixels = (disp == (256 << 5)); disp.setTo(0, bad_pixels); - disp.setTo(0, mask_l); if (use_filter_) { cv::cuda::GpuMat l_gpu, disp_gpu, disp_gpu_out; @@ -59,7 +58,15 @@ void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, con disp.convertTo(disp, CV_32F, 1.0f/16.0f); } -void FixstarsSGM::compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp) { - // todo: allow using without mask - LOG(FATAL) << "libSGM: not using mask (required)!"; -} +void FixstarsSGM::setMask(Mat &mask) { + CHECK(mask.type() == CV_8UC1) << "mask type must be CV_8U"; + + if (!ssgm_) { // todo: move to constructor + ssgm_ = new sgm::StereoSGM(mask.cols, mask.rows, max_disp_, 8, 16, + sgm::EXECUTE_INOUT_HOST2HOST, + sgm::StereoSGM::Parameters(10,120,0.95f,true)); + } + + mask_l_ = mask; + ssgm_->setMask((uint8_t*) mask.data, mask.cols); +} \ No newline at end of file diff --git a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp index a17ca1bbfdfddb82981ede823c6e878bc1d9c723..dd92efdb206aa97897cc561f45cb57f546fb76e9 100644 --- a/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp +++ b/components/rgbd-sources/src/algorithms/fixstars_sgm.hpp @@ -27,7 +27,7 @@ class FixstarsSGM : public ftl::Disparity { explicit FixstarsSGM(nlohmann::json &config); void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp) override; - void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, const cv::Mat &mask_l) override; + void setMask(cv::Mat &mask) override; /* Factory creator */ static inline Disparity *create(ftl::Configurable *p, const std::string &name) { diff --git a/components/rgbd-sources/src/algorithms/rtcensus.cpp b/components/rgbd-sources/src/algorithms/rtcensus.cpp index cd0e92ea706fea7fcaa8469b52cae31983170ca8..dbafd2d3737d5dca90a85fe6b125e7579245efb9 100644 --- a/components/rgbd-sources/src/algorithms/rtcensus.cpp +++ b/components/rgbd-sources/src/algorithms/rtcensus.cpp @@ -14,7 +14,7 @@ * Equation numbering uses [1] unless otherwise stated */ -#include <glog/logging.h> +#include <loguru.hpp> #include <cmath> #include <vector> diff --git a/components/rgbd-sources/src/algorithms/rtcensus_sgm.cpp b/components/rgbd-sources/src/algorithms/rtcensus_sgm.cpp index 26c673a03a971b82d489a9a4e56830439b690386..be534d29014522aa6d1f97e911e8bcbf98a9af25 100644 --- a/components/rgbd-sources/src/algorithms/rtcensus_sgm.cpp +++ b/components/rgbd-sources/src/algorithms/rtcensus_sgm.cpp @@ -18,7 +18,7 @@ #include <tuple> #include <bitset> #include <cmath> -#include <glog/logging.h> +#include <loguru.hpp> using ftl::algorithms::RTCensusSGM; using std::vector; diff --git a/components/rgbd-sources/src/calibrate.cpp b/components/rgbd-sources/src/calibrate.cpp index 3439fe5df9bb08074a28d5b2cc486b1d8168b107..7e94ac9efcc140c4b3e8e4c3815392eb54d53863 100644 --- a/components/rgbd-sources/src/calibrate.cpp +++ b/components/rgbd-sources/src/calibrate.cpp @@ -2,7 +2,7 @@ * Copyright 2019 Nicolas Pope */ -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/config.h> #include <ftl/configuration.hpp> diff --git a/components/rgbd-sources/src/disparity.cpp b/components/rgbd-sources/src/disparity.cpp index a6af11e7dab463d7b1ade286b024538f27172230..e482fa60cc632346589d75d737756f0507a3af81 100644 --- a/components/rgbd-sources/src/disparity.cpp +++ b/components/rgbd-sources/src/disparity.cpp @@ -3,7 +3,7 @@ */ #include "disparity.hpp" -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/config.h> #include <ftl/configuration.hpp> @@ -18,7 +18,7 @@ Disparity::Disparity(nlohmann::json &config) max_disp_(value("maximum", 256)) {} Disparity *Disparity::create(ftl::Configurable *parent, const std::string &name) { - nlohmann::json &config = ftl::config::resolve(parent->getConfig()[name]); + nlohmann::json &config = ftl::config::resolve((!parent->getConfig()[name].is_null()) ? parent->getConfig()[name] : ftl::config::resolve(parent->getConfig())[name]); // ftl::config::resolve(parent->getConfig()[name]); //auto alg = parent->get<std::string>("algorithm"); if (!config["algorithm"].is_string()) { @@ -33,7 +33,7 @@ Disparity *Disparity::create(ftl::Configurable *parent, const std::string &name) void Disparity::_register(const std::string &n, std::function<Disparity*(ftl::Configurable *, const std::string &)> f) { if (!algorithms__) algorithms__ = new std::map<std::string, std::function<Disparity*(ftl::Configurable *, const std::string &)>>; - LOG(INFO) << "Register disparity algorithm: " << n; + //LOG(INFO) << "Register disparity algorithm: " << n; (*algorithms__)[n] = f; } diff --git a/components/rgbd-sources/src/disparity.hpp b/components/rgbd-sources/src/disparity.hpp index ebe5b9b0849df4a0831679f86600d72a7b348c0b..f5c98f399811bd50e1776f8b0f86fc851e41f9d4 100644 --- a/components/rgbd-sources/src/disparity.hpp +++ b/components/rgbd-sources/src/disparity.hpp @@ -24,14 +24,13 @@ class Disparity : public ftl::Configurable { virtual void setMinDisparity(size_t min) { min_disp_ = min; } virtual void setMaxDisparity(size_t max) { max_disp_ = max; } + virtual void setMask(cv::Mat &mask) { mask_l_ = mask; } + /** * Pure virtual function representing the actual computation of * disparity from left and right images to be implemented. */ virtual void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp)=0; - virtual void compute(const cv::Mat &l, const cv::Mat &r, cv::Mat &disp, const cv::Mat &mask_l) { - compute(l, r, disp); - } /** * Factory registration class. @@ -56,6 +55,7 @@ class Disparity : public ftl::Configurable { //nlohmann::json &config_; size_t min_disp_; size_t max_disp_; + cv::Mat mask_l_; private: static std::map<std::string,std::function<Disparity*(ftl::Configurable *, const std::string &)>> *algorithms__; diff --git a/components/rgbd-sources/src/local.cpp b/components/rgbd-sources/src/local.cpp index f7ed7ebd20b8596c6d739ce238e8e8c65babc10f..e66396597ddb99e53913299e8db89ca422953513 100644 --- a/components/rgbd-sources/src/local.cpp +++ b/components/rgbd-sources/src/local.cpp @@ -2,7 +2,7 @@ * Copyright 2019 Nicolas Pope */ -#include <glog/logging.h> +#include <loguru.hpp> #include <string> #include <chrono> diff --git a/components/rgbd-sources/src/rgbd_source.cpp b/components/rgbd-sources/src/rgbd_source.cpp index eb79f92465fc6a55a9ded9733a9c0fc74c11ae56..17c6443fc3f60cb2dccaaad1884ae6b62305b1fa 100644 --- a/components/rgbd-sources/src/rgbd_source.cpp +++ b/components/rgbd-sources/src/rgbd_source.cpp @@ -57,18 +57,19 @@ bool RGBDSource::snapshot(const std::string &fileprefix) { } RGBDSource *RGBDSource::create(nlohmann::json &config, ftl::net::Universe *net) { - if (config["type"].type_name() != "string") { - LOG(ERROR) << "Missing RGB-D source type: " << config["type"].type_name(); + auto &cfg = ftl::config::resolve(config); + if (cfg["type"].type_name() != "string") { + LOG(ERROR) << "Missing RGB-D source type: " << cfg["type"].type_name(); //return nullptr; } - if (sources__->count(config["type"].get<string>()) != 1) return nullptr; - return (*sources__)[config["type"].get<string>()](config, net); + if (sources__->count(cfg["type"].get<string>()) != 1) return nullptr; + return (*sources__)[cfg["type"].get<string>()](config, net); } void RGBDSource::_register(const std::string &n, std::function<RGBDSource*(nlohmann::json&,ftl::net::Universe*)> f) { if (!sources__) sources__ = new std::map<std::string, std::function<RGBDSource*(nlohmann::json&,ftl::net::Universe*)>>; - LOG(INFO) << "Register RGB-D Source: " << n; + //LOG(INFO) << "Register RGB-D Source: " << n; (*sources__)[n] = f; } diff --git a/components/rgbd-sources/src/rgbd_streamer.cpp b/components/rgbd-sources/src/rgbd_streamer.cpp index 60b87c969a6d825805139e152434245ebe4f0788..472f05cf2a24cf93b8af8e0da5c57a29ecedb01e 100644 --- a/components/rgbd-sources/src/rgbd_streamer.cpp +++ b/components/rgbd-sources/src/rgbd_streamer.cpp @@ -169,7 +169,7 @@ void Streamer::_schedule() { shared_lock<shared_mutex> slk(s.second->mutex); if (s.second->state != 0) { - LOG(ERROR) << "Stream not ready to schedule on time: " << uri; + LOG(WARNING) << "Stream not ready to schedule on time: " << uri; continue; } if (s.second->clients[0].size() == 0) { diff --git a/components/rgbd-sources/src/snapshot.cpp b/components/rgbd-sources/src/snapshot.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5142612b9d58b01ac0be7fb3172021e2de663d91 --- /dev/null +++ b/components/rgbd-sources/src/snapshot.cpp @@ -0,0 +1,276 @@ +#include <ftl/snapshot.hpp> + +#include <nlohmann/json.hpp> + +using namespace ftl::rgbd; + +using cv::Mat; +using Eigen::Matrix4f; + +using cv::imencode; +using cv::imdecode; + +using std::string; +using std::vector; + +using Eigen::Matrix4f; + +// TODO: move to camera_params +using ftl::rgbd::CameraParameters; + +void to_json(nlohmann::json& j, const CameraParameters &p) { + j = nlohmann::json{ + {"fx", p.fx}, + {"fy", p.fy}, + {"cx", p.cx}, + {"cy", p.cy}, + {"width", p.width}, + {"height", p.height}, + {"minDepth", p.minDepth}, + {"maxDepth", p.maxDepth} + }; +} + +void from_json(const nlohmann::json& j, CameraParameters &p) { + j.at("fx").get_to(p.fx); + j.at("fy").get_to(p.fy); + j.at("cx").get_to(p.cx); + j.at("cy").get_to(p.cy); + j.at("width").get_to(p.width); + j.at("height").get_to(p.height); + j.at("minDepth").get_to(p.minDepth); + j.at("maxDepth").get_to(p.maxDepth); +} +// + +SnapshotWriter::SnapshotWriter(const string &filename) { + archive_ = archive_write_new(); + if (!archive_) goto error3; + entry_ = archive_entry_new(); + if (!entry_) goto error2; + + if (archive_write_set_format_pax_restricted(archive_) != ARCHIVE_OK) + goto error1; + + // todo make compression optional (or remove it) + if (archive_write_add_filter_gzip(archive_) != ARCHIVE_OK) + goto error1; + if (archive_write_open_filename(archive_, filename.c_str()) != ARCHIVE_OK) + goto error1; + + return; + + error1: + archive_entry_free(entry_); + error2: + LOG(ERROR) << archive_error_string(archive_); + archive_write_free(archive_); + error3: + // throw exception; otherwise destructor might be called + throw std::runtime_error("SnapshotWriter failed"); +} + +SnapshotWriter::~SnapshotWriter() { + archive_entry_free(entry_); + archive_write_close(archive_); + archive_write_free(archive_); +} + +bool SnapshotWriter::addFile(const string &name, const uchar *buf, const size_t len) { + archive_entry_clear(entry_); + archive_entry_set_pathname(entry_, name.c_str()); + archive_entry_set_size(entry_, len); + archive_entry_set_filetype(entry_, AE_IFREG); + archive_entry_set_perm(entry_, 0644); + + size_t l = len; + if (archive_write_header(archive_, entry_) != ARCHIVE_OK) goto error; + + while (true) { + ssize_t ret_w = archive_write_data(archive_, buf, l); + if (ret_w == 0) { break; } + if (ret_w < 0) { goto error; } + else { + l -= ret_w; + buf = buf + ret_w; + } + } + return true; + + error: + LOG(ERROR) << archive_error_string(archive_); + return false; +} + +bool SnapshotWriter::addFile(const string &name, const vector<uchar> &buf) { + return addFile(name, buf.data(), buf.size()); +} + +bool SnapshotWriter::addMat(const string &name, const Mat &mat, const std::string &format) { + if (mat.rows == 0 || mat.cols == 0) { + LOG(ERROR) << "empty mat"; + return false; + } + + vector<uchar> buf; + vector<int> params; + bool retval = true; + retval &= imencode("." + format, mat, buf, params); + retval &= addFile(name + "." + format, buf); + return retval; +} + +bool SnapshotWriter::addEigenMatrix4f(const string &name, const Matrix4f &m, const string &format) { + Mat tmp; + cv::eigen2cv(m, tmp); + return addMat(name, tmp, format); +} + +bool SnapshotWriter::addCameraRGBD(const string &name, const Mat &rgb, const Mat &depth, + const Matrix4f &pose, const CameraParameters ¶ms) { + bool retval = true; + retval &= addMat(name + "-RGB", rgb); + retval &= addMat(name + "-D", depth); + retval &= addEigenMatrix4f(name + "-POSE", pose); + + nlohmann::json j; + to_json(j, params); + string str_params = j.dump(); + retval &= addFile(name + "-PARAMS.json", (uchar*) str_params.c_str(), str_params.size()); + return retval; +} + + +SnapshotReader::SnapshotReader(const string &filename) { + archive_ = archive_read_new(); + if (!archive_) goto error2; + archive_read_support_format_all(archive_); + archive_read_support_filter_all(archive_); + + if (archive_read_open_filename(archive_, filename.c_str(), 4096) != ARCHIVE_OK) + goto error1; + + readArchive(); + return; + + error1: + LOG(ERROR) << archive_error_string(archive_); + archive_read_free(archive_); + error2: + // throw exception; otherwise destructor might be called + throw std::runtime_error("SnapshotReader failed"); +} + +SnapshotReader::~SnapshotReader() { + archive_read_free(archive_); +} + +bool SnapshotReader::readEntry(vector<uchar> &data) { + if (!archive_entry_size_is_set(entry_)) { + LOG(ERROR) << "entry size unknown"; + return false; + } + + size_t size = archive_entry_size(entry_); + size_t size_read = 0; + data.resize(size); + uchar *buf = data.data(); + + while(true) { + ssize_t size_read_new = archive_read_data(archive_, buf + size_read, size - size_read); + if (size_read_new < 0) return false; + if (size_read_new == 0) return true; + size_read += size_read_new; + } +} + +SnapshotEntry& SnapshotReader::getEntry(const string &id) { + /*if (data_.find(id) == data_.end()) { + data_.emplace(id, SnapshotEntry{}); + }*/ + return data_[id]; +} + +/* read all entries to data_ */ +bool SnapshotReader::readArchive() { + int retval = ARCHIVE_OK; + vector<uchar> data; + + while((retval = archive_read_next_header(archive_, &entry_)) == ARCHIVE_OK) { + string path = string(archive_entry_pathname(entry_)); + if (path.rfind("-") == string::npos) { + LOG(WARNING) << "unrecognized file " << path; + continue; + } + string id = path.substr(0, path.find("-")); + + SnapshotEntry &snapshot = getEntry(id); + + // TODO: verify that input is valid + // TODO: check that earlier results are not overwritten (status) + + if (path.rfind("-RGB.") != string::npos) { + if (!readEntry(data)) continue; + snapshot.rgb = cv::imdecode(data, cv::IMREAD_COLOR); + snapshot.status &= ~1; + } + else if (path.rfind("-D.") != string::npos) { + if (!readEntry(data)) continue; + snapshot.depth = cv::imdecode(data, cv::IMREAD_ANYDEPTH); + snapshot.status &= ~(1 << 1); + } + else if (path.rfind("-POSE.pfm") != string::npos) { + if (!readEntry(data)) continue; + Mat m_ = cv::imdecode(Mat(data), 0); + if ((m_.rows != 4) || (m_.cols != 4)) continue; + cv::Matx44f pose_(m_); + cv::cv2eigen(pose_, snapshot.pose); + snapshot.status &= ~(1 << 2); + } + else if (path.rfind("-PARAMS.json") != string::npos) { + if (!readEntry(data)) continue; + nlohmann::json j = nlohmann::json::parse(string((const char*) data.data(), data.size())); + from_json(j, snapshot.params); + snapshot.status &= ~(1 << 3); + } + else { + LOG(WARNING) << "unknown file " << path; + } + } + + if (retval != ARCHIVE_EOF) { + LOG(ERROR) << archive_error_string(archive_); + return false; + } + + return true; +} + +vector<string> SnapshotReader::getIds() { + vector<string> res; + res.reserve(data_.size()); + for(auto itr = data_.begin(); itr != data_.end(); ++itr) { + res.push_back(itr->first); + } + return res; +} + +bool SnapshotReader::getCameraRGBD(const string &id, Mat &rgb, Mat &depth, + Matrix4f &pose, CameraParameters ¶ms) { + if (data_.find(id) == data_.end()) { + LOG(ERROR) << "entry not found: " << id; + return false; + } + + SnapshotEntry item = getEntry(id); + + if (item.status != 0) { + LOG(ERROR) << "entry incomplete: " << id; + } + + rgb = item.rgb; + depth = item.depth; + params = item.params; + pose = item.pose; + return true; +} \ No newline at end of file diff --git a/components/rgbd-sources/src/snapshot_source.cpp b/components/rgbd-sources/src/snapshot_source.cpp new file mode 100644 index 0000000000000000000000000000000000000000..40751e58a523f5b2db8fe3d1788ab456ff1ef7b8 --- /dev/null +++ b/components/rgbd-sources/src/snapshot_source.cpp @@ -0,0 +1,15 @@ +#include "ftl/snapshot_source.hpp" + +#include <opencv2/opencv.hpp> +#include <Eigen/Eigen> +#include <opencv2/core/eigen.hpp> + +using namespace ftl::rgbd; + +using std::string; + +SnapshotSource::SnapshotSource(nlohmann::json &config, SnapshotReader &reader, const string &id) : RGBDSource(config) { + Eigen::Matrix4f pose; + reader.getCameraRGBD(id, rgb_, depth_, pose, params_); + setPose(pose); +} diff --git a/components/rgbd-sources/src/stereovideo_source.cpp b/components/rgbd-sources/src/stereovideo_source.cpp index 6264caff28ee5458125c518e80ef56c8907ffffb..daff717e7f014066c664d0f7fcbed2f6e49cd660 100644 --- a/components/rgbd-sources/src/stereovideo_source.cpp +++ b/components/rgbd-sources/src/stereovideo_source.cpp @@ -1,4 +1,4 @@ -#include <glog/logging.h> +#include <loguru.hpp> #include <ftl/stereovideo_source.hpp> #include <ftl/configuration.hpp> #include "calibrate.hpp" @@ -75,7 +75,8 @@ StereoVideoSource::StereoVideoSource(nlohmann::json &config, const string &file) mask_l_ = (mask_l == 0); disp_ = Disparity::create(this, "disparity"); - if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << config["disparity"]; + if (!disp_) LOG(FATAL) << "Unknown disparity algorithm : " << *get<ftl::config::json_t>("disparity"); + disp_->setMask(mask_l_); LOG(INFO) << "StereoVideo source ready..."; ready_ = true; @@ -114,7 +115,7 @@ void StereoVideoSource::grab() { calib_->rectified(left_, right_); cv::Mat disp; - disp_->compute(left_, right_, disp, mask_l_); + disp_->compute(left_, right_, disp); unique_lock<mutex> lk(mutex_); left_.copyTo(rgb_); diff --git a/config/config.jsonc b/config/config.jsonc index d86ca12ca4d7ba2ba61b3f521c375a5d657eab60..457cffe60fe107bfcfe6f273656a9172608642a9 100644 --- a/config/config.jsonc +++ b/config/config.jsonc @@ -127,7 +127,7 @@ "SDFTruncationScale": 0.05, "SDFIntegrationWeightSample": 10, "SDFIntegrationWeightMax": 255, - "hash_renderer": true + "hash_renderer": false } }, @@ -144,13 +144,13 @@ "peers": ["tcp://localhost:9001"] }, "sources": [ - {"type": "net", "uri":"ftl://utu.fi/vision_default/source"} + {"type": "net", "uri":"ftl://utu.fi#vision_default/source"} ], "display": { "$ref": "#displays/left" }, "virtual": { "$ref": "#virtual_cams/default" }, "voxelhash": { "$ref": "#hash_conf/default" }, "registration": { - "reference-source" : "ftl://utu.fi/vision_default/source", + "reference-source" : "ftl://utu.fi#vision_default/source", "calibration" : { "max_error": 25, "run": false, @@ -163,16 +163,17 @@ "reconstruction_lab": { "net": { - "peers": ["tcp://ftl-node-4:9001"] + "peers": ["tcp://ftl-node-4:9001", "tcp://ftl-node-5:9001"] }, "sources": [ - {"type": "net", "uri":"ftl://utu.fi/node4"} + {"type": "net", "uri":"ftl://utu.fi/node4#vision_default/source"}, + {"type": "net", "uri":"ftl://utu.fi/node5#vision_default/source"} ], "display": { "$ref": "#displays/left" }, "virtual": { "$ref": "#virtual_cams/default" }, "voxelhash": { "$ref": "#hash_conf/default" }, "registration": { - "reference-source" : "ftl://utu.fi/node4", + "reference-source" : "ftl://utu.fi/node4#vision_default/source", "calibration" : { "max_error": 25, "run": false, @@ -180,14 +181,22 @@ "delay" : 500, "patternsize" : [9, 6] } - } + }, + "stream": {} }, + "gui_node5": { + "net": { + "peers": ["tcp://ftl-node-5:9001"] + }, + "sources": [{"type": "net", "uri": "ftl://utu.fi/node5#vision_default/source"}] + }, + "gui_default": { "net": { - "peers": ["tcp://ftl-node-4:9001"] + "peers": ["tcp://localhost:9001"] }, - "sources": [{"type": "net", "uri": "ftl://utu.fi/vision_default/source"}] + "sources": [{"type": "net", "uri": "ftl://utu.fi#vision_default/source"}] } }