diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2269239913b1e9e77532883da8cf4e563d68f99..a79ad1944436beb30f82d1149dd1c840780a0287 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,6 @@ variables: CPPLIB_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/src/caosdb-cpplib/testenv:$CI_COMMIT_REF_NAME CPPINTTEST_PIPELINE: https://gitlab.indiscale.com/api/v4/projects/111/trigger/pipeline - CPPINTTEST_BRANCHES: https://gitlab.indiscale.com/api/v4/projects/111/repository/branches GIT_SUBMODULE_STRATEGY: normal ## FOR DEBUGGING @@ -50,7 +49,6 @@ info: - echo "Pipeline triggered by $TRIGGERED_BY_REPO@$TRIGGERED_BY_REF ($TRIGGERED_BY_HASH)" - echo "$CPPLIB_REGISTRY_IMAGE" - echo "$CPPINTTEST_PIPELINE" - - echo "$CPPINTTEST_BRANCHES" - echo "$GIT_SUBMODULE_STRATEGY" # Build a docker image in which tests for this repository can run diff --git a/CMakeLists.txt b/CMakeLists.txt index 901120436b2d562741ee239890a2dc65a6d38aea..4a70cb28277060f30a0a030ddbf31f1511513374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -289,7 +289,7 @@ if(_LINTING) else() message(STATUS "clang-tidy: ${clang_tidy}") set(_CMAKE_CXX_CLANG_TIDY_CHECKS - "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return") + "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type") set(_CMAKE_C_CLANG_TIDY_CHECKS "${_CMAKE_CXX_CLANG_TIDY_CHECKS}") set(_CMAKE_CXX_CLANG_TIDY "${clang_tidy}" "--header-filter=caosdb/.*[^\(\.pb\.h\)]$" diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 360292a3b5a51cad63e8a5526bff2db81af65a26..7ee267a5dc578199e4b1e6846ab0878ffe463067 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -1,8 +1,13 @@ # GENERAL +* >=conan-1.37.2 * >=cmake-3.14 * >=gcc-10.2.0 | >=clang-11 +# OPTIONAL + +* For checking the schema of a json configuration file: >=jsonschema-3.2.0 + # BUILD DOCS * doxygen diff --git a/README_SETUP.md b/README_SETUP.md index bcda25acc80f00ef9801357f786d973face177fa..34ae13a3eb91037e4bcb183c7ca8fe2eba160a3c 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -1,5 +1,9 @@ # How to Develop and Use Libcaosdb +## Dependencies + +* See [DEPENDENCIES.md](Dependencies.md) + ## Build We use [cmake](https://cmake.org) as build tool. diff --git a/caosdb-client-configuration-schema.json b/caosdb-client-configuration-schema.json index 882f757636ee82cce994c779b5e174276a89540d..6c8013552a66532e453887a2dda61322efacc74d 100644 --- a/caosdb-client-configuration-schema.json +++ b/caosdb-client-configuration-schema.json @@ -24,6 +24,30 @@ "$ref": "#/definitions/connection_configuration" } }, + "logging": { + "type": "object", + "description": "Configuration of logging", + "properties": { + "level": { + "allOf": [ + { + "$ref": "#/definitions/log_level_configuration" + }, + "default": "warn", + { + "description": "Global severity filter. Only log messages with at least the given severity are processed by the logging framework. 'off' means that the logging frame work is turned off entirely. Defaults to " + } + ] + }, + "sinks": { + "type": "object", + "description": "Configuration of sinks, i.e. the log files, console or other logging frame works where the log messages are supposed to end up eventually.", + "additionalProperties": { + "$ref": "#/definitions/sink_configuration" + } + } + } + }, "extension": { "type": "object", "description": "A reserved configuration object which may be used to store additional options for particular clients and special extensions.", @@ -31,6 +55,58 @@ } }, "definitions": { + "log_level_configuration": { + "type": "string", + "enum": ["all", "trace", "debug", "info", "warn", "error", "fatal", "off"] + }, + "sink_configuration": { + "type": "object", + "description": "A single sink configuration.", + "additionalProperties": false, + "properties": { + "level": { + "allOf": [ + { + "$ref": "#/definitions/log_level_configuration" + }, + { + "description": "Sink severity filter. Only log messages with at least the given severity are passed to this sink. 'off' means that this sink is turned off entirely. Defaults to the global log level." + } + ] + }, + "destination": { + "type": "string", + "enum": [ "console", "file", "syslog"], + "description": "The type of sink-backend. 'console' writes to the STDERR, 'file' to a text file and 'syslog' uses the syslog API." + }, + "directory": { + "type": "string", + "description": "Path to local directory. All log files are being stored to this directory. If not specified, the current working directory will be used." + }, + "local_address": { + "type": "string", + "description": "Local IP address to initiate connection to the syslog server. If not specified, the default local address will be used." + }, + "target_address": { + "type": "string", + "description": "Remote IP address of the syslog server. If not specified, the local address will be used. " + } + }, + "required": [ "destination" ], + "allOf": [ { + "if": {"properties": {"destination": { "const": "console" } } }, + "then": {"propertyNames" : { "enum": ["level", "destination"] } } + }, + { + "if": {"properties": {"destination": { "const": "file" } } }, + "then": {"propertyNames" : { "enum": ["level", "destination", "directory"] } } + }, + { + "if": {"properties": {"destination": { "const": "syslog" } } }, + "then": {"propertyNames" : { "enum": ["level", "destination", "target_address", "local_address"] } } + } + ] + }, "connection_configuration": { "type": "object", "description": "A single connection configuration.", diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 51afe439da1a1237dbbb73ac94c15177f37c03cc..126c54ef2f7d4c26a43a163774c97b7c362643dd 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -28,7 +28,12 @@ set(libcaosdb_INCL ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/exceptions.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/log_level.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/message_code.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/status_code.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.h + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction_status.h ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utility.h ) diff --git a/include/caosdb/configuration.h b/include/caosdb/configuration.h index f6f207574efee6cc611dd6e50b7dd1dd1995a2eb..6426ab4b9e5b6dbfebb3cce4d67983df58a57a6a 100644 --- a/include/caosdb/configuration.h +++ b/include/caosdb/configuration.h @@ -29,11 +29,12 @@ #include "caosdb/authentication.h" // for Authenticator, PlainPassw... #include "caosdb/certificate_provider.h" // for CertificateProvider, path #include "caosdb/exceptions.h" // for ConfigurationError -#include "caosdb/utility.h" // for load_json_file -#include "grpcpp/security/credentials.h" // for ChannelCredentials -#include <iosfwd> // for ostream -#include <memory> // for unique_ptr, shared_ptr -#include <string> // for string +#include "caosdb/logging.h" +#include "caosdb/utility.h" // for load_json_file +#include "grpcpp/security/credentials.h" // for ChannelCredentials +#include <iosfwd> // for ostream +#include <memory> // for unique_ptr, shared_ptr +#include <string> // for string namespace caosdb::configuration { using boost::filesystem::exists; @@ -47,6 +48,8 @@ using caosdb::exceptions::ConfigurationError; using caosdb::utility::load_json_file; using grpc::ChannelCredentials; +const std::string logger_name = "caosdb::configuration"; + /** * @brief Configuration of the CaosDB connection. */ @@ -99,6 +102,32 @@ public: [[nodiscard]] auto ToString() const -> std::string override; }; +/** + * Helper class (no state, just member functions) which should only be used by + * the ConfigurationManager to initialize the logging framework from the stored + * configuration. + */ +class LoggingConfigurationHelper { +public: + friend class ConfigurationManager; + +private: + auto CreateConsoleSinkConfiguration(const object &from, + const std::string &name, int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration>; + auto CreateSyslogSinkConfiguration(const object &from, + const std::string &name, int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration>; + auto CreateFileSinkConfiguration(const object &from, const std::string &name, + int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration>; + auto CreateSinkConfiguration(const object &from, const std::string &name, + int default_level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration>; + auto CreateLoggingConfiguration(const object &from) const + -> caosdb::logging::LoggingConfiguration; +}; + /** * Helper class (no state, just member functions) which should only be used by * the ConfigurationManager to construct Connection instances from the stored @@ -112,7 +141,7 @@ private: /** * @param from - a single connection configuration. */ - inline auto CreateCertificateProvider(const object &from) const + auto CreateCertificateProvider(const object &from) const -> std::unique_ptr<CertificateProvider>; /** @@ -159,12 +188,12 @@ public: /** * See mReset. */ - inline static auto Reset() -> void { GetInstance().mReset(); } + inline static auto Reset() noexcept -> int { return GetInstance().mReset(); } /** * See mClear. */ - inline static auto Clear() -> void { GetInstance().mClear(); } + inline static auto Clear() noexcept -> int { return GetInstance().mClear(); } /** * See mLoadSingleJSONConfiguration. @@ -204,6 +233,8 @@ public: private: value json_configuration; ConnectionConfigurationHelper connection_configuration_helper; + LoggingConfigurationHelper logging_configuration_helper; + inline ConfigurationManager() { InitializeDefaults(); }; /** @@ -213,7 +244,7 @@ private: * first existing file from the LIBCAOSDB_CONFIGURATION_FILES_PRECEDENCE list * of file locations. */ - auto InitializeDefaults() -> void; + auto InitializeDefaults() -> int; /** * Return a json object representing the current configuration. @@ -237,7 +268,7 @@ private: * The current configuration is deleted and a new configuration is being * loaded via InitializeDefaults. */ - auto mReset() -> void; + auto mReset() noexcept -> int; /** * Clear this ConfigurationManager. @@ -247,7 +278,7 @@ private: * In contrast to mReset, this method only deletes the current configuration * but does not load a new one via InitializeDefaults. */ - auto mClear() -> void; + auto mClear() noexcept -> int; /** * Load a configuration from a json file. diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h index 5fad0d26c69b9b10b1739a0f1322a9f3b3485509..22bdc6f9f5b524ae895469d25d540f9679e65353 100644 --- a/include/caosdb/connection.h +++ b/include/caosdb/connection.h @@ -27,6 +27,9 @@ * @date 2021-05-18 * @brief Configuration and setup of the connection. */ +#include <map> // for map +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string, basic_string #include "boost/filesystem/path.hpp" // for path #include "caosdb/authentication.h" // for Authenticator #include "caosdb/configuration.h" // for ConnectionConfigura... @@ -34,11 +37,8 @@ #include "caosdb/info.h" // for VersionInfo #include "caosdb/info/v1alpha1/main.grpc.pb.h" // for GeneralInfoService:... #include "caosdb/transaction.h" // for Transaction +#include "caosdb/transaction_status.h" // for TransactionStatus #include "grpcpp/channel.h" // for Channel -#include <iosfwd> // for ostream -#include <map> // for map -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string, basic_string namespace caosdb::connection { using boost::filesystem::path; @@ -48,6 +48,7 @@ using caosdb::entity::v1alpha1::EntityTransactionService; using caosdb::info::VersionInfo; using caosdb::info::v1alpha1::GeneralInfoService; using caosdb::transaction::Transaction; +using caosdb::transaction::TransactionStatus; /** * @brief A reusable connection to a CaosDBServer. @@ -81,11 +82,11 @@ public: * * Clients need to call RetrieveVersionInfo() or * RetrieveVersionInfoNoExceptions() before the version info is locally - * available. Otherwise an empty instance is being returned. + * available. Otherwise a nullptr is being returned. */ [[nodiscard]] inline auto GetVersionInfo() const noexcept - -> const VersionInfo & { - return this->version_info; + -> const VersionInfo * { + return this->version_info.get(); }; [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>; @@ -99,7 +100,7 @@ private: std::unique_ptr<GeneralInfoService::Stub> general_info_service; /// The server's version. It's mutable because it is rather a cache than a /// data member which is subject to change. - mutable VersionInfo version_info; + mutable std::unique_ptr<VersionInfo> version_info; /// Service for entity transactions. We use a shared pointer because /// Transaction instances also own this service stub. std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service; diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h index f25b6dc3e7af69d088cbfe4c5b3356b806d12dc6..4803ae7ca7de2d9dfad688ed4a586e11418e7ad9 100644 --- a/include/caosdb/entity.h +++ b/include/caosdb/entity.h @@ -28,9 +28,11 @@ #ifndef CAOSDB_ENTITY_H #define CAOSDB_ENTITY_H -#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RepeatedField -#include <memory> // for unique_ptr -#include <string> // for string +#include "caosdb/message_code.h" +#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RepeatedPtrField +#include "google/protobuf/util/json_util.h" +#include <memory> // for unique_ptr +#include <string> // for string namespace caosdb::entity { @@ -56,13 +58,14 @@ class Parents { public: inline Parents(){}; explicit inline Parents( - ::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Parent> + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent> *wrapped) : wrapped(wrapped){}; [[nodiscard]] auto At(int index) const -> const Parent &; private: - ::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Parent> *wrapped; + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Parent> + *wrapped; friend class Entity; }; @@ -97,13 +100,49 @@ class Properties { public: inline Properties(){}; explicit inline Properties( - ::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Property> + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Property> *wrapped) : wrapped(wrapped){}; [[nodiscard]] auto At(int index) const -> const Property &; private: - ::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Property> + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Property> + *wrapped; + friend class Entity; +}; + +class Message { +public: + explicit inline Message(caosdb::entity::v1alpha1::Message *wrapped) + : wrapped(wrapped){}; + [[nodiscard]] inline auto GetCode() const -> MessageCode { + return get_message_code(wrapped->code()); + } + [[nodiscard]] inline auto GetDescription() const -> std::string { + return wrapped->description(); + } + +private: + caosdb::entity::v1alpha1::Message *wrapped; +}; + +/** + * Container for Messages. + */ +class Messages { +public: + inline Messages() : wrapped(nullptr){}; + explicit inline Messages( + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message> + *wrapped) + : wrapped(wrapped){}; + [[nodiscard]] inline auto Size() const -> int { return wrapped->size(); } + [[nodiscard]] inline auto At(int index) const -> const Message { + return Message(&(wrapped->at(index))); + } + +private: + ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message> *wrapped; friend class Entity; }; @@ -114,13 +153,10 @@ private: class Entity { public: explicit inline Entity(caosdb::entity::v1alpha1::Entity *wrapped) - : wrapped(wrapped), - properties( - (::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Property> - *)wrapped->mutable_properties()), - parents( - (::google::protobuf::RepeatedField<caosdb::entity::v1alpha1::Parent> *) - wrapped->mutable_parents()){}; + : wrapped(wrapped) { + errors.wrapped = this->wrapped->mutable_errors(); + }; + [[nodiscard]] inline auto GetId() const -> const std::string & { return wrapped->id(); }; @@ -147,11 +183,26 @@ public: [[nodiscard]] auto GetParents() const -> const Parents &; [[nodiscard]] auto GetProperties() const -> const Properties &; + [[nodiscard]] inline auto GetErrors() const -> const Messages & { + return errors; + } + [[nodiscard]] inline auto HasErrors() const -> bool { + return this->errors.wrapped->size() > 0; + } + + inline auto ToString() const -> const std::string { + google::protobuf::util::JsonOptions options; + std::string out; + google::protobuf::util::MessageToJsonString(*(this->wrapped.get()), &out, + options); + return out; + } private: std::unique_ptr<caosdb::entity::v1alpha1::Entity> wrapped; Properties properties; Parents parents; + Messages errors; }; } // namespace caosdb::entity diff --git a/include/caosdb/exceptions.h b/include/caosdb/exceptions.h index a0971a13be02adc6c16837011f7df2c45fb7c089..cd6032358f1523ad27f8004e245ebc83e3454508 100644 --- a/include/caosdb/exceptions.h +++ b/include/caosdb/exceptions.h @@ -21,46 +21,71 @@ #ifndef CAOSDB_EXCEPTIONS_H #define CAOSDB_EXCEPTIONS_H +#include "caosdb/status_code.h" #include <stdexcept> #include <string> namespace caosdb::exceptions { +using caosdb::StatusCode; using std::runtime_error; +/** + * @brief Generic exception class of the caosdb client library. + */ +class GenericException : public runtime_error { +public: + explicit GenericException(StatusCode code, const std::string &what_arg) + : runtime_error(what_arg), code(code) {} + [[nodiscard]] inline auto GetCode() const -> StatusCode { return this->code; } + +private: + StatusCode code; +}; + /** * @brief Exception for authentication errors. */ -class AuthenticationError : public runtime_error { +class AuthenticationError : public GenericException { public: explicit AuthenticationError(const std::string &what_arg) - : runtime_error(what_arg) {} + : GenericException(StatusCode::AUTHENTICATION_ERROR, what_arg) {} }; /** * @brief The connection to the CaosDB server is down. */ -class ConnectionError : public runtime_error { +class ConnectionError : public GenericException { public: explicit ConnectionError(const std::string &what_arg) - : runtime_error(what_arg) {} + : GenericException(StatusCode::CONNECTION_ERROR, what_arg) {} }; /** - * @brief The connection is known to the ConnectionManager under this name. + * @brief The transaction terminated unsuccessfully. */ -class UnknownConnectionError : public runtime_error { +class TransactionError : public GenericException { public: - explicit UnknownConnectionError(const std::string &what_arg) - : runtime_error(what_arg) {} + explicit TransactionError(const std::string &what_arg) + : GenericException(StatusCode::GENERIC_TRANSACTION_ERROR, what_arg) {} }; /** - * @brief Exception for errors of the ConnectionManager. + * @brief Exception for errors of the ConfigurationManager or other components + * of the configuration. */ -class ConfigurationError : public runtime_error { +class ConfigurationError : public GenericException { public: explicit ConfigurationError(const std::string &what_arg) - : runtime_error(what_arg) {} + : GenericException(StatusCode::CONFIGURATION_ERROR, what_arg) {} +}; + +/** + * @brief The connection isn't known to the ConnectionManager under this name. + */ +class UnknownConnectionError : public GenericException { +public: + explicit UnknownConnectionError(const std::string &what_arg) + : GenericException(StatusCode::UNKNOWN_CONNECTION_ERROR, what_arg) {} }; } // namespace caosdb::exceptions diff --git a/include/caosdb/info.h b/include/caosdb/info.h index 751539eeeb95800bc76aba0cb59de5f5b106f5b2..a9e94c1077f809bd5e2e28c225773e91b13a9e98 100644 --- a/include/caosdb/info.h +++ b/include/caosdb/info.h @@ -54,13 +54,13 @@ public: * server behind the given connection. */ explicit inline VersionInfo(ProtoVersionInfo *info) : info(info){}; - [[nodiscard]] inline auto GetMajor() const -> uint32_t { + [[nodiscard]] inline auto GetMajor() const -> int32_t { return this->info->major(); } - [[nodiscard]] inline auto GetMinor() const -> uint32_t { + [[nodiscard]] inline auto GetMinor() const -> int32_t { return this->info->minor(); } - [[nodiscard]] inline auto GetPatch() const -> uint32_t { + [[nodiscard]] inline auto GetPatch() const -> int32_t { return this->info->patch(); } [[nodiscard]] inline auto GetPreRelease() const -> const std::string & { diff --git a/include/caosdb/log_level.h b/include/caosdb/log_level.h new file mode 100644 index 0000000000000000000000000000000000000000..4cbfeb338bd61fb1bda24820022f37165b7a74d4 --- /dev/null +++ b/include/caosdb/log_level.h @@ -0,0 +1,35 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef CAOSDB_LOG_LEVELS_H +#define CAOSDB_LOG_LEVELS_H + +#define CAOSDB_LOG_LEVEL_OFF 1000000 +#define CAOSDB_LOG_LEVEL_FATAL 700 +#define CAOSDB_LOG_LEVEL_ERROR 600 +#define CAOSDB_LOG_LEVEL_WARN 500 +#define CAOSDB_LOG_LEVEL_INFO 400 +#define CAOSDB_LOG_LEVEL_DEBUG 300 +#define CAOSDB_LOG_LEVEL_TRACE 200 +#define CAOSDB_LOG_LEVEL_ALL 0 +#define CAOSDB_DEFAULT_LOG_LEVEL 500 + +#endif diff --git a/include/caosdb/logging.h b/include/caosdb/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..170dba48a6ce647dd286dfc217d69bb27e8237cc --- /dev/null +++ b/include/caosdb/logging.h @@ -0,0 +1,160 @@ + +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef CAOSDB_LOGGING_H +#define CAOSDB_LOGGING_H + +#include "caosdb/log_level.h" +#include <cstddef> +#include <string> +#include <iostream> +#include <vector> +#include <memory> +#include <boost/log/sources/severity_channel_logger.hpp> +#include <boost/log/sources/record_ostream.hpp> +#include <boost/log/utility/setup/common_attributes.hpp> +#include <boost/log/utility/setup/settings.hpp> +#include <boost/log/utility/setup/from_settings.hpp> +#include <boost/log/utility/setup/formatter_parser.hpp> +#include <boost/log/sources/global_logger_storage.hpp> + +#define CAOSDB_LOG_FATAL(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_FATAL) +#define CAOSDB_LOG_ERROR(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_ERROR) +#define CAOSDB_LOG_WARN(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_WARN) +#define CAOSDB_LOG_INFO(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_INFO) +#define CAOSDB_LOG_DEBUG(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_DEBUG) +#define CAOSDB_LOG_TRACE(Channel) \ + BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel, \ + CAOSDB_LOG_LEVEL_TRACE) + +namespace caosdb::logging { + +const std::string logger_name = "caosdb::logging"; + +typedef boost::log::sources::severity_channel_logger<int, std::string> + boost_logger_class; +BOOST_LOG_INLINE_GLOBAL_LOGGER_DEFAULT(logger, boost_logger_class) + +class LevelConfiguration { +public: + LevelConfiguration(int level) : level(level){}; + [[nodiscard]] inline auto GetLevel() const -> int { return this->level; } + +private: + int level; +}; + +class SinkConfiguration; + +class LoggingConfiguration : public LevelConfiguration { +public: + virtual ~LoggingConfiguration() = default; + LoggingConfiguration(int level); + auto AddSink(const std::shared_ptr<SinkConfiguration> &sink) -> void; + auto GetSinks() const + -> const std::vector<std::shared_ptr<SinkConfiguration>> &; + +private: + std::vector<std::shared_ptr<SinkConfiguration>> sinks; +}; + +auto initialize_logging_defaults() -> int; +auto initialize_logging(const LoggingConfiguration &configuration) -> void; + +class SinkConfiguration : public LevelConfiguration { +public: + virtual ~SinkConfiguration() = default; + SinkConfiguration(std::string name, int level); + [[nodiscard]] auto GetName() const -> const std::string &; + [[nodiscard]] virtual auto GetDestination() const -> const std::string & = 0; + + friend auto initialize_logging_defaults() -> int; + friend auto + initialize_logging(const LoggingConfiguration &logging_configuration) -> void; + +protected: + virtual auto Configure(boost::log::settings &settings) const -> void; + +private: + std::string name; +}; + +class ConsoleSinkConfiguration : public SinkConfiguration { +public: + virtual ~ConsoleSinkConfiguration() = default; + ConsoleSinkConfiguration(const std::string &name, int level); + [[nodiscard]] auto GetDestination() const -> const std::string & override; + friend auto initialize_logging_defaults() -> int; + friend auto + initialize_logging(const LoggingConfiguration &logging_configuration) -> void; + +protected: + typedef SinkConfiguration sink_configuration; + virtual auto Configure(boost::log::settings &settings) const -> void override; + +private: + const std::string destination = "Console"; +}; + +class FileSinkConfiguration : public SinkConfiguration { +public: + virtual ~FileSinkConfiguration() = default; + FileSinkConfiguration(const std::string &name, int level); + [[nodiscard]] virtual auto GetDestination() const + -> const std::string & override; + auto SetDirectory(const std::string &directory) -> void; + friend auto initialize_logging_defaults() -> int; + friend auto + initialize_logging(const LoggingConfiguration &logging_configuration) -> void; + +protected: + typedef SinkConfiguration sink_configuration; + virtual auto Configure(boost::log::settings &settings) const -> void override; + +private: + const std::string destination = "TextFile"; + std::string directory = "./"; +}; + +class SyslogSinkConfiguration : public SinkConfiguration { +public: + virtual ~SyslogSinkConfiguration() = default; + SyslogSinkConfiguration(const std::string &name, int level); + [[nodiscard]] virtual auto GetDestination() const + -> const std::string & override; + +private: + const std::string destination = "Syslog"; +}; + +} // namespace caosdb::logging +#endif diff --git a/include/caosdb/message_code.h b/include/caosdb/message_code.h new file mode 100644 index 0000000000000000000000000000000000000000..a5b65ca68cae8938abb2929888b8201d993e706b --- /dev/null +++ b/include/caosdb/message_code.h @@ -0,0 +1,61 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef CAOSDB_MESSAGE_CODE_H +#define CAOSDB_MESSAGE_CODE_H + +#include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RepeatedField + +/** + * MessageCodes for entity messages. + * + * In contrast to the status codes, the message codes are part of the CaosDB + * API. Messages (and their codes) represent the state of the entities in a + * transaction or the server. + */ + +namespace caosdb::entity { + +enum MessageCode { + UNSPECIFIED = caosdb::entity::v1alpha1::MessageCode::MESSAGE_CODE_UNSPECIFIED, + UNKNOWN = caosdb::entity::v1alpha1::MessageCode::MESSAGE_CODE_UNKNOWN, + ENTITY_DOES_NOT_EXIST = + caosdb::entity::v1alpha1::MessageCode::MESSAGE_CODE_ENTITY_DOES_NOT_EXIST, +}; + +[[nodiscard]] inline auto get_message_code(int code) -> MessageCode { + // TODO(tf) smarter, less forgot-it-prone implementation + static MessageCode all_codes[] = { + MessageCode::UNSPECIFIED, + MessageCode::UNKNOWN, + MessageCode::ENTITY_DOES_NOT_EXIST, + }; + + for (MessageCode known_code : all_codes) { + if (known_code == code) { + return known_code; + } + } + return MessageCode::UNKNOWN; +} + +} // namespace caosdb::entity +#endif diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h new file mode 100644 index 0000000000000000000000000000000000000000..6fb8db3eea71a34038fa347df40f9be7ccbba70e --- /dev/null +++ b/include/caosdb/status_code.h @@ -0,0 +1,53 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef CAOSDB_STATUS_CODE_H +#define CAOSDB_STATUS_CODE_H + +/** + * StatusCodes represent the status of this client, it's connections, + * configuration and so on. + * + * In contrast to MessageCodes, these status codes do not represent the status + * of the entities of a transaction or of the server (or only inasmuch the + * GENERIC_TRANSACTION_ERROR indicates that *there are* errors in a + * transaction). + */ + +namespace caosdb { + +enum StatusCode { + INITIAL = -2, + EXECUTING = -1, + SUCCESS = 0, + AUTHENTICATION_ERROR = 16, + CONNECTION_ERROR = 14, + GENERIC_RPC_ERROR = 20, + GENERIC_ERROR = 21, + GENERIC_TRANSACTION_ERROR = 22, + CONFIGURATION_ERROR = 23, + UNKNOWN_CONNECTION_ERROR = 24, +}; + +auto get_status_description(int code) -> const std::string &; + +} // namespace caosdb +#endif diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h index 1dc69605999b2690506ef4a4575fbc4126018dd3..4e4fb2a76a0b4fb65824d1c2e54843062d382aef 100644 --- a/include/caosdb/transaction.h +++ b/include/caosdb/transaction.h @@ -19,27 +19,24 @@ * */ +#ifndef CAOSDB_TRANSACTION_H +#define CAOSDB_TRANSACTION_H /** * @brief Creation and execution of transactions. */ - -#ifndef CAOSDB_TRANSACTION_H -#define CAOSDB_TRANSACTION_H - -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string -#include "caosdb/localization.h" +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string #include "caosdb/entity.h" // for Entity #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe... #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity, RetrieveReq... -#include <memory> // for shared_ptr, unique_ptr -#include <string> // for string +#include "caosdb/transaction_status.h" // for TransactionStatus namespace caosdb::transaction { using caosdb::entity::Entity; using ProtoEntity = caosdb::entity::v1alpha1::Entity; using caosdb::entity::v1alpha1::EntityTransactionService; using caosdb::entity::v1alpha1::RetrieveRequest; +using caosdb::transaction::TransactionStatus; class ResultSet { public: @@ -57,154 +54,13 @@ private: std::unique_ptr<Entity> entity; }; -enum StatusCode { - INITIAL = -2, - EXECUTING = -1, - SUCCESS = 0, - UNAUTHENTICATED = 16, - UNAVAILABLE = 14, - GENERIC_RPC_ERROR = 20, - GENERIC_TRANSACTION_ERROR = 21, -}; - -/** - * State of a transaction instance. - */ -class TransactionStatus { -public: - inline static const auto INITIAL = TransactionStatus( - 0, StatusCode::INITIAL, - _("The transaction has not been executed yet and clients can still " - "change it.")); - static const auto EXECUTING = - TransactionStatus(10, StatusCode::EXECUTING, - _("The transaction is currently being executed.")); - static const auto SUCCESS = TransactionStatus( - 20, StatusCode::SUCCESS, - _("The transaction has been executed and it terminated successfully.")); - static const auto - RPC_ERROR = TransactionStatus( - 100, StatusCode::GENERIC_RPC_ERROR, - _("The attempt to execute this transaction was not successful because an " - "error occured in the transport or RPC protocol layer.")), - static const auto CONNECTION_ERROR = TransactionStatus( - 200, StatusCode::UNAVAILABLE, - _("The attempt to execute this transaction was not successful because " - "the connection to the server could not be established.")); - static const auto AUTHENTICATION_ERROR = TransactionStatus( - 300, StatusCode::UNAUTHENTICATED, - _("The attempt to execute this transaction has not been executed at all " - "because the authentication did not succeed.")); - static const auto TRANSACTION_ERROR = TransactionStatus( - 1000, StatusCode::GENERIC_TRANSACTION_ERROR, - _("The transaction terminated unsuccessfully with transaction errors.")); - - /** - * Return true if this TransactionStatus represents a terminated state. - */ - inline auto IsTerminated() const -> bool { - return this->numeric_value >= 20; - }; - - /** - * Return true if this TransactionStatus represents an erroneous state. - */ - inline auto IsError() const -> bool { return this->numeric_value >= 100; }; - - /** - * Equals operator is overloaded so we can use this class is if it were an - * Enum. - */ - inline int operator==(const TransactionStatus &other) { - return this->numeric_value == other.numeric_value; - }; - - /** - * Instantiate and return a new instance with the given code and description. - * - * Instances with a code and a description may be used to describe an - * erroneous state in more detail. - * - * This method should not be called on non-error states. - */ - inline auto Instantiate(StatusCode code, const std::string &details) const - -> const TransactionStatus { - return TransactionStatus(this->numeric_value, code, - this->description + " " + detail); - }; - - /** - * Instantiate and return a new instance with the given code. - * - * Instances with a code may be used to describe an erroneous state in more - * detail. - * - * This method should not be called on non-error states. - */ - inline auto Instantiate(StatusCode code) const -> const TransactionStatus { - return TransactionStatus(this->numeric_value, code, this->description); - } - - /** - * Instantiate and return a new instance with the given code and description. - * - * Instances descriptions may be used to describe an erroneous state in more - * detail. - * - * This method should no be called on non-error states. - */ - inline auto Instantiate(const std::string &details) const - -> const TransactionStatus { - return TransactionStatus(this->numeric_value, this->code, - this->description + " " + details); - } - - /** - * Return a description of the erroneous state. - * - * Returns an empty string if there is no description. - */ - inline auto GetDescription() const -> const std::string & { - return this->description; - } - - /** - * Return the status code of the state. - */ - inline auto GetCode() const -> StatusCode { return this->code; } - -private: - /** - * The numeric_value is for class-internal use only. - * - * We use it to group errors with different error codes together, e.g. all - * transaction errors. - */ - const int numeric_value; - - /** - * The code is an identifier of errors and should not be confused with the - * class-internal numeric_value. - */ - const StatusCode code; - - /** - * Description of the error - */ - const std::string description; - - TransactionStatus(int numeric_value, StatusCode code, - const std::string &description) - : numeric_value(numeric_value), code(code), description(description){}; -}; - /** * @brief Create a transaction via `CaosDBConnection.createTransaction()` */ class Transaction { private: std::unique_ptr<ResultSet> result_set; - TransactionStatus state = TransactionStatus::INITIAL; + TransactionStatus status = TransactionStatus::INITIAL(); std::shared_ptr<EntityTransactionService::Stub> service_stub; RetrieveRequest request; // TODO(tf) std::string error_message; @@ -214,40 +70,38 @@ public: auto RetrieveById(const std::string &id) -> void; /** - * Execute this transaction in blocking mode and return the state. + * Execute this transaction in blocking mode and return the status. */ auto Execute() -> TransactionStatus; /** * Execute this transaction in non-blocking mode and return immediately. * - * A client may request the current state at any time via GetState(). + * A client may request the current status at any time via GetStatus(). * * Use WaitForIt() to join the back-ground execution of this transaction. */ - auto ExecuteAsynchronously() -> void; + auto ExecuteAsynchronously() noexcept -> void; /** - * Join the background execution and return the state when the execution + * Join the background execution and return the status when the execution * terminates. * * Use this after ExecuteAsynchronously(). */ - auto WaitForIt() -> TransactionStatus; + [[nodiscard]] auto WaitForIt() const noexcept -> TransactionStatus; /** - * Return the current state of the transaction. + * Return the current status of the transaction. */ - [[nodiscard]] inline auto GetState() const -> TransactionStatus { - return this->state; + [[nodiscard]] inline auto GetStatus() const -> TransactionStatus { + return this->status; } [[nodiscard]] inline auto GetResultSet() const -> const ResultSet & { const ResultSet *result_set = this->result_set.get(); return *result_set; } - - auto GetErrors }; } // namespace caosdb::transaction diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h new file mode 100644 index 0000000000000000000000000000000000000000..8645aceb3749efbbcf8c6cfec5aa96394b029ed7 --- /dev/null +++ b/include/caosdb/transaction_status.h @@ -0,0 +1,160 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +#ifndef CAOSDB_TRANSACTION_STATUS_H +#define CAOSDB_TRANSACTION_STATUS_H + +/** + * TransactionStatus indicates the current status of a transaction and, when it + * has already terminated, whether the transaction has been successful or not. + */ +#include "caosdb/status_code.h" +#include "caosdb/exceptions.h" +#include <memory> // for shared_ptr, unique_ptr +#include <string> // for string + +namespace caosdb::transaction { +using caosdb::StatusCode; +using caosdb::exceptions::AuthenticationError; +using caosdb::exceptions::ConnectionError; +using caosdb::exceptions::GenericException; +using caosdb::exceptions::TransactionError; + +/** + * Status of a Request or Transaction. + */ +class TransactionStatus { +public: + inline static auto INITIAL() -> const TransactionStatus & { + static const TransactionStatus initial( + StatusCode::INITIAL, caosdb::get_status_description(StatusCode::INITIAL)); + return initial; + } + inline static auto EXECUTING() -> const TransactionStatus & { + static const TransactionStatus executing( + StatusCode::EXECUTING, + caosdb::get_status_description(StatusCode::EXECUTING)); + return executing; + } + inline static auto SUCCESS() -> const TransactionStatus & { + static const TransactionStatus success( + StatusCode::SUCCESS, caosdb::get_status_description(StatusCode::SUCCESS)); + return success; + } + inline static auto RPC_ERROR(const std::string &details) + -> const TransactionStatus { + return TransactionStatus( + StatusCode::GENERIC_RPC_ERROR, + caosdb::get_status_description(StatusCode::GENERIC_RPC_ERROR) + + " Original error: " + details); + } + inline static auto CONNECTION_ERROR() -> const TransactionStatus & { + static const TransactionStatus connection_error( + StatusCode::CONNECTION_ERROR, + caosdb::get_status_description(StatusCode::CONNECTION_ERROR)); + + return connection_error; + } + inline static auto AUTHENTICATION_ERROR(const std::string &details) + -> const TransactionStatus { + return TransactionStatus( + StatusCode::AUTHENTICATION_ERROR, + caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR) + + " Original error: " + details); + } + inline static auto AUTHENTICATION_ERROR() -> const TransactionStatus & { + static const TransactionStatus authentication_error( + StatusCode::AUTHENTICATION_ERROR, + caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR)); + + return authentication_error; + } + inline static auto TRANSACTION_ERROR() -> const TransactionStatus & { + static const TransactionStatus transaction_error( + StatusCode::GENERIC_TRANSACTION_ERROR, + caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR)); + return transaction_error; + } + inline static auto TRANSACTION_ERROR(const std::string &details) + -> const TransactionStatus { + return TransactionStatus( + StatusCode::GENERIC_TRANSACTION_ERROR, + caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR) + + " Original error: " + details); + } + + inline auto ThrowExceptionIfError() const -> void { + if (!IsError()) { + return; + } + switch (this->code) { + case StatusCode::CONNECTION_ERROR: + throw ConnectionError(this->description); + case StatusCode::AUTHENTICATION_ERROR: + throw AuthenticationError(this->description); + case StatusCode::GENERIC_TRANSACTION_ERROR: + throw TransactionError(this->description); + default: + throw GenericException(StatusCode::GENERIC_ERROR, this->description); + } + } + + /** + * Return true if this TransactionStatus represents a terminated state. + */ + inline auto IsTerminated() const -> bool { return this->code >= 0; }; + + /** + * Return true if this TransactionStatus represents an erroneous state. + */ + inline auto IsError() const -> bool { return this->code > 0; }; + + /** + * Return a description of the erroneous state. + * + * Returns an empty string if there is no description. + */ + inline auto GetDescription() const -> const std::string & { + return this->description; + } + + /** + * Return the status code of the state. + */ + inline auto GetCode() const -> StatusCode { return this->code; } + +private: + /** + * The code is an identifier of errors. + */ + StatusCode code; + + /** + * Description of the error + */ + std::string description; + + TransactionStatus(StatusCode code, const std::string &description) + : code(code), description(description){}; +}; + +} // namespace caosdb::transaction +#endif diff --git a/include/ccaosdb.h b/include/ccaosdb.h index 47bf568bca96c68c8457766d2279475b434de386..3d57a019556d0d1a3edf50d0106d9f463e2a2693 100644 --- a/include/ccaosdb.h +++ b/include/ccaosdb.h @@ -85,6 +85,11 @@ typedef struct { */ const char *caosdb_utility_get_env_var(const char *name, const char *fall_back); +/** + * Return a description of the status code. + */ +const char *caosdb_get_status_description(int code); + /** * Create a pem-file certificate provider. * diff --git a/proto b/proto index 3f867687cc71e42e964051b35f856abf6b1c8f09..4845aa8e479b85b50a130530bbc96a0cab4f8688 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 3f867687cc71e42e964051b35f856abf6b1c8f09 +Subproject commit 4845aa8e479b85b50a130530bbc96a0cab4f8688 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 147bddc8312d260d1a00518ea781b5ca2aec330e..ce1997523888e90403dc4f3997a58981b5ba161e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ # add all source files to this list set(libcaosdb_SRC ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/authentication.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/configuration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.cpp diff --git a/src/caosdb/configuration.cpp b/src/caosdb/configuration.cpp index 2fed3e47ca474c48fa06a8180560ae9cb65de3cb..7d6a6f0c66be8d7ef058a603b8fea7c0da11574a 100644 --- a/src/caosdb/configuration.cpp +++ b/src/caosdb/configuration.cpp @@ -19,19 +19,29 @@ * */ #include "caosdb/configuration.h" -#include "boost/iterator/iterator_facade.hpp" // for iterator_facade_base -#include "boost/json/impl/object.hpp" // for object::at, object::begin -#include "boost/json/string.hpp" // for string -#include "boost/json/string_view.hpp" // for string_view -#include "caosdb/authentication.h" // for PlainPasswordAuthentic... -#include "caosdb/connection.h" // for TlsConnectionConfiguration -#include "caosdb/constants.h" // for LIBCAOSDB_CONFIGURATIO... -#include "caosdb/exceptions.h" // for ConfigurationError -#include "caosdb/utility.h" // for load_string_file -#include <cassert> // for assert -#include <cstdlib> // for getenv -#include <grpcpp/security/credentials.h> // for SslCredentials -#include <string> // for char_traits, string +#include "boost/iterator/iterator_facade.hpp" // for iterator_facad... +#include "boost/json/impl/object.hpp" // for object::at +#include "boost/json/string.hpp" // for string +#include "boost/json/string_view.hpp" // for string_view +#include "boost/log/core/record.hpp" // for record +#include "boost/log/sources/record_ostream.hpp" // for basic_record_o... +#include "boost/preprocessor/seq/limits/enum_256.hpp" // for BOOST_PP_SEQ_E... +#include "boost/preprocessor/seq/limits/size_256.hpp" // for BOOST_PP_SEQ_S... +#include "caosdb/authentication.h" // for Authenticator +#include "caosdb/connection.h" // for ConnectionManager +#include "caosdb/constants.h" // for LIBCAOSDB_CONF... +#include "caosdb/exceptions.h" // for ConfigurationE... +#include "caosdb/log_level.h" // for CAOSDB_DEFAULT... +#include "caosdb/status_code.h" // for StatusCode +#include "caosdb/utility.h" // for get_home_direc... +#include <bits/exception.h> // for exception +#include <cassert> // for assert +#include <cstdlib> // for getenv +#include <grpcpp/security/credentials.h> // for SslCredentials +#include <iterator> // for next +#include <map> // for map +#include <stdexcept> // for out_of_range +#include <string> // for string, operator+ namespace caosdb::configuration { using boost::filesystem::exists; @@ -42,6 +52,11 @@ using caosdb::authentication::Authenticator; using caosdb::authentication::PlainPasswordAuthenticator; using caosdb::connection::ConnectionManager; using caosdb::exceptions::ConfigurationError; +using caosdb::logging::ConsoleSinkConfiguration; +using caosdb::logging::FileSinkConfiguration; +using caosdb::logging::LoggingConfiguration; +using caosdb::logging::SinkConfiguration; +using caosdb::logging::SyslogSinkConfiguration; using caosdb::utility::get_home_directory; using caosdb::utility::load_json_file; using caosdb::utility::load_string_file; @@ -254,14 +269,117 @@ auto ConnectionConfigurationHelper::CreateConnectionConfiguration( authenticator.get()); } -auto ConfigurationManager::mReset() -> void { - mClear(); - InitializeDefaults(); +auto LoggingConfigurationHelper::CreateConsoleSinkConfiguration( + const object & /*from*/, const std::string &name, int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration> { + auto result = std::make_shared<ConsoleSinkConfiguration>(name, level); + return result; } -auto ConfigurationManager::mClear() -> void { - json_configuration = value(nullptr); - ConnectionManager::Reset(); +auto LoggingConfigurationHelper::CreateSyslogSinkConfiguration( + const object & /*from*/, const std::string &name, int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration> { + auto result = std::make_shared<SyslogSinkConfiguration>(name, level); + return result; +} + +auto LoggingConfigurationHelper::CreateFileSinkConfiguration( + const object &from, const std::string &name, int level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration> { + auto result = std::make_shared<FileSinkConfiguration>(name, level); + if (from.contains("directory")) { + result->SetDirectory(from.at("directory").as_string().c_str()); + } + return result; +} + +auto LoggingConfigurationHelper::CreateSinkConfiguration( + const object &from, const std::string &name, int default_level) const + -> std::shared_ptr<caosdb::logging::SinkConfiguration> { + assert(from.contains("destination")); + const auto &destination = + std::string(from.at("destination").as_string().c_str()); + int level = from.contains("level") + ? static_cast<int>(from.at("level").as_int64()) + : default_level; + + if (destination == "file") { + return CreateFileSinkConfiguration(from, name, level); + } else if (destination == "console") { + return CreateConsoleSinkConfiguration(from, name, level); + } else if (destination == "syslog") { + return CreateSyslogSinkConfiguration(from, name, level); + } else { + throw ConfigurationError("Unknown sink destination: " + destination); + } +} + +auto LoggingConfigurationHelper::CreateLoggingConfiguration( + const object &from) const -> LoggingConfiguration { + auto default_level_str = from.contains("level") + ? std::string(from.at("level").as_string().c_str()) + : ""; + int default_level = 0; + static std::map<std::string, int> log_level_names = { + {"", CAOSDB_DEFAULT_LOG_LEVEL}, {"off", CAOSDB_LOG_LEVEL_OFF}, + {"fatal", CAOSDB_LOG_LEVEL_FATAL}, {"error", CAOSDB_LOG_LEVEL_ERROR}, + {"warn", CAOSDB_LOG_LEVEL_WARN}, {"info", CAOSDB_LOG_LEVEL_INFO}, + {"debug", CAOSDB_LOG_LEVEL_DEBUG}, {"trace", CAOSDB_LOG_LEVEL_TRACE}, + {"all", CAOSDB_LOG_LEVEL_ALL}}; + + try { + default_level = CAOSDB_DEFAULT_LOG_LEVEL; + } catch (const std::out_of_range &exc) { + throw ConfigurationError("Unknown log level: " + default_level_str); + } + + auto result = LoggingConfiguration(default_level); + if (default_level == CAOSDB_LOG_LEVEL_OFF) { + return result; + } + + const auto &sinks = from.at("sinks").as_object(); + if (!sinks.empty()) { + const auto *elem = sinks.begin(); + + while (elem != sinks.end()) { + result.AddSink(CreateSinkConfiguration( + elem->value().as_object(), elem->key().to_string(), default_level)); + elem = std::next(elem); + } + } + + return result; +} + +auto ConfigurationManager::mReset() noexcept -> int { + try { + mClear(); + InitializeDefaults(); + return StatusCode::SUCCESS; + } catch (const caosdb::exceptions::GenericException &exc) { + return exc.GetCode(); + } catch (const std::exception &exc) { + CAOSDB_LOG_ERROR(logger_name) + << "Unknown error during the reset of the ConfigurationManager: " + << exc.what(); + return StatusCode::CONFIGURATION_ERROR; + } +} + +auto ConfigurationManager::mClear() noexcept -> int { + try { + json_configuration = value(nullptr); + ConnectionManager::Reset(); + return StatusCode::SUCCESS; + } catch (const caosdb::exceptions::GenericException &exc) { + return exc.GetCode(); + } catch (const std::exception &exc) { + CAOSDB_LOG_ERROR(logger_name) + << "Unknown error during the reset of the ConfigurationManager: " + << exc.what(); + return StatusCode::CONFIGURATION_ERROR; + } } auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) @@ -274,7 +392,6 @@ auto ConfigurationManager::mLoadSingleJSONConfiguration(const path &json_file) } json_configuration = load_json_file(json_file); - // TODO(far future) validate against json-schema } auto ConfigurationManager::mGetConnectionConfiguration( @@ -345,7 +462,7 @@ auto ConfigurationManager::GetConnection(const std::string &name) const "' has not been defined."); } -auto ConfigurationManager::InitializeDefaults() -> void { +auto ConfigurationManager::InitializeDefaults() -> int { // find the configuration file... std::unique_ptr<path> configuration_file_path; @@ -389,9 +506,21 @@ auto ConfigurationManager::InitializeDefaults() -> void { if (configuration_file_path != nullptr) { // TODO(tf): log which file has been used. mLoadSingleJSONConfiguration(*configuration_file_path); + } + + if (this->json_configuration.as_object().contains("logging")) { + LoggingConfiguration logging_configuration = + logging_configuration_helper.CreateLoggingConfiguration( + json_configuration.at("logging").as_object()); + logging::initialize_logging(logging_configuration); } else { - // TODO(tf): log warning: no configuration files has been found + logging::initialize_logging_defaults(); + CAOSDB_LOG_WARN(logger_name) << "No configuration has been found."; } + + return 0; } +const int IS_INITIALIZED = ConfigurationManager::Reset(); + } // namespace caosdb::configuration diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp index 43217a4527f78f0425b5c636a1452a4b3b1b734c..4ff50d24d329fa6ece3a8e87d304378f3d157782 100644 --- a/src/caosdb/connection.cpp +++ b/src/caosdb/connection.cpp @@ -21,31 +21,28 @@ */ #include "caosdb/connection.h" #include "caosdb/configuration.h" // for ConnectionConfigur... -#include "caosdb/exceptions.h" // for AuthenticationError +#include "caosdb/exceptions.h" // for ConfigurationError #include "caosdb/info.h" // for VersionInfo #include "caosdb/info/v1alpha1/main.grpc.pb.h" // for GeneralInfoService -#include "caosdb/info/v1alpha1/main.pb.h" // for GetVersionInfoResp... +#include "caosdb/info/v1alpha1/main.pb.h" // for GetVersionInfoRequest #include "caosdb/transaction.h" // for Transaction +#include "caosdb/transaction_status.h" // for TransactionStatus #include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH... #include <grpcpp/create_channel.h> // for CreateChannel #include <grpcpp/impl/codegen/client_context.h> // for ClientContext #include <grpcpp/impl/codegen/status.h> // for Status -#include <iostream> // for operator<< -#include <memory> // for allocator, shared_ptr -#include <stdexcept> // for runtime_error #include <string> // for string, operator+ namespace caosdb::connection { using caosdb::configuration::ConfigurationManager; using caosdb::configuration::ConnectionConfiguration; using caosdb::entity::v1alpha1::EntityTransactionService; -using caosdb::exceptions::AuthenticationError; -using caosdb::exceptions::ConnectionError; using caosdb::info::VersionInfo; using caosdb::info::v1alpha1::GeneralInfoService; using caosdb::info::v1alpha1::GetVersionInfoRequest; using caosdb::info::v1alpha1::GetVersionInfoResponse; using caosdb::transaction::Transaction; +using caosdb::transaction::TransactionStatus; Connection::Connection(const ConnectionConfiguration &configuration) { const std::string target = @@ -63,20 +60,27 @@ auto Connection::RetrieveVersionInfoNoExceptions() const noexcept const GetVersionInfoRequest request; GetVersionInfoResponse response; grpc::ClientContext context; - const grpc::Status status = + const grpc::Status grpc_status = this->general_info_service->GetVersionInfo(&context, request, &response); - TransactionStatus &status = TransactionStatus::SUCCESS; - if (!status.ok()) { - switch (status.error_code()) { + auto status = TransactionStatus::SUCCESS(); + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { case grpc::StatusCode::UNAUTHENTICATED: - status = TransactionStatus::AUTHENTICATION_ERROR; + status = + TransactionStatus::AUTHENTICATION_ERROR(grpc_status.error_message()); + break; case grpc::StatusCode::UNAVAILABLE: - status = TransactionStatus::CONNECTION_ERROR; + status = TransactionStatus::CONNECTION_ERROR(); + break; default: - status = TransactionStatus::RPC_ERROR.Instantiate( - std::to_string(status.error_code()) + " - " _(status.error_message())); + auto error_message = grpc_status.error_message(); + status = TransactionStatus::RPC_ERROR( + std::to_string(grpc_status.error_code()) + " - " + error_message); } + } else { + this->version_info = + std::make_unique<VersionInfo>(response.release_version_info()); } return status; @@ -85,10 +89,8 @@ auto Connection::RetrieveVersionInfoNoExceptions() const noexcept auto Connection::RetrieveVersionInfo() const -> const VersionInfo & { TransactionStatus status = RetrieveVersionInfoNoExceptions(); - if (status.IsError()) { - throw status.CreateException(); - } - return GetVersionInfo(); + status.ThrowExceptionIfError(); + return *GetVersionInfo(); } [[nodiscard]] auto Connection::CreateTransaction() const diff --git a/src/caosdb/logging.cpp b/src/caosdb/logging.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a2e85de52e72dfae327e4381ecdb8cb17be04122 --- /dev/null +++ b/src/caosdb/logging.cpp @@ -0,0 +1,155 @@ +/* + * This file is a part of the CaosDB Project. + * + * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com> + * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ +#include "caosdb/logging.h" +#include "caosdb/log_level.h" +#include <boost/log/sources/global_logger_storage.hpp> +#include <boost/log/sources/record_ostream.hpp> +#include <boost/log/sources/severity_channel_logger.hpp> +#include <boost/log/utility/setup/common_attributes.hpp> +#include <boost/log/utility/setup/formatter_parser.hpp> +#include <boost/log/utility/setup/from_settings.hpp> +#include <boost/log/utility/setup/settings.hpp> +#include <cstddef> +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +namespace caosdb::logging { + +LoggingConfiguration::LoggingConfiguration(int level) + : LevelConfiguration(level) {} + +auto LoggingConfiguration::AddSink( + const std::shared_ptr<SinkConfiguration> &sink) -> void { + this->sinks.push_back(sink); +} + +auto LoggingConfiguration::GetSinks() const + -> const std::vector<std::shared_ptr<SinkConfiguration>> & { + return this->sinks; +} + +SinkConfiguration::SinkConfiguration(std::string name, int level) + : LevelConfiguration(level), name(std::move(name)) {} +[[nodiscard]] auto SinkConfiguration::GetName() const -> const std::string & { + return this->name; +} + +auto SinkConfiguration::Configure(boost::log::settings &settings) const + -> void { + CAOSDB_LOG_TRACE(logger_name) + << "Enter SinkConfiguration::Configure(&settings)"; + auto sink = "Sinks." + GetName(); + settings[sink]["Destination"] = GetDestination(); + settings[sink]["Filter"] = "%Severity% >= " + std::to_string(GetLevel()); + settings[sink]["AutoFlush"] = true; + settings[sink]["Format"] = "[%TimeStamp%][%Severity%] %Channel% - %Message%"; +} + +ConsoleSinkConfiguration::ConsoleSinkConfiguration(const std::string &name, + int level) + : SinkConfiguration(name, level) {} +[[nodiscard]] auto ConsoleSinkConfiguration::GetDestination() const + -> const std::string & { + CAOSDB_LOG_TRACE(logger_name) + << "Enter ConsoleSinkConfiguration::GetDestination()"; + return this->destination; +} + +auto ConsoleSinkConfiguration::Configure(boost::log::settings &settings) const + -> void { + CAOSDB_LOG_TRACE(logger_name) + << "Enter ConsoleSinkConfiguration::Configure(&settings)"; + sink_configuration::Configure(settings); +} + +FileSinkConfiguration::FileSinkConfiguration(const std::string &name, int level) + : SinkConfiguration(name, level) {} +[[nodiscard]] auto FileSinkConfiguration::GetDestination() const + -> const std::string & { + CAOSDB_LOG_TRACE(logger_name) + << "Enter FileSinkConfiguration::GetDestination()"; + return this->destination; +} +auto FileSinkConfiguration::SetDirectory(const std::string &directory) -> void { + this->directory = std::string(directory); +} + +auto FileSinkConfiguration::Configure(boost::log::settings &settings) const + -> void { + CAOSDB_LOG_TRACE(logger_name) + << "Enter FileSinkConfiguration::Configure(&settings)"; + sink_configuration::Configure(settings); + settings["Sink." + GetName() + ".Target"] = this->directory; +} + +SyslogSinkConfiguration::SyslogSinkConfiguration(const std::string &name, + int level) + : SinkConfiguration(name, level) {} +[[nodiscard]] auto SyslogSinkConfiguration::GetDestination() const + -> const std::string & { + return this->destination; +} + +auto initialize_logging_defaults() -> int { + const static std::vector<std::shared_ptr<SinkConfiguration>> default_sinks = { + std::make_shared<ConsoleSinkConfiguration>("DEFAULT_SINK_1", + CAOSDB_DEFAULT_LOG_LEVEL)}; + + boost::log::settings default_settings; + + default_settings["Core.DisableLogging"] = false; + + for (const auto &sink : default_sinks) { + sink->Configure(default_settings); + } + + boost::log::init_from_settings(default_settings); + auto core = boost::log::core::get(); + core->add_global_attribute("TimeStamp", + boost::log::attributes::local_clock()); + + CAOSDB_LOG_DEBUG(logger_name) << "Initialized default settings."; + + return 0; +} + +auto initialize_logging(const LoggingConfiguration &configuration) -> void { + boost::log::settings new_settings; + + if (configuration.GetLevel() == CAOSDB_LOG_LEVEL_OFF) { + new_settings["Core.DisableLogging"] = true; + return; + } else { + new_settings["Core.DisableLogging"] = false; + } + + for (const auto &sink : configuration.GetSinks()) { + sink->Configure(new_settings); + } + + boost::log::init_from_settings(new_settings); + + CAOSDB_LOG_DEBUG(logger_name) << "Initialized logging with custom settings."; +} + +} // namespace caosdb::logging diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp index 31b552a58bde9110d4064f2c5dc6186c64432057..94156745554790ad157d446609865b0e59aafb31 100644 --- a/src/caosdb/transaction.cpp +++ b/src/caosdb/transaction.cpp @@ -20,22 +20,59 @@ #include "caosdb/transaction.h" #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS... #include "caosdb/entity/v1alpha1/main.pb.h" // for RetrieveRequest -#include "caosdb/exceptions.h" // for AuthenticationError +#include "grpcpp/grpcpp.h" // for CompletionQueue +#include "grpcpp/impl/codegen/async_unary_call.h" // for ClientAsyncRespons... +#include "grpcpp/impl/codegen/client_context.h" // for ClientContext +#include "grpcpp/impl/codegen/completion_queue.h" // for CompletionQueue +#include "grpcpp/impl/codegen/status.h" // for Status #include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH... -#include <grpcpp/impl/codegen/client_context.h> // for ClientContext -#include <grpcpp/impl/codegen/status.h> // for Status -#include <iostream> // for basic_ostream::ope... -#include <memory> // for make_unique, share... -#include <stdexcept> // for runtime_error +#include <cassert> // for assert +#include <memory> // for allocator, unique_ptr #include <utility> // for move +namespace caosdb { + +// TODO(tf) move to another source file. +auto get_status_description(int code) -> const std::string & { + static const std::string MISSING_DESCRIPTION = "MISSING DESCRIPTION"; + static const std::map<int, std::string> descriptions = { + {StatusCode::INITIAL, "The transaction has not been executed yet and " + "clients can stil change it."}, + {StatusCode::EXECUTING, "The transaction is currently being executed."}, + {StatusCode::SUCCESS, "The action was successful"}, + {StatusCode::CONNECTION_ERROR, + "The attempt to execute this transaction was not successful because the " + "connection to the server could not be established."}, + {StatusCode::AUTHENTICATION_ERROR, + "The attempt to execute this transaction has not been executed at all " + "because the authentication did not succeed."}, + {StatusCode::GENERIC_RPC_ERROR, + "The attempt to execute this transaction was not successful because an " + "error occured in the transport or RPC protocol layer."}, + {StatusCode::GENERIC_ERROR, + "An error occured. Please review the logs for more information."}, + {StatusCode::GENERIC_TRANSACTION_ERROR, + "The transaction terminated unsuccessfully with transaction errors."}, + {StatusCode::CONFIGURATION_ERROR, + "An error occurred during the configuration of the ConfigurationManager."}, + {StatusCode::UNKNOWN_CONNECTION_ERROR, + "The ConnectionManager does not know any connection of this name."}}; + try { + return descriptions.at(code); + } catch (const std::out_of_range &exc) { + return MISSING_DESCRIPTION; + } +} + +} // namespace caosdb + namespace caosdb::transaction { using caosdb::entity::v1alpha1::EntityTransactionService; using caosdb::entity::v1alpha1::RetrieveRequest; using caosdb::entity::v1alpha1::RetrieveResponse; +using grpc::ClientAsyncResponseReader; using ProtoEntity = caosdb::entity::v1alpha1::Entity; -using caosdb::exceptions::AuthenticationError; -using caosdb::exceptions::ConnectionError; +using grpc::CompletionQueue; [[nodiscard]] auto UniqueResult::GetEntity() const -> const Entity & { const Entity *result = this->entity.get(); @@ -56,40 +93,60 @@ auto Transaction::RetrieveById(const std::string &id) -> void { } auto Transaction::Execute() -> TransactionStatus { - // TODO(tf) throw error if not int INIT state - this->state = TransactionStatus::EXECUTING; + ExecuteAsynchronously(); + this->status.ThrowExceptionIfError(); + return this->status; +} + +auto Transaction::ExecuteAsynchronously() noexcept -> void { + this->status = TransactionStatus::EXECUTING(); + grpc::Status grpc_status; + CompletionQueue cq; RetrieveResponse response; + grpc::ClientContext context; - const grpc::Status status = - this->service_stub->Retrieve(&context, this->request, &response); + std::unique_ptr<ClientAsyncResponseReader<RetrieveResponse>> rpc( + this->service_stub->PrepareAsyncRetrieve(&context, this->request, &cq)); + rpc->StartCall(); + + int tag = 1; + void *send_tag = static_cast<void *>(&tag); + rpc->Finish(&response, &grpc_status, send_tag); + void *recv_tag = nullptr; + bool ok = false; + + // TODO(tf) make this actually asynchronous by moving this to WaitForIt() + cq.Next(&recv_tag, &ok); + assert(recv_tag == send_tag); + assert(ok); + + // const grpc::Status grpc_status = + // this->service_stub->Retrieve(&context, this->request, &response); - if (!status.ok()) { - // TODO - switch (status.error_code()) { + if (!grpc_status.ok()) { + switch (grpc_status.error_code()) { case grpc::StatusCode::UNAUTHENTICATED: - this->state = TransactionStatus::ERROR.Instantiate( - StatusCode::UNAUTHENTICATED, status.error_message()); + this->status = TransactionStatus::AUTHENTICATION_ERROR(); + break; case grpc::StatusCode::UNAVAILABLE: - this->state = TransactionStatus::ERROR.Instantiate( - StatusCode::UNAVAILABLE, status.error_message()); + this->status = TransactionStatus::CONNECTION_ERROR(); + break; default: - this->state = TransactionStatus::ERROR.Instantiate( - StatusCode::GENERIC_RPC_ERROR, status.error_message()); + auto error_details = std::to_string(grpc_status.error_code()) + " - " + + grpc_status.error_message(); + this->status = TransactionStatus::RPC_ERROR(error_details); } } else { - this->state = TransactionStatus::SUCCESS; + this->status = TransactionStatus::SUCCESS(); } auto *entity = response.release_entity(); - auto result_set = std::make_unique<UniqueResult>(entity); - this->result_set = std::move(result_set); - - return this->state; + this->result_set = std::make_unique<UniqueResult>(entity); } -auto Transaction::ExecuteAsynchronously() -> TransactionStatus {} - -auto Transaction::WaitForIt() -> TransactionStatus {} +auto Transaction::WaitForIt() const noexcept -> TransactionStatus { + return this->status; +} } // namespace caosdb::transaction diff --git a/src/ccaosdb.cpp b/src/ccaosdb.cpp index 42ca0488bfaa6fe99f94c1acf2b4f7fa5d32fb07..ee80b61bec67df01b662b970d3c0422cb48950a2 100644 --- a/src/ccaosdb.cpp +++ b/src/ccaosdb.cpp @@ -2,12 +2,29 @@ #include "caosdb/connection.h" #include "caosdb/constants.h" #include "caosdb/utility.h" +#include "caosdb/status_code.h" +#include "caosdb/logging.h" #include <cassert> +#include <exception> #include <iostream> #include <stdio.h> extern "C" { +/* + * Macro for wrapping every function into a try-catch clause. If an exception + * occurs, the given StatusCode is being returned. + */ +#define ERROR_RETURN_CODE(code, fun, body) \ + fun { \ + try { \ + body \ + } catch (const std::exception &exc) { \ + CAOSDB_LOG_FATAL("ccaosdb") << "Exception: " << exc.what(); \ + return caosdb::StatusCode::code; \ + } \ + } + int caosdb_constants_LIBCAOSDB_VERSION_MAJOR() { return caosdb::LIBCAOSDB_VERSION_MAJOR; } @@ -41,143 +58,185 @@ const char *caosdb_utility_get_env_var(const char *name, return caosdb::utility::get_env_var(name, fall_back); } -int caosdb_connection_create_pem_file_certificate_provider( - caosdb_connection_certificate_provider *out, const char *path) { - out->wrapped_certificate_provider = - new caosdb::configuration::PemFileCertificateProvider(std::string(path)); - return 0; -} - -int caosdb_connection_delete_certificate_provider( - caosdb_connection_certificate_provider *provider) { - delete static_cast<caosdb::configuration::CertificateProvider *>( - provider->wrapped_certificate_provider); - return 0; -} - -int caosdb_authentication_create_plain_password_authenticator( - caosdb_authentication_authenticator *out, const char *username, - const char *password) { - out->wrapped_authenticator = - new caosdb::authentication::PlainPasswordAuthenticator( - std::string(username), std::string(password)); - return 0; -} - -int caosdb_authentication_delete_authenticator( - caosdb_authentication_authenticator *authenticator) { - delete static_cast<caosdb::authentication::Authenticator *>( - authenticator->wrapped_authenticator); - return 0; -} - -int caosdb_connection_create_tls_connection_configuration( - caosdb_connection_connection_configuration *out, const char *host, - const int port, caosdb_authentication_authenticator *authenticator, - caosdb_connection_certificate_provider *provider) { - - auto host_str = std::string(host); - if (authenticator != nullptr && provider != nullptr) { - auto wrapped_provider = - static_cast<caosdb::configuration::CertificateProvider *>( - provider->wrapped_certificate_provider); - auto wrapped_authenticator = - static_cast<caosdb::authentication::Authenticator *>( - authenticator->wrapped_authenticator); - out->wrapped_connection_configuration = - new caosdb::configuration::TlsConnectionConfiguration( - host_str, port, *wrapped_provider, *wrapped_authenticator); - } else if (authenticator != nullptr) { - auto wrapped_authenticator = - static_cast<caosdb::authentication::Authenticator *>( - authenticator->wrapped_authenticator); - out->wrapped_connection_configuration = - new caosdb::configuration::TlsConnectionConfiguration( - host_str, port, *wrapped_authenticator); - } else if (provider != nullptr) { - auto wrapped_provider = - static_cast<caosdb::configuration::CertificateProvider *>( - provider->wrapped_certificate_provider); +const char *caosdb_get_status_description(int code) { + return caosdb::get_status_description(code).c_str(); +} + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_connection_create_pem_file_certificate_provider( + caosdb_connection_certificate_provider *out, + const char *path), + { + out->wrapped_certificate_provider = + new caosdb::configuration::PemFileCertificateProvider( + std::string(path)); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_delete_certificate_provider( + caosdb_connection_certificate_provider *provider), + { + delete static_cast<caosdb::configuration::CertificateProvider *>( + provider->wrapped_certificate_provider); + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_authentication_create_plain_password_authenticator( + caosdb_authentication_authenticator *out, + const char *username, const char *password), + { + out->wrapped_authenticator = + new caosdb::authentication::PlainPasswordAuthenticator( + std::string(username), std::string(password)); + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_authentication_delete_authenticator( + caosdb_authentication_authenticator *authenticator), + { + delete static_cast<caosdb::authentication::Authenticator *>( + authenticator->wrapped_authenticator); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_create_tls_connection_configuration( + caosdb_connection_connection_configuration *out, const char *host, + const int port, caosdb_authentication_authenticator *authenticator, + caosdb_connection_certificate_provider *provider), + { + auto host_str = std::string(host); + if (authenticator != nullptr && provider != nullptr) { + auto wrapped_provider = + static_cast<caosdb::configuration::CertificateProvider *>( + provider->wrapped_certificate_provider); + auto wrapped_authenticator = + static_cast<caosdb::authentication::Authenticator *>( + authenticator->wrapped_authenticator); + out->wrapped_connection_configuration = + new caosdb::configuration::TlsConnectionConfiguration( + host_str, port, *wrapped_provider, *wrapped_authenticator); + } else if (authenticator != nullptr) { + auto wrapped_authenticator = + static_cast<caosdb::authentication::Authenticator *>( + authenticator->wrapped_authenticator); + out->wrapped_connection_configuration = + new caosdb::configuration::TlsConnectionConfiguration( + host_str, port, *wrapped_authenticator); + } else if (provider != nullptr) { + auto wrapped_provider = + static_cast<caosdb::configuration::CertificateProvider *>( + provider->wrapped_certificate_provider); + out->wrapped_connection_configuration = + new caosdb::configuration::TlsConnectionConfiguration( + host_str, port, *wrapped_provider); + } else { + out->wrapped_connection_configuration = + new caosdb::configuration::TlsConnectionConfiguration(host_str, port); + } + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_create_insecure_connection_configuration( + caosdb_connection_connection_configuration *out, const char *host, + const int port), + { out->wrapped_connection_configuration = - new caosdb::configuration::TlsConnectionConfiguration(host_str, port, - *wrapped_provider); - } else { - out->wrapped_connection_configuration = - new caosdb::configuration::TlsConnectionConfiguration(host_str, port); - } - return 0; -} - -int caosdb_connection_create_insecure_connection_configuration( - caosdb_connection_connection_configuration *out, const char *host, - const int port) { - out->wrapped_connection_configuration = - new caosdb::configuration::InsecureConnectionConfiguration(host, port); - return 0; -} - -int caosdb_connection_delete_connection_configuration( - caosdb_connection_connection_configuration *configuration) { - delete static_cast<caosdb::configuration::ConnectionConfiguration *>( - configuration->wrapped_connection_configuration); - return 0; -} - -int caosdb_connection_create_connection( - caosdb_connection_connection *out, - const caosdb_connection_connection_configuration *configuration) { - caosdb::configuration::ConnectionConfiguration *config = - static_cast<caosdb::configuration::ConnectionConfiguration *>( + new caosdb::configuration::InsecureConnectionConfiguration(host, port); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_delete_connection_configuration( + caosdb_connection_connection_configuration *configuration), + { + delete static_cast<caosdb::configuration::ConnectionConfiguration *>( configuration->wrapped_connection_configuration); - out->wrapped_connection = new caosdb::connection::Connection(*config); - return 0; -} - -int caosdb_connection_delete_connection( - caosdb_connection_connection *connection) { - delete static_cast<caosdb::connection::Connection *>( - connection->wrapped_connection); - return 0; -} - -int caosdb_connection_get_version_info( - caosdb_info_version_info *out, - const caosdb_connection_connection *connection) { - auto *wrapped_connection = static_cast<caosdb::connection::Connection *>( - connection->wrapped_connection); - auto version_info = wrapped_connection->GetVersionInfo(); - - out->major = (int)version_info->GetMajor(); - out->minor = (int)version_info->GetMinor(); - out->patch = (int)version_info->GetPatch(); - - // copy pre_release, needs local variable because out->pre_release is const - char *pre_release = - (char *)malloc(sizeof(char) * (version_info->GetPreRelease().length() + 1)); - strcpy(pre_release, version_info->GetPreRelease().c_str()); - out->pre_release = pre_release; - - // copy build, needs local variable because out->build is const - char *build = - (char *)malloc(sizeof(char) * (version_info->GetBuild().length() + 1)); - strcpy(build, version_info->GetBuild().c_str()); - out->build = build; - - return 0; -} - -int caosdb_connection_connection_manager_get_default_connection( - caosdb_connection_connection *out) { - out->wrapped_connection = - caosdb::connection::ConnectionManager::GetDefaultConnection().get(); - return 0; -} - -int caosdb_connection_connection_manager_get_connection( - caosdb_connection_connection *out, const char *name) { - out->wrapped_connection = - caosdb::connection::ConnectionManager::GetConnection(std::string(name)) - .get(); - return 0; -} + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_create_connection( + caosdb_connection_connection *out, + const caosdb_connection_connection_configuration *configuration), + { + caosdb::configuration::ConnectionConfiguration *config = + static_cast<caosdb::configuration::ConnectionConfiguration *>( + configuration->wrapped_connection_configuration); + out->wrapped_connection = new caosdb::connection::Connection(*config); + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_connection_delete_connection( + caosdb_connection_connection *connection), + { + delete static_cast<caosdb::connection::Connection *>( + connection->wrapped_connection); + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_get_version_info( + caosdb_info_version_info *out, + const caosdb_connection_connection *connection), + { + auto *wrapped_connection = static_cast<caosdb::connection::Connection *>( + connection->wrapped_connection); + + auto status = wrapped_connection->RetrieveVersionInfoNoExceptions(); + if (status.IsError()) { + return status.GetCode(); + } + auto version_info = wrapped_connection->GetVersionInfo(); + + out->major = (int)version_info->GetMajor(); + out->minor = (int)version_info->GetMinor(); + out->patch = (int)version_info->GetPatch(); + + // copy pre_release, needs local variable because out->pre_release is const + char *pre_release = (char *)malloc( + sizeof(char) * (version_info->GetPreRelease().length() + 1)); + strcpy(pre_release, version_info->GetPreRelease().c_str()); + out->pre_release = pre_release; + + // copy build, needs local variable because out->build is const + char *build = + (char *)malloc(sizeof(char) * (version_info->GetBuild().length() + 1)); + strcpy(build, version_info->GetBuild().c_str()); + out->build = build; + + return 0; + }) + +ERROR_RETURN_CODE( + GENERIC_ERROR, + int caosdb_connection_connection_manager_get_default_connection( + caosdb_connection_connection *out), + { + out->wrapped_connection = + caosdb::connection::ConnectionManager::GetDefaultConnection().get(); + return 0; + }) + +ERROR_RETURN_CODE(GENERIC_ERROR, + int caosdb_connection_connection_manager_get_connection( + caosdb_connection_connection *out, const char *name), + { + out->wrapped_connection = + caosdb::connection::ConnectionManager::GetConnection( + std::string(name)) + .get(); + return 0; + }) } diff --git a/src/ccaosdbcli.c b/src/ccaosdbcli.c index ba30b8d6493598e4b06991788ae8fe6001ec1cfb..90cf6ed7e1d5b1c82bd1ea268c68681cd6e666b0 100644 --- a/src/ccaosdbcli.c +++ b/src/ccaosdbcli.c @@ -11,11 +11,15 @@ int main(void) { caosdb_connection_connection_manager_get_default_connection(&connection); caosdb_info_version_info version_info; - caosdb_connection_get_version_info(&version_info, &connection); - - printf("Server version: %d.%d.%d-%s-%s\n", version_info.major, - version_info.minor, version_info.patch, version_info.pre_release, - version_info.build); + int status = caosdb_connection_get_version_info(&version_info, &connection); + if (status == 0) { + printf("Server version: %d.%d.%d-%s-%s\n", version_info.major, + version_info.minor, version_info.patch, version_info.pre_release, + version_info.build); + /*} else {*/ + /*printf("An error occured: ERROR %d - %s\n", status,*/ + /*caosdb_get_status_description(status));*/ + } return 0; } diff --git a/src/cxxcaosdbcli.cpp b/src/cxxcaosdbcli.cpp index 0603af132cf7f1febbbd67e344a13f3c84253594..b33026d22b27b5c506891ca98f449ec99494345c 100644 --- a/src/cxxcaosdbcli.cpp +++ b/src/cxxcaosdbcli.cpp @@ -31,6 +31,7 @@ #include <string> // for operator<<, char_traits auto main() -> int { + std::cout << "CaosDB C++ client (libcaosdb " << caosdb::LIBCAOSDB_VERSION_MINOR << "." << caosdb::LIBCAOSDB_VERSION_MINOR << "." @@ -41,6 +42,7 @@ auto main() -> int { const auto &connection = caosdb::connection::ConnectionManager::GetDefaultConnection(); + connection->RetrieveVersionInfoNoExceptions(); // get version info of the server const auto &v_info = connection->GetVersionInfo(); std::cout << "Server Version: " << v_info->GetMajor() << "." diff --git a/test/test_configuration.cpp b/test/test_configuration.cpp index fa5ecb2fc77e69132226044f4a7a129129f4e320..abc797fbe97fb408e0ff05ea5deab2a8581f194a 100644 --- a/test/test_configuration.cpp +++ b/test/test_configuration.cpp @@ -19,12 +19,15 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ -#include "caosdb/configuration.h" // for ConfigurationManager, Configurati... +#include "caosdb/configuration.h" // for ConfigurationError, Configuration... #include "caosdb/exceptions.h" // for ConfigurationError +#include "caosdb/log_level.h" // for CAOSDB_DEFAULT_LOG_LEVEL, CAOSDB_... +#include "caosdb/logging.h" // for ConsoleSinkConfiguration, Logging... #include "caosdb_test_utility.h" // for EXPECT_THROW_MESSAGE, TEST_DATA_DIR #include <gtest/gtest-message.h> // for Message #include <gtest/gtest-test-part.h> // for TestPartResult, SuiteApiResolver -#include <gtest/gtest_pred_impl.h> // for Test, TestInfo, TEST +#include <gtest/gtest_pred_impl.h> // for TestInfo, TEST_F, Test +#include <memory> // for make_shared #include <string> // for operator+, allocator, string namespace caosdb::configuration { @@ -76,4 +79,15 @@ TEST_F(test_configuration, get_default_connection_configuration_error) { ConfigurationManager::Clear(); } +TEST_F(test_configuration, initialize_logging) { + auto logging_configuration = + caosdb::logging::LoggingConfiguration(CAOSDB_LOG_LEVEL_ALL); + auto console_sink = + std::make_shared<caosdb::logging::ConsoleSinkConfiguration>( + "console", CAOSDB_DEFAULT_LOG_LEVEL); + logging_configuration.AddSink(console_sink); + + initialize_logging(logging_configuration); +} + } // namespace caosdb::configuration diff --git a/test/test_data/test_caosdb_client.json b/test/test_data/test_caosdb_client.json index 10102bc84b612afd2f836374421310670a2c1c53..2007413021b332c3bd32686363e241804a8d62ab 100644 --- a/test/test_data/test_caosdb_client.json +++ b/test/test_data/test_caosdb_client.json @@ -20,6 +20,20 @@ } } }, + "logging": { + "level": "info", + "sinks": { + "stderr": { + "destination": "console" + }, + "file" : { + "destination": "file" + }, + "syslog": { + "destination": "syslog" + } + } + }, "extension": { "this is my": "special option" } diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp index e970b6df14ae4d407ad3eb2825a1d1a01fcb6692..c2c76969580f7d915fd6bb4555fa32a1f91e11bb 100644 --- a/test/test_transaction.cpp +++ b/test/test_transaction.cpp @@ -25,6 +25,7 @@ #include "caosdb/entity/v1alpha1/main.pb.h" // for Entity #include "caosdb/exceptions.h" // for ConnectionError #include "caosdb/transaction.h" // for Transaction, UniqueResult +#include "caosdb/transaction_status.h" // for ConnectionError #include "caosdb_test_utility.h" // for EXPECT_THROW_MESSAGE #include "gtest/gtest-message.h" // for Message #include "gtest/gtest-test-part.h" // for SuiteApiResolver, TestPa... @@ -45,8 +46,10 @@ TEST(test_transaction, create_transaction) { auto transaction = connection.CreateTransaction(); transaction->RetrieveById("100"); - EXPECT_THROW_MESSAGE(transaction->Execute(), ConnectionError, - "failed to connect to all addresses"); + EXPECT_THROW_MESSAGE( + transaction->Execute(), ConnectionError, + "The attempt to execute this transaction was not successful because the " + "connection to the server could not be established."); } TEST(test_transaction, unique_result) {