diff --git a/CHANGELOG.md b/CHANGELOG.md index 4208fea..4e76a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,25 @@ # Changelog +## 0.38.2 - 2025-07-01 + +### Enhancements +- Added `LogPlatformInfo()` function which logs the client version, compiler, and + operating system info to aid troubleshooting +- Added compiler and operating system info to the user agent to aid troubleshooting +- Standardized `client` info sent by live clients to match historical +- Added methods to the client builders to extend the user agents with a custom string + +### Bug fixes +- Fixed missing implementation for `LiveBuilder::SetBufferSize()` +- Fixed checking of warnings from server in Historical API in `TimeseriesGetRange()` and + `TimeseriesGetRangeToFile()` ## 0.38.1 - 2025-06-25 ### Enhancements - Added `range_by_schema` field to `DatasetRange` struct -- Changed historical `TimeseriesGetRange` and `TimeseriesGetRangeToFile` methods to use - a `POST` request to allow for requesting supported maximum of 2000 symbols +- Changed historical `TimeseriesGetRange()` and `TimeseriesGetRangeToFile()` methods to + use a `POST` request to allow for requesting supported maximum of 2000 symbols - Added logging around `Historical::BatchDownload` - Changed the following Venue, Publisher, and Dataset descriptions: - "ICE Futures Europe (Financials)" renamed to "ICE Europe Financials" diff --git a/CMakeLists.txt b/CMakeLists.txt index a9b1264..623d28a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24..4.0) project( databento - VERSION 0.38.1 + VERSION 0.38.2 LANGUAGES CXX DESCRIPTION "Official Databento client library" ) @@ -60,7 +60,7 @@ if(IS_BIG_ENDIAN) endif() # -# Add version header +# Add templated headers # configure_file( @@ -68,6 +68,11 @@ configure_file( include/${PROJECT_NAME}/version.hpp @ONLY ) +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/system.hpp.in + include/${PROJECT_NAME}/system.hpp + @ONLY +) # # Find all headers and implementation files @@ -237,7 +242,7 @@ target_include_directories( PUBLIC $ $ - $ # for generated version.hpp + $ # for generated version.hpp and system.hpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ) @@ -310,6 +315,7 @@ install( ) install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME}/system.hpp ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME}/version.hpp DESTINATION ${INCLUDE_INSTALL_DIR} diff --git a/cmake/system.hpp.in b/cmake/system.hpp.in new file mode 100644 index 0000000..f7f3a51 --- /dev/null +++ b/cmake/system.hpp.in @@ -0,0 +1,6 @@ +#pragma once + +#define DATABENTO_CXX_COMPILER_ID "@CMAKE_CXX_COMPILER_ID@" +#define DATABENTO_CXX_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@" +#define DATABENTO_SYSTEM_ID "@CMAKE_SYSTEM_NAME@" +#define DATABENTO_SYSTEM_VERSION "@CMAKE_SYSTEM_VERSION@" diff --git a/cmake/version.hpp.in b/cmake/version.hpp.in index 67973db..dee1519 100644 --- a/cmake/version.hpp.in +++ b/cmake/version.hpp.in @@ -1,10 +1,7 @@ -#ifndef @PROJECT_NAME_UPPERCASE@_VERSION_H_ -#define @PROJECT_NAME_UPPERCASE@_VERSION_H_ +#pragma once -#define @PROJECT_NAME_UPPERCASE@_VERSION "@PROJECT_VERSION@" +#define DATABENTO_VERSION "@PROJECT_VERSION@" -#define @PROJECT_NAME_UPPERCASE@_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ -#define @PROJECT_NAME_UPPERCASE@_MINOR_VERSION @PROJECT_VERSION_MINOR@ -#define @PROJECT_NAME_UPPERCASE@_PATCH_VERSION @PROJECT_VERSION_PATCH@ - -#endif // @PROJECT_NAME_UPPERCASE@_VERSION_H_ +#define DATABENTO_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ +#define DATABENTO_MINOR_VERSION @PROJECT_VERSION_MINOR@ +#define DATABENTO_PATCH_VERSION @PROJECT_VERSION_PATCH@ diff --git a/examples/historical/timeseries_get_range.cpp b/examples/historical/timeseries_get_range.cpp index ea3c2f4..d94aa86 100644 --- a/examples/historical/timeseries_get_range.cpp +++ b/examples/historical/timeseries_get_range.cpp @@ -1,4 +1,4 @@ -#include // setw +#include #include "databento/constants.hpp" #include "databento/enums.hpp" diff --git a/include/databento/constants.hpp b/include/databento/constants.hpp index 2043503..5770ca1 100644 --- a/include/databento/constants.hpp +++ b/include/databento/constants.hpp @@ -3,6 +3,9 @@ #include #include +#include "databento/system.hpp" // DATABENTO_CXX_COMPILER_*, DATABENTO_SYSTEM_* +#include "databento/version.hpp" // DATABENTO_VERSION + namespace databento { static constexpr auto kApiVersion = 0; static constexpr auto kApiVersionStr = "0"; @@ -29,6 +32,11 @@ static constexpr auto kAssetCstrLen = 11; // The multiplier for converting the `length` field in `RecordHeader` to bytes. static constexpr std::size_t kRecordHeaderLengthMultiplier = 4; +static constexpr auto kUserAgent = + "Databento/" DATABENTO_VERSION " C++/" DATABENTO_CXX_COMPILER_ID + "/" DATABENTO_CXX_COMPILER_VERSION " " DATABENTO_SYSTEM_ID + "/" DATABENTO_SYSTEM_VERSION; + // This is not a comprehensive list of datasets, for that see the `Dataset` // enum. namespace dataset { diff --git a/include/databento/detail/dbn_buffer_decoder.hpp b/include/databento/detail/dbn_buffer_decoder.hpp index 204ef72..e86c7a5 100644 --- a/include/databento/detail/dbn_buffer_decoder.hpp +++ b/include/databento/detail/dbn_buffer_decoder.hpp @@ -7,6 +7,7 @@ #include "databento/detail/buffer.hpp" #include "databento/detail/zstd_stream.hpp" +#include "databento/enums.hpp" #include "databento/record.hpp" #include "databento/timeseries.hpp" @@ -14,9 +15,11 @@ namespace databento::detail { class DbnBufferDecoder { public: // The instance cannot outlive the lifetime of these references. - DbnBufferDecoder(const MetadataCallback& metadata_callback, + DbnBufferDecoder(VersionUpgradePolicy upgrade_policy, + const MetadataCallback& metadata_callback, const RecordCallback& record_callback) - : metadata_callback_{metadata_callback}, + : upgrade_policy_{upgrade_policy}, + metadata_callback_{metadata_callback}, record_callback_{record_callback}, zstd_stream_{std::make_unique()}, zstd_buffer_{static_cast(zstd_stream_.Input())} {} @@ -49,6 +52,7 @@ class DbnBufferDecoder { return stream; } + const VersionUpgradePolicy upgrade_policy_; const MetadataCallback& metadata_callback_; const RecordCallback& record_callback_; ZstdDecodeStream zstd_stream_; diff --git a/include/databento/detail/http_client.hpp b/include/databento/detail/http_client.hpp index 2ac109e..fc8c3f5 100644 --- a/include/databento/detail/http_client.hpp +++ b/include/databento/detail/http_client.hpp @@ -33,11 +33,11 @@ class HttpClient { private: static bool IsErrorStatus(int status_code); - static httplib::ResponseHandler MakeStreamResponseHandler(int& out_status); static void CheckStatusAndStreamRes(const std::string& path, int status_code, std::string&& err_body, const httplib::Result& res); + httplib::ResponseHandler MakeStreamResponseHandler(int& out_status); nlohmann::json CheckAndParseResponse(const std::string& path, httplib::Result&& res) const; void CheckWarnings(const httplib::Response& response) const; diff --git a/include/databento/historical.hpp b/include/databento/historical.hpp index 8ca6720..82e07d0 100644 --- a/include/databento/historical.hpp +++ b/include/databento/historical.hpp @@ -10,22 +10,22 @@ #include "databento/datetime.hpp" // DateRange, DateTimeRange, UnixNanos #include "databento/dbn_file_store.hpp" #include "databento/detail/http_client.hpp" // HttpClient -#include "databento/enums.hpp" // BatchState, Delivery, DurationInterval, Schema, SType +#include "databento/enums.hpp" // BatchState, Delivery, DurationInterval, Schema, SType, VersionUpgradePolicy #include "databento/metadata.hpp" // DatasetConditionDetail, DatasetRange, FieldDetail, PublisherDetail, UnitPricesForMode #include "databento/symbology.hpp" // SymbologyResolution #include "databento/timeseries.hpp" // KeepGoing, MetadataCallback, RecordCallback namespace databento { +// Forward declarations +class HistoricalBuilder; class ILogReceiver; // A client for interfacing with Databento's historical market data API. class Historical { public: + // WARNING: Will be deprecated in the future in favor of the builder Historical(ILogReceiver* log_receiver, std::string key, HistoricalGateway gateway); - // Primarily for unit tests - Historical(ILogReceiver* log_receiver, std::string key, std::string gateway, - std::uint16_t port); /* * Getters @@ -227,8 +227,17 @@ class Historical { const std::filesystem::path& file_path); private: + friend HistoricalBuilder; + using HttplibParams = std::multimap; + Historical(ILogReceiver* log_receiver, std::string key, + HistoricalGateway gateway, VersionUpgradePolicy upgrade_policy, + std::string user_agent_ext); + Historical(ILogReceiver* log_receiver, std::string key, std::string gateway, + std::uint16_t port, VersionUpgradePolicy upgrade_policy, + std::string user_agent_ext); + BatchJob BatchSubmitJob(const HttplibParams& params); void DownloadFile(const std::string& url, const std::filesystem::path& output_path); @@ -247,6 +256,8 @@ class Historical { ILogReceiver* log_receiver_; const std::string key_; const std::string gateway_; + const std::string user_agent_ext_; + const VersionUpgradePolicy upgrade_policy_; detail::HttpClient client_; }; @@ -255,22 +266,43 @@ class HistoricalBuilder { public: HistoricalBuilder() = default; + /* + * Required setters + */ + // Sets `key_` based on the environment variable DATABENTO_API_KEY. // // NOTE: This is not thread-safe if `std::setenv` is used elsewhere in the // program. HistoricalBuilder& SetKeyFromEnv(); HistoricalBuilder& SetKey(std::string key); - HistoricalBuilder& SetGateway(HistoricalGateway gateway); + + /* + * Optional setters + */ + + // Set the version upgrade policy for when streaming DBN data from a prior + // version. Defaults to upgrading to DBNv3 (if not already). + HistoricalBuilder& SetUpgradePolicy(VersionUpgradePolicy upgrade_policy); // Sets the receiver of the logs to be used by the client. HistoricalBuilder& SetLogReceiver(ILogReceiver* log_receiver); + HistoricalBuilder& SetGateway(HistoricalGateway gateway); + // Overrides the gateway and port. This is an advanced method. + HistoricalBuilder& SetAddress(std::string gateway, std::uint16_t port); + // Appends to the default user agent. + HistoricalBuilder& ExtendUserAgent(std::string extension); + // Attempts to construct an instance of Historical or throws an exception if // no key has been set. Historical Build(); private: ILogReceiver* log_receiver_{}; - std::string key_; HistoricalGateway gateway_{HistoricalGateway::Bo1}; + std::string gateway_override_{}; + std::uint16_t port_{}; + std::string key_; + VersionUpgradePolicy upgrade_policy_{VersionUpgradePolicy::UpgradeToV3}; + std::string user_agent_ext_; }; } // namespace databento diff --git a/include/databento/live.hpp b/include/databento/live.hpp index 9496fd5..000b75a 100644 --- a/include/databento/live.hpp +++ b/include/databento/live.hpp @@ -20,7 +20,7 @@ class LiveBuilder { LiveBuilder(); /* - * Required settters + * Required setters */ // Sets `key_` based on the environment variable DATABENTO_API_KEY. @@ -33,13 +33,13 @@ class LiveBuilder { LiveBuilder& SetDataset(std::string dataset); /* - * Optional settters + * Optional setters */ // Whether to append the gateway send timestamp after each DBN message. LiveBuilder& SetSendTsOut(bool send_ts_out); // Set the version upgrade policy for when receiving DBN data from a prior - // version. Defaults to upgrading to DBNv2 (if not already). + // version. Defaults to upgrading to DBNv3 (if not already). LiveBuilder& SetUpgradePolicy(VersionUpgradePolicy upgrade_policy); // Sets the receiver of the logs to be used by the client. LiveBuilder& SetLogReceiver(ILogReceiver* log_receiver); @@ -49,13 +49,15 @@ class LiveBuilder { LiveBuilder& SetAddress(std::string gateway, std::uint16_t port); // Overrides the size of the buffer used for reading data from the TCP socket. LiveBuilder& SetBufferSize(std::size_t size); + // Appends to the default user agent. + LiveBuilder& ExtendUserAgent(std::string extension); /* * Build a live client instance */ - // Attempts to construct an instance of a blocking live client or throws an - // exception. + // Attempts to construct an instance of a blocking live client or throws + // an exception. LiveBlocking BuildBlocking(); // Attempts to construct an instance of a threaded live client or throws an // exception. @@ -74,5 +76,6 @@ class LiveBuilder { VersionUpgradePolicy upgrade_policy_{VersionUpgradePolicy::UpgradeToV3}; std::optional heartbeat_interval_{}; std::size_t buffer_size_; + std::string user_agent_ext_; }; } // namespace databento diff --git a/include/databento/live_blocking.hpp b/include/databento/live_blocking.hpp index 2ec5517..38a7d3b 100644 --- a/include/databento/live_blocking.hpp +++ b/include/databento/live_blocking.hpp @@ -96,12 +96,12 @@ class LiveBlocking { LiveBlocking(ILogReceiver* log_receiver, std::string key, std::string dataset, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size); + std::size_t buffer_size, std::string user_agent_ext); LiveBlocking(ILogReceiver* log_receiver, std::string key, std::string dataset, std::string gateway, std::uint16_t port, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size); + std::size_t buffer_size, std::string user_agent_ext); std::string DetermineGateway() const; std::uint64_t Authenticate(); @@ -118,14 +118,15 @@ class LiveBlocking { static constexpr std::size_t kMaxStrLen = 24L * 1024; ILogReceiver* log_receiver_; - std::string key_; - std::string dataset_; - std::string gateway_; - std::uint16_t port_; - bool send_ts_out_; + const std::string key_; + const std::string dataset_; + const std::string gateway_; + const std::string user_agent_ext_; + const std::uint16_t port_; + const bool send_ts_out_; std::uint8_t version_{}; - VersionUpgradePolicy upgrade_policy_; - std::optional heartbeat_interval_; + const VersionUpgradePolicy upgrade_policy_; + const std::optional heartbeat_interval_; detail::TcpClient client_; std::uint32_t sub_counter_{}; std::vector subscriptions_; diff --git a/include/databento/live_threaded.hpp b/include/databento/live_threaded.hpp index 05e9bba..688e0c3 100644 --- a/include/databento/live_threaded.hpp +++ b/include/databento/live_threaded.hpp @@ -109,12 +109,12 @@ class LiveThreaded { LiveThreaded(ILogReceiver* log_receiver, std::string key, std::string dataset, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size); + std::size_t buffer_size, std::string user_agent_ext); LiveThreaded(ILogReceiver* log_receiver, std::string key, std::string dataset, std::string gateway, std::uint16_t port, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size); + std::size_t buffer_size, std::string user_agent_ext); // unique_ptr to be movable std::unique_ptr impl_; diff --git a/include/databento/log.hpp b/include/databento/log.hpp index 1398c3a..740bb11 100644 --- a/include/databento/log.hpp +++ b/include/databento/log.hpp @@ -47,4 +47,7 @@ class ConsoleLogReceiver : public ILogReceiver { std::ostream& operator<<(std::ostream& out, LogLevel level); const char* ToString(LogLevel level); + +void LogPlatformInfo(); +void LogPlatformInfo(ILogReceiver* log_receiver); } // namespace databento diff --git a/pkg/PKGBUILD b/pkg/PKGBUILD index 013976e..b783070 100644 --- a/pkg/PKGBUILD +++ b/pkg/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Databento _pkgname=databento-cpp pkgname=databento-cpp-git -pkgver=0.38.1 +pkgver=0.38.2 pkgrel=1 pkgdesc="Official C++ client for Databento" arch=('any') diff --git a/src/detail/dbn_buffer_decoder.cpp b/src/detail/dbn_buffer_decoder.cpp index 88b669c..55afbf0 100644 --- a/src/detail/dbn_buffer_decoder.cpp +++ b/src/detail/dbn_buffer_decoder.cpp @@ -9,8 +9,6 @@ using databento::detail::DbnBufferDecoder; databento::KeepGoing DbnBufferDecoder::Process(const char* data, std::size_t length) { - constexpr auto kUpgradePolicy = VersionUpgradePolicy::UpgradeToV3; - zstd_buffer_->WriteAll(data, length); while (true) { const auto read_size = zstd_stream_.ReadSome(dbn_buffer_.WriteBegin(), @@ -43,7 +41,7 @@ databento::KeepGoing DbnBufferDecoder::Process(const char* data, // alignment dbn_buffer_.Shift(); ts_out_ = metadata.ts_out; - metadata.Upgrade(kUpgradePolicy); + metadata.Upgrade(upgrade_policy_); if (metadata_callback_) { metadata_callback_(std::move(metadata)); } @@ -58,8 +56,9 @@ databento::KeepGoing DbnBufferDecoder::Process(const char* data, if (dbn_buffer_.ReadCapacity() < bytes_needed_) { break; } - record = DbnDecoder::DecodeRecordCompat( - input_version_, kUpgradePolicy, ts_out_, &compat_buffer_, record); + record = + DbnDecoder::DecodeRecordCompat(input_version_, upgrade_policy_, + ts_out_, &compat_buffer_, record); if (record_callback_(record) == KeepGoing::Stop) { return KeepGoing::Stop; } diff --git a/src/detail/http_client.cpp b/src/detail/http_client.cpp index ba63f5d..abe81e4 100644 --- a/src/detail/http_client.cpp +++ b/src/detail/http_client.cpp @@ -3,16 +3,16 @@ #include // seconds #include // ostringstream +#include "databento/constants.hpp" // kUserAgent #include "databento/exceptions.hpp" // HttpResponseError, HttpRequestError, JsonResponseError -#include "databento/log.hpp" // ILogReceiver, LogLevel -#include "databento/version.hpp" // DATABENTO_VERSION +#include "databento/log.hpp" // ILogReceiver, LogLevel using databento::detail::HttpClient; constexpr std::chrono::seconds kTimeout{100}; const httplib::Headers HttpClient::kHeaders{ {"accept", "application/json"}, - {"user-agent", "Databento/" DATABENTO_VERSION " C++"}, + {"user-agent", kUserAgent}, }; HttpClient::HttpClient(databento::ILogReceiver* log_receiver, @@ -97,10 +97,11 @@ void HttpClient::PostRawStream(const std::string& path, httplib::ResponseHandler HttpClient::MakeStreamResponseHandler( int& out_status) { - return [&out_status](const httplib::Response& resp) { + return [this, &out_status](const httplib::Response& resp) { if (HttpClient::IsErrorStatus(resp.status)) { out_status = resp.status; } + CheckWarnings(resp); return true; }; } diff --git a/src/historical.cpp b/src/historical.cpp index ec2b1a5..a398ee5 100644 --- a/src/historical.cpp +++ b/src/historical.cpp @@ -128,13 +128,29 @@ Historical::Historical(ILogReceiver* log_receiver, std::string key, : log_receiver_{log_receiver}, key_{std::move(key)}, gateway_{UrlFromGateway(gateway)}, + upgrade_policy_{VersionUpgradePolicy::UpgradeToV3}, client_{log_receiver, key_, gateway_} {} Historical::Historical(ILogReceiver* log_receiver, std::string key, - std::string gateway, std::uint16_t port) + HistoricalGateway gateway, + VersionUpgradePolicy upgrade_policy, + std::string user_agent_ext) + : log_receiver_{log_receiver}, + key_{std::move(key)}, + gateway_{UrlFromGateway(gateway)}, + user_agent_ext_{std::move(user_agent_ext)}, + upgrade_policy_{upgrade_policy}, + client_{log_receiver, key_, gateway_} {} + +Historical::Historical(ILogReceiver* log_receiver, std::string key, + std::string gateway, std::uint16_t port, + VersionUpgradePolicy upgrade_policy, + std::string user_agent_ext) : log_receiver_{log_receiver}, key_{std::move(key)}, gateway_{std::move(gateway)}, + user_agent_ext_{std::move(user_agent_ext)}, + upgrade_policy_{upgrade_policy}, client_{log_receiver, key_, gateway_, port} {} static const std::string kBatchSubmitJobEndpoint = "Historical::BatchSubmitJob"; @@ -870,7 +886,8 @@ enum class DecoderState : std::uint8_t { void Historical::TimeseriesGetRange(const HttplibParams& params, const MetadataCallback& metadata_callback, const RecordCallback& record_callback) { - detail::DbnBufferDecoder decoder{metadata_callback, record_callback}; + detail::DbnBufferDecoder decoder{upgrade_policy_, metadata_callback, + record_callback}; bool early_exit = false; this->client_.PostRawStream( @@ -961,8 +978,7 @@ databento::DbnFileStore Historical::TimeseriesGetRangeToFile( return true; }); } // Flush out_file - return DbnFileStore{log_receiver_, file_path, - VersionUpgradePolicy::UpgradeToV3}; + return DbnFileStore{log_receiver_, file_path, upgrade_policy_}; } using databento::HistoricalBuilder; @@ -992,6 +1008,24 @@ HistoricalBuilder& HistoricalBuilder::SetLogReceiver( return *this; } +HistoricalBuilder& HistoricalBuilder::SetUpgradePolicy( + VersionUpgradePolicy upgrade_policy) { + upgrade_policy_ = upgrade_policy; + return *this; +} + +HistoricalBuilder& HistoricalBuilder::SetAddress(std::string gateway, + std::uint16_t port) { + gateway_override_ = std::move(gateway); + port_ = port; + return *this; +} + +HistoricalBuilder& HistoricalBuilder::ExtendUserAgent(std::string extension) { + user_agent_ext_ = std::move(extension); + return *this; +} + Historical HistoricalBuilder::Build() { if (key_.empty()) { throw Exception{"'key' is unset"}; @@ -999,5 +1033,10 @@ Historical HistoricalBuilder::Build() { if (log_receiver_ == nullptr) { log_receiver_ = databento::ILogReceiver::Default(); } - return Historical{log_receiver_, key_, gateway_}; + if (gateway_override_.empty()) { + return Historical{log_receiver_, key_, gateway_, upgrade_policy_, + user_agent_ext_}; + } + return Historical{log_receiver_, key_, gateway_override_, port_, + upgrade_policy_, user_agent_ext_}; } diff --git a/src/live.cpp b/src/live.cpp index 7345311..68d230e 100644 --- a/src/live.cpp +++ b/src/live.cpp @@ -72,18 +72,28 @@ LiveBuilder& LiveBuilder::SetAddress(std::string gateway, std::uint16_t port) { return *this; } +LiveBuilder& LiveBuilder::SetBufferSize(std::size_t size) { + buffer_size_ = size; + return *this; +} + +LiveBuilder& LiveBuilder::ExtendUserAgent(std::string extension) { + user_agent_ext_ = std::move(extension); + return *this; +} + databento::LiveBlocking LiveBuilder::BuildBlocking() { Validate(); if (gateway_.empty()) { return databento::LiveBlocking{log_receiver_, key_, dataset_, send_ts_out_, upgrade_policy_, heartbeat_interval_, - buffer_size_}; + buffer_size_, user_agent_ext_}; } return databento::LiveBlocking{ - log_receiver_, key_, dataset_, gateway_, - port_, send_ts_out_, upgrade_policy_, heartbeat_interval_, - buffer_size_}; + log_receiver_, key_, dataset_, gateway_, + port_, send_ts_out_, upgrade_policy_, heartbeat_interval_, + buffer_size_, user_agent_ext_}; } databento::LiveThreaded LiveBuilder::BuildThreaded() { @@ -92,12 +102,12 @@ databento::LiveThreaded LiveBuilder::BuildThreaded() { return databento::LiveThreaded{log_receiver_, key_, dataset_, send_ts_out_, upgrade_policy_, heartbeat_interval_, - buffer_size_}; + buffer_size_, user_agent_ext_}; } return databento::LiveThreaded{ - log_receiver_, key_, dataset_, gateway_, - port_, send_ts_out_, upgrade_policy_, heartbeat_interval_, - buffer_size_}; + log_receiver_, key_, dataset_, gateway_, + port_, send_ts_out_, upgrade_policy_, heartbeat_interval_, + buffer_size_, user_agent_ext_}; } void LiveBuilder::Validate() { diff --git a/src/live_blocking.cpp b/src/live_blocking.cpp index d6356dc..9f26973 100644 --- a/src/live_blocking.cpp +++ b/src/live_blocking.cpp @@ -19,7 +19,6 @@ #include "databento/log.hpp" // ILogReceiver #include "databento/record.hpp" // Record #include "databento/symbology.hpp" // JoinSymbolStrings -#include "databento/version.hpp" // DATABENTO_VERSION #include "dbn_constants.hpp" // kMetadataPreludeSize using databento::LiveBlocking; @@ -32,12 +31,13 @@ LiveBlocking::LiveBlocking( ILogReceiver* log_receiver, std::string key, std::string dataset, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size) + std::size_t buffer_size, std::string user_agent_ext) : log_receiver_{log_receiver}, key_{std::move(key)}, dataset_{std::move(dataset)}, gateway_{DetermineGateway()}, + user_agent_ext_{std::move(user_agent_ext)}, port_{13000}, send_ts_out_{send_ts_out}, upgrade_policy_{upgrade_policy}, @@ -51,11 +51,12 @@ LiveBlocking::LiveBlocking( std::string gateway, std::uint16_t port, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size) + std::size_t buffer_size, std::string user_agent_ext) : log_receiver_{log_receiver}, key_{std::move(key)}, dataset_{std::move(dataset)}, gateway_{std::move(gateway)}, + user_agent_ext_{std::move(user_agent_ext)}, port_{port}, send_ts_out_{send_ts_out}, upgrade_policy_{upgrade_policy}, @@ -323,7 +324,10 @@ std::string LiveBlocking::GenerateCramReply(std::string_view challenge_key) { std::string LiveBlocking::EncodeAuthReq(std::string_view auth) { std::ostringstream req_stream; req_stream << "auth=" << auth << "|dataset=" << dataset_ << "|encoding=dbn|" - << "ts_out=" << send_ts_out_ << "|client=C++ " DATABENTO_VERSION; + << "ts_out=" << send_ts_out_ << "|client=" << kUserAgent; + if (!user_agent_ext_.empty()) { + req_stream << ' ' << user_agent_ext_; + } if (heartbeat_interval_.has_value()) { req_stream << "|heartbeat_interval_s=" << heartbeat_interval_->count(); } diff --git a/src/live_threaded.cpp b/src/live_threaded.cpp index 6b4d5ad..afb1757 100644 --- a/src/live_threaded.cpp +++ b/src/live_threaded.cpp @@ -59,21 +59,22 @@ LiveThreaded::LiveThreaded( ILogReceiver* log_receiver, std::string key, std::string dataset, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size) - : impl_{std::make_unique( - log_receiver, std::move(key), std::move(dataset), send_ts_out, - upgrade_policy, heartbeat_interval, buffer_size)} {} + std::size_t buffer_size, std::string user_agent_ext) + : impl_{std::make_unique(log_receiver, std::move(key), + std::move(dataset), send_ts_out, + upgrade_policy, heartbeat_interval, + buffer_size, std::move(user_agent_ext))} {} LiveThreaded::LiveThreaded( ILogReceiver* log_receiver, std::string key, std::string dataset, std::string gateway, std::uint16_t port, bool send_ts_out, VersionUpgradePolicy upgrade_policy, std::optional heartbeat_interval, - std::size_t buffer_size) + std::size_t buffer_size, std::string user_agent_ext) : impl_{std::make_unique( log_receiver, std::move(key), std::move(dataset), std::move(gateway), - port, send_ts_out, upgrade_policy, heartbeat_interval, buffer_size)} { -} + port, send_ts_out, upgrade_policy, heartbeat_interval, buffer_size, + std::move(user_agent_ext))} {} const std::string& LiveThreaded::Key() const { return impl_->blocking.Key(); } diff --git a/src/log.cpp b/src/log.cpp index b63987a..883c236 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -2,6 +2,11 @@ #include #include +#include + +#include "databento/system.hpp" +#include "databento/version.hpp" +#include "stream_op_helper.hpp" databento::ILogReceiver* databento::ILogReceiver::Default() { static const std::unique_ptr gDefaultLogger{ @@ -50,4 +55,20 @@ const char* ToString(LogLevel level) { }; } } + +void LogPlatformInfo() { LogPlatformInfo(ILogReceiver::Default()); } + +void LogPlatformInfo(ILogReceiver* log_receiver) { + std::ostringstream ss; + StreamOpBuilder{ss} + .SetSpacer(" ") + .Build() + .AddField("client_version", DATABENTO_VERSION) + .AddField("compiler", DATABENTO_CXX_COMPILER_ID) + .AddField("compiler_version", DATABENTO_CXX_COMPILER_VERSION) + .AddField("os", DATABENTO_SYSTEM_ID) + .AddField("os_version", DATABENTO_SYSTEM_VERSION) + .Finish(); + log_receiver->Receive(LogLevel::Info, ss.str()); +} } // namespace databento diff --git a/tests/src/historical_tests.cpp b/tests/src/historical_tests.cpp index 529783d..e9589b9 100644 --- a/tests/src/historical_tests.cpp +++ b/tests/src/historical_tests.cpp @@ -46,6 +46,14 @@ constexpr auto kApiKey = "HIST_SECRET"; class HistoricalTests : public ::testing::Test { protected: + Historical Client(int port) { + return databento::HistoricalBuilder{} + .SetLogReceiver(&logger_) + .SetKey(kApiKey) + .SetAddress("localhost", static_cast(port)) + .Build(); + } + std::filesystem::path tmp_path_{std::filesystem::temp_directory_path()}; mock::MockHttpServer mock_server_{kApiKey}; mock::MockLogReceiver logger_ = @@ -80,7 +88,8 @@ TEST_F(HistoricalTests, TestBatchSubmitJob) { {"state", "done"}, {"stype_in", "raw_symbol"}, {"stype_out", "instrument_id"}, - /* test the fact the API returns a string when there's only one symbol */ + /* test the fact the API returns a string when there's only one symbol + */ {"symbols", "CLH3"}, {"ts_expiration", "2022-11-30 15:29:43.148303+00:00"}, {"ts_process_done", "2022-10-31 15:29:43.148303+00:00"}, @@ -103,8 +112,7 @@ TEST_F(HistoricalTests, TestBatchSubmitJob) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.BatchSubmitJob(dataset::kXnasItch, {"CLH3"}, Schema::Trades, {"2022-05-17", "2022-07-03"}); @@ -187,8 +195,7 @@ TEST_F(HistoricalTests, TestBatchListJobs) { mock_server_.MockGetJson("/v0/batch.list_jobs", kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.BatchListJobs(); ASSERT_EQ(res.size(), 2); const std::vector symbols{"GEZ2", "GEH3"}; @@ -208,8 +215,7 @@ TEST_F(HistoricalTests, TestBatchListFiles) { mock_server_.MockGetJson("/v0/batch.list_files", {{"job_id", kJobId}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.BatchListFiles(kJobId); ASSERT_EQ(res.size(), 1); const auto& file_desc = res[0]; @@ -246,8 +252,7 @@ TEST_F(HistoricalTests, TestBatchDownloadAll) { mock_server_.MockGetJson("/v0/job_id/test_metadata.json", {{"key", "value"}}); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); ASSERT_FALSE(temp_metadata_file.Exists()); ASSERT_FALSE(temp_dbn_file.Exists()); const std::vector paths = @@ -279,8 +284,7 @@ TEST_F(HistoricalTests, TestBatchDownloadSingle) { mock_server_.MockGetJson("/v0/job_id/test_metadata.json", {{"key", "value"}}); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); ASSERT_FALSE(temp_metadata_file.Exists()); const std::filesystem::path path = target.BatchDownload(tmp_path_, kJobId, "test_metadata.json"); @@ -295,8 +299,7 @@ TEST_F(HistoricalTests, TestBatchDownloadSingleInvalidFile) { kListFilesResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); ASSERT_THROW(target.BatchDownload(tmp_path_, kJobId, "test_metadata.js"), InvalidArgumentError); } @@ -315,8 +318,7 @@ TEST_F(HistoricalTests, TestMetadataListPublishers) { mock_server_.MockGetJson("/v0/metadata.list_publishers", kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListPublishers(); EXPECT_EQ(res.size(), kResp.size()); const auto glbx_exp = @@ -335,8 +337,7 @@ TEST_F(HistoricalTests, TestMetadataListDatasets_Simple) { mock_server_.MockGetJson("/v0/metadata.list_datasets", kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListDatasets(); EXPECT_EQ(res.size(), kResp.size()); EXPECT_EQ(res[0], kResp[0]); @@ -349,8 +350,7 @@ TEST_F(HistoricalTests, TestMetadataListDatasets_Full) { {{"start_date", "2021-01-05"}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListDatasets(DateRange{"2021-01-05"}); EXPECT_EQ(res.size(), kResp.size()); EXPECT_EQ(res[0], kResp[0]); @@ -364,8 +364,7 @@ TEST_F(HistoricalTests, TestMetadataListSchemas_Simple) { {{"dataset", dataset::kGlbxMdp3}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListSchemas(dataset::kGlbxMdp3); const std::vector kExp{ Schema::Mbo, Schema::Mbp1, Schema::Mbp10, @@ -385,8 +384,7 @@ TEST_F(HistoricalTests, TestMetadataListSchemas_Full) { {{"dataset", dataset::kGlbxMdp3}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListSchemas(dataset::kGlbxMdp3); const std::vector kExp{Schema::Mbo, Schema::Mbp1, Schema::Ohlcv1M, Schema::Ohlcv1H, Schema::Ohlcv1D}; @@ -405,8 +403,7 @@ TEST_F(HistoricalTests, TestMetadataListFields) { {{"encoding", "dbn"}, {"schema", "trades"}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListFields(Encoding::Dbn, Schema::Trades); const std::vector kExp{ {"length", "uint8_t"}, {"rtype", "uint8_t"}, {"dataset_id", "uint16_t"}}; @@ -433,8 +430,7 @@ TEST_F(HistoricalTests, TestMetadataGetDatasetCondition) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetDatasetCondition( dataset::kXnasItch, {"2022-11-06", "2022-11-10"}); const std::vector kExp{ @@ -454,8 +450,7 @@ TEST_F(HistoricalTests, TestMetadataListUnitPrices) { {{"dataset", dataset::kGlbxMdp3}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataListUnitPrices(dataset::kGlbxMdp3); const UnitPricesForMode kExp{ FeedMode::HistoricalStreaming, @@ -485,8 +480,7 @@ TEST_F(HistoricalTests, TestMetadataGetDatasetRange) { {{"dataset", dataset::kXnasItch}}, kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetDatasetRange(dataset::kXnasItch); EXPECT_EQ(res.start, "2017-05-21T00:00:00.000000000Z"); EXPECT_EQ(res.end, "2022-12-01T00:00:00.000000000Z"); @@ -503,8 +497,7 @@ TEST_F(HistoricalTests, TestMetadataGetRecordCount) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetRecordCount( dataset::kGlbxMdp3, {"2020-06-06T00:00", "2021-03-02T00:00"}, {"ESZ3", "ESH4"}, Schema::Trades); @@ -522,8 +515,7 @@ TEST_F(HistoricalTests, TestMetadataGetBillableSize_Simple) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetBillableSize( dataset::kGlbxMdp3, {"2020-06-06T00:00", "2021-03-02T00:00"}, kAllSymbols, Schema::Trades); @@ -542,8 +534,7 @@ TEST_F(HistoricalTests, TestMetadataGetBillableSize_Full) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetBillableSize( dataset::kGlbxMdp3, {"2020-06-06T00:00", "2021-03-02T00:00"}, {"NG.FUT", "LNG.FUT"}, Schema::Tbbo, SType::Parent, {}); @@ -561,8 +552,7 @@ TEST_F(HistoricalTests, TestMetadataGetCost_Simple) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetCost( dataset::kGlbxMdp3, {"2020-06-06T00:00", "2021-03-02T00:00"}, {"MESN1", "MESQ1"}, Schema::Trades); @@ -582,8 +572,7 @@ TEST_F(HistoricalTests, TestMetadataGetCost_Full) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.MetadataGetCost( dataset::kGlbxMdp3, {"2020-06-06T00:00", "2021-03-02T00:00"}, {"MES.OPT", "EW.OPT"}, Schema::Tbbo, FeedMode::HistoricalStreaming, @@ -623,8 +612,7 @@ TEST_F(HistoricalTests, TestSymbologyResolve) { kResp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const auto res = target.SymbologyResolve( dataset::kGlbxMdp3, {"ESM2"}, SType::RawSymbol, SType::InstrumentId, {"2022-06-06", "2022-06-10"}); @@ -653,8 +641,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_Basic) { TEST_DATA_DIR "/test_data.mbo.v3.dbn.zst"); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); std::unique_ptr metadata_ptr; std::vector mbo_records; target.TimeseriesGetRange( @@ -688,8 +675,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_NoMetadataCallback) { TEST_DATA_DIR "/test_data.tbbo.v3.dbn.zst"); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); std::vector mbo_records; target.TimeseriesGetRange(dataset::kGlbxMdp3, {"2022-10-21T13:30", "2022-10-21T20:00"}, {"CYZ2"}, @@ -707,8 +693,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_BadRequest) { mock_server_.MockBadPostRequest("/v0/timeseries.get_range", resp); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); try { target.TimeseriesGetRange( dataset::kGlbxMdp3, @@ -733,8 +718,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_CallbackException) { TEST_DATA_DIR "/test_data.mbo.v3.dbn.zst"); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); ASSERT_THROW( target.TimeseriesGetRange( dataset::kGlbxMdp3, @@ -751,8 +735,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_Cancellation) { TEST_DATA_DIR "/test_data.mbo.v3.dbn.zst"); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); std::uint32_t call_count = 0; target.TimeseriesGetRange( dataset::kGlbxMdp3, @@ -782,8 +765,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_LargeChunks) { Record{&mbp1.hd}, kRecordCount, 75'000); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); std::size_t counter = 0; target.TimeseriesGetRange(ToString(Dataset::IfusImpact), {"2024-05", "2025-05"}, kAllSymbols, Schema::Mbp1, @@ -814,8 +796,8 @@ TEST_F(HistoricalTests, TestTimeseriesGetRange_UnreadBytes) { EXPECT_THAT(msg, testing::EndsWith( "Partial or incomplete record remaining of 20 bytes")); }}; - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + + databento::Historical target = Client(port); std::size_t counter = 0; target.TimeseriesGetRange(ToString(Dataset::IfusImpact), {"2024-05", "2025-05"}, kAllSymbols, Schema::Mbp1, @@ -842,8 +824,7 @@ TEST_F(HistoricalTests, TestTimeseriesGetRangeToFile) { TEST_DATA_DIR "/test_data.tbbo.v3.dbn.zst"); const auto port = mock_server_.ListenOnThread(); - databento::Historical target{&logger_, kApiKey, "localhost", - static_cast(port)}; + databento::Historical target = Client(port); const TempFile temp_file{testing::TempDir() + "/TestTimeseriesGetRangeToFile"}; target.TimeseriesGetRangeToFile(dataset::kGlbxMdp3, diff --git a/tests/src/log_tests.cpp b/tests/src/log_tests.cpp index d14b965..b027e18 100644 --- a/tests/src/log_tests.cpp +++ b/tests/src/log_tests.cpp @@ -4,7 +4,10 @@ #include #include "databento/log.hpp" +#include "databento/system.hpp" +#include "databento/version.hpp" #include "gmock/gmock.h" +#include "mock/mock_log_receiver.hpp" namespace databento::tests { class ConsoleLogReceiverTests : public testing::Test { @@ -35,4 +38,18 @@ TEST(ILogReceiverTests, TestDefault) { ASSERT_NE(log_receiver, nullptr); ASSERT_NE(dynamic_cast(log_receiver), nullptr); } + +TEST(ILogReceiverTests, TestLogPlatformInfo) { + mock::MockLogReceiver receiver{ + [](auto, LogLevel lvl, const std::string& msg) { + EXPECT_EQ(lvl, LogLevel::Info); + EXPECT_THAT(msg, testing::HasSubstr(DATABENTO_SYSTEM_ID)); + EXPECT_THAT(msg, testing::HasSubstr(DATABENTO_SYSTEM_VERSION)); + EXPECT_THAT(msg, testing::HasSubstr(DATABENTO_CXX_COMPILER_ID)); + EXPECT_THAT(msg, testing::HasSubstr(DATABENTO_CXX_COMPILER_VERSION)); + EXPECT_THAT(msg, testing::HasSubstr(DATABENTO_VERSION)); + }}; + LogPlatformInfo(&receiver); + ASSERT_EQ(receiver.CallCount(), 1); +} } // namespace databento::tests