diff --git a/CONFIG.md b/CONFIG.md index 8d5e39d0..4e341dd9 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -131,6 +131,8 @@ Refer to the hardcoded default json example as a model in case of doubt. | *asset* | **preferredPaymentCurrencies** | Ordered array of coin acronyms (ex: `["USDT", "BTC"]`)) | Coins that can be used for smart buy and sell as base payment currency. They should be ordered by decreasing priority. | | *query* | **acceptEncoding** | Comma separated list of accepted encodings (ex: `"br, gzip, deflate"`), or empty | Sets list of accepted encodings that will be passed to `curl` requests as `Accept-Encoding` header. More information [here](https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html) | | *query* | **dustAmountsThreshold** | Unordered array of monetary amounts (ex: `["1 USDT", "0.000001 BTC"]`) | For dust sweeper option, if total balance of a currency is convertible to any of these monetary amounts and if their amount is under the threshold, it will be eligible for the selling | +| *query* | **logLevels.requestsCall** | String log level for requests call ("off", "critical", "warning", "info", etc) | Specifies the log level for this exchange requests call. It prints the full public URL and the HTTP request type (GET, POST, etc) | +| *query* | **logLevels.requestsAnswer** | String log level for requests call ("off", "critical", "warning", "info", etc) | Specifies the log level for this exchange requests replies. It prints the full answer if it is in *json* type, otherwise it will be truncated to a maximum of around 10 Ki to avoid logging too much data. | | *query* | **dustSweeperMaxNbTrades** | Positive integer | Maximum number of trades performed by the automatic dust sweeper process. A high value may have a higher chance of successfully sell to 0 the wanted currency, at the cost of more trades (and fees) paid to the exchange. | | *query* | **privateAPIRate** | Duration string (ex: `500ms`) | Minimum time between two consecutive requests of private account | | *query* | **publicAPIRate** | Duration string (ex: `250ms`) | Minimum time between two consecutive requests of public account | diff --git a/src/api/common/src/exchangeprivateapi.cpp b/src/api/common/src/exchangeprivateapi.cpp index 587d3621..32fc3fb3 100644 --- a/src/api/common/src/exchangeprivateapi.cpp +++ b/src/api/common/src/exchangeprivateapi.cpp @@ -72,7 +72,7 @@ void ExchangePrivate::addBalance(BalancePortfolio &balancePortfolio, MonetaryAmo if (optConvertedAmountEquiCurrency) { equivalentInMainCurrency = *optConvertedAmountEquiCurrency; } else { - log::warn("Cannot convert {} into {} on {}", amount.currencyStr(), equiCurrency, exchangeName); + log::warn("Cannot convert {} -> {} on {}", amount.currencyStr(), equiCurrency, exchangeName); equivalentInMainCurrency = MonetaryAmount(0, equiCurrency); } log::debug("{} Balance {} (eq. {})", exchangeName, amount, equivalentInMainCurrency); diff --git a/src/api/exchanges/src/binanceprivateapi.cpp b/src/api/exchanges/src/binanceprivateapi.cpp index bfe2ef88..c94cc668 100644 --- a/src/api/exchanges/src/binanceprivateapi.cpp +++ b/src/api/exchanges/src/binanceprivateapi.cpp @@ -168,6 +168,8 @@ BinancePrivate::BinancePrivate(const CoincenterInfo& coincenterInfo, BinancePubl PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), coincenterInfo.getRunMode()), _tradableCurrenciesCache( diff --git a/src/api/exchanges/src/binancepublicapi.cpp b/src/api/exchanges/src/binancepublicapi.cpp index f7acff81..108168b6 100644 --- a/src/api/exchanges/src/binancepublicapi.cpp +++ b/src/api/exchanges/src/binancepublicapi.cpp @@ -99,6 +99,8 @@ BinancePublic::CommonInfo::CommonInfo(const CoincenterInfo& coincenterInfo, cons PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(_exchangeInfo.publicAPIRate()) .setAcceptedEncoding(_exchangeInfo.acceptEncoding()) + .setRequestCallLogLevel(_exchangeInfo.requestsCallLogLevel()) + .setRequestAnswerLogLevel(_exchangeInfo.requestsAnswerLogLevel()) .build(), runMode) {} diff --git a/src/api/exchanges/src/bithumbprivateapi.cpp b/src/api/exchanges/src/bithumbprivateapi.cpp index 08b56611..7e874200 100644 --- a/src/api/exchanges/src/bithumbprivateapi.cpp +++ b/src/api/exchanges/src/bithumbprivateapi.cpp @@ -301,6 +301,8 @@ BithumbPrivate::BithumbPrivate(const CoincenterInfo& config, BithumbPublic& bith PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _currencyOrderInfoRefreshTime(exchangeInfo().getAPICallUpdateFrequency(kCurrencyInfoBithumb)), diff --git a/src/api/exchanges/src/bithumbpublicapi.cpp b/src/api/exchanges/src/bithumbpublicapi.cpp index 95c7db97..b71732b0 100644 --- a/src/api/exchanges/src/bithumbpublicapi.cpp +++ b/src/api/exchanges/src/bithumbpublicapi.cpp @@ -73,6 +73,8 @@ BithumbPublic::BithumbPublic(const CoincenterInfo& config, FiatConverter& fiatCo PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().publicAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _tradableCurrenciesCache( diff --git a/src/api/exchanges/src/huobiprivateapi.cpp b/src/api/exchanges/src/huobiprivateapi.cpp index ff55e0ca..48c56d19 100644 --- a/src/api/exchanges/src/huobiprivateapi.cpp +++ b/src/api/exchanges/src/huobiprivateapi.cpp @@ -83,6 +83,8 @@ HuobiPrivate::HuobiPrivate(const CoincenterInfo& coincenterInfo, HuobiPublic& hu PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), coincenterInfo.getRunMode()), _accountIdCache(CachedResultOptions(std::chrono::hours(96), _cachedResultVault), _curlHandle, apiKey), diff --git a/src/api/exchanges/src/huobipublicapi.cpp b/src/api/exchanges/src/huobipublicapi.cpp index 890847c8..65fc846c 100644 --- a/src/api/exchanges/src/huobipublicapi.cpp +++ b/src/api/exchanges/src/huobipublicapi.cpp @@ -42,6 +42,8 @@ HuobiPublic::HuobiPublic(const CoincenterInfo& config, FiatConverter& fiatConver PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(_exchangeInfo.publicAPIRate()) .setAcceptedEncoding(_exchangeInfo.acceptEncoding()) + .setRequestCallLogLevel(_exchangeInfo.requestsCallLogLevel()) + .setRequestAnswerLogLevel(_exchangeInfo.requestsAnswerLogLevel()) .build(), config.getRunMode()), _healthCheckCurlHandle( diff --git a/src/api/exchanges/src/krakenprivateapi.cpp b/src/api/exchanges/src/krakenprivateapi.cpp index a69a2569..53313701 100644 --- a/src/api/exchanges/src/krakenprivateapi.cpp +++ b/src/api/exchanges/src/krakenprivateapi.cpp @@ -134,6 +134,8 @@ KrakenPrivate::KrakenPrivate(const CoincenterInfo& config, KrakenPublic& krakenP PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _depositWalletsCache( diff --git a/src/api/exchanges/src/krakenpublicapi.cpp b/src/api/exchanges/src/krakenpublicapi.cpp index 39e096c3..73d6c2c6 100644 --- a/src/api/exchanges/src/krakenpublicapi.cpp +++ b/src/api/exchanges/src/krakenpublicapi.cpp @@ -77,6 +77,8 @@ KrakenPublic::KrakenPublic(const CoincenterInfo& config, FiatConverter& fiatConv PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().publicAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _tradableCurrenciesCache( @@ -492,9 +494,9 @@ MarketOrderBook KrakenPublic::OrderBookFunc::operator()(Market mk, int count) { } KrakenPublic::TickerFunc::Last24hTradedVolumeAndLatestPricePair KrakenPublic::TickerFunc::operator()(Market mk) { - Market krakenMarket(_tradableCurrenciesCache.get().getOrThrow(mk.base()).altCode(), - _tradableCurrenciesCache.get().getOrThrow(mk.quote()).altCode()); - json result = PublicQuery(_curlHandle, "/public/Ticker", {{"pair", krakenMarket.assetsPairStrUpper()}}); + const Market krakenMarket(_tradableCurrenciesCache.get().getOrThrow(mk.base()).altCode(), + _tradableCurrenciesCache.get().getOrThrow(mk.quote()).altCode()); + const json result = PublicQuery(_curlHandle, "/public/Ticker", {{"pair", krakenMarket.assetsPairStrUpper()}}); for (const auto& [krakenAssetPair, details] : result.items()) { std::string_view last24hVol = details["v"][1].get(); std::string_view lastTickerPrice = details["c"][0].get(); diff --git a/src/api/exchanges/src/kucoinprivateapi.cpp b/src/api/exchanges/src/kucoinprivateapi.cpp index a59cd491..4989c25e 100644 --- a/src/api/exchanges/src/kucoinprivateapi.cpp +++ b/src/api/exchanges/src/kucoinprivateapi.cpp @@ -161,6 +161,8 @@ KucoinPrivate::KucoinPrivate(const CoincenterInfo& coincenterInfo, KucoinPublic& PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), coincenterInfo.getRunMode()), _depositWalletsCache( diff --git a/src/api/exchanges/src/kucoinpublicapi.cpp b/src/api/exchanges/src/kucoinpublicapi.cpp index f39db12a..eea44a13 100644 --- a/src/api/exchanges/src/kucoinpublicapi.cpp +++ b/src/api/exchanges/src/kucoinpublicapi.cpp @@ -41,6 +41,8 @@ KucoinPublic::KucoinPublic(const CoincenterInfo& config, FiatConverter& fiatConv PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().publicAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _tradableCurrenciesCache( diff --git a/src/api/exchanges/src/upbitprivateapi.cpp b/src/api/exchanges/src/upbitprivateapi.cpp index b3c88d6a..ab5bd1ef 100644 --- a/src/api/exchanges/src/upbitprivateapi.cpp +++ b/src/api/exchanges/src/upbitprivateapi.cpp @@ -109,6 +109,8 @@ UpbitPrivate::UpbitPrivate(const CoincenterInfo& config, UpbitPublic& upbitPubli PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().privateAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _tradableCurrenciesCache( diff --git a/src/api/exchanges/src/upbitpublicapi.cpp b/src/api/exchanges/src/upbitpublicapi.cpp index f370428a..d9cc05d0 100644 --- a/src/api/exchanges/src/upbitpublicapi.cpp +++ b/src/api/exchanges/src/upbitpublicapi.cpp @@ -38,6 +38,8 @@ UpbitPublic::UpbitPublic(const CoincenterInfo& config, FiatConverter& fiatConver PermanentCurlOptions::Builder() .setMinDurationBetweenQueries(exchangeInfo().publicAPIRate()) .setAcceptedEncoding(exchangeInfo().acceptEncoding()) + .setRequestCallLogLevel(exchangeInfo().requestsCallLogLevel()) + .setRequestAnswerLogLevel(exchangeInfo().requestsAnswerLogLevel()) .build(), config.getRunMode()), _marketsCache(CachedResultOptions(exchangeInfo().getAPICallUpdateFrequency(kMarkets), _cachedResultVault), diff --git a/src/engine/src/coincenterinfo_create.cpp b/src/engine/src/coincenterinfo_create.cpp index 549509cb..841fe95a 100644 --- a/src/engine/src/coincenterinfo_create.cpp +++ b/src/engine/src/coincenterinfo_create.cpp @@ -52,30 +52,33 @@ MonitoringInfo MonitoringInfo_Create(std::string_view programName, const Coincen CoincenterInfo CoincenterInfo_Create(std::string_view programName, const CoincenterCmdLineOptions &cmdLineOptions, settings::RunMode runMode) { - auto dataDir = cmdLineOptions.dataDir; + const auto dataDir = cmdLineOptions.dataDir; - json generalConfigData = LoadGeneralConfigAndOverrideOptionsFromCLI(cmdLineOptions); + const json generalConfigData = LoadGeneralConfigAndOverrideOptionsFromCLI(cmdLineOptions); - Duration fiatConversionQueryRate = ParseDuration(generalConfigData["fiatConversion"]["rate"].get()); - ApiOutputType apiOutputType = ApiOutputTypeFromString(generalConfigData["apiOutputType"].get()); + const Duration fiatConversionQueryRate = + ParseDuration(generalConfigData.at("fiatConversion").at("rate").get()); + const ApiOutputType apiOutputType = + ApiOutputTypeFromString(generalConfigData.at("apiOutputType").get()); // Create LoggingInfo first as it is a RAII structure re-initializing spdlog loggers. // It will be held by GeneralConfig and then itself by CoincenterInfo though. - LoggingInfo loggingInfo(LoggingInfo::WithLoggersCreation::kYes, dataDir, - static_cast(generalConfigData["log"])); + const auto &logConfigJsonPart = static_cast(generalConfigData.at("log")); + LoggingInfo loggingInfo(LoggingInfo::WithLoggersCreation::kYes, dataDir, logConfigJsonPart); - RequestsConfig requestsConfig(generalConfigData["requests"]["concurrency"]["nbMaxParallelRequests"].get()); + RequestsConfig requestsConfig( + generalConfigData.at("requests").at("concurrency").at("nbMaxParallelRequests").get()); GeneralConfig generalConfig(std::move(loggingInfo), std::move(requestsConfig), fiatConversionQueryRate, apiOutputType); - LoadConfiguration loadConfiguration(dataDir, LoadConfiguration::ExchangeConfigFileType::kProd); + const LoadConfiguration loadConfiguration(dataDir, LoadConfiguration::ExchangeConfigFileType::kProd); - File currencyAcronymsTranslatorFile(dataDir, File::Type::kStatic, "currencyacronymtranslator.json", - File::IfError::kThrow); - File stableCoinsFile(dataDir, File::Type::kStatic, "stablecoins.json", File::IfError::kThrow); - File currencyPrefixesTranslatorFile(dataDir, File::Type::kStatic, "currency_prefix_translator.json", - File::IfError::kThrow); + const File currencyAcronymsTranslatorFile(dataDir, File::Type::kStatic, "currencyacronymtranslator.json", + File::IfError::kThrow); + const File stableCoinsFile(dataDir, File::Type::kStatic, "stablecoins.json", File::IfError::kThrow); + const File currencyPrefixesTranslatorFile(dataDir, File::Type::kStatic, "currency_prefix_translator.json", + File::IfError::kThrow); return CoincenterInfo(runMode, loadConfiguration, std::move(generalConfig), MonitoringInfo_Create(programName, cmdLineOptions), currencyAcronymsTranslatorFile, @@ -84,7 +87,7 @@ CoincenterInfo CoincenterInfo_Create(std::string_view programName, const Coincen ExchangeSecretsInfo ExchangeSecretsInfo_Create(const CoincenterCmdLineOptions &cmdLineOptions) { if (cmdLineOptions.noSecrets) { - StringOptionParser anyParser(*cmdLineOptions.noSecrets); + const StringOptionParser anyParser(*cmdLineOptions.noSecrets); return ExchangeSecretsInfo(anyParser.getExchanges()); } diff --git a/src/http-request/include/curlhandle.hpp b/src/http-request/include/curlhandle.hpp index 4886dd69..b0fd5721 100644 --- a/src/http-request/include/curlhandle.hpp +++ b/src/http-request/include/curlhandle.hpp @@ -79,6 +79,8 @@ class CurlHandle { TimePoint _lastQueryTime{}; BestURLPicker _bestUrlPicker; string _queryData; + log::level::level_enum _requestCallLogLevel; + log::level::level_enum _requestAnswerLogLevel; }; // Simple RAII class managing global init and clean up of Curl library. diff --git a/src/http-request/include/permanentcurloptions.hpp b/src/http-request/include/permanentcurloptions.hpp index 7715aff5..6d0cd246 100644 --- a/src/http-request/include/permanentcurloptions.hpp +++ b/src/http-request/include/permanentcurloptions.hpp @@ -2,6 +2,7 @@ #include +#include "cct_log.hpp" #include "cct_string.hpp" #include "timedef.hpp" @@ -19,6 +20,9 @@ class PermanentCurlOptions { bool followLocation() const { return _followLocation; } + log::level::level_enum requestCallLogLevel() const { return _requestCallLogLevel; } + log::level::level_enum requestAnswerLogLevel() const { return _requestAnswerLogLevel; } + class Builder { public: Builder() noexcept = default; @@ -58,33 +62,51 @@ class PermanentCurlOptions { return *this; } + Builder &setRequestCallLogLevel(log::level::level_enum requestCallLogLevel) { + _requestCallLogLevel = requestCallLogLevel; + return *this; + } + + Builder &setRequestAnswerLogLevel(log::level::level_enum requestAnswerLogLevel) { + _requestAnswerLogLevel = requestAnswerLogLevel; + return *this; + } + Builder &setFollowLocation() { _followLocation = true; return *this; } PermanentCurlOptions build() { - return {std::move(_userAgent), std::move(_acceptedEncoding), _minDurationBetweenQueries, _followLocation}; + return {std::move(_userAgent), std::move(_acceptedEncoding), _minDurationBetweenQueries, + _requestCallLogLevel, _requestAnswerLogLevel, _followLocation}; } private: string _userAgent; string _acceptedEncoding; Duration _minDurationBetweenQueries{}; + log::level::level_enum _requestCallLogLevel = log::level::level_enum::info; + log::level::level_enum _requestAnswerLogLevel = log::level::level_enum::trace; bool _followLocation = false; }; private: PermanentCurlOptions(string userAgent, string acceptedEncoding, Duration minDurationBetweenQueries, + log::level::level_enum requestCallLogLevel, log::level::level_enum requestAnswerLogLevel, bool followLocation) : _userAgent(std::move(userAgent)), _acceptedEncoding(std::move(acceptedEncoding)), _minDurationBetweenQueries(minDurationBetweenQueries), + _requestCallLogLevel(requestCallLogLevel), + _requestAnswerLogLevel(requestAnswerLogLevel), _followLocation(followLocation) {} string _userAgent; string _acceptedEncoding; Duration _minDurationBetweenQueries; + log::level::level_enum _requestCallLogLevel; + log::level::level_enum _requestAnswerLogLevel; bool _followLocation; }; diff --git a/src/http-request/src/curlhandle.cpp b/src/http-request/src/curlhandle.cpp index 78e9725e..a2bd4b06 100644 --- a/src/http-request/src/curlhandle.cpp +++ b/src/http-request/src/curlhandle.cpp @@ -53,7 +53,7 @@ size_t CurlWriteCallback(const char *contents, size_t size, size_t nmemb, void * template void CurlSetLogIfError(CURL *curl, CURLoption curlOption, T value) { static_assert(std::is_integral_v || std::is_pointer_v); - CURLcode code = curl_easy_setopt(curl, curlOption, value); + const CURLcode code = curl_easy_setopt(curl, curlOption, value); if (code != CURLE_OK) { if constexpr (std::is_integral_v || std::is_same_v) { log::error("Curl error {} setting option {} to {}", static_cast(code), static_cast(curlOption), value); @@ -77,7 +77,9 @@ CurlHandle::CurlHandle(const BestURLPicker &bestURLPicker, AbstractMetricGateway const PermanentCurlOptions &permanentCurlOptions, settings::RunMode runMode) : _pMetricGateway(pMetricGateway), _minDurationBetweenQueries(permanentCurlOptions.minDurationBetweenQueries()), - _bestUrlPicker(bestURLPicker) { + _bestUrlPicker(bestURLPicker), + _requestCallLogLevel(permanentCurlOptions.requestCallLogLevel()), + _requestAnswerLogLevel(permanentCurlOptions.requestAnswerLogLevel()) { if (settings::AreQueryResponsesOverriden(runMode)) { _handle = nullptr; } else { @@ -146,17 +148,17 @@ std::string_view CurlHandle::query(std::string_view endpoint, const CurlOptions !postData.empty() && (opts.requestType() != HttpRequestType::kPost || queryResponseOverrideMode); const int8_t baseUrlPos = _bestUrlPicker.nextBaseURLPos(); - std::string_view baseUrl = _bestUrlPicker.getBaseURL(baseUrlPos); - std::string_view postDataStr = postData.str(); + const std::string_view baseUrl = _bestUrlPicker.getBaseURL(baseUrlPos); + const std::string_view postDataStr = postData.str(); string modifiedURL(baseUrl.size() + endpoint.size() + (appendParametersInQueryStr ? (1U + postDataStr.size()) : 0U), '?'); - auto it = std::ranges::copy(baseUrl, modifiedURL.begin()).out; - it = std::ranges::copy(endpoint, it).out; + auto modifiedUrlOutIt = std::ranges::copy(baseUrl, modifiedURL.begin()).out; + modifiedUrlOutIt = std::ranges::copy(endpoint, modifiedUrlOutIt).out; string jsonStr; // Declared here as its scope should be valid until the actual curl call std::string_view optsStr; if (appendParametersInQueryStr) { - it = std::ranges::copy(postDataStr, it + 1).out; + modifiedUrlOutIt = std::ranges::copy(postDataStr, modifiedUrlOutIt + 1).out; } else if (opts.isPostDataInJsonFormat() && !postData.empty()) { jsonStr = postData.toJson().dump(); optsStr = jsonStr; @@ -166,8 +168,8 @@ std::string_view CurlHandle::query(std::string_view endpoint, const CurlOptions if (queryResponseOverrideMode) { // Query response override mode - std::string_view path(modifiedURL.begin() + baseUrl.size(), modifiedURL.end()); - std::string_view response = FlatQueryResponseMap::Get(_queryData, path); + const std::string_view path(modifiedURL.begin() + baseUrl.size(), modifiedURL.end()); + const std::string_view response = FlatQueryResponseMap::Get(_queryData, path); if (response.empty()) { throw exception("No response for path '{}'", path); } @@ -230,17 +232,18 @@ std::string_view CurlHandle::query(std::string_view endpoint, const CurlOptions } } - log::info("{} {}{}{}", ToString(opts.requestType()), modifiedURL, optsStr.empty() ? "" : "?", optsStr); + log::log(_requestCallLogLevel, "{} {}{}{}", ToString(opts.requestType()), modifiedURL, optsStr.empty() ? "" : "?", + optsStr); // Actually make the query - TimePoint t1 = Clock::now(); + const TimePoint t1 = Clock::now(); const CURLcode res = curl_easy_perform(curl); // Get reply if (res != CURLE_OK) { throw exception("Unexpected response from curl: Error {}", static_cast(res)); } // Store stats - uint32_t queryRTInMs = static_cast(GetTimeFrom(t1).count()); + const uint32_t queryRTInMs = static_cast(GetTimeFrom(t1).count()); _bestUrlPicker.storeResponseTimePerBaseURL(baseUrlPos, queryRTInMs); static constexpr int kReleaseMemoryRequestsFrequency = 100; @@ -258,17 +261,15 @@ std::string_view CurlHandle::query(std::string_view endpoint, const CurlOptions static_cast(queryRTInMs)); } - if (log::get_level() <= log::level::trace) { - // Avoid polluting the logs for large response which are more likely to be HTML - const bool isJsonAnswer = _queryData.starts_with('{') || _queryData.starts_with('['); - constexpr std::size_t kMaxLenResponse = 1000; - if (!isJsonAnswer && _queryData.size() > kMaxLenResponse) { - std::string_view outPrinted(_queryData.begin(), - _queryData.begin() + std::min(_queryData.size(), kMaxLenResponse)); - log::trace("Truncated non JSON response {}...", outPrinted); - } else { - log::trace("Full{}JSON response {}", isJsonAnswer ? " " : " non ", _queryData); - } + // Avoid polluting the logs for large response which are more likely to be HTML + const bool isJsonAnswer = _queryData.starts_with('{') || _queryData.starts_with('['); + static constexpr std::size_t kMaxLenResponse = 1000; + if (!isJsonAnswer && _queryData.size() > kMaxLenResponse) { + const std::string_view outPrinted(_queryData.begin(), + _queryData.begin() + std::min(_queryData.size(), kMaxLenResponse)); + log::log(_requestAnswerLogLevel, "Truncated non JSON response {}...", outPrinted); + } else { + log::log(_requestAnswerLogLevel, "Full{}JSON response {}", isJsonAnswer ? " " : " non ", _queryData); } return _queryData; diff --git a/src/main/src/main.cpp b/src/main/src/main.cpp index d3daf7a4..742b9c79 100644 --- a/src/main/src/main.cpp +++ b/src/main/src/main.cpp @@ -1,9 +1,9 @@ #include #include #include +#include #include "cct_invalid_argument_exception.hpp" -#include "cct_log.hpp" #include "coincentercommands.hpp" #include "processcommandsfromcli.hpp" #include "runmodes.hpp" @@ -20,10 +20,10 @@ int main(int argc, const char* argv[]) { cct::settings::RunMode::kProd); } } catch (const cct::invalid_argument& e) { - cct::log::critical("Invalid argument: {}", e.what()); + std::cerr << "Invalid argument: " << e.what() << std::endl; return EXIT_FAILURE; } catch (const std::exception& e) { - cct::log::critical("{}", e.what()); + std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/src/main/src/processcommandsfromcli.cpp b/src/main/src/processcommandsfromcli.cpp index a167da56..0666e0b8 100644 --- a/src/main/src/processcommandsfromcli.cpp +++ b/src/main/src/processcommandsfromcli.cpp @@ -32,8 +32,7 @@ void ProcessCommandsFromCLI(std::string_view programName, const CoincenterComman log::debug("normal termination after {} command(s) processed", nbCommandsProcessed); } catch (const exception &e) { // Log exception here as LoggingInfo is still configured at this point (will be destroyed immediately afterwards) - log::critical("Exception: {}", e.what()); - throw e; + log::critical("{}", e.what()); } } } // namespace cct \ No newline at end of file diff --git a/src/objects/include/exchangeinfo.hpp b/src/objects/include/exchangeinfo.hpp index 282fbdb9..f9ab1ed2 100644 --- a/src/objects/include/exchangeinfo.hpp +++ b/src/objects/include/exchangeinfo.hpp @@ -3,6 +3,7 @@ #include #include "apiquerytypeenum.hpp" +#include "cct_log.hpp" #include "cct_string.hpp" #include "currencycode.hpp" #include "currencycodeset.hpp" @@ -24,7 +25,8 @@ class ExchangeInfo { CurrencyCodeVector &&excludedAllCurrencies, CurrencyCodeVector &&excludedCurrenciesWithdraw, CurrencyCodeVector &&preferredPaymentCurrencies, MonetaryAmountByCurrencySet &&dustAmountsThreshold, const APIUpdateFrequencies &apiUpdateFrequencies, Duration publicAPIRate, Duration privateAPIRate, - std::string_view acceptEncoding, int dustSweeperMaxNbTrades, bool multiTradeAllowedByDefault, + std::string_view acceptEncoding, int dustSweeperMaxNbTrades, log::level::level_enum requestsCallLogLevel, + log::level::level_enum requestsAnswerLogLevel, bool multiTradeAllowedByDefault, bool validateDepositAddressesInFile, bool placeSimulateRealOrder, bool validateApiKey); /// Get a reference to the list of statically excluded currency codes to consider for the exchange, @@ -45,6 +47,12 @@ class ExchangeInfo { /// at the cost of more fees paid to the exchange. int dustSweeperMaxNbTrades() const { return _dustSweeperMaxNbTrades; } + // Log level for request calls + log::level::level_enum requestsCallLogLevel() const { return LevelFromPos(_requestsCallLogLevel); } + + // Log level for requests replies, should it be json, or any other type + log::level::level_enum requestsAnswerLogLevel() const { return LevelFromPos(_requestsAnswerLogLevel); } + /// Apply the general maker fee defined for this exchange on given MonetaryAmount. /// In other words, convert a gross amount into a net amount with maker fees MonetaryAmount applyFee(MonetaryAmount mk, FeeType feeType) const { @@ -99,6 +107,8 @@ class ExchangeInfo { MonetaryAmount _generalMakerRatio; MonetaryAmount _generalTakerRatio; int16_t _dustSweeperMaxNbTrades; // max number of trades of a dust sweeper attempt per currency + int8_t _requestsCallLogLevel; + int8_t _requestsAnswerLogLevel; bool _multiTradeAllowedByDefault; bool _validateDepositAddressesInFile; bool _placeSimulateRealOrder; diff --git a/src/objects/include/logginginfo.hpp b/src/objects/include/logginginfo.hpp index c3480b7d..f5777160 100644 --- a/src/objects/include/logginginfo.hpp +++ b/src/objects/include/logginginfo.hpp @@ -53,13 +53,6 @@ class LoggingInfo { static void CreateOutputLogger(); - static constexpr int8_t PosFromLevel(log::level::level_enum level) { - return static_cast(log::level::off) - static_cast(level); - } - static constexpr log::level::level_enum LevelFromPos(int8_t levelPos) { - return static_cast(static_cast(log::level::off) - levelPos); - } - using TrackedCommandTypes = FlatSet; std::string_view _dataDir = kDefaultDataDir; diff --git a/src/objects/src/exchangeinfo.cpp b/src/objects/src/exchangeinfo.cpp index 25b688b3..46bc1047 100644 --- a/src/objects/src/exchangeinfo.cpp +++ b/src/objects/src/exchangeinfo.cpp @@ -8,6 +8,7 @@ #include "apiquerytypeenum.hpp" #include "cct_const.hpp" #include "cct_exception.hpp" +#include "cct_invalid_argument_exception.hpp" #include "cct_log.hpp" #include "cct_string.hpp" #include "currencycodevector.hpp" @@ -45,6 +46,16 @@ string BuildUpdateFrequenciesString(const ExchangeInfo::APIUpdateFrequencies &ap ret.push_back(']'); return ret; } + +auto DustSweeperMaxNbTrades(int dustSweeperMaxNbTrades) { + if (dustSweeperMaxNbTrades > static_cast(std::numeric_limits::max())) { + throw invalid_argument("{} is too large"); + } + if (dustSweeperMaxNbTrades < 0) { + throw invalid_argument("{} should be positive"); + } + return static_cast(dustSweeperMaxNbTrades); +} } // namespace ExchangeInfo::ExchangeInfo(std::string_view exchangeNameStr, std::string_view makerStr, std::string_view takerStr, @@ -53,6 +64,7 @@ ExchangeInfo::ExchangeInfo(std::string_view exchangeNameStr, std::string_view ma MonetaryAmountByCurrencySet &&dustAmountsThreshold, const APIUpdateFrequencies &apiUpdateFrequencies, Duration publicAPIRate, Duration privateAPIRate, std::string_view acceptEncoding, int dustSweeperMaxNbTrades, + log::level::level_enum requestsCallLogLevel, log::level::level_enum requestsAnswerLogLevel, bool multiTradeAllowedByDefault, bool validateDepositAddressesInFile, bool placeSimulateRealOrder, bool validateApiKey) : _excludedCurrenciesAll(std::move(excludedAllCurrencies)), @@ -65,7 +77,9 @@ ExchangeInfo::ExchangeInfo(std::string_view exchangeNameStr, std::string_view ma _acceptEncoding(acceptEncoding), _generalMakerRatio((MonetaryAmount(100) - MonetaryAmount(makerStr)) / 100), _generalTakerRatio((MonetaryAmount(100) - MonetaryAmount(takerStr)) / 100), - _dustSweeperMaxNbTrades(dustSweeperMaxNbTrades), + _dustSweeperMaxNbTrades(DustSweeperMaxNbTrades(dustSweeperMaxNbTrades)), + _requestsCallLogLevel(PosFromLevel(requestsCallLogLevel)), + _requestsAnswerLogLevel(PosFromLevel(requestsAnswerLogLevel)), _multiTradeAllowedByDefault(multiTradeAllowedByDefault), _validateDepositAddressesInFile(validateDepositAddressesInFile), _placeSimulateRealOrder(placeSimulateRealOrder), @@ -82,6 +96,9 @@ ExchangeInfo::ExchangeInfo(std::string_view exchangeNameStr, std::string_view ma log::trace(" - Preferred payment currencies : {}", BuildConcatenatedString(_preferredPaymentCurrencies)); log::trace(" - Dust amounts threshold : {}", BuildConcatenatedString(_dustAmountsThreshold)); log::trace(" - Dust sweeper nb max trades : {}", _dustSweeperMaxNbTrades); + log::trace(" - Requests call log level : {}", log::level::to_string_view(LevelFromPos(_requestsCallLogLevel))); + log::trace(" - Requests answer log level : {}", + log::level::to_string_view(LevelFromPos(_requestsAnswerLogLevel))); log::trace(" - General update frequencies : {} for public, {} for private", DurationToString(publicAPIRate), DurationToString(privateAPIRate)); log::trace(" - Accept encoding : {}", _acceptEncoding); diff --git a/src/objects/src/exchangeinfodefault.hpp b/src/objects/src/exchangeinfodefault.hpp index 6b98f83c..bdde87cd 100644 --- a/src/objects/src/exchangeinfodefault.hpp +++ b/src/objects/src/exchangeinfodefault.hpp @@ -52,6 +52,10 @@ struct ExchangeInfoDefault { "0.000001 BTC" ], "dustSweeperMaxNbTrades": 7, + "logLevels": { + "requestsCall": "info", + "requestsAnswer": "trace" + }, "multiTradeAllowedByDefault": false, "placeSimulateRealOrder": false, "updateFrequency": { @@ -166,6 +170,10 @@ struct ExchangeInfoDefault { "0.5 XRP" ], "dustSweeperMaxNbTrades": 5, + "logLevels": { + "requestsCall": "info", + "requestsAnswer": "trace" + }, "multiTradeAllowedByDefault": true, "privateAPIRate": "1055ms", "publicAPIRate": "1236ms", diff --git a/src/objects/src/exchangeinfomap.cpp b/src/objects/src/exchangeinfomap.cpp index 53ab03fb..6c3d2fe7 100644 --- a/src/objects/src/exchangeinfomap.cpp +++ b/src/objects/src/exchangeinfomap.cpp @@ -3,9 +3,11 @@ #include #include "cct_const.hpp" +#include "cct_log.hpp" #include "durationstring.hpp" #include "exchangeinfodefault.hpp" #include "exchangeinfoparser.hpp" +#include "parseloglevel.hpp" namespace cct { @@ -41,25 +43,30 @@ ExchangeInfoMap ComputeExchangeInfoMap(std::string_view fileName, const json &js queryTopLevelOption.getDuration(exchangeName, kUpdFreqOptStr, "depositWallet"), queryTopLevelOption.getDuration(exchangeName, kUpdFreqOptStr, "currencyInfo")}}; - bool multiTradeAllowedByDefault = queryTopLevelOption.getBool(exchangeName, "multiTradeAllowedByDefault"); - bool validateDepositAddressesInFile = + const bool multiTradeAllowedByDefault = queryTopLevelOption.getBool(exchangeName, "multiTradeAllowedByDefault"); + const bool validateDepositAddressesInFile = withdrawTopLevelOption.getBool(exchangeName, "validateDepositAddressesInFile"); - bool placeSimulatedRealOrder = queryTopLevelOption.getBool(exchangeName, "placeSimulateRealOrder"); - bool validateApiKey = queryTopLevelOption.getBool(exchangeName, "validateApiKey"); + const bool placeSimulatedRealOrder = queryTopLevelOption.getBool(exchangeName, "placeSimulateRealOrder"); + const bool validateApiKey = queryTopLevelOption.getBool(exchangeName, "validateApiKey"); MonetaryAmountByCurrencySet dustAmountsThresholds( queryTopLevelOption.getMonetaryAmountsArray(exchangeName, "dustAmountsThreshold")); - int dustSweeperMaxNbTrades = queryTopLevelOption.getInt(exchangeName, "dustSweeperMaxNbTrades"); + const int dustSweeperMaxNbTrades = queryTopLevelOption.getInt(exchangeName, "dustSweeperMaxNbTrades"); + + const log::level::level_enum requestsCallLogLevel = + LevelFromPos(LogPosFromLogStr(queryTopLevelOption.getStr(exchangeName, "logLevels", "requestsCall"))); + const log::level::level_enum requestsAnswerLogLevel = + LevelFromPos(LogPosFromLogStr(queryTopLevelOption.getStr(exchangeName, "logLevels", "requestsAnswer"))); map.insert_or_assign( exchangeName, - ExchangeInfo(exchangeName, makerStr, takerStr, - assetTopLevelOption.getUnorderedCurrencyUnion(exchangeName, "allExclude"), - assetTopLevelOption.getUnorderedCurrencyUnion(exchangeName, "withdrawExclude"), - assetTopLevelOption.getCurrenciesArray(exchangeName, kPreferredPaymentCurrenciesOptName), - std::move(dustAmountsThresholds), std::move(apiUpdateFrequencies), publicAPIRate, privateAPIRate, - acceptEncoding, dustSweeperMaxNbTrades, multiTradeAllowedByDefault, validateDepositAddressesInFile, - placeSimulatedRealOrder, validateApiKey)); + ExchangeInfo( + exchangeName, makerStr, takerStr, assetTopLevelOption.getUnorderedCurrencyUnion(exchangeName, "allExclude"), + assetTopLevelOption.getUnorderedCurrencyUnion(exchangeName, "withdrawExclude"), + assetTopLevelOption.getCurrenciesArray(exchangeName, kPreferredPaymentCurrenciesOptName), + std::move(dustAmountsThresholds), std::move(apiUpdateFrequencies), publicAPIRate, privateAPIRate, + acceptEncoding, dustSweeperMaxNbTrades, requestsCallLogLevel, requestsAnswerLogLevel, + multiTradeAllowedByDefault, validateDepositAddressesInFile, placeSimulatedRealOrder, validateApiKey)); } // namespace cct // Print json unused values diff --git a/src/objects/src/logginginfo.cpp b/src/objects/src/logginginfo.cpp index 7d533cd1..877e13f7 100644 --- a/src/objects/src/logginginfo.cpp +++ b/src/objects/src/logginginfo.cpp @@ -8,54 +8,13 @@ #include #include "cct_const.hpp" -#include "cct_exception.hpp" #include "cct_fixedcapacityvector.hpp" +#include "parseloglevel.hpp" #include "timestring.hpp" -#ifndef CCT_MSVC -#include "static_string_view_helpers.hpp" -#endif #include "unitsparser.hpp" namespace cct { -namespace { -constexpr int8_t kMaxLogLevel = 6; - -int8_t LogPosFromLogStr(std::string_view logStr) { - if (logStr.size() == 1) { - int8_t logLevelPos = logStr.front() - '0'; - if (logLevelPos < 0 || logLevelPos > kMaxLogLevel) { - throw exception("Unrecognized log level {}. Possible values are 0-{}", logStr, '0' + kMaxLogLevel); - } - return logLevelPos; - } - if (logStr == "off") { - return 0; - } - if (logStr == "critical") { - return 1; - } - if (logStr == "error") { - return 2; - } - if (logStr == "warning") { - return 3; - } - if (logStr == "info") { - return 4; - } - if (logStr == "debug") { - return 5; - } - if (logStr == "trace") { - return 6; - } - throw exception("Unrecognized log level name {}. Possible values are off|critical|error|warning|info|debug|trace", - logStr); -} - -} // namespace - LoggingInfo::LoggingInfo(WithLoggersCreation withLoggersCreation, std::string_view dataDir) : _dataDir(dataDir) { if (withLoggersCreation == WithLoggersCreation::kYes) { createLoggers(); diff --git a/src/tech/CMakeLists.txt b/src/tech/CMakeLists.txt index a01a0b9e..e09e20df 100644 --- a/src/tech/CMakeLists.txt +++ b/src/tech/CMakeLists.txt @@ -64,7 +64,7 @@ add_unit_test( -if(WIN32) +if(MSVC) add_unit_test( gethostname_test src/gethostname.cpp @@ -90,6 +90,14 @@ add_unit_test( test/mathhelpers_test.cpp ) +add_unit_test( + parseloglevel_test + src/parseloglevel.cpp + test/parseloglevel_test.cpp + DEFINITIONS + CCT_DISABLE_SPDLOG +) + add_unit_test( simpletable_test src/simpletable.cpp diff --git a/src/tech/include/cct_log.hpp b/src/tech/include/cct_log.hpp index 38dbc039..3db17dc6 100644 --- a/src/tech/include/cct_log.hpp +++ b/src/tech/include/cct_log.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #ifdef CCT_DISABLE_SPDLOG #include #else @@ -9,29 +11,36 @@ namespace cct { #ifdef CCT_DISABLE_SPDLOG namespace log { -inline void critical(std::string_view, ...) {} -inline void error(std::string_view, ...) {} -inline void warn(std::string_view, ...) {} -inline void info(std::string_view, ...) {} -inline void debug(std::string_view, ...) {} -inline void trace(std::string_view, ...) {} - -inline int get_level() { return 0; } +constexpr void critical(std::string_view, ...) {} +constexpr void error(std::string_view, ...) {} +constexpr void warn(std::string_view, ...) {} +constexpr void info(std::string_view, ...) {} +constexpr void debug(std::string_view, ...) {} +constexpr void trace(std::string_view, ...) {} struct level { using level_enum = int; - static constexpr int trace = 6; - static constexpr int debug = 5; - static constexpr int info = 4; + static constexpr int trace = 0; + static constexpr int debug = 1; + static constexpr int info = 2; static constexpr int warn = 3; - static constexpr int error = 2; - static constexpr int critical = 1; - static constexpr int off = 0; + static constexpr int error = 4; + static constexpr int critical = 5; + static constexpr int off = 6; }; +constexpr int get_level() { return static_cast(level::off); } + } // namespace log #else namespace log = spdlog; #endif + +constexpr int8_t PosFromLevel(log::level::level_enum level) { + return static_cast(log::level::off) - static_cast(level); +} +constexpr log::level::level_enum LevelFromPos(int8_t levelPos) { + return static_cast(static_cast(log::level::off) - levelPos); +} } // namespace cct \ No newline at end of file diff --git a/src/tech/include/parseloglevel.hpp b/src/tech/include/parseloglevel.hpp new file mode 100644 index 00000000..fd40eeef --- /dev/null +++ b/src/tech/include/parseloglevel.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace cct { +int8_t LogPosFromLogStr(std::string_view logStr); +} \ No newline at end of file diff --git a/src/tech/src/parseloglevel.cpp b/src/tech/src/parseloglevel.cpp new file mode 100644 index 00000000..03cbec63 --- /dev/null +++ b/src/tech/src/parseloglevel.cpp @@ -0,0 +1,39 @@ +#include "parseloglevel.hpp" + +#include "cct_exception.hpp" + +namespace cct { +int8_t LogPosFromLogStr(std::string_view logStr) { + if (logStr.size() == 1) { + static constexpr int8_t kMaxLogLevel = 6; + int8_t logLevelPos = logStr.front() - '0'; + if (logLevelPos < 0 || logLevelPos > kMaxLogLevel) { + throw exception("Unrecognized log level {}. Possible values are 0-{}", logStr, '0' + kMaxLogLevel); + } + return logLevelPos; + } + if (logStr == "off") { + return 0; + } + if (logStr == "critical") { + return 1; + } + if (logStr == "error") { + return 2; + } + if (logStr == "warning") { + return 3; + } + if (logStr == "info") { + return 4; + } + if (logStr == "debug") { + return 5; + } + if (logStr == "trace") { + return 6; + } + throw exception("Unrecognized log level name {}. Possible values are off|critical|error|warning|info|debug|trace", + logStr); +} +} // namespace cct \ No newline at end of file diff --git a/src/tech/test/parseloglevel_test.cpp b/src/tech/test/parseloglevel_test.cpp new file mode 100644 index 00000000..22dcb46a --- /dev/null +++ b/src/tech/test/parseloglevel_test.cpp @@ -0,0 +1,15 @@ +#include "parseloglevel.hpp" + +#include + +#include "cct_exception.hpp" + +namespace cct { +TEST(ParseLogLevel, InvalidLogName) { EXPECT_THROW(LogPosFromLogStr("invalid"), exception); } + +TEST(ParseLogLevel, ValidLogName) { + EXPECT_EQ(LogPosFromLogStr("off"), 0); + EXPECT_EQ(LogPosFromLogStr("critical"), 1); + EXPECT_EQ(LogPosFromLogStr("trace"), 6); +} +} // namespace cct