diff --git a/src/common/src/bson-dsl.md b/src/common/src/bson-dsl.md index 678570eb307..209d70225ee 100644 --- a/src/common/src/bson-dsl.md +++ b/src/common/src/bson-dsl.md @@ -262,6 +262,12 @@ Generate a UTF-8 value from the given null-terminated character array beginning at `zstr`. +#### `oid(const bson_oid_t* oid)` + +Generate an ObjectId value from the given C expression that evaluates to +a bson_oid_t pointer. + + #### `utf8_w_len(const char* str, int len)` Generate a UTF-8 value from the character array beginning at `str`, with length diff --git a/src/common/src/common-bson-dsl-private.h b/src/common/src/common-bson-dsl-private.h index 9331dd04c1d..a628acd8087 100644 --- a/src/common/src/common-bson-dsl-private.h +++ b/src/common/src/common-bson-dsl-private.h @@ -246,6 +246,13 @@ BSON_IF_GNU_LIKE (_Pragma ("GCC diagnostic ignored \"-Wshadow\"")) #define _bsonArrayOperation_boolean(X) _bsonArrayAppendValue (boolean (X)) #define _bsonValueOperation_boolean(b) _bsonValueOperation_bool (b) +#define _bsonValueOperation_oid(o) \ + if (!bson_append_oid (_bsonBuildAppendArgs, (o))) { \ + bsonBuildError = "Error while appending oid(" _bsonDSL_str (o) ")"; \ + } else \ + ((void) 0) +#define _bsonArrayOperation_oid(X) _bsonArrayAppendValue (oid (X)) + #define _bsonValueOperation_null \ if (!bson_append_null (_bsonBuildAppendArgs)) { \ bsonBuildError = "Error while appending a null"; \ diff --git a/src/common/src/common-oid-private.h b/src/common/src/common-oid-private.h new file mode 100644 index 00000000000..b6121545475 --- /dev/null +++ b/src/common/src/common-oid-private.h @@ -0,0 +1,36 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common-prelude.h" + +#ifndef MONGO_C_DRIVER_COMMON_OID_PRIVATE_H +#define MONGO_C_DRIVER_COMMON_OID_PRIVATE_H + +#include + +BSON_BEGIN_DECLS + +extern const bson_oid_t kZeroObjectId; + +void +mcommon_oid_set_zero (bson_oid_t *oid); + +bool +mcommon_oid_is_zero (const bson_oid_t *oid); + +BSON_END_DECLS + +#endif /* MONGO_C_DRIVER_COMMON_OID_PRIVATE_H */ diff --git a/src/common/src/common-oid.c b/src/common/src/common-oid.c new file mode 100644 index 00000000000..687f47fe577 --- /dev/null +++ b/src/common/src/common-oid.c @@ -0,0 +1,33 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +const bson_oid_t kZeroObjectId = {{0}}; + +void +mcommon_oid_set_zero (bson_oid_t *oid) +{ + BSON_ASSERT (oid); + memset (oid, 0, sizeof *oid); +} + +bool +mcommon_oid_is_zero (const bson_oid_t *oid) +{ + BSON_ASSERT (oid); + return bson_oid_equal_unsafe (oid, &kZeroObjectId); +} diff --git a/src/common/tests/test-common-oid.c b/src/common/tests/test-common-oid.c new file mode 100644 index 00000000000..c17566f01ba --- /dev/null +++ b/src/common/tests/test-common-oid.c @@ -0,0 +1,42 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TestSuite.h" + +#include + +static void +test_mcommon_oid_zero (void) +{ + bson_oid_t oid; + bson_oid_init_from_string (&oid, "000000000000000000000000"); + BSON_ASSERT (true == bson_oid_equal (&oid, &kZeroObjectId)); + BSON_ASSERT (true == mcommon_oid_is_zero (&oid)); + bson_oid_init_from_string (&oid, "010000000000000000000000"); + BSON_ASSERT (false == mcommon_oid_is_zero (&oid)); + bson_oid_init_from_string (&oid, "000000000000000000000001"); + BSON_ASSERT (false == mcommon_oid_is_zero (&oid)); + bson_oid_init_from_string (&oid, "ffffffffffffffffffffffff"); + BSON_ASSERT (false == mcommon_oid_is_zero (&oid)); + mcommon_oid_set_zero (&oid); + BSON_ASSERT (true == mcommon_oid_is_zero (&oid)); +} + +void +test_mcommon_oid_install (TestSuite *suite) +{ + TestSuite_Add (suite, "/mcommon/oid/zero", test_mcommon_oid_zero); +} diff --git a/src/libbson/src/bson/bson-macros.h b/src/libbson/src/bson/bson-macros.h index c207f17cd7c..399b595bb1a 100644 --- a/src/libbson/src/bson/bson-macros.h +++ b/src/libbson/src/bson/bson-macros.h @@ -22,6 +22,7 @@ #include +#include #ifdef __cplusplus #include @@ -190,12 +191,44 @@ #define BSON_FUNC __func__ #endif -#define BSON_ASSERT(test) \ - do { \ - if (!(BSON_LIKELY (test))) { \ - fprintf (stderr, "%s:%d %s(): precondition failed: %s\n", __FILE__, (int) (__LINE__), BSON_FUNC, #test); \ - abort (); \ - } \ + +#if defined(_MSC_VER) +#define BSON_INLINE __inline +#else +#define BSON_INLINE __inline__ +#endif + + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define BSON_NORETURN noreturn +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define BSON_NORETURN _Noreturn +#elif defined(__GNUC__) && 2 < __GNUC__ + (8 <= __GNUC_MINOR__) +#define BSON_NORETURN __attribute__ ((__noreturn__)) +#else +#define BSON_NORETURN +#endif + + +static BSON_INLINE BSON_NORETURN void +_bson_assert_failed_on_line (const char *file, int line, const char *func, const char *test) +{ + fprintf (stderr, "%s:%d %s(): assertion failed: %s\n", file, line, func, test); + abort (); +} + +static BSON_INLINE BSON_NORETURN void +_bson_assert_failed_on_param (const char *param, const char *func) +{ + fprintf (stderr, "The parameter: %s, in function %s, cannot be NULL\n", param, func); + abort (); +} + +#define BSON_ASSERT(test) \ + do { \ + if (!(BSON_LIKELY (test))) { \ + _bson_assert_failed_on_line (__FILE__, (int) (__LINE__), BSON_FUNC, #test); \ + } \ } while (0) /** @@ -203,12 +236,7 @@ * success. */ #define BSON_ASSERT_INLINE(Assertion, Value) \ - ((void) ((Assertion) \ - ? (0) \ - : ((fprintf ( \ - stderr, "%s:%d %s(): Assertion '%s' failed", __FILE__, (int) (__LINE__), BSON_FUNC, #Assertion), \ - abort ()), \ - 0)), \ + ((void) ((Assertion) ? (0) : (_bson_assert_failed_on_line (__FILE__, (int) (__LINE__), BSON_FUNC, #Assertion), 0)), \ Value) /** @@ -225,12 +253,11 @@ #define BSON_ASSERT_PTR_INLINE(Pointer) BSON_ASSERT_INLINE ((Pointer) != NULL, (Pointer)) /* Used for asserting parameters to provide a more precise error message */ -#define BSON_ASSERT_PARAM(param) \ - do { \ - if ((BSON_UNLIKELY (param == NULL))) { \ - fprintf (stderr, "The parameter: %s, in function %s, cannot be NULL\n", #param, BSON_FUNC); \ - abort (); \ - } \ +#define BSON_ASSERT_PARAM(param) \ + do { \ + if ((BSON_UNLIKELY (param == NULL))) { \ + _bson_assert_failed_on_param (#param, BSON_FUNC); \ + } \ } while (0) // `BSON_OPTIONAL_PARAM` is a documentation-only macro to document X may be NULL. @@ -294,13 +321,6 @@ #endif -#if defined(_MSC_VER) -#define BSON_INLINE __inline -#else -#define BSON_INLINE __inline__ -#endif - - #ifdef _MSC_VER #define BSON_ENSURE_ARRAY_PARAM_SIZE(_n) #define BSON_TYPEOF decltype diff --git a/src/libbson/src/bson/bson-timegm.c b/src/libbson/src/bson/bson-timegm.c index 3753aa9bf6f..0641e856184 100644 --- a/src/libbson/src/bson/bson-timegm.c +++ b/src/libbson/src/bson/bson-timegm.c @@ -31,14 +31,6 @@ #define ATTRIBUTE_FORMAT(spec) /* empty */ #endif -#if !defined _Noreturn && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112) -#if 2 < __GNUC__ + (8 <= __GNUC_MINOR__) -#define _Noreturn __attribute__ ((__noreturn__)) -#else -#define _Noreturn -#endif -#endif - #if !defined(__STDC_VERSION__) && !defined restrict #define restrict /* empty */ #endif diff --git a/src/libmongoc/.gitignore b/src/libmongoc/.gitignore index cd9a9c49cc5..6c54a64bf95 100644 --- a/src/libmongoc/.gitignore +++ b/src/libmongoc/.gitignore @@ -34,6 +34,7 @@ example-scram example-sdam-monitoring example-session example-start-at-optime +example-structured-log example-transaction example-update fam diff --git a/src/libmongoc/CMakeLists.txt b/src/libmongoc/CMakeLists.txt index 22da24f6765..0e31e0460b5 100644 --- a/src/libmongoc/CMakeLists.txt +++ b/src/libmongoc/CMakeLists.txt @@ -648,6 +648,7 @@ set (MONGOC_SOURCES ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs-download.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs-upload.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-socket.c + ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-structured-log.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-timeout.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-topology.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-topology-background-monitoring.c @@ -664,6 +665,7 @@ set (MONGOC_SOURCES ${mongo-c-driver_SOURCE_DIR}/src/common/src/common-atomic.c ${mongo-c-driver_SOURCE_DIR}/src/common/src/common-b64.c ${mongo-c-driver_SOURCE_DIR}/src/common/src/common-md5.c + ${mongo-c-driver_SOURCE_DIR}/src/common/src/common-oid.c ${mongo-c-driver_SOURCE_DIR}/src/common/src/common-thread.c ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-crypto.c @@ -762,6 +764,7 @@ set (HEADERS ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-file.h ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-gridfs.h ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-stream-socket.h + ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-structured-log.h ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-topology-description.h ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-uri.h ${PROJECT_SOURCE_DIR}/src/mongoc/mongoc-version-functions.h @@ -1031,6 +1034,7 @@ endif () set (test-libmongoc-sources ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-atomic.c ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-cmp.c + ${mongo-c-driver_SOURCE_DIR}/src/common/tests/test-common-oid.c ${mongo-c-driver_SOURCE_DIR}/src/libbson/tests/corpus-test.c ${mongo-c-driver_SOURCE_DIR}/src/libbson/tests/corpus-test.h ${mongo-c-driver_SOURCE_DIR}/src/libbson/tests/test-atomic.c @@ -1141,6 +1145,7 @@ set (test-libmongoc-sources ${PROJECT_SOURCE_DIR}/tests/test-mongoc-ssl.c ${PROJECT_SOURCE_DIR}/tests/test-mongoc-stream.c ${PROJECT_SOURCE_DIR}/tests/test-mongoc-streamable-hello.c + ${PROJECT_SOURCE_DIR}/tests/test-mongoc-structured-log.c ${PROJECT_SOURCE_DIR}/tests/test-mongoc-thread.c ${PROJECT_SOURCE_DIR}/tests/test-mongoc-timeout.c ${PROJECT_SOURCE_DIR}/tests/test-mongoc-topology-description.c @@ -1292,7 +1297,9 @@ if (ENABLE_EXAMPLES AND ENABLE_SHARED) mongoc_add_example (example-gridfs ${PROJECT_SOURCE_DIR}/examples/example-gridfs.c) mongoc_add_example (example-gridfs-bucket ${PROJECT_SOURCE_DIR}/examples/example-gridfs-bucket.c) if (NOT WIN32 AND ENABLE_EXAMPLES) + # Examples that use pthreads mongoc_add_example (example-pool ${PROJECT_SOURCE_DIR}/examples/example-pool.c) + mongoc_add_example (example-structured-log ${PROJECT_SOURCE_DIR}/examples/example-structured-log.c) endif () mongoc_add_example (example-scram ${PROJECT_SOURCE_DIR}/examples/example-scram.c) mongoc_add_example (example-sdam-monitoring ${PROJECT_SOURCE_DIR}/examples/example-sdam-monitoring.c) diff --git a/src/libmongoc/doc/logging.rst b/src/libmongoc/doc/logging.rst index ea35eb3fadd..1a0b7cd1cfd 100644 --- a/src/libmongoc/doc/logging.rst +++ b/src/libmongoc/doc/logging.rst @@ -3,133 +3,16 @@ Logging ======= -MongoDB C driver Logging Abstraction +The MongoDB C driver has two different types of logging available: -Synopsis --------- +* The original ``mongoc_log`` facility supports freeform string messages that originate from the driver itself or from application code. This has been retroactively termed "unstructured logging". +* A new ``mongoc_structured_log`` facility reports messages from the driver itself using a BSON format defined across driver implementations by the `MongoDB Logging Specification `_. -.. code-block:: c +These two systems are configured and used independently. - typedef enum { - MONGOC_LOG_LEVEL_ERROR, - MONGOC_LOG_LEVEL_CRITICAL, - MONGOC_LOG_LEVEL_WARNING, - MONGOC_LOG_LEVEL_MESSAGE, - MONGOC_LOG_LEVEL_INFO, - MONGOC_LOG_LEVEL_DEBUG, - MONGOC_LOG_LEVEL_TRACE, - } mongoc_log_level_t; - - #define MONGOC_ERROR(...) - #define MONGOC_CRITICAL(...) - #define MONGOC_WARNING(...) - #define MONGOC_MESSAGE(...) - #define MONGOC_INFO(...) - #define MONGOC_DEBUG(...) - - typedef void (*mongoc_log_func_t) (mongoc_log_level_t log_level, - const char *log_domain, - const char *message, - void *user_data); - - void - mongoc_log_set_handler (mongoc_log_func_t log_func, void *user_data); - void - mongoc_log (mongoc_log_level_t log_level, - const char *log_domain, - const char *format, - ...); - const char * - mongoc_log_level_str (mongoc_log_level_t log_level); - void - mongoc_log_default_handler (mongoc_log_level_t log_level, - const char *log_domain, - const char *message, - void *user_data); - void - mongoc_log_trace_enable (void); - void - mongoc_log_trace_disable (void); - -The MongoDB C driver comes with an abstraction for logging that you can use in your application, or integrate with an existing logging system. - -Macros ------- - -To make logging a little less painful, various helper macros are provided. See the following example. - -.. code-block:: c - - #undef MONGOC_LOG_DOMAIN - #define MONGOC_LOG_DOMAIN "my-custom-domain" - - MONGOC_WARNING ("An error occurred: %s", strerror (errno)); - -.. _custom_log_handlers: - -Custom Log Handlers -------------------- - -The default log handler prints a timestamp and the log message to ``stdout``, or to ``stderr`` for warnings, critical messages, and errors. - You can override the handler with ``mongoc_log_set_handler()``. - Your handler function is called in a mutex for thread safety. - -For example, you could register a custom handler to suppress messages at INFO level and below: - -.. code-block:: c - - void - my_logger (mongoc_log_level_t log_level, - const char *log_domain, - const char *message, - void *user_data) - { - /* smaller values are more important */ - if (log_level < MONGOC_LOG_LEVEL_INFO) { - mongoc_log_default_handler (log_level, log_domain, message, user_data); - } - } - - int - main (int argc, char *argv[]) - { - mongoc_log_set_handler (my_logger, NULL); - mongoc_init (); - - /* ... your code ... */ - - mongoc_cleanup (); - return 0; - } - -Note that in the example above ``mongoc_log_set_handler()`` is called before ``mongoc_init()``. -Otherwise, some log traces could not be processed by the log handler. - -To restore the default handler: - -.. code-block:: c - - mongoc_log_set_handler (mongoc_log_default_handler, NULL); - -Disable logging ---------------- - -To disable all logging, including warnings, critical messages and errors, provide an empty log handler: - -.. code-block:: c - - mongoc_log_set_handler (NULL, NULL); - -Tracing -------- - -If compiling your own copy of the MongoDB C driver, consider configuring with ``-DENABLE_TRACING=ON`` to enable function tracing and hex dumps of network packets to ``STDERR`` and ``STDOUT`` during development and debugging. - -This is especially useful when debugging what may be going on internally in the driver. - -Trace messages can be enabled and disabled by calling ``mongoc_log_trace_enable()`` and ``mongoc_log_trace_disable()`` - -.. note:: - - Compiling the driver with ``-DENABLE_TRACING=ON`` will affect its performance. Disabling tracing with ``mongoc_log_trace_disable()`` significantly reduces the overhead, but cannot remove it completely. +.. toctree:: + :titlesonly: + :maxdepth: 1 + unstructured_log + structured_log diff --git a/src/libmongoc/doc/mongoc_client_pool_set_apm_callbacks.rst b/src/libmongoc/doc/mongoc_client_pool_set_apm_callbacks.rst index 50e794b7207..1f33f929db2 100644 --- a/src/libmongoc/doc/mongoc_client_pool_set_apm_callbacks.rst +++ b/src/libmongoc/doc/mongoc_client_pool_set_apm_callbacks.rst @@ -15,7 +15,7 @@ Synopsis Register a set of callbacks to receive Application Performance Monitoring events. -The ``callbacks`` are copied by the pool and may be destroyed at any time after. If a ``context`` is passed, it is the application's responsibility to ensure ``context`` remains valid for the lifetime of the pool. +The ``callbacks`` are copied by the pool and may be safely destroyed by the caller after this API call completes. If a ``context`` is passed, it is the application's responsibility to ensure ``context`` remains valid for the lifetime of the pool. Parameters ---------- diff --git a/src/libmongoc/doc/mongoc_client_pool_set_structured_log_opts.rst b/src/libmongoc/doc/mongoc_client_pool_set_structured_log_opts.rst new file mode 100644 index 00000000000..4f3c0f8afed --- /dev/null +++ b/src/libmongoc/doc/mongoc_client_pool_set_structured_log_opts.rst @@ -0,0 +1,42 @@ +:man_page: mongoc_client_pool_set_structured_log_opts + +mongoc_client_pool_set_structured_log_opts() +============================================ + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_client_pool_set_structured_log_opts (mongoc_client_pool_t *pool, + const mongoc_structured_log_opts_t *opts); + +Reconfigures this client pool's structured logging subsystem. See :doc:`structured_log`. + +The :symbol:`mongoc_structured_log_opts_t` is copied by the pool and may be safely destroyed by the caller after this API call completes. +The application is responsible for ensuring any ``user_data`` referenced by ``opts`` remains valid for the lifetime of the pool. + +By default, the :symbol:`mongoc_client_pool_t` will have log options captured from the environment during :symbol:`mongoc_client_pool_new`. +See :symbol:`mongoc_structured_log_opts_new` for a list of the supported options. + +The structured logging subsystem may be disabled by passing NULL as ``opts`` or equivalently by passing NULL as the :symbol:`mongoc_structured_log_func_t` in :symbol:`mongoc_structured_log_opts_set_handler`. + +Parameters +---------- + +* ``pool``: A :symbol:`mongoc_client_pool_t`. +* ``opts``: A :symbol:`mongoc_structured_log_opts_t` allocated with :symbol:`mongoc_structured_log_opts_new`, or NULL to disable structured logging. + +Returns +------- + +Returns true when used correctly. If called multiple times per pool or after the first client is initialized, returns false and logs a warning. + +.. include:: includes/mongoc_client_pool_call_once.txt + +Thread safety within the handler is the application's responsibility. Handlers may be invoked concurrently by multiple pool users. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_client_pool_t.rst b/src/libmongoc/doc/mongoc_client_pool_t.rst index 783540fb9c5..8dbe1d8b44a 100644 --- a/src/libmongoc/doc/mongoc_client_pool_t.rst +++ b/src/libmongoc/doc/mongoc_client_pool_t.rst @@ -43,5 +43,6 @@ Example mongoc_client_pool_set_error_api mongoc_client_pool_set_server_api mongoc_client_pool_set_ssl_opts + mongoc_client_pool_set_structured_log_opts mongoc_client_pool_try_pop diff --git a/src/libmongoc/doc/mongoc_client_set_apm_callbacks.rst b/src/libmongoc/doc/mongoc_client_set_apm_callbacks.rst index 7201a8d3906..2615974ec80 100644 --- a/src/libmongoc/doc/mongoc_client_set_apm_callbacks.rst +++ b/src/libmongoc/doc/mongoc_client_set_apm_callbacks.rst @@ -15,7 +15,7 @@ Synopsis Register a set of callbacks to receive Application Performance Monitoring events. -The ``callbacks`` are copied by the client and may be destroyed at any time after. If a ``context`` is passed, it is the application's responsibility to ensure ``context`` remains valid for the lifetime of the client. +The ``callbacks`` are copied by the client and may be safely destroyed by the caller after this API call completes. If a ``context`` is passed, it is the application's responsibility to ensure ``context`` remains valid for the lifetime of the client. Parameters ---------- diff --git a/src/libmongoc/doc/mongoc_client_set_structured_log_opts.rst b/src/libmongoc/doc/mongoc_client_set_structured_log_opts.rst new file mode 100644 index 00000000000..95002b5c8ee --- /dev/null +++ b/src/libmongoc/doc/mongoc_client_set_structured_log_opts.rst @@ -0,0 +1,41 @@ +:man_page: mongoc_client_set_structured_log_opts + +mongoc_client_set_structured_log_opts() +======================================= + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_client_set_structured_log_opts (mongoc_client_t *client, + const mongoc_structured_log_opts_t *opts); + +Reconfigures this client's structured logging subsystem. See :doc:`structured_log`. + +This function must not be called on clients that are part of a :symbol:`mongoc_client_pool_t`. +Structured logging for pools must be configured with :symbol:`mongoc_client_pool_set_structured_log_opts` before the first call to :symbol:`mongoc_client_pool_pop`. + +The :symbol:`mongoc_structured_log_opts_t` is copied by the client and may be safely destroyed by the caller after this API call completes. +The application is responsible for ensuring any ``user_data`` referenced by ``opts`` remains valid for the lifetime of the client. + +By default, the :symbol:`mongoc_client_t` will have log options captured from the environment during :symbol:`mongoc_client_new`. +See :symbol:`mongoc_structured_log_opts_new` for a list of the supported options. + +The structured logging subsystem may be disabled by passing NULL as ``opts`` or equivalently by passing NULL as the :symbol:`mongoc_structured_log_func_t` in :symbol:`mongoc_structured_log_opts_set_handler`. + +Parameters +---------- + +* ``client``: A :symbol:`mongoc_client_t`. +* ``opts``: A :symbol:`mongoc_structured_log_opts_t` allocated with :symbol:`mongoc_structured_log_opts_new`, or NULL to disable structured logging. + +Returns +------- + +Returns true when used correctly. If called on a pooled client, returns false and logs a warning. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_client_t.rst b/src/libmongoc/doc/mongoc_client_t.rst index 06747c93b6d..e3dabf9e74f 100644 --- a/src/libmongoc/doc/mongoc_client_t.rst +++ b/src/libmongoc/doc/mongoc_client_t.rst @@ -91,6 +91,7 @@ Example mongoc_client_set_sockettimeoutms mongoc_client_set_ssl_opts mongoc_client_set_stream_initiator + mongoc_client_set_structured_log_opts mongoc_client_set_write_concern mongoc_client_start_session mongoc_client_watch diff --git a/src/libmongoc/doc/mongoc_structured_log_component_t.rst b/src/libmongoc/doc/mongoc_structured_log_component_t.rst new file mode 100644 index 00000000000..58a3d9bb6e1 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_component_t.rst @@ -0,0 +1,34 @@ +:man_page: mongoc_structured_log_component_t + +mongoc_structured_log_component_t +================================= + +Synopsis +-------- + +.. code-block:: c + + typedef enum { + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND = 0, + MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY = 1, + MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION = 2, + MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION = 3, + } mongoc_structured_log_component_t; + +``mongoc_structured_log_component_t`` enumerates the structured logging components. +Applications should never rely on having an exhaustive list of all log components. +Instead, use :symbol:`mongoc_structured_log_opts_set_max_level_for_all_components` to set a default level if needed. + +Functions +--------- + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_get_component_name + mongoc_structured_log_get_named_component + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_entry_get_component.rst b/src/libmongoc/doc/mongoc_structured_log_entry_get_component.rst new file mode 100644 index 00000000000..df89472db1a --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_entry_get_component.rst @@ -0,0 +1,26 @@ +:man_page: mongoc_structured_log_entry_get_component + +mongoc_structured_log_entry_get_component() +=========================================== + +Synopsis +-------- + +.. code-block:: c + + mongoc_structured_log_component_t + mongoc_structured_log_entry_get_component (const mongoc_structured_log_entry_t *entry); + +Parameters +---------- + +* ``entry``: A :symbol:`mongoc_structured_log_entry_t` pointer. + +Returns +------- + +The :symbol:`mongoc_structured_log_component_t` associated with this log entry. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_entry_get_level.rst b/src/libmongoc/doc/mongoc_structured_log_entry_get_level.rst new file mode 100644 index 00000000000..14c6eb6a985 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_entry_get_level.rst @@ -0,0 +1,26 @@ +:man_page: mongoc_structured_log_entry_get_level + +mongoc_structured_log_entry_get_level() +======================================= + +Synopsis +-------- + +.. code-block:: c + + mongoc_structured_log_level_t + mongoc_structured_log_entry_get_level (const mongoc_structured_log_entry_t *entry); + +Parameters +---------- + +* ``entry``: A :symbol:`mongoc_structured_log_entry_t` pointer. + +Returns +------- + +The :symbol:`mongoc_structured_log_level_t` associated with this log entry. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_entry_get_message_string.rst b/src/libmongoc/doc/mongoc_structured_log_entry_get_message_string.rst new file mode 100644 index 00000000000..e0ae8ce1519 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_entry_get_message_string.rst @@ -0,0 +1,31 @@ +:man_page: mongoc_structured_log_entry_get_message_string + +mongoc_structured_log_entry_get_message_string() +================================================ + +Synopsis +-------- + +.. code-block:: c + + const char * + mongoc_structured_log_entry_get_message_string (const mongoc_structured_log_entry_t *entry); + +Parameters +---------- + +* ``entry``: A :symbol:`mongoc_structured_log_entry_t` pointer. + +Returns +------- + +A string, guaranteed to be valid only during the lifetime of the structured log handler. +It should not be freed or modified. + +Identical to the value of the ``message`` key in the document returned by :symbol:`mongoc_structured_log_entry_message_as_bson`. + +This is not a complete string representation of the structured log, but rather a standardized identifier for a particular log event. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_entry_message_as_bson.rst b/src/libmongoc/doc/mongoc_structured_log_entry_message_as_bson.rst new file mode 100644 index 00000000000..f83eab380c5 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_entry_message_as_bson.rst @@ -0,0 +1,30 @@ +:man_page: mongoc_structured_log_entry_message_as_bson + +mongoc_structured_log_entry_message_as_bson() +============================================= + +Synopsis +-------- + +.. code-block:: c + + bson_t * + mongoc_structured_log_entry_message_as_bson (const mongoc_structured_log_entry_t *entry); + +Make a new copy, as a :symbol:`bson_t`, of the log entry's standardized BSON representation. +When possible, a log handler should avoid serializing log messages that will be discarded. +Each call allocates an independent copy of the message that must be freed. + +Parameters +---------- + +* ``entry``: A :symbol:`mongoc_structured_log_entry_t` pointer. + +Returns +------- + +A new allocated :symbol:`bson_t` that must be freed with a call to :symbol:`bson_destroy`. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_entry_t.rst b/src/libmongoc/doc/mongoc_structured_log_entry_t.rst new file mode 100644 index 00000000000..3cb23f8c3ee --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_entry_t.rst @@ -0,0 +1,31 @@ +:man_page: mongoc_structured_log_entry_t + +mongoc_structured_log_entry_t +============================= + +Synopsis +-------- + +.. code-block:: c + + typedef struct mongoc_structured_log_entry_t mongoc_structured_log_entry_t; + +``mongoc_structured_log_entry_t`` is an opaque structure which represents the temporary state of an in-progress log entry. +It can only be used during a :symbol:`mongoc_structured_log_func_t`, it is not valid after the log handler returns. +Use the functions below to query individual aspects of the log entry. + +Functions +--------- + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_entry_get_component + mongoc_structured_log_entry_get_level + mongoc_structured_log_entry_get_message_string + mongoc_structured_log_entry_message_as_bson + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_func_t.rst b/src/libmongoc/doc/mongoc_structured_log_func_t.rst new file mode 100644 index 00000000000..d2f3f6a0ca6 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_func_t.rst @@ -0,0 +1,26 @@ +:man_page: mongoc_structured_log_func_t + +mongoc_structured_log_func_t +============================ + +Synopsis +-------- + +.. code-block:: c + + typedef void (*mongoc_structured_log_func_t) + (const mongoc_structured_log_entry_t *entry, void *user_data); + +Callback function for :symbol:`mongoc_structured_log_opts_set_handler`. +Structured log handlers must be thread-safe if they will be used with :symbol:`mongoc_client_pool_t`. +Handlers must avoid unbounded recursion, preferably by avoiding the use of any ``libmongoc`` client or pool which uses the same handler. + +Parameters +---------- + +* ``entry``: A :symbol:`mongoc_structured_log_entry_t` pointer, only valid during the handler invocation. +* ``user_data``: Optional user data from :symbol:`mongoc_structured_log_opts_set_handler`. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_get_component_name.rst b/src/libmongoc/doc/mongoc_structured_log_get_component_name.rst new file mode 100644 index 00000000000..bb3a6593be4 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_get_component_name.rst @@ -0,0 +1,27 @@ +:man_page: mongoc_structured_log_get_component_name + +mongoc_structured_log_get_component_name() +========================================== + +Synopsis +-------- + +.. code-block:: c + + const char * + mongoc_structured_log_get_component_name (mongoc_structured_log_component_t component); + +Parameters +---------- + +* ``component``: Log component as a :symbol:`mongoc_structured_log_component_t`. + +Returns +------- + +If the component is known, returns a pointer to a constant string that should not be freed. +If the component has no known name, returns NULL. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_get_level_name.rst b/src/libmongoc/doc/mongoc_structured_log_get_level_name.rst new file mode 100644 index 00000000000..29ae9f9a205 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_get_level_name.rst @@ -0,0 +1,27 @@ +:man_page: mongoc_structured_log_get_level_name + +mongoc_structured_log_get_level_name() +====================================== + +Synopsis +-------- + +.. code-block:: c + + const char * + mongoc_structured_log_get_level_name (mongoc_structured_log_level_t level); + +Parameters +---------- + +* ``level``: Log level as a :symbol:`mongoc_structured_log_level_t`. + +Returns +------- + +If the level is known, returns a pointer to a constant string that should not be freed. +If the level has no known name, returns NULL. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_get_named_component.rst b/src/libmongoc/doc/mongoc_structured_log_get_named_component.rst new file mode 100644 index 00000000000..a941fce3044 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_get_named_component.rst @@ -0,0 +1,30 @@ +:man_page: mongoc_structured_log_get_named_component + +mongoc_structured_log_get_named_component() +=========================================== + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_structured_log_get_named_component (const char *name, mongoc_structured_log_component_t *out); + +Look up a component by name. Case insensitive. + +Parameters +---------- + +* ``name``: A name to look up as a log component. +* ``out``: On success, the corresponding :symbol:`mongoc_structured_log_component_t` is written here. + +Returns +------- + +If the component name is known, returns ``true`` and writes the component enum to ``*out``. +If the component name is not known, returns ``false`` and does not write ``*out``. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_get_named_level.rst b/src/libmongoc/doc/mongoc_structured_log_get_named_level.rst new file mode 100644 index 00000000000..74849684356 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_get_named_level.rst @@ -0,0 +1,30 @@ +:man_page: mongoc_structured_log_get_named_level + +mongoc_structured_log_get_named_level() +======================================= + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_structured_log_get_named_level (const char *name, mongoc_structured_log_level_t *out); + +Look up a log level by name. Case insensitive. + +Parameters +---------- + +* ``name``: A name to look up as a log level. +* ``out``: On success, the corresponding :symbol:`mongoc_structured_log_level_t` is written here. + +Returns +------- + +If the level name is known, returns ``true`` and writes the level enum to ``*out``. +If the level name is not known, returns ``false`` and does not write ``*out``. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_level_t.rst b/src/libmongoc/doc/mongoc_structured_log_level_t.rst new file mode 100644 index 00000000000..96108f41e6b --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_level_t.rst @@ -0,0 +1,37 @@ +:man_page: mongoc_structured_log_level_t + +mongoc_structured_log_level_t +============================= + +Synopsis +-------- + +.. code-block:: c + + typedef enum { + MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY = 0, + MONGOC_STRUCTURED_LOG_LEVEL_ALERT = 1, + MONGOC_STRUCTURED_LOG_LEVEL_CRITICAL = 2, + MONGOC_STRUCTURED_LOG_LEVEL_ERROR = 3, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING = 4, + MONGOC_STRUCTURED_LOG_LEVEL_NOTICE = 5, + MONGOC_STRUCTURED_LOG_LEVEL_INFO = 6, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG = 7, + MONGOC_STRUCTURED_LOG_LEVEL_TRACE = 8, + } mongoc_structured_log_level_t; + +``mongoc_structured_log_level_t`` enumerates the available log levels for use with structured logging. + +Functions +--------- + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_get_level_name + mongoc_structured_log_get_named_level + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_destroy.rst b/src/libmongoc/doc/mongoc_structured_log_opts_destroy.rst new file mode 100644 index 00000000000..2f1f15ee61e --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_destroy.rst @@ -0,0 +1,23 @@ +:man_page: mongoc_structured_log_opts_destroy + +mongoc_structured_log_opts_destroy() +==================================== + +Synopsis +-------- + +.. code-block:: c + + void + mongoc_structured_log_opts_destroy (mongoc_structured_log_opts_t *opts); + +Parameters +---------- + +* ``opts``: Pointer to a :symbol:`mongoc_structured_log_opts_t` allocated with :symbol:`mongoc_structured_log_opts_new`, or NULL. + +Description +----------- + +This function releases all resources associated with a :symbol:`mongoc_structured_log_opts_t`. +Does nothing if ``opts`` is NULL. diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_get_max_level_for_component.rst b/src/libmongoc/doc/mongoc_structured_log_opts_get_max_level_for_component.rst new file mode 100644 index 00000000000..58be3bbc377 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_get_max_level_for_component.rst @@ -0,0 +1,30 @@ +:man_page: mongoc_structured_log_opts_get_max_level_for_component + +mongoc_structured_log_opts_get_max_level_for_component() +======================================================== + +Synopsis +-------- + +.. code-block:: c + + mongoc_structured_log_level_t + mongoc_structured_log_opts_get_max_level_for_component (const mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component); + +Parameters +---------- + +* ``opts``: Structured log options, allocated with :symbol:`mongoc_structured_log_opts_new`. +* ``component``: Log component as a :symbol:`mongoc_structured_log_component_t`. + +Returns +------- + +Returns the configured maximum log level for a specific component, as a :symbol:`mongoc_structured_log_level_t`. +This may be the last value set with :symbol:`mongoc_structured_log_opts_set_max_level_for_component` or :symbol:`mongoc_structured_log_opts_set_max_level_for_all_components`, or it may be the default obtained from environment variables. +If an invalid or unknown component enum is given, returns the lowest log level. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_new.rst b/src/libmongoc/doc/mongoc_structured_log_opts_new.rst new file mode 100644 index 00000000000..78295e4fb94 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_new.rst @@ -0,0 +1,69 @@ +:man_page: mongoc_structured_log_opts_new + +mongoc_structured_log_opts_new() +================================ + +Synopsis +-------- + +.. code-block:: c + + mongoc_structured_log_opts_t * + mongoc_structured_log_opts_new (void); + +Creates a new :symbol:`mongoc_structured_log_opts_t`, filled with defaults captured from the current environment. + +Sets a default log handler which would write a text representation of each log message to ``stderr``, ``stdout``, or another file configurable using ``MONGODB_LOG_PATH``. +This setting has no effect if the default handler is replaced using :symbol:`mongoc_structured_log_opts_set_handler`. + +Environment variable errors are non-fatal, and result in one-time warnings delivered as an unstructured log. + +Per-component maximum levels are initialized equivalently to: + +.. code-block:: c + + mongoc_structured_log_opts_set_max_level_for_all_components(opts, MONGOC_STRUCTURED_LOG_LEVEL_WARNING); + mongoc_structured_log_opts_set_max_levels_from_env(opts); + +Environment Variables +--------------------- + +This is a full list of the captured environment variables. + +* ``MONGODB_LOG_MAX_DOCUMENT_LENGTH``: Maximum length for JSON-serialized documents that appear within a log message. + It may be a number, in bytes, or ``unlimited`` (case insensitive). + By default, the limit is 1000 bytes. + This limit affects interior documents like commands and replies, not the total length of a structured log message. + +* ``MONGODB_LOG_PATH``: A file path or one of the special strings ``stderr`` or ``stdout`` (case insensitive) specifying the destination for structured logs seen by the default handler. + By default, it writes to ``stderr``. + This path will be captured during ``mongoc_structured_log_opts_new()``, but it will not immediately be opened. + If the file can't be opened, a warning is then written to the unstructured log and the handler writes structured logs to ``stderr`` instead. + + .. warning:: When a file path is given for ``MONGODB_LOG_PATH``, each log instance (one stand-alone client or pool) will separately open this file for append. + The results are operating system specific. On UNIX-like platforms each instance's output will be interleaved, in most cases without splitting individual log messages. Notably on Windows the file will be opened in exclusive mode by the first instance and subsequent instances will fail, falling back on the default of ``stderr``. + Applications that use multiple processes or multiple client pools will likely want to supply a log handler that annotates each message with information about its originating log instance. + +* ``MONGODB_LOG_COMMAND``: A log level name to set as the maximum for ``MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND``. +* ``MONGODB_LOG_TOPOLOGY``: A log level name to set as the maximum for ``MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY``. +* ``MONGODB_LOG_SERVER_SELECTION``: A log level name to set as the maximum for ``MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION``. +* ``MONGODB_LOG_CONNECTION``: A log level name to set as the maximum for ``MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION``. +* ``MONGODB_LOG_ALL``: A log level name applied to all components not otherwise specified. + +Note that log level names are always case insensitive. +This is a full list of recognized names, including allowed aliases: + +* ``emergency``, ``off`` +* ``alert`` +* ``critical`` +* ``error`` +* ``warning``, ``warn`` +* ``notice`` +* ``informational``, ``info`` +* ``debug`` +* ``trace`` + +Returns +------- + +A newly allocated :symbol:`mongoc_structured_log_opts_t`. diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_set_handler.rst b/src/libmongoc/doc/mongoc_structured_log_opts_set_handler.rst new file mode 100644 index 00000000000..b817c20fa13 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_set_handler.rst @@ -0,0 +1,36 @@ +:man_page: mongoc_structured_log_opts_set_handler + +mongoc_structured_log_opts_set_handler() +======================================== + +Synopsis +-------- + +.. code-block:: c + + void + mongoc_structured_log_opts_set_handler (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_func_t log_func, + void *user_data); + +Sets the function to be called to handle structured log messages, as a :symbol:`mongoc_structured_log_func_t`. + +The callback is given a :symbol:`mongoc_structured_log_entry_t` as a handle for obtaining additional information about the log message. +This entry pointer is only valid during a callback, because it's a low cost reference to temporary data. + +Structured log handlers must be thread-safe if they will be used with :symbol:`mongoc_client_pool_t`. +Handlers must avoid unbounded recursion, preferably by avoiding the use of any ``libmongoc`` client or pool which uses the same handler. + +This function always replaces the default log handler from :symbol:`mongoc_structured_log_opts_new`, if it was still set. +If the ``log_func`` is set to NULL, structured logging will be disabled. + +Parameters +---------- + +* ``opts``: Structured log options, allocated with :symbol:`mongoc_structured_log_opts_new`. +* ``log_func``: The handler to install, a :symbol:`mongoc_structured_log_func_t`, or NULL to disable structured logging. +* ``user_data``: Optional user data, passed on to the handler. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_all_components.rst b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_all_components.rst new file mode 100644 index 00000000000..32ca1feb21a --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_all_components.rst @@ -0,0 +1,32 @@ +:man_page: mongoc_structured_log_opts_set_max_level_for_all_components + +mongoc_structured_log_opts_set_max_level_for_all_components() +============================================================= + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_structured_log_opts_set_max_level_for_all_components (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_level_t level); + +Sets all per-component maximum log levels to the same value. +Only log messages at or below this severity level will be passed to :symbol:`mongoc_structured_log_func_t`. +Effective even for logging components not known at compile-time. + +Parameters +---------- + +* ``opts``: Structured log options, allocated with :symbol:`mongoc_structured_log_opts_new`. +* ``level``: The max log level for all components, as a :symbol:`mongoc_structured_log_level_t`. + +Returns +------- + +Returns ``true`` on success, or ``false`` if the supplied parameters were incorrect. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_component.rst b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_component.rst new file mode 100644 index 00000000000..a72df802c60 --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_level_for_component.rst @@ -0,0 +1,36 @@ +:man_page: mongoc_structured_log_opts_set_max_level_for_component + +mongoc_structured_log_opts_set_max_level_for_component() +======================================================== + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_structured_log_opts_set_max_level_for_component (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component, + mongoc_structured_log_level_t level); + +Sets the maximum log level per-component. +Only log messages at or below this severity level will be passed to :symbol:`mongoc_structured_log_func_t`. + +By default, each component's log level may come from environment variables. +See :symbol:`mongoc_structured_log_opts_set_max_levels_from_env`. + +Parameters +---------- + +* ``opts``: Structured log options, allocated with :symbol:`mongoc_structured_log_opts_new`. +* ``component``: The component to set a max log level. for, as a :symbol:`mongoc_structured_log_component_t`. +* ``level``: The new max log level for this component, as a :symbol:`mongoc_structured_log_level_t`. + +Returns +------- + +Returns ``true`` on success, or ``false`` if the supplied parameters were incorrect. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_set_max_levels_from_env.rst b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_levels_from_env.rst new file mode 100644 index 00000000000..f48692c6dcb --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_set_max_levels_from_env.rst @@ -0,0 +1,36 @@ +:man_page: mongoc_structured_log_opts_set_max_levels_from_env + +mongoc_structured_log_opts_set_max_levels_from_env() +==================================================== + +Synopsis +-------- + +.. code-block:: c + + bool + mongoc_structured_log_opts_set_max_levels_from_env (mongoc_structured_log_opts_t *opts); + +Sets any maximum log levels requested by environment variables: ``MONGODB_LOG_ALL`` for all components, followed by per-component log levels ``MONGODB_LOG_COMMAND``, ``MONGODB_LOG_CONNECTION``, ``MONGODB_LOG_TOPOLOGY``, and ``MONGODB_LOG_SERVER_SELECTION``. + +Expects the value to be recognizable by :symbol:`mongoc_structured_log_get_named_level`. +Parse errors may cause a warning message, delivered via unstructured logging. + +Component levels with no valid environment variable setting will be left unmodified. + +This happens automatically when :symbol:`mongoc_structured_log_opts_new` establishes defaults. +Any subsequent programmatic modifications to the :symbol:`mongoc_structured_log_opts_t` will override the environment variable settings. +For applications that desire the opposite behavior, where environment variables may override programmatic settings, they may call ``mongoc_structured_log_opts_set_max_levels_from_env()`` after calling :symbol:`mongoc_structured_log_opts_set_max_level_for_component` and :symbol:`mongoc_structured_log_opts_set_max_level_for_all_components`. +This will process the environment a second time, allowing it to override customized defaults. + +Returns +------- + +Returns ``true`` on success. +If warnings are encountered in the environment, returns ``false`` and may log additional information to the unstructured logging facility. +Note that, by design, these errors are by default non-fatal. +When :symbol:`mongoc_structured_log_opts_new` internally calls this function, it ignores the return value. + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/mongoc_structured_log_opts_t.rst b/src/libmongoc/doc/mongoc_structured_log_opts_t.rst new file mode 100644 index 00000000000..414c24d804a --- /dev/null +++ b/src/libmongoc/doc/mongoc_structured_log_opts_t.rst @@ -0,0 +1,35 @@ +:man_page: mongoc_structured_log_opts_t + +mongoc_structured_log_opts_t +============================ + +Synopsis +-------- + +.. code-block:: c + + typedef struct mongoc_structured_log_opts_t mongoc_structured_log_opts_t; + +``mongoc_structured_log_opts_t`` is an opaque type that contains options for the structured logging subsystem: per-component log levels, a maximum logged document length, and a handler function. + +Create a ``mongoc_structured_log_opts_t`` with :symbol:`mongoc_structured_log_opts_new`, set options and a callback on it, then pass it to :symbol:`mongoc_client_set_structured_log_opts` or :symbol:`mongoc_client_pool_set_structured_log_opts`. +Must be destroyed by calling :symbol:`mongoc_structured_log_opts_destroy`. + +Functions +--------- + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_opts_new + mongoc_structured_log_opts_destroy + mongoc_structured_log_opts_set_handler + mongoc_structured_log_opts_set_max_level_for_component + mongoc_structured_log_opts_set_max_level_for_all_components + mongoc_structured_log_opts_set_max_levels_from_env + mongoc_structured_log_opts_get_max_level_for_component + +.. seealso:: + + | :doc:`structured_log` diff --git a/src/libmongoc/doc/structured_log.rst b/src/libmongoc/doc/structured_log.rst new file mode 100644 index 00000000000..dd139c0770a --- /dev/null +++ b/src/libmongoc/doc/structured_log.rst @@ -0,0 +1,120 @@ +:man_page: mongoc_structured_log + +Structured Logging +================== + +This document describes a newer "structured" logging facility which reports messages from the driver itself using a BSON format defined across driver implementations by the `MongoDB Logging Specification `_. +See :doc:`unstructured_log` for the original freeform logging facility. + +These two systems are configured and used independently. + +Unstructured logging is global to the entire process, but structured logging is configured separately for each :symbol:`mongoc_client_t` or :symbol:`mongoc_client_pool_t`. +See :symbol:`mongoc_client_set_structured_log_opts` and :symbol:`mongoc_client_pool_set_structured_log_opts`. + +Options +------- + +Structured log settings are tracked explicitly by a :symbol:`mongoc_structured_log_opts_t` instance. + +Like other drivers supporting structured logging, we take default settings from environment variables and offer additional optional programmatic configuration. +Environment variables are captured during :symbol:`mongoc_structured_log_opts_new`, refer there for a full list of the supported variables. + +Normally environment variables provide defaults that can be overridden programmatically. +To request the opposite behavior, where your programmatic defaults can be overridden by the environment, see :symbol:`mongoc_structured_log_opts_set_max_levels_from_env`. + +Structured log messages may be filtered in arbitrary ways by the handler, but as both a performance optimization and a convenience, a built-in filter limits the maximum log level of reported messages with a per-component setting. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_opts_t + + +Levels and Components +--------------------- + +Log levels and components are defined as :symbol:`mongoc_structured_log_level_t` and :symbol:`mongoc_structured_log_component_t` enumerations. Utilities are provided to convert between these values and their standard string representations. The string values are case-insensitive. + +.. code-block:: c + + typedef enum { + MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY = 0, // "Emergency" ("off" also accepted) + MONGOC_STRUCTURED_LOG_LEVEL_ALERT = 1, // "Alert" + MONGOC_STRUCTURED_LOG_LEVEL_CRITICAL = 2, // "Critical" + MONGOC_STRUCTURED_LOG_LEVEL_ERROR = 3, // "Error" + MONGOC_STRUCTURED_LOG_LEVEL_WARNING = 4, // "Warning" ("warn" also accepted) + MONGOC_STRUCTURED_LOG_LEVEL_NOTICE = 5, // "Notice" + MONGOC_STRUCTURED_LOG_LEVEL_INFO = 6, // "Informational" ("info" also accepted) + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG = 7, // "Debug" + MONGOC_STRUCTURED_LOG_LEVEL_TRACE = 8, // "Trace" + } mongoc_structured_log_level_t; + + typedef enum { + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND = 0, // "command" + MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY = 1, // "topology" + MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION = 2, // "serverSelection" + MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION = 3, // "connection" + } mongoc_structured_log_component_t; + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_level_t + mongoc_structured_log_component_t + +.. seealso:: + + mongoc_structured_log_get_level_name + mongoc_structured_log_get_named_level + mongoc_structured_log_get_component_name + mongoc_structured_log_get_named_component + + +Log Handlers +------------ + +Each :symbol:`mongoc_client_pool_t` or standalone :symbol:`mongoc_client_t` has its own instance of the structured logging subsystem, with its own settings and handler. + +When using :symbol:`mongoc_client_pool_t`, the pooled clients all share a common logging instance. Handlers must be thread-safe. + +The handler is called for each log entry with a level no greater than its component's maximum. +A :symbol:`mongoc_structured_log_entry_t` pointer provides access to further details, during the handler only. + +Handlers must take care not to re-enter ``libmongoc`` with the same :symbol:`mongoc_client_t` or :symbol:`mongoc_client_pool_t` that the handler has been called by. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_func_t + + +Log Entries +----------- + +Each log entry is represented within the handler by a short-lived :symbol:`mongoc_structured_log_entry_t` pointer. +During the handler, this pointer can be used to access the individual properties of an entry: its level, component, and message. + +The message will be assembled as a :symbol:`bson_t` only when explicitly requested by a call to :symbol:`mongoc_structured_log_entry_message_as_bson`. +This results in a standalone document that may be retained for any amount of time and must be explicitly destroyed. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + mongoc_structured_log_entry_t + + +Example +------- +.. literalinclude:: ../examples/example-structured-log.c + :language: c + :caption: example-structured-log.c + +.. seealso:: + + mongoc_structured_log_entry_get_component + mongoc_structured_log_entry_get_level + mongoc_structured_log_entry_message_as_bson diff --git a/src/libmongoc/doc/unstructured_log.rst b/src/libmongoc/doc/unstructured_log.rst new file mode 100644 index 00000000000..1e2eb3c7117 --- /dev/null +++ b/src/libmongoc/doc/unstructured_log.rst @@ -0,0 +1,132 @@ +:man_page: mongoc_unstructured_log + +Unstructured Logging +==================== + +This is the original logging facility that supports freeform string messages originating from the driver itself or from application code. This has been retroactively termed "unstructured logging". +See :doc:`structured_log` for the newer standardized logging facility. + +.. code-block:: c + + typedef enum { + MONGOC_LOG_LEVEL_ERROR, + MONGOC_LOG_LEVEL_CRITICAL, + MONGOC_LOG_LEVEL_WARNING, + MONGOC_LOG_LEVEL_MESSAGE, + MONGOC_LOG_LEVEL_INFO, + MONGOC_LOG_LEVEL_DEBUG, + MONGOC_LOG_LEVEL_TRACE, + } mongoc_log_level_t; + + #define MONGOC_ERROR(...) + #define MONGOC_CRITICAL(...) + #define MONGOC_WARNING(...) + #define MONGOC_MESSAGE(...) + #define MONGOC_INFO(...) + #define MONGOC_DEBUG(...) + + typedef void (*mongoc_log_func_t) (mongoc_log_level_t log_level, + const char *log_domain, + const char *message, + void *user_data); + + void + mongoc_log_set_handler (mongoc_log_func_t log_func, void *user_data); + void + mongoc_log (mongoc_log_level_t log_level, + const char *log_domain, + const char *format, + ...); + const char * + mongoc_log_level_str (mongoc_log_level_t log_level); + void + mongoc_log_default_handler (mongoc_log_level_t log_level, + const char *log_domain, + const char *message, + void *user_data); + void + mongoc_log_trace_enable (void); + void + mongoc_log_trace_disable (void); + +This abstraction can be used for logging in your application, or you can integrate the driver with an existing logging system. + +Macros +------ + +To make logging a little less painful, various helper macros are provided. See the following example. + +.. code-block:: c + + #undef MONGOC_LOG_DOMAIN + #define MONGOC_LOG_DOMAIN "my-custom-domain" + + MONGOC_WARNING ("An error occurred: %s", strerror (errno)); + +.. _custom_log_handlers: + +Custom Log Handlers +------------------- + +The default log handler prints a timestamp and the log message to ``stdout``, or to ``stderr`` for warnings, critical messages, and errors. +You can override the handler with ``mongoc_log_set_handler()``. +Your handler function is called in a mutex for thread safety. + +For example, you could register a custom handler to suppress messages at INFO level and below: + +.. code-block:: c + + void + my_logger (mongoc_log_level_t log_level, + const char *log_domain, + const char *message, + void *user_data) + { + /* smaller values are more important */ + if (log_level < MONGOC_LOG_LEVEL_INFO) { + mongoc_log_default_handler (log_level, log_domain, message, user_data); + } + } + + int + main (int argc, char *argv[]) + { + mongoc_log_set_handler (my_logger, NULL); + mongoc_init (); + + /* ... your code ... */ + + mongoc_cleanup (); + return 0; + } + +Note that in the example above ``mongoc_log_set_handler()`` is called before ``mongoc_init()``. +Otherwise, some log traces could not be processed by the log handler. + +To restore the default handler: + +.. code-block:: c + + mongoc_log_set_handler (mongoc_log_default_handler, NULL); + +Disable logging +--------------- + +To disable all logging, including warnings, critical messages and errors, provide an empty log handler: + +.. code-block:: c + + mongoc_log_set_handler (NULL, NULL); + +Tracing +------- + +If compiling your own copy of the MongoDB C driver, consider configuring with ``-DENABLE_TRACING=ON`` to enable function tracing and hex dumps of network packets to ``STDERR`` and ``STDOUT`` during development and debugging. + +This is especially useful when debugging what may be going on internally in the driver. + +Trace messages can be enabled and disabled by calling ``mongoc_log_trace_enable()`` and ``mongoc_log_trace_disable()`` + +.. note:: + + Compiling the driver with ``-DENABLE_TRACING=ON`` will affect its performance. Disabling tracing with ``mongoc_log_trace_disable()`` significantly reduces the overhead, but cannot remove it completely. diff --git a/src/libmongoc/examples/example-sdam-monitoring.c b/src/libmongoc/examples/example-sdam-monitoring.c index de617c87fbb..b5afd515c1f 100644 --- a/src/libmongoc/examples/example-sdam-monitoring.c +++ b/src/libmongoc/examples/example-sdam-monitoring.c @@ -108,14 +108,13 @@ topology_changed (const mongoc_apm_topology_changed_t *event) prefs = mongoc_read_prefs_new (MONGOC_READ_SECONDARY); - /* it is safe, and unfortunately necessary, to cast away const here */ - if (mongoc_topology_description_has_readable_server ((mongoc_topology_description_t *) new_td, prefs)) { + if (mongoc_topology_description_has_readable_server (new_td, prefs)) { printf (" secondary AVAILABLE\n"); } else { printf (" secondary UNAVAILABLE\n"); } - if (mongoc_topology_description_has_writable_server ((mongoc_topology_description_t *) new_td)) { + if (mongoc_topology_description_has_writable_server (new_td)) { printf (" primary AVAILABLE\n"); } else { printf (" primary UNAVAILABLE\n"); diff --git a/src/libmongoc/examples/example-structured-log.c b/src/libmongoc/examples/example-structured-log.c new file mode 100644 index 00000000000..b6ba56d6c29 --- /dev/null +++ b/src/libmongoc/examples/example-structured-log.c @@ -0,0 +1,157 @@ +/* gcc example-structured-log.c -o example-structured-log \ + * $(pkg-config --cflags --libs libmongoc-1.0) */ + +#include +#include +#include +#include + +static pthread_mutex_t handler_mutex; + +static void +example_handler (const mongoc_structured_log_entry_t *entry, void *user_data) +{ + mongoc_structured_log_component_t component = mongoc_structured_log_entry_get_component (entry); + mongoc_structured_log_level_t level = mongoc_structured_log_entry_get_level (entry); + const char *message_string = mongoc_structured_log_entry_get_message_string (entry); + + /* + * With a single-threaded mongoc_client_t, handlers will always be called + * by the thread that owns the client. On a mongoc_client_pool_t, handlers + * are shared by multiple threads and must be reentrant. + * + * Note that unstructured logging includes a global mutex in the API, + * but structured logging allows applications to avoid lock contention + * even when multiple threads are issuing commands simultaneously. + * + * Simple apps like this example can achieve thread safety by adding their + * own global mutex. For other apps, this would be a performance bottleneck + * and it would be more appropriate for handlers to process their log + * messages concurrently. + * + * In this example, our mutex protects access to a global log counter. + * In a real application, you may need to protect access to a shared stream + * or queue. + */ + pthread_mutex_lock (&handler_mutex); + + static unsigned log_serial_number = 0; + + printf ("%u. Log entry with component=%s level=%s message_string='%s'\n", + ++log_serial_number, + mongoc_structured_log_get_component_name (component), + mongoc_structured_log_get_level_name (level), + message_string); + + /* + * At this point, the handler might make additional filtering decisions + * before asking for a bson_t. As an example, let's log the component and + * level for all messages but only show contents for command logs. + */ + if (component == MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND) { + bson_t *message = mongoc_structured_log_entry_message_as_bson (entry); + char *json = bson_as_relaxed_extended_json (message, NULL); + printf ("Full log message, as json: %s\n", json); + bson_destroy (message); + bson_free (json); + } + + pthread_mutex_unlock (&handler_mutex); +} + +int +main (void) +{ + const char *uri_string = "mongodb://localhost:27017"; + int result = EXIT_FAILURE; + bson_error_t error; + mongoc_uri_t *uri = NULL; + mongoc_structured_log_opts_t *log_opts = NULL; + mongoc_client_t *client = NULL; + mongoc_client_pool_t *pool = NULL; + + /* + * Note that structured logging only applies per-client or per-pool, + * and it won't be used during or before mongoc_init. + */ + mongoc_init (); + + /* + * Logging options are represented by a mongoc_structured_log_opts_t, + * which can be copied into a mongoc_client_t or mongoc_client_pool_t + * using mongoc_client_set_structured_log_opts() or + * mongoc_client_pool_set_structured_log_opts(), respectively. + * + * Default settings are captured from the environment into + * this structure when it's constructed. + */ + log_opts = mongoc_structured_log_opts_new (); + + /* + * For demonstration purposes, set up a handler that receives all possible log messages. + */ + pthread_mutex_init (&handler_mutex, NULL); + mongoc_structured_log_opts_set_max_level_for_all_components (log_opts, MONGOC_STRUCTURED_LOG_LEVEL_TRACE); + mongoc_structured_log_opts_set_handler (log_opts, example_handler, NULL); + + /* + * By default libmongoc proceses log options from the environment first, + * and then allows you to apply programmatic overrides. To request the + * opposite behavior, allowing the environment to override programmatic + * defaults, you can ask for the environment to be re-read after setting + * your own defaults. + */ + mongoc_structured_log_opts_set_max_levels_from_env (log_opts); + + /* + * Create a MongoDB URI object. This example assumes a local server. + */ + uri = mongoc_uri_new_with_error (uri_string, &error); + if (!uri) { + fprintf (stderr, "URI parse error: %s\n", error.message); + goto done; + } + + /* + * Create a new client pool. + */ + pool = mongoc_client_pool_new (uri); + if (!pool) { + goto done; + } + + /* + * Set the client pool's log options. + * This must happen only once, and only before the first mongoc_client_pool_pop. + * There's no need to keep log_opts after this point. + */ + mongoc_client_pool_set_structured_log_opts (pool, log_opts); + + /* + * Check out a client, and do some work that we'll see logs from. + * This example just sends a 'ping' command. + */ + client = mongoc_client_pool_pop (pool); + if (!client) { + goto done; + } + + bson_t *command = BCON_NEW ("ping", BCON_INT32 (1)); + bson_t reply; + bool command_ret = mongoc_client_command_simple (client, "admin", command, NULL, &reply, &error); + bson_destroy (command); + bson_destroy (&reply); + mongoc_client_pool_push (pool, client); + if (!command_ret) { + fprintf (stderr, "Command error: %s\n", error.message); + goto done; + } + + result = EXIT_SUCCESS; +done: + mongoc_uri_destroy (uri); + mongoc_structured_log_opts_destroy (log_opts); + mongoc_client_pool_destroy (pool); + mongoc_cleanup (); + return result; +} diff --git a/src/libmongoc/src/mongoc/mongoc-apm.c b/src/libmongoc/src/mongoc/mongoc-apm.c index 221d225b181..ef693d62903 100644 --- a/src/libmongoc/src/mongoc/mongoc-apm.c +++ b/src/libmongoc/src/mongoc/mongoc-apm.c @@ -18,8 +18,7 @@ #include "mongoc-apm-private.h" #include "mongoc-cmd-private.h" #include "mongoc-handshake-private.h" - -static bson_oid_t kObjectIdZero = {{0}}; +#include /* * An Application Performance Management (APM) implementation, complying with @@ -370,7 +369,7 @@ mongoc_apm_command_started_get_server_id (const mongoc_apm_command_started_t *ev const bson_oid_t * mongoc_apm_command_started_get_service_id (const mongoc_apm_command_started_t *event) { - if (0 == bson_oid_compare (&event->service_id, &kObjectIdZero)) { + if (mcommon_oid_is_zero (&event->service_id)) { /* serviceId is unset. */ return NULL; } @@ -466,7 +465,7 @@ mongoc_apm_command_succeeded_get_server_id (const mongoc_apm_command_succeeded_t const bson_oid_t * mongoc_apm_command_succeeded_get_service_id (const mongoc_apm_command_succeeded_t *event) { - if (0 == bson_oid_compare (&event->service_id, &kObjectIdZero)) { + if (mcommon_oid_is_zero (&event->service_id)) { /* serviceId is unset. */ return NULL; } @@ -562,7 +561,7 @@ mongoc_apm_command_failed_get_server_id (const mongoc_apm_command_failed_t *even const bson_oid_t * mongoc_apm_command_failed_get_service_id (const mongoc_apm_command_failed_t *event) { - if (0 == bson_oid_compare (&event->service_id, &kObjectIdZero)) { + if (mcommon_oid_is_zero (&event->service_id)) { /* serviceId is unset. */ return NULL; } diff --git a/src/libmongoc/src/mongoc/mongoc-client-pool.c b/src/libmongoc/src/mongoc/mongoc-client-pool.c index 8dd7c8604e0..4437798a8e9 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-pool.c +++ b/src/libmongoc/src/mongoc/mongoc-client-pool.c @@ -47,16 +47,17 @@ struct _mongoc_client_pool_t { uint32_t max_pool_size; uint32_t size; #ifdef MONGOC_ENABLE_SSL - bool ssl_opts_set; mongoc_ssl_opt_t ssl_opts; + bool ssl_opts_set; #endif bool apm_callbacks_set; + bool error_api_set; + bool structured_log_opts_set; + bool client_initialized; mongoc_apm_callbacks_t apm_callbacks; void *apm_context; int32_t error_api_version; - bool error_api_set; mongoc_server_api_t *api; - bool client_initialized; // `last_known_serverids` is a sorted array of uint32_t. mongoc_array_t last_known_serverids; }; @@ -281,7 +282,6 @@ _initialize_new_client (mongoc_client_pool_t *pool, mongoc_client_t *client) client, pool->topology->scanner->initiator, pool->topology->scanner->initiator_context); pool->client_initialized = true; - client->is_pooled = true; client->error_api_version = pool->error_api_version; _mongoc_client_set_apm_callbacks_private (client, &pool->apm_callbacks, pool->apm_context); @@ -608,6 +608,29 @@ mongoc_client_pool_set_apm_callbacks (mongoc_client_pool_t *pool, mongoc_apm_cal return true; } +bool +mongoc_client_pool_set_structured_log_opts (mongoc_client_pool_t *pool, const mongoc_structured_log_opts_t *opts) +{ + BSON_ASSERT_PARAM (pool); + BSON_OPTIONAL_PARAM (opts); + + /* The documented restriction for most pool options: They can be set at most once, + * and only before the first client is initialized. Structured logging is generally + * expected to warn but not quit when encountering initialization errors. */ + if (pool->structured_log_opts_set) { + MONGOC_WARNING ("mongoc_client_pool_set_structured_log_opts can only be called once per pool"); + return false; + } else if (pool->client_initialized) { + MONGOC_WARNING ("mongoc_client_pool_set_structured_log_opts can only be called before mongoc_client_pool_pop"); + return false; + } else { + // Now we can be sure no other threads are relying on concurrent access to the instance yet. + mongoc_topology_set_structured_log_opts (pool->topology, opts); + pool->structured_log_opts_set = true; + return true; + } +} + bool mongoc_client_pool_set_error_api (mongoc_client_pool_t *pool, int32_t version) { diff --git a/src/libmongoc/src/mongoc/mongoc-client-pool.h b/src/libmongoc/src/mongoc/mongoc-client-pool.h index 779aec7d5aa..025d5b60956 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-pool.h +++ b/src/libmongoc/src/mongoc/mongoc-client-pool.h @@ -28,6 +28,7 @@ #ifdef MONGOC_ENABLE_SSL #include "mongoc-ssl.h" #endif +#include "mongoc-structured-log.h" #include "mongoc-uri.h" @@ -69,6 +70,8 @@ mongoc_client_pool_enable_auto_encryption (mongoc_client_pool_t *pool, bson_error_t *error); MONGOC_EXPORT (bool) mongoc_client_pool_set_server_api (mongoc_client_pool_t *pool, const mongoc_server_api_t *api, bson_error_t *error); +MONGOC_EXPORT (bool) +mongoc_client_pool_set_structured_log_opts (mongoc_client_pool_t *pool, const mongoc_structured_log_opts_t *opts); BSON_END_DECLS diff --git a/src/libmongoc/src/mongoc/mongoc-client-private.h b/src/libmongoc/src/mongoc/mongoc-client-private.h index 39d540e07a8..ef88ea87c25 100644 --- a/src/libmongoc/src/mongoc/mongoc-client-private.h +++ b/src/libmongoc/src/mongoc/mongoc-client-private.h @@ -92,7 +92,6 @@ struct _mongoc_client_t { mongoc_uri_t *uri; mongoc_cluster_t cluster; bool in_exhaust; - bool is_pooled; mongoc_stream_initiator_t initiator; void *initiator_data; diff --git a/src/libmongoc/src/mongoc/mongoc-client.c b/src/libmongoc/src/mongoc/mongoc-client.c index 80f15f83055..b5f75b41116 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.c +++ b/src/libmongoc/src/mongoc/mongoc-client.c @@ -57,6 +57,7 @@ #include "mongoc-change-stream-private.h" #include "mongoc-client-session-private.h" #include "mongoc-cursor-private.h" +#include "mongoc-structured-log-private.h" #ifdef MONGOC_ENABLE_SSL #include "mongoc-stream-tls.h" @@ -1122,6 +1123,11 @@ _mongoc_client_new_from_topology (mongoc_topology_t *topology) } #endif + mongoc_structured_log (topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION, + "Client created"); + mongoc_counter_clients_active_inc (); return client; @@ -2604,6 +2610,24 @@ mongoc_client_set_apm_callbacks (mongoc_client_t *client, mongoc_apm_callbacks_t return _mongoc_client_set_apm_callbacks_private (client, callbacks, context); } + +bool +mongoc_client_set_structured_log_opts (mongoc_client_t *client, const mongoc_structured_log_opts_t *opts) +{ + BSON_ASSERT_PARAM (client); + BSON_OPTIONAL_PARAM (opts); + + if (client->topology->single_threaded) { + mongoc_topology_set_structured_log_opts (client->topology, opts); + return true; + } else { + MONGOC_WARNING ("Cannot set structured log options on a pooled client, use " + "mongoc_client_pool_set_structured_log_opts before the first mongoc_client_pool_pop"); + return false; + } +} + + mongoc_server_description_t * mongoc_client_get_server_description (mongoc_client_t *client, uint32_t server_id) { @@ -2920,7 +2944,7 @@ mongoc_client_set_server_api (mongoc_client_t *client, const mongoc_server_api_t BSON_ASSERT_PARAM (client); BSON_ASSERT_PARAM (api); - if (client->is_pooled) { + if (!client->topology->single_threaded) { bson_set_error (error, MONGOC_ERROR_CLIENT, MONGOC_ERROR_CLIENT_API_FROM_POOL, diff --git a/src/libmongoc/src/mongoc/mongoc-client.h b/src/libmongoc/src/mongoc/mongoc-client.h index 1c1c4b5cbcd..243fe0834e5 100644 --- a/src/libmongoc/src/mongoc/mongoc-client.h +++ b/src/libmongoc/src/mongoc/mongoc-client.h @@ -36,6 +36,7 @@ #include "mongoc-ssl.h" #endif #include "mongoc-stream.h" +#include "mongoc-structured-log.h" #include "mongoc-uri.h" #include "mongoc-write-concern.h" #include "mongoc-read-concern.h" @@ -228,6 +229,8 @@ mongoc_client_set_ssl_opts (mongoc_client_t *client, const mongoc_ssl_opt_t *opt #endif MONGOC_EXPORT (bool) mongoc_client_set_apm_callbacks (mongoc_client_t *client, mongoc_apm_callbacks_t *callbacks, void *context); +MONGOC_EXPORT (bool) +mongoc_client_set_structured_log_opts (mongoc_client_t *client, const mongoc_structured_log_opts_t *opts); MONGOC_EXPORT (mongoc_server_description_t *) mongoc_client_get_server_description (mongoc_client_t *client, uint32_t server_id) BSON_GNUC_WARN_UNUSED_RESULT; MONGOC_EXPORT (mongoc_server_description_t **) diff --git a/src/libmongoc/src/mongoc/mongoc-cluster.c b/src/libmongoc/src/mongoc/mongoc-cluster.c index fab09a2635b..eb5b408d923 100644 --- a/src/libmongoc/src/mongoc/mongoc-cluster.c +++ b/src/libmongoc/src/mongoc/mongoc-cluster.c @@ -56,9 +56,11 @@ #include "mongoc-handshake-private.h" #include "mongoc-cluster-aws-private.h" #include "mongoc-error-private.h" +#include "mongoc-structured-log-private.h" #include #include +#include #include @@ -508,7 +510,7 @@ mongoc_cluster_run_command_monitored (mongoc_cluster_t *cluster, mongoc_cmd_t *c bson_t encrypted = BSON_INITIALIZER; bson_t decrypted = BSON_INITIALIZER; mongoc_cmd_t encrypted_cmd; - bool is_redacted = false; + bool is_redacted_by_apm = false; server_stream = cmd->server_stream; server_id = server_stream->sd->id; @@ -532,9 +534,18 @@ mongoc_cluster_run_command_monitored (mongoc_cluster_t *cluster, mongoc_cmd_t *c } } + mongoc_structured_log ( + cluster->client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command started", + int32 ("requestId", request_id), + server_description (server_stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + cmd (cmd, DATABASE_NAME, COMMAND_NAME, OPERATION_ID, COMMAND)); + if (callbacks->started) { mongoc_apm_command_started_init_with_cmd ( - &started_event, cmd, request_id, &is_redacted, cluster->client->apm_context); + &started_event, cmd, request_id, &is_redacted_by_apm, cluster->client->apm_context); callbacks->started (&started_event); mongoc_apm_command_started_cleanup (&started_event); @@ -542,8 +553,10 @@ mongoc_cluster_run_command_monitored (mongoc_cluster_t *cluster, mongoc_cmd_t *c retval = mongoc_cluster_run_opmsg (cluster, cmd, reply, error); - if (retval && callbacks->succeeded) { + if (retval) { bson_t fake_reply = BSON_INITIALIZER; + int64_t duration = bson_get_monotonic_time () - started; + /* * Unacknowledged writes must provide a CommandSucceededEvent with an * {ok: 1} reply. @@ -552,42 +565,71 @@ mongoc_cluster_run_command_monitored (mongoc_cluster_t *cluster, mongoc_cmd_t *c if (!cmd->is_acknowledged) { bson_append_int32 (&fake_reply, "ok", 2, 1); } - mongoc_apm_command_succeeded_init (&succeeded_event, - bson_get_monotonic_time () - started, - cmd->is_acknowledged ? reply : &fake_reply, + + mongoc_structured_log ( + cluster->client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command succeeded", + int32 ("requestId", request_id), + monotonic_time_duration (duration), + server_description (server_stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + cmd (cmd, DATABASE_NAME, COMMAND_NAME, OPERATION_ID), + cmd_reply (cmd, cmd->is_acknowledged ? reply : &fake_reply)); + + if (callbacks->succeeded) { + mongoc_apm_command_succeeded_init (&succeeded_event, + duration, + cmd->is_acknowledged ? reply : &fake_reply, + cmd->command_name, + cmd->db_name, + request_id, + cmd->operation_id, + &server_stream->sd->host, + server_id, + &server_stream->sd->service_id, + server_stream->sd->server_connection_id, + is_redacted_by_apm, + cluster->client->apm_context); + + callbacks->succeeded (&succeeded_event); + mongoc_apm_command_succeeded_cleanup (&succeeded_event); + } + + bson_destroy (&fake_reply); + } else { + int64_t duration = bson_get_monotonic_time () - started; + + mongoc_structured_log ( + cluster->client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command failed", + int32 ("requestId", request_id), + monotonic_time_duration (duration), + server_description (server_stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + cmd (cmd, DATABASE_NAME, COMMAND_NAME, OPERATION_ID), + cmd_failure (cmd, reply, error)); + + if (callbacks->failed) { + mongoc_apm_command_failed_init (&failed_event, + duration, cmd->command_name, cmd->db_name, + error, + reply, request_id, cmd->operation_id, &server_stream->sd->host, server_id, &server_stream->sd->service_id, server_stream->sd->server_connection_id, - is_redacted, + is_redacted_by_apm, cluster->client->apm_context); - callbacks->succeeded (&succeeded_event); - mongoc_apm_command_succeeded_cleanup (&succeeded_event); - bson_destroy (&fake_reply); - } - if (!retval && callbacks->failed) { - mongoc_apm_command_failed_init (&failed_event, - bson_get_monotonic_time () - started, - cmd->command_name, - cmd->db_name, - error, - reply, - request_id, - cmd->operation_id, - &server_stream->sd->host, - server_id, - &server_stream->sd->service_id, - server_stream->sd->server_connection_id, - is_redacted, - cluster->client->apm_context); - - callbacks->failed (&failed_event); - mongoc_apm_command_failed_cleanup (&failed_event); + callbacks->failed (&failed_event); + mongoc_apm_command_failed_cleanup (&failed_event); + } } if (retval && _mongoc_cse_is_enabled (cluster->client)) { @@ -2003,9 +2045,9 @@ _mongoc_cluster_stream_for_server (mongoc_cluster_t *cluster, mongoc_topology_description_invalidate_server (tdmod.new_td, server_id, err_ptr); mongoc_cluster_disconnect_node (cluster, server_id); /* This is not load balanced mode, so there are no service IDs associated - * with connections. Pass kZeroServiceId to clear the entire connection + * with connections. Pass kZeroObjectId to clear the entire connection * pool to this server. */ - _mongoc_topology_description_clear_connection_pool (tdmod.new_td, server_id, &kZeroServiceId); + _mongoc_topology_description_clear_connection_pool (tdmod.new_td, server_id, &kZeroObjectId); if (!topology->single_threaded) { _mongoc_topology_background_monitoring_cancel_check (topology, server_id); diff --git a/src/libmongoc/src/mongoc/mongoc-cursor-legacy.c b/src/libmongoc/src/mongoc/mongoc-cursor-legacy.c index 818cb09f630..0b42c576654 100644 --- a/src/libmongoc/src/mongoc/mongoc-cursor-legacy.c +++ b/src/libmongoc/src/mongoc/mongoc-cursor-legacy.c @@ -32,6 +32,7 @@ #include "mongoc-write-concern-private.h" #include "mongoc-read-prefs-private.h" #include "mongoc-rpc-private.h" +#include "mongoc-structured-log-private.h" #include @@ -46,13 +47,26 @@ _mongoc_cursor_monitor_legacy_get_more (mongoc_cursor_t *cursor, mongoc_server_s ENTRY; client = cursor->client; + _mongoc_cursor_prepare_getmore_command (cursor, &doc); + + mongoc_structured_log ( + client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command started", + int32 ("requestId", client->cluster.request_id), + server_description (server_stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + utf8_n ("databaseName", cursor->ns, cursor->dblen), + utf8 ("commandName", "getMore"), + int64 ("operationId", cursor->operation_id), + bson_as_json ("command", &doc)); + if (!client->apm_callbacks.started) { /* successful */ + bson_destroy (&doc); RETURN (true); } - _mongoc_cursor_prepare_getmore_command (cursor, &doc); - db = bson_strndup (cursor->ns, cursor->dblen); mongoc_apm_command_started_init (&event, &doc, @@ -82,18 +96,11 @@ _mongoc_cursor_monitor_legacy_query (mongoc_cursor_t *cursor, mongoc_server_stream_t *server_stream) { bson_t doc; - mongoc_client_t *client; char *db; bool r; ENTRY; - client = cursor->client; - if (!client->apm_callbacks.started) { - /* successful */ - RETURN (true); - } - bson_init (&doc); db = bson_strndup (cursor->ns, cursor->dblen); diff --git a/src/libmongoc/src/mongoc/mongoc-cursor.c b/src/libmongoc/src/mongoc/mongoc-cursor.c index 948194b50b6..f891e854fb3 100644 --- a/src/libmongoc/src/mongoc/mongoc-cursor.c +++ b/src/libmongoc/src/mongoc/mongoc-cursor.c @@ -29,6 +29,7 @@ #include "mongoc-write-concern-private.h" #include "mongoc-read-prefs-private.h" #include "mongoc-aggregate-private.h" +#include "mongoc-structured-log-private.h" #include #include @@ -644,6 +645,19 @@ _mongoc_cursor_monitor_command (mongoc_cursor_t *cursor, ENTRY; client = cursor->client; + + mongoc_structured_log ( + client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command started", + int32 ("requestId", client->cluster.request_id), + server_description (server_stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + utf8_n ("databaseName", cursor->ns, cursor->dblen), + utf8 ("commandName", cmd_name), + int64 ("operationId", cursor->operation_id), + bson_as_json ("command", cmd)); + if (!client->apm_callbacks.started) { /* successful */ RETURN (true); @@ -710,10 +724,6 @@ _mongoc_cursor_monitor_succeeded (mongoc_cursor_t *cursor, client = cursor->client; - if (!client->apm_callbacks.succeeded) { - EXIT; - } - /* we sent OP_QUERY/OP_GETMORE, fake a reply to find/getMore command: * {ok: 1, cursor: {id: 17, ns: "...", first/nextBatch: [ ... docs ... ]}} */ @@ -730,23 +740,38 @@ _mongoc_cursor_monitor_succeeded (mongoc_cursor_t *cursor, bson_destroy (&docs_array); - mongoc_apm_command_succeeded_init (&event, - duration, - &reply, - cmd_name, - db, - client->cluster.request_id, - cursor->operation_id, - &stream->sd->host, - stream->sd->id, - &stream->sd->service_id, - stream->sd->server_connection_id, - false, - client->apm_context); - - client->apm_callbacks.succeeded (&event); + mongoc_structured_log (client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command succeeded", + int32 ("requestId", client->cluster.request_id), + server_description (stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + utf8 ("databaseName", db), + utf8 ("commandName", cmd_name), + int64 ("operationId", cursor->operation_id), + monotonic_time_duration (duration), + cmd_name_reply (cmd_name, &reply)); + + if (client->apm_callbacks.succeeded) { + mongoc_apm_command_succeeded_init (&event, + duration, + &reply, + cmd_name, + db, + client->cluster.request_id, + cursor->operation_id, + &stream->sd->host, + stream->sd->id, + &stream->sd->service_id, + stream->sd->server_connection_id, + false, + client->apm_context); + + client->apm_callbacks.succeeded (&event); + + mongoc_apm_command_succeeded_cleanup (&event); + } - mongoc_apm_command_succeeded_cleanup (&event); bson_destroy (&reply); bson_free (db); @@ -767,34 +792,45 @@ _mongoc_cursor_monitor_failed (mongoc_cursor_t *cursor, client = cursor->client; - if (!client->apm_callbacks.failed) { - EXIT; - } - /* we sent OP_QUERY/OP_GETMORE, fake a reply to find/getMore command: * {ok: 0} */ bsonBuildDecl (reply, kv ("ok", int32 (0))); char *db = bson_strndup (cursor->ns, cursor->dblen); - mongoc_apm_command_failed_init (&event, - duration, - cmd_name, - db, - &cursor->error, - &reply, - client->cluster.request_id, - cursor->operation_id, - &stream->sd->host, - stream->sd->id, - &stream->sd->service_id, - stream->sd->server_connection_id, - false, - client->apm_context); - - client->apm_callbacks.failed (&event); - - mongoc_apm_command_failed_cleanup (&event); + mongoc_structured_log (client->topology->structured_log, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Command failed", + int32 ("requestId", client->cluster.request_id), + server_description (stream->sd, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + utf8 ("databaseName", db), + utf8 ("commandName", cmd_name), + int64 ("operationId", cursor->operation_id), + monotonic_time_duration (duration), + bson_as_json ("failure", &reply)); + + if (client->apm_callbacks.failed) { + mongoc_apm_command_failed_init (&event, + duration, + cmd_name, + db, + &cursor->error, + &reply, + client->cluster.request_id, + cursor->operation_id, + &stream->sd->host, + stream->sd->id, + &stream->sd->service_id, + stream->sd->server_connection_id, + false, + client->apm_context); + + client->apm_callbacks.failed (&event); + + mongoc_apm_command_failed_cleanup (&event); + } + bson_destroy (&reply); bson_free (db); diff --git a/src/libmongoc/src/mongoc/mongoc-error-private.h b/src/libmongoc/src/mongoc/mongoc-error-private.h index f3bd4f0578b..eb9310f5c72 100644 --- a/src/libmongoc/src/mongoc/mongoc-error-private.h +++ b/src/libmongoc/src/mongoc/mongoc-error-private.h @@ -16,6 +16,9 @@ #include "mongoc-prelude.h" +#ifndef MONGOC_ERROR_PRIVATE_H +#define MONGOC_ERROR_PRIVATE_H + #include #include @@ -93,4 +96,15 @@ _mongoc_error_is_auth (const bson_error_t *error); void _mongoc_error_append (bson_error_t *error, const char *s); +typedef enum { + MONGOC_ERROR_CONTENT_FLAG_CODE = (1 << 0), + MONGOC_ERROR_CONTENT_FLAG_DOMAIN = (1 << 1), + MONGOC_ERROR_CONTENT_FLAG_MESSAGE = (1 << 2), +} mongoc_error_content_flags_t; + +bool +mongoc_error_append_contents_to_bson (const bson_error_t *error, bson_t *bson, mongoc_error_content_flags_t flags); + BSON_END_DECLS + +#endif /* MONGOC_ERROR_PRIVATE_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-error.c b/src/libmongoc/src/mongoc/mongoc-error.c index 8b172d430f3..5fd6998f57c 100644 --- a/src/libmongoc/src/mongoc/mongoc-error.c +++ b/src/libmongoc/src/mongoc/mongoc-error.c @@ -320,3 +320,21 @@ _mongoc_error_append (bson_error_t *error, const char *s) const size_t remaining = sizeof (error->message) - error_len; bson_strncpy (error->message + error_len, s, remaining); } + +bool +mongoc_error_append_contents_to_bson (const bson_error_t *error, bson_t *bson, mongoc_error_content_flags_t flags) +{ + BSON_ASSERT_PARAM (error); + BSON_ASSERT_PARAM (bson); + + if ((flags & MONGOC_ERROR_CONTENT_FLAG_CODE) && !BSON_APPEND_INT32 (bson, "code", error->code)) { + return false; + } + if ((flags & MONGOC_ERROR_CONTENT_FLAG_DOMAIN) && !BSON_APPEND_INT32 (bson, "domain", error->domain)) { + return false; + } + if ((flags & MONGOC_ERROR_CONTENT_FLAG_MESSAGE) && !BSON_APPEND_UTF8 (bson, "message", error->message)) { + return false; + } + return true; +} diff --git a/src/libmongoc/src/mongoc/mongoc-server-description-private.h b/src/libmongoc/src/mongoc/mongoc-server-description-private.h index 452d8c3186b..125718b68c8 100644 --- a/src/libmongoc/src/mongoc/mongoc-server-description-private.h +++ b/src/libmongoc/src/mongoc/mongoc-server-description-private.h @@ -122,7 +122,7 @@ struct _mongoc_server_description_t { /* _generation_map_ stores all generations for all service IDs associated * with this server. _generation_map_ is only accessed on the server * description for monitoring. In non-load-balanced mode, there are no - * service IDs. The only server generation is mapped from kZeroServiceID */ + * service IDs. The only server generation is mapped from kZeroObjectId */ mongoc_generation_map_t *_generation_map_; bson_oid_t service_id; int64_t server_connection_id; @@ -218,9 +218,19 @@ mongoc_server_description_topology_version_cmp (const bson_t *tv1, const bson_t void mongoc_server_description_set_topology_version (mongoc_server_description_t *sd, const bson_t *tv); -extern const bson_oid_t kZeroServiceId; - bool mongoc_server_description_has_service_id (const mongoc_server_description_t *description); +typedef enum { + MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_HOST = (1 << 0), + MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_PORT = (1 << 1), + MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_CONNECTION_ID = (1 << 2), + MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVICE_ID = (1 << 3), +} mongoc_server_description_content_flags_t; + +bool +mongoc_server_description_append_contents_to_bson (const mongoc_server_description_t *sd, + bson_t *bson, + mongoc_server_description_content_flags_t flags); + #endif diff --git a/src/libmongoc/src/mongoc/mongoc-server-description.c b/src/libmongoc/src/mongoc/mongoc-server-description.c index ab1202995a0..84a6c8babac 100644 --- a/src/libmongoc/src/mongoc/mongoc-server-description.c +++ b/src/libmongoc/src/mongoc/mongoc-server-description.c @@ -27,15 +27,12 @@ #include #include +#include #include #define ALPHA 0.2 -static bson_oid_t kObjectIdZero = {{0}}; - -const bson_oid_t kZeroServiceId = {{0}}; - static bool _match_tag_set (const mongoc_server_description_t *sd, bson_iter_t *tag_set_iter); @@ -96,8 +93,8 @@ mongoc_server_description_reset (mongoc_server_description_t *sd) sd->me = NULL; sd->current_primary = NULL; sd->set_version = MONGOC_NO_SET_VERSION; - bson_oid_copy_unsafe (&kObjectIdZero, &sd->election_id); - bson_oid_copy_unsafe (&kObjectIdZero, &sd->service_id); + mcommon_oid_set_zero (&sd->election_id); + mcommon_oid_set_zero (&sd->service_id); sd->server_connection_id = MONGOC_NO_SERVER_CONNECTION_ID; } @@ -259,7 +256,7 @@ mongoc_server_description_has_set_version (const mongoc_server_description_t *de bool mongoc_server_description_has_election_id (const mongoc_server_description_t *description) { - return 0 != bson_oid_compare (&description->election_id, &kObjectIdZero); + return !mcommon_oid_is_zero (&description->election_id); } /* @@ -462,7 +459,7 @@ mongoc_server_description_set_election_id (mongoc_server_description_t *descript if (election_id) { bson_oid_copy_unsafe (election_id, &description->election_id); } else { - bson_oid_copy_unsafe (&kObjectIdZero, &description->election_id); + mcommon_oid_set_zero (&description->election_id); } } @@ -1223,8 +1220,41 @@ mongoc_server_description_set_topology_version (mongoc_server_description_t *sd, bool mongoc_server_description_has_service_id (const mongoc_server_description_t *description) { - if (0 == bson_oid_compare (&description->service_id, &kZeroServiceId)) { + return !mcommon_oid_is_zero (&description->service_id); +} + +bool +mongoc_server_description_append_contents_to_bson (const mongoc_server_description_t *sd, + bson_t *bson, + mongoc_server_description_content_flags_t flags) +{ + BSON_ASSERT_PARAM (sd); + BSON_ASSERT_PARAM (bson); + + if ((flags & MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_HOST) && + !BSON_APPEND_UTF8 (bson, "serverHost", sd->host.host)) { + return false; + } + if ((flags & MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_PORT) && + !BSON_APPEND_INT32 (bson, "serverPort", sd->host.port)) { return false; } + if (flags & MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVER_CONNECTION_ID) { + int64_t server_connection_id = sd->server_connection_id; + if (MONGOC_NO_SERVER_CONNECTION_ID != server_connection_id) { + if (!BSON_APPEND_INT64 (bson, "serverConnectionId", server_connection_id)) { + return false; + } + } + } + if (flags & MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG_SERVICE_ID) { + if (mongoc_server_description_has_service_id (sd)) { + char str[25]; + bson_oid_to_string (&sd->service_id, str); + if (!BSON_APPEND_UTF8 (bson, "serviceId", str)) { + return false; + } + } + } return true; } diff --git a/src/libmongoc/src/mongoc/mongoc-structured-log-private.h b/src/libmongoc/src/mongoc/mongoc-structured-log-private.h new file mode 100644 index 00000000000..376a88a97dd --- /dev/null +++ b/src/libmongoc/src/mongoc/mongoc-structured-log-private.h @@ -0,0 +1,454 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongoc-prelude.h" + +#ifndef MONGOC_STRUCTURED_LOG_PRIVATE_H +#define MONGOC_STRUCTURED_LOG_PRIVATE_H + +#include +#include +#include "mongoc-cmd-private.h" +#include "mongoc-error-private.h" +#include "mongoc-server-description-private.h" +#include "mongoc-structured-log.h" + +BSON_BEGIN_DECLS + +typedef struct mongoc_structured_log_instance_t mongoc_structured_log_instance_t; + +#define MONGOC_STRUCTURED_LOG_DEFAULT_LEVEL MONGOC_STRUCTURED_LOG_LEVEL_WARNING +#define MONGOC_STRUCTURED_LOG_DEFAULT_MAX_DOCUMENT_LENGTH 1000 + +/** + * @brief Allocate a new instance of the structured logging system + * @param opts Options, copied into the new instance. + * + * Must be paired with mongoc_structured_log_instance_destroy(). + * + * Once created, an instance has immutable settings. To change the handler + * or the max level per component filters, the instance will be replaced. + * One instance can be used by mongoc_structured_log() calls on multiple + * threads concurrently. + */ +mongoc_structured_log_instance_t * +mongoc_structured_log_instance_new (const mongoc_structured_log_opts_t *opts); + +/** + * @brief Destroy an instance of the structured logging system. + * + * All threads must be finished using the mongoc_structured_log_instance_t + * before it is destroyed. + */ +void +mongoc_structured_log_instance_destroy (mongoc_structured_log_instance_t *instance); + +/** + * @def mongoc_structured_log(instance, level, component, message, ...) + * @brief Write to the libmongoc structured log. + * + * @param instance Structured log instance, as a mongoc_structured_log_instance_t* expression + * @param level Log level, as a mongoc_structured_log_level_t expression + * @param component Log component, as a mongoc_structured_log_component_t expression + * @param message Log message, as a const char* expression + * @param ... Optional list of log 'items' that specify additional information to include. + * + * The instance, level, component, and message expressions are always evaluated. + * Any expressions in the optional items list are only evaluated if the log + * hasn't been disabled by a component's maximum log level setting or by + * unsetting the global structured log handler. + * + * Each log 'item' may represent a deferred operation that has minimal cost + * unless mongoc_structured_log_entry_message_as_bson is actually invoked. + * + * Calls implementation functions _mongoc_structured_log_should_log() before + * building the table of item information and _mongoc_structured_log_with_entry() + * once the table is built. + */ +#define mongoc_structured_log(_structured_log_instance, _level, _component, ...) \ + _bsonDSL_eval (_mongoc_structured_log_with_end_of_list ( \ + _structured_log_instance, _level, _component, __VA_ARGS__, end_of_list ())) + +#define _mongoc_structured_log_with_end_of_list(_structured_log_instance, _level, _component, _message, ...) \ + do { \ + mongoc_structured_log_entry_t _entry = {.envelope.instance = (_structured_log_instance), \ + .envelope.level = (_level), \ + .envelope.component = (_component), \ + .envelope.message = (_message)}; \ + if (_mongoc_structured_log_should_log (&_entry.envelope)) { \ + const mongoc_structured_log_builder_stage_t _builder[] = { \ + _mongoc_structured_log_items_to_stages (__VA_ARGS__)}; \ + _entry.builder = _builder; \ + _mongoc_structured_log_with_entry (&_entry); \ + } \ + } while (0) + +#define _mongoc_structured_log_items_to_stages(...) \ + _bsonDSL_mapMacro (_mongoc_structured_log_item_to_stages, ~, __VA_ARGS__) + +#define _mongoc_structured_log_flag_expr(_action, _constant, _counter) | (_constant##_##_action) + +#define _mongoc_structured_log_item_to_stages(_action, _constant, _counter) _mongoc_structured_log_item_##_action + +#define _mongoc_structured_log_item_end_of_list() {.func = NULL}, + +/** + * @def utf8(key, value) + * @brief Structured log item, referencing a NUL-terminated utf8 string value. + * + * @param key Key as a const char * expression, or NULL to skip this item. + * @param value UTF8 value as a const char * expression, or NULL for a null value. + */ +#define _mongoc_structured_log_item_utf8(_key_or_null, _value_utf8) \ + {.func = _mongoc_structured_log_append_utf8, .arg1.utf8 = (_key_or_null), .arg2.utf8 = (_value_utf8)}, + +/** + * @def utf8_n(key, value, value_len) + * @brief Structured log item, referencing a utf8 string value with explicit length. + * + * @param key Document key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value UTF8 value as a const char * expression, or NULL for a null value. May have embedded NUL bytes. + * @param value_len UTF8 value length in bytes, as an int32_t expression. + */ +#define _mongoc_structured_log_item_utf8_n(_key_literal, _value_utf8, _value_len) \ + _mongoc_structured_log_item_utf8_nn (_key_literal, strlen (_key_literal), _value_utf8, _value_len) + +/** + * @def utf8_nn(key, key_len, value, value_len) + * @brief Structured log item, referencing a utf8 string with explicit key and value lengths. + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param key_len UTF8 value length in bytes, as an int32_t expression. + * @param value UTF8 value as a const char * expression, or NULL for a null value. May have embedded NUL bytes. + * @param value_len UTF8 value length in bytes, as an int32_t expression. + */ +#define _mongoc_structured_log_item_utf8_nn(_key_or_null, _key_len, _value_utf8, _value_len) \ + {.func = _mongoc_structured_log_append_utf8_n_stage0, .arg1.utf8 = (_key_or_null), .arg2.int32 = (_key_len)}, \ + {.func = _mongoc_structured_log_append_utf8_n_stage1, .arg1.utf8 = (_value_utf8), .arg2.int32 = (_value_len)}, + +/** + * @def int32(key, value) + * @brief Structured log item, 32-bit integer + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value Value as an int32_t expression. + */ +#define _mongoc_structured_log_item_int32(_key_or_null, _value_int32) \ + {.func = _mongoc_structured_log_append_int32, .arg1.utf8 = (_key_or_null), .arg2.int32 = (_value_int32)}, + +/** + * @def int64(key, value) + * @brief Structured log item, 64-bit integer + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value Value as an int64_t expression. + */ +#define _mongoc_structured_log_item_int64(_key_or_null, _value_int64) \ + {.func = _mongoc_structured_log_append_int64, .arg1.utf8 = (_key_or_null), .arg2.int64 = (_value_int64)}, + +/** + * @def boolean(key, value) + * @brief Structured log item, boolean + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value Value as a bool expression. + */ +#define _mongoc_structured_log_item_boolean(_key_or_null, _value_boolean) \ + {.func = _mongoc_structured_log_append_boolean, .arg1.utf8 = (_key_or_null), .arg2.boolean = (_value_boolean)}, + +/** + * @def error(key, value) + * @brief Structured log item, bson_error_t document + * + * Serializes the fields of a bson_error_t as a subdocument with the indicated key. + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value Error as a const bson_error_t * expression, or NULL for a null value. + */ +#define _mongoc_structured_log_item_error(_key_or_null, _error_or_null) \ + {.func = _mongoc_structured_log_append_error, .arg1.utf8 = (_key_or_null), .arg2.error = (_error_or_null)}, + +/** + * @def oid_as_hex(key, value) + * @brief Structured log item, bson_oid_t converted to a hex string + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value OID to convert as a const bson_oid_t * expression, or NULL for a null value. + */ +#define _mongoc_structured_log_item_oid_as_hex(_key_or_null, _value_oid) \ + {.func = _mongoc_structured_log_append_oid_as_hex, .arg1.utf8 = (_key_or_null), .arg2.oid = (_value_oid)}, + +/** + * @def bson_as_json(key, value) + * @brief Structured log item, bson_t converted to a JSON string + * + * Always uses relaxed extended JSON format, and the current applicable + * maximum document length for structured logging. + * + * @param key Key as a NUL-terminated const char * expression, or NULL to skip this item. + * @param value BSON to convert as a const bson_t * expression, or NULL for a null value. + */ +#define _mongoc_structured_log_item_bson_as_json(_key_or_null, _value_bson) \ + {.func = _mongoc_structured_log_append_bson_as_json, .arg1.utf8 = (_key_or_null), .arg2.bson = (_value_bson)}, + +typedef enum { + MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_COMMAND = (1 << 0), + MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_DATABASE_NAME = (1 << 1), + MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_COMMAND_NAME = (1 << 2), + MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_OPERATION_ID = (1 << 3), +} mongoc_structured_log_cmd_content_flags_t; + +/** + * @def cmd(cmd, ...) + * @brief Structured log item, mongoc_cmd_t fields with automatic redaction + * + * @param cmd Borrowed command reference, as a const mongo_cmd_t * expression. Required. + * @param ... Fields to include. Order is not significant. Any of: COMMAND, DATABASE_NAME, COMMAND_NAME, OPERATION_ID. + * */ +#define _mongoc_structured_log_item_cmd(_cmd, ...) \ + {.func = _mongoc_structured_log_append_cmd, \ + .arg1.cmd = (_cmd), \ + .arg2.cmd_flags = \ + (0 _bsonDSL_mapMacro (_mongoc_structured_log_flag_expr, MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG, __VA_ARGS__))}, + +/** + * @def cmd_reply(cmd, reply) + * @brief Structured log item, command reply for mongoc_cmd_t with automatic redaction + * + * @param cmd Borrowed command reference, as a const mongo_cmd_t * expression. Required. + * @param reply Borrowed reference to reply document, as a const bson_t * expression. Required. + */ +#define _mongoc_structured_log_item_cmd_reply(_cmd, _reply_bson) \ + {.func = _mongoc_structured_log_append_cmd_reply, .arg1.cmd = (_cmd), .arg2.bson = (_reply_bson)}, + +/** + * @def cmd_name_reply(cmd_name, reply) + * @brief Structured log item, reply for named command with automatic redaction + * + * For cases where a mongo_cmd_t is not available; makes redaction decisions based + * on command name but not body, so it's unsuitable for the "hello" reply. + * + * @param cmd_name Command name as a const char * expression. Required. + * @param reply Borrowed reference to reply document, as a const bson_t * expression. Required. + */ +#define _mongoc_structured_log_item_cmd_name_reply(_cmd_name, _reply_bson) \ + {.func = _mongoc_structured_log_append_cmd_name_reply, .arg1.utf8 = (_cmd_name), .arg2.bson = (_reply_bson)}, + +/** + * @def cmd_failure(cmd, reply, error) + * @brief Structured log item, failure for mongoc_cmd_t with automatic redaction + * + * The 'error' is examined to determine whether this is a client-side or server-side failure. + * The command's name and body may influence the reply's redaction. + * + * @param cmd Borrowed command reference, as a const mongo_cmd_t * expression. Required. + * @param reply Borrowed reference to reply document, as a const bson_t * expression. Required. + * @param error Borrowed reference to a libmongoc error, as a const bson_error_t * expression. Required. + */ +#define _mongoc_structured_log_item_cmd_failure(_cmd, _reply_bson, _error) \ + {.func = _mongoc_structured_log_append_cmd_failure_stage0, .arg1.cmd = (_cmd), .arg2.bson = (_reply_bson)}, \ + {.func = _mongoc_structured_log_append_cmd_failure_stage1, .arg1.error = (_error)}, + +/** + * @def cmd_name_failure(cmd_name, reply, error) + * @brief Structured log item, failure for named command with automatic redaction + * + * For cases where a mongo_cmd_t is not available; makes redaction decisions based + * on command name but not body, so it's unsuitable for "hello" errors. + * + * The 'error' is examined to determine whether this is a client-side or server-side failure. + * The command's name and body may influence the reply's redaction. + * + * @param cmd_name Command name as a const char * expression. Required. + * @param reply Borrowed reference to reply document, as a const bson_t * expression. Required. + * @param error Borrowed reference to a libmongoc error, as a const bson_error_t * expression. Required. + */ +#define _mongoc_structured_log_item_cmd_name_failure(_cmd_name, _reply_bson, _error) \ + {.func = _mongoc_structured_log_append_cmd_name_failure_stage0, \ + .arg1.utf8 = (_cmd_name), \ + .arg2.bson = (_reply_bson)}, \ + {.func = _mongoc_structured_log_append_cmd_name_failure_stage1, .arg1.error = (_error)}, + +/** + * @def server_description(sd, ...) + * @brief Structured log item, mongoc_server_description_t fields + * + * @param sd Borrowed server description reference, as a const mongoc_server_description_t * expression. Required. + * @param ... Fields to include. Order is not significant. Any of: SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, + * SERVICE_ID. + * */ +#define _mongoc_structured_log_item_server_description(_server_description, ...) \ + {.func = _mongoc_structured_log_append_server_description, \ + .arg1.server_description = (_server_description), \ + .arg2.server_description_flags = \ + (0 _bsonDSL_mapMacro (_mongoc_structured_log_flag_expr, MONGOC_SERVER_DESCRIPTION_CONTENT_FLAG, __VA_ARGS__))}, + +/** + * @def monotonic_time_duration(duration) + * @brief Structured log item, standard format for a duration in monotonic time. + * @param duration Duration in microseconds, as an int64_t expression. + * + * Includes milliseconds for consistency across drivers, and microseconds as the highest available resolution. + */ +#define _mongoc_structured_log_item_monotonic_time_duration(_duration) \ + _mongoc_structured_log_item_int32 ("durationMS", (int32_t) ((_duration) / 1000)) \ + _mongoc_structured_log_item_int64 ("durationMicros", (_duration)) + +typedef struct mongoc_structured_log_builder_stage_t mongoc_structured_log_builder_stage_t; + +typedef const mongoc_structured_log_builder_stage_t *(*mongoc_structured_log_builder_func_t) ( + bson_t *bson, const mongoc_structured_log_builder_stage_t *stage, const mongoc_structured_log_opts_t *opts); + +struct mongoc_structured_log_builder_stage_t { + // Why "stages" instead of a variable size argument list per item? + // This approach keeps function pointers and other types of data + // separated, reducing opportunities for malicious control flow. + // Most items are one stage. Items that need more arguments can use + // multiple consecutive stages, leaving the extra stages' function + // pointers unused and set to placeholder values which can be checked. + mongoc_structured_log_builder_func_t func; // NULL sentinel here + union { + const bson_error_t *error; + const char *utf8; + const mongoc_cmd_t *cmd; + const mongoc_server_description_t *server_description; + } arg1; + union { + bool boolean; + bson_oid_t *oid; + const bson_error_t *error; + const bson_t *bson; + const char *utf8; + int32_t int32; + int64_t int64; + mongoc_error_content_flags_t error_flags; + mongoc_structured_log_cmd_content_flags_t cmd_flags; + mongoc_server_description_content_flags_t server_description_flags; + } arg2; + // Avoid adding an arg3, prefer to use additional stages +}; + +typedef struct mongoc_structured_log_envelope_t { + mongoc_structured_log_instance_t *instance; + mongoc_structured_log_level_t level; + mongoc_structured_log_component_t component; + const char *message; +} mongoc_structured_log_envelope_t; + +struct mongoc_structured_log_entry_t { + mongoc_structured_log_envelope_t envelope; + const mongoc_structured_log_builder_stage_t *builder; // Required +}; + +void +mongoc_structured_log_get_handler (const mongoc_structured_log_opts_t *opts, + mongoc_structured_log_func_t *log_func, + void **user_data); + +bool +_mongoc_structured_log_should_log (const mongoc_structured_log_envelope_t *envelope); + +void +_mongoc_structured_log_with_entry (const mongoc_structured_log_entry_t *entry); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8_n_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8_n_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_int32 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_int64 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_boolean (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_error (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_oid_as_hex (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_bson_as_json (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_reply (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_reply (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_failure_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_failure_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_failure_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_failure_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_server_description (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts); + +BSON_END_DECLS + +#endif /* MONGOC_STRUCTURED_LOG_PRIVATE_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-structured-log.c b/src/libmongoc/src/mongoc/mongoc-structured-log.c new file mode 100644 index 00000000000..aad46d3c349 --- /dev/null +++ b/src/libmongoc/src/mongoc/mongoc-structured-log.c @@ -0,0 +1,874 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common-atomic-private.h" +#include "common-oid-private.h" +#include "common-thread-private.h" +#include "mongoc-apm-private.h" +#include "mongoc-error-private.h" +#include "mongoc-structured-log-private.h" +#include "mongoc-structured-log.h" +#include "mongoc-util-private.h" + +#define STRUCTURED_LOG_COMPONENT_TABLE_SIZE (1 + (size_t) MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION) + +// Environment variables with default level for each log component +static const char *gStructuredLogComponentEnvVars[] = { + "MONGODB_LOG_COMMAND", "MONGODB_LOG_TOPOLOGY", "MONGODB_LOG_SERVER_SELECTION", "MONGODB_LOG_CONNECTION"}; + +// Canonical names for log components +static const char *gStructuredLogComponentNames[] = {"command", "topology", "serverSelection", "connection"}; + +// Canonical names for log levels +static const char *gStructuredLogLevelNames[] = { + "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug", "Trace"}; + +// Additional valid names for log levels +static const struct { + const char *name; + mongoc_structured_log_level_t level; +} gStructuredLogLevelAliases[] = {{.name = "off", .level = (mongoc_structured_log_level_t) 0}, + {.name = "warn", .level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING}, + {.name = "info", .level = MONGOC_STRUCTURED_LOG_LEVEL_INFO}}; + +// Shared mutable data for the default handler +typedef struct mongoc_structured_log_default_handler_shared_t { + bson_mutex_t mutex; + FILE *stream; + bool stream_fclose_on_destroy; +} mongoc_structured_log_default_handler_shared_t; + +struct mongoc_structured_log_opts_t { + mongoc_structured_log_func_t handler_func; + void *handler_user_data; + mongoc_structured_log_level_t max_level_per_component[STRUCTURED_LOG_COMPONENT_TABLE_SIZE]; + int32_t max_document_length; + char *default_handler_path; +}; + +struct mongoc_structured_log_instance_t { + struct mongoc_structured_log_opts_t opts; // Immutable capture of log_opts, func != NULL + mongoc_structured_log_default_handler_shared_t default_handler_shared; // Inner mutability +}; + +bson_t * +mongoc_structured_log_entry_message_as_bson (const mongoc_structured_log_entry_t *entry) +{ + BSON_ASSERT_PARAM (entry); + bson_t *bson = bson_new (); + BSON_APPEND_UTF8 (bson, "message", entry->envelope.message); + const mongoc_structured_log_builder_stage_t *stage = entry->builder; + const mongoc_structured_log_opts_t *opts = &entry->envelope.instance->opts; + while (stage->func) { + stage = stage->func (bson, stage, opts); + } + return bson; +} + +mongoc_structured_log_level_t +mongoc_structured_log_entry_get_level (const mongoc_structured_log_entry_t *entry) +{ + BSON_ASSERT_PARAM (entry); + return entry->envelope.level; +} + +mongoc_structured_log_component_t +mongoc_structured_log_entry_get_component (const mongoc_structured_log_entry_t *entry) +{ + BSON_ASSERT_PARAM (entry); + return entry->envelope.component; +} + +const char * +mongoc_structured_log_entry_get_message_string (const mongoc_structured_log_entry_t *entry) +{ + // Note that 'message' happens to have static lifetime right now (all messages are literals) + // but our API only guarantees a lifetime that matches 'entry'. + BSON_ASSERT_PARAM (entry); + return entry->envelope.message; +} + +mongoc_structured_log_level_t +mongoc_structured_log_opts_get_max_level_for_component (const mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component) +{ + BSON_ASSERT_PARAM (opts); + unsigned table_index = (unsigned) component; + if (table_index < STRUCTURED_LOG_COMPONENT_TABLE_SIZE) { + return opts->max_level_per_component[table_index]; + } else { + // As documented, unknown component enums return the lowest possible log level. + return MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY; + } +} + +void +mongoc_structured_log_opts_set_handler (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_func_t log_func, + void *user_data) +{ + BSON_ASSERT_PARAM (opts); + opts->handler_func = log_func; + opts->handler_user_data = user_data; +} + +void +mongoc_structured_log_get_handler (const mongoc_structured_log_opts_t *opts, + mongoc_structured_log_func_t *log_func, + void **user_data) +{ + BSON_ASSERT_PARAM (opts); + *log_func = opts->handler_func; + *user_data = opts->handler_user_data; +} + +bool +mongoc_structured_log_opts_set_max_level_for_component (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component, + mongoc_structured_log_level_t level) +{ + BSON_ASSERT_PARAM (opts); + if (level >= MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY && level <= MONGOC_STRUCTURED_LOG_LEVEL_TRACE) { + unsigned table_index = (unsigned) component; + if (table_index < STRUCTURED_LOG_COMPONENT_TABLE_SIZE) { + opts->max_level_per_component[table_index] = level; + return true; + } + } + return false; +} + +bool +mongoc_structured_log_opts_set_max_level_for_all_components (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_level_t level) +{ + BSON_ASSERT_PARAM (opts); + for (int component = 0; component < STRUCTURED_LOG_COMPONENT_TABLE_SIZE; component++) { + if (!mongoc_structured_log_opts_set_max_level_for_component ( + opts, (mongoc_structured_log_component_t) component, level)) { + // Fine to stop on the first error, always means 'level' is wrong and none of these will succeed. + return false; + } + } + return true; +} + +bool +_mongoc_structured_log_should_log (const mongoc_structured_log_envelope_t *envelope) +{ + // Note that the instance's max_level_per_component table will also be + // set to zeroes if logging is disabled. See mongoc_structured_log_instance_new. + unsigned table_index = (unsigned) envelope->component; + BSON_ASSERT (table_index < STRUCTURED_LOG_COMPONENT_TABLE_SIZE); + return envelope->level <= envelope->instance->opts.max_level_per_component[table_index]; +} + +void +_mongoc_structured_log_with_entry (const mongoc_structured_log_entry_t *entry) +{ + // By now, func is not allowed to be NULL. See mongoc_structured_log_instance_new. + mongoc_structured_log_instance_t *instance = entry->envelope.instance; + mongoc_structured_log_func_t func = instance->opts.handler_func; + BSON_ASSERT (func); + func (entry, instance->opts.handler_user_data); +} + +static bool +_mongoc_structured_log_get_log_level_from_env (const char *variable, + mongoc_structured_log_level_t *out, + int volatile *err_count_atomic) +{ + const char *level = getenv (variable); + if (!level) { + return false; + } + if (mongoc_structured_log_get_named_level (level, out)) { + return true; + } + // Only log the first instance of each error per process + if (0 == mcommon_atomic_int_fetch_add (err_count_atomic, 1, mcommon_memory_order_seq_cst)) { + MONGOC_WARNING ("Invalid log level '%s' read from environment variable %s. Ignoring it.", level, variable); + } + return false; +} + +const char * +mongoc_structured_log_get_level_name (mongoc_structured_log_level_t level) +{ + unsigned table_index = (unsigned) level; + const size_t table_size = sizeof gStructuredLogLevelNames / sizeof gStructuredLogLevelNames[0]; + return table_index < table_size ? gStructuredLogLevelNames[table_index] : NULL; +} + +bool +mongoc_structured_log_get_named_level (const char *name, mongoc_structured_log_level_t *out) +{ + BSON_ASSERT_PARAM (name); + BSON_ASSERT_PARAM (out); + + // First check canonical names + { + const size_t table_size = sizeof gStructuredLogLevelNames / sizeof gStructuredLogLevelNames[0]; + for (unsigned table_index = 0; table_index < table_size; table_index++) { + if (!strcasecmp (name, gStructuredLogLevelNames[table_index])) { + *out = (mongoc_structured_log_level_t) table_index; + return true; + } + } + } + // Check additional acceptable names + { + const size_t table_size = sizeof gStructuredLogLevelAliases / sizeof gStructuredLogLevelAliases[0]; + for (unsigned table_index = 0; table_index < table_size; table_index++) { + const char *alias = gStructuredLogLevelAliases[table_index].name; + mongoc_structured_log_level_t level = gStructuredLogLevelAliases[table_index].level; + if (!strcasecmp (name, alias)) { + *out = level; + return true; + } + } + } + return false; +} + +const char * +mongoc_structured_log_get_component_name (mongoc_structured_log_component_t component) +{ + unsigned table_index = (unsigned) component; + const size_t table_size = sizeof gStructuredLogComponentNames / sizeof gStructuredLogComponentNames[0]; + return table_index < table_size ? gStructuredLogComponentNames[table_index] : NULL; +} + +bool +mongoc_structured_log_get_named_component (const char *name, mongoc_structured_log_component_t *out) +{ + BSON_ASSERT_PARAM (name); + BSON_ASSERT_PARAM (out); + + const size_t table_size = sizeof gStructuredLogComponentNames / sizeof gStructuredLogComponentNames[0]; + for (unsigned table_index = 0; table_index < table_size; table_index++) { + if (!strcasecmp (name, gStructuredLogComponentNames[table_index])) { + *out = (mongoc_structured_log_component_t) table_index; + return true; + } + } + return false; +} + +static int32_t +_mongoc_structured_log_get_max_document_length_from_env (void) +{ + const char *variable = "MONGODB_LOG_MAX_DOCUMENT_LENGTH"; + const char *max_length_str = getenv (variable); + + if (!max_length_str) { + return MONGOC_STRUCTURED_LOG_DEFAULT_MAX_DOCUMENT_LENGTH; + } + + if (!strcasecmp (max_length_str, "unlimited")) { + return BSON_MAX_LEN_UNLIMITED; + } + + char *endptr; + long int_value = strtol (max_length_str, &endptr, 10); + if (int_value >= 0 && int_value <= INT32_MAX && endptr != max_length_str && !*endptr) { + return (int32_t) int_value; + } + + // Only log the first instance of each error per process + static int err_count_atomic; + if (0 == mcommon_atomic_int_fetch_add (&err_count_atomic, 1, mcommon_memory_order_seq_cst)) { + MONGOC_WARNING ("Invalid length '%s' read from environment variable %s. Ignoring it.", max_length_str, variable); + } + return MONGOC_STRUCTURED_LOG_DEFAULT_MAX_DOCUMENT_LENGTH; +} + +bool +mongoc_structured_log_opts_set_max_levels_from_env (mongoc_structured_log_opts_t *opts) +{ + BSON_ASSERT_PARAM (opts); + + bool all_ok = true; + mongoc_structured_log_level_t level; + + // Errors are not fatal by default; always reported by return value, and reported the first time only via a log + // warning. + static int err_count_all_atomic; + static int err_count_per_component_atomic[STRUCTURED_LOG_COMPONENT_TABLE_SIZE]; + + if (_mongoc_structured_log_get_log_level_from_env ("MONGODB_LOG_ALL", &level, &err_count_all_atomic)) { + BSON_ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, level)); + } else { + all_ok = false; + } + + for (int component = 0; component < STRUCTURED_LOG_COMPONENT_TABLE_SIZE; component++) { + if (_mongoc_structured_log_get_log_level_from_env ( + gStructuredLogComponentEnvVars[component], &level, &err_count_per_component_atomic[component])) { + BSON_ASSERT (mongoc_structured_log_opts_set_max_level_for_component ( + opts, (mongoc_structured_log_component_t) component, level)); + } else { + all_ok = false; + } + } + + return all_ok; +} + +static void +_mongoc_structured_log_default_handler_open_stream (mongoc_structured_log_default_handler_shared_t *shared, + const char *path) +{ + // shared->mutex must already be locked + + if (!path || !strcasecmp (path, "stderr")) { + // Default or explicit stderr + shared->stream = stderr; + shared->stream_fclose_on_destroy = false; + } else if (!strcasecmp (path, "stdout")) { + shared->stream = stdout; + shared->stream_fclose_on_destroy = false; + } else { + FILE *file = fopen (path, "a"); + if (file) { + shared->stream = file; + shared->stream_fclose_on_destroy = true; + } else { + char errmsg_buf[BSON_ERROR_BUFFER_SIZE]; + const char *errmsg = bson_strerror_r (errno, errmsg_buf, sizeof errmsg_buf); + MONGOC_WARNING ("Failed to open log file '%s' with error: '%s'. Logging to stderr instead.", path, errmsg); + shared->stream = stderr; + shared->stream_fclose_on_destroy = false; + } + } +} + +static FILE * +_mongoc_structured_log_default_handler_get_stream (mongoc_structured_log_instance_t *instance) +{ + // instance->default_handler_shared->mutex must already be locked + { + FILE *log_stream = instance->default_handler_shared.stream; + if (log_stream) { + return log_stream; + } + } + _mongoc_structured_log_default_handler_open_stream (&instance->default_handler_shared, + instance->opts.default_handler_path); + { + FILE *log_stream = instance->default_handler_shared.stream; + BSON_ASSERT (log_stream); + return log_stream; + } +} + +static void +_mongoc_structured_log_default_handler (const mongoc_structured_log_entry_t *entry, void *user_data) +{ + BSON_UNUSED (user_data); + mongoc_structured_log_instance_t *instance = entry->envelope.instance; + + // We can serialize the message before taking the default_handler_shared mutex + bson_t *bson_message = mongoc_structured_log_entry_message_as_bson (entry); + char *json_message = bson_as_relaxed_extended_json (bson_message, NULL); + bson_destroy (bson_message); + + const char *level_name = mongoc_structured_log_get_level_name (mongoc_structured_log_entry_get_level (entry)); + const char *component_name = + mongoc_structured_log_get_component_name (mongoc_structured_log_entry_get_component (entry)); + + bson_mutex_lock (&instance->default_handler_shared.mutex); + fprintf (_mongoc_structured_log_default_handler_get_stream (instance), + "MONGODB_LOG %s %s %s\n", + level_name, + component_name, + json_message); + bson_mutex_unlock (&instance->default_handler_shared.mutex); + + bson_free (json_message); +} + +static void +_mongoc_structured_log_no_handler (const mongoc_structured_log_entry_t *entry, void *user_data) +{ + // Stub, for when logging is disabled. Only possible to call at MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY. + BSON_UNUSED (entry); + BSON_UNUSED (user_data); +} + +mongoc_structured_log_opts_t * +mongoc_structured_log_opts_new (void) +{ + mongoc_structured_log_opts_t *opts = (mongoc_structured_log_opts_t *) bson_malloc0 (sizeof *opts); + + /* Capture default state from the environment now. + * Note that error return values from mongoc_structured_log_opts_set_* must be ignored here. + * If environment variables can't be parsed, warnings will be logged once but we must, by specification, + * continue to provide structured logging using whatever valid or default settings remain. */ + opts->default_handler_path = bson_strdup (getenv ("MONGODB_LOG_PATH")); + opts->max_document_length = _mongoc_structured_log_get_max_document_length_from_env (); + (void) mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_DEFAULT_LEVEL); + (void) mongoc_structured_log_opts_set_max_levels_from_env (opts); + + // Set default handler. Its shared state is allocated later, as part of instance_t. + mongoc_structured_log_opts_set_handler (opts, _mongoc_structured_log_default_handler, NULL); + + return opts; +} + +void +mongoc_structured_log_opts_destroy (mongoc_structured_log_opts_t *opts) +{ + if (opts) { + bson_free (opts->default_handler_path); + bson_free (opts); + } +} + +mongoc_structured_log_instance_t * +mongoc_structured_log_instance_new (const mongoc_structured_log_opts_t *opts) +{ + /* Creating the instance captures an immutable copy of the options. + * We also make a transformation that simplifies the critical path in + * _mongoc_structured_log_should_log so that it only needs to check the + * per-component table: In the instance, NULL handlers are no longer + * allowed. If structured logging is disabled, the per-component table + * will be set to the lowest possible levels and a stub handler function + * is set in case of 'emergency' logs. + * + * 'opts' is optional; if NULL, structured logging is disabled. + * (To request default options, you still need to use + * mongoc_structured_log_opts_new) */ + + mongoc_structured_log_instance_t *instance = (mongoc_structured_log_instance_t *) bson_malloc0 (sizeof *instance); + bson_mutex_init (&instance->default_handler_shared.mutex); + + if (opts) { + instance->opts.default_handler_path = bson_strdup (opts->default_handler_path); + instance->opts.max_document_length = opts->max_document_length; + instance->opts.handler_func = opts->handler_func; + instance->opts.handler_user_data = opts->handler_user_data; + } + if (instance->opts.handler_func) { + if (opts) { + memcpy (instance->opts.max_level_per_component, + opts->max_level_per_component, + sizeof instance->opts.max_level_per_component); + } + } else { + // No handler; leave the max_level_per_component table zero'ed, and add a stub handler for emergency level only. + instance->opts.handler_func = _mongoc_structured_log_no_handler; + } + + return instance; +} + +void +mongoc_structured_log_instance_destroy (mongoc_structured_log_instance_t *instance) +{ + if (instance) { + bson_mutex_destroy (&instance->default_handler_shared.mutex); + bson_free (instance->opts.default_handler_path); + if (instance->default_handler_shared.stream_fclose_on_destroy) { + fclose (instance->default_handler_shared.stream); + } + bson_free (instance); + } +} + +static char * +_mongoc_structured_log_inner_document_to_json (const bson_t *document, + size_t *length, + const mongoc_structured_log_opts_t *opts) +{ + bson_json_opts_t *json_opts = bson_json_opts_new (BSON_JSON_MODE_RELAXED, opts->max_document_length); + char *json = bson_as_json_with_opts (document, length, json_opts); + bson_json_opts_destroy (json_opts); + return json; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const char *key_or_null = stage->arg1.utf8; + if (key_or_null) { + bson_append_utf8 (bson, key_or_null, -1, stage->arg2.utf8, -1); + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8_n_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + BSON_ASSERT (stage[1].func == _mongoc_structured_log_append_utf8_n_stage1); + const char *key_or_null = stage[0].arg1.utf8; + int32_t key_len = stage[0].arg2.int32; + const char *value = stage[1].arg1.utf8; + int32_t value_len = stage[1].arg2.int32; + if (key_or_null) { + bson_append_utf8 (bson, key_or_null, key_len, value, value_len); + } + return stage + 2; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_utf8_n_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + // Never called, marks the second stage in a two-stage utf8_n + BSON_UNUSED (bson); + BSON_UNUSED (stage); + BSON_UNUSED (opts); + BSON_ASSERT (false); + return NULL; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_int32 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const char *key_or_null = stage->arg1.utf8; + if (key_or_null) { + bson_append_int32 (bson, key_or_null, -1, stage->arg2.int32); + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_int64 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const char *key_or_null = stage->arg1.utf8; + if (key_or_null) { + bson_append_int64 (bson, key_or_null, -1, stage->arg2.int64); + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_boolean (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const char *key_or_null = stage->arg1.utf8; + if (key_or_null) { + bson_append_bool (bson, key_or_null, -1, stage->arg2.boolean); + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_oid_as_hex (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const char *key_or_null = stage->arg1.utf8; + const bson_oid_t *oid_or_null = stage->arg2.oid; + if (key_or_null) { + if (oid_or_null) { + char str[25]; + bson_oid_to_string (oid_or_null, str); + bson_append_utf8 (bson, key_or_null, -1, str, 24); + } else { + bson_append_null (bson, key_or_null, -1); + } + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_bson_as_json (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + const char *key_or_null = stage->arg1.utf8; + const bson_t *bson_or_null = stage->arg2.bson; + if (key_or_null) { + if (bson_or_null) { + size_t json_length; + char *json = _mongoc_structured_log_inner_document_to_json (bson_or_null, &json_length, opts); + if (json) { + bson_append_utf8 (bson, key_or_null, -1, json, json_length); + bson_free (json); + } + } else { + bson_append_null (bson, key_or_null, -1); + } + } + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + const mongoc_cmd_t *cmd = stage->arg1.cmd; + const mongoc_structured_log_cmd_content_flags_t flags = stage->arg2.cmd_flags; + BSON_ASSERT (cmd); + + if (flags & MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_DATABASE_NAME) { + BSON_APPEND_UTF8 (bson, "databaseName", cmd->db_name); + } + if (flags & MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_COMMAND_NAME) { + BSON_APPEND_UTF8 (bson, "commandName", cmd->command_name); + } + if (flags & MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_OPERATION_ID) { + BSON_APPEND_INT64 (bson, "operationId", cmd->operation_id); + } + if (flags & MONGOC_STRUCTURED_LOG_CMD_CONTENT_FLAG_COMMAND) { + if (mongoc_apm_is_sensitive_command_message (cmd->command_name, cmd->command)) { + BSON_APPEND_UTF8 (bson, "command", "{}"); + } else { + bson_t *command_copy = NULL; + + if (cmd->payloads_count > 0) { + // @todo This is a performance bottleneck, we shouldn't be copying + // a potentially large command to serialize a potentially very + // small part of it. We should be appending JSON to a single buffer + // for all nesting levels, constrained by length limit, while visiting + // borrowed references to each command attribute and each payload. CDRIVER-4814 + command_copy = bson_copy (cmd->command); + _mongoc_cmd_append_payload_as_array (cmd, command_copy); + } + + size_t json_length; + char *json = _mongoc_structured_log_inner_document_to_json ( + command_copy ? command_copy : cmd->command, &json_length, opts); + if (json) { + const char *key = "command"; + bson_append_utf8 (bson, key, strlen (key), json, json_length); + bson_free (json); + } + bson_destroy (command_copy); + } + } + + return stage + 1; +} + +static void +_mongoc_structured_log_append_redacted_cmd_reply (bson_t *bson, + bool is_sensitive, + const bson_t *reply, + const mongoc_structured_log_opts_t *opts) +{ + if (is_sensitive) { + BSON_APPEND_UTF8 (bson, "reply", "{}"); + } else { + size_t json_length; + char *json = _mongoc_structured_log_inner_document_to_json (reply, &json_length, opts); + if (json) { + const char *key = "reply"; + bson_append_utf8 (bson, key, strlen (key), json, json_length); + bson_free (json); + } + } +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_reply (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + const mongoc_cmd_t *cmd = stage->arg1.cmd; + const bson_t *reply = stage->arg2.bson; + + BSON_ASSERT (cmd); + BSON_ASSERT (reply); + + bool is_sensitive = mongoc_apm_is_sensitive_command_message (cmd->command_name, cmd->command) || + mongoc_apm_is_sensitive_command_message (cmd->command_name, reply); + _mongoc_structured_log_append_redacted_cmd_reply (bson, is_sensitive, reply, opts); + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_reply (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + const char *cmd_name = stage->arg1.utf8; + const bson_t *reply = stage->arg2.bson; + + BSON_ASSERT (cmd_name); + BSON_ASSERT (reply); + + bool is_sensitive = mongoc_apm_is_sensitive_command_message (cmd_name, reply); + _mongoc_structured_log_append_redacted_cmd_reply (bson, is_sensitive, reply, opts); + return stage + 1; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_error (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + const char *key_or_null = stage->arg1.utf8; + const bson_error_t *error_or_null = stage->arg2.error; + if (key_or_null) { + if (error_or_null) { + bson_t child; + if (BSON_APPEND_DOCUMENT_BEGIN (bson, key_or_null, &child)) { + mongoc_error_append_contents_to_bson (error_or_null, + &child, + MONGOC_ERROR_CONTENT_FLAG_MESSAGE | MONGOC_ERROR_CONTENT_FLAG_CODE | + MONGOC_ERROR_CONTENT_FLAG_DOMAIN); + bson_append_document_end (bson, &child); + } + } else { + bson_append_null (bson, key_or_null, -1); + } + } + return stage + 1; +} + +static void +_mongoc_structured_log_append_redacted_cmd_failure (bson_t *bson, + bool is_sensitive, + const bson_t *reply, + const bson_error_t *error) +{ + bool is_server_side = error->domain == MONGOC_ERROR_SERVER || error->domain == MONGOC_ERROR_WRITE_CONCERN_ERROR; + if (is_server_side) { + if (is_sensitive) { + // Redacted server-side message, must be a document with at most 'code', 'codeName', 'errorLabels' + bson_t failure; + bson_iter_t iter; + if (BSON_APPEND_DOCUMENT_BEGIN (bson, "failure", &failure)) { + bson_iter_init (&iter, reply); + while (bson_iter_next (&iter)) { + const char *key = bson_iter_key (&iter); + if (!strcmp (key, "code") || !strcmp (key, "codeName") || !strcmp (key, "errorLabels")) { + bson_append_iter (&failure, key, bson_iter_key_len (&iter), &iter); + } + } + bson_append_document_end (bson, &failure); + } + } else { + // Non-redacted server side message, pass through + BSON_APPEND_DOCUMENT (bson, "failure", reply); + } + } else { + /* Client-side errors converted directly from bson_error_t, never redacted. + * In addition to the bson_error_t fields, client side errors may include errorLabels: + * https://mongoc.org/libmongoc/current/errors.html#error-labels */ + bson_t failure; + if (BSON_APPEND_DOCUMENT_BEGIN (bson, "failure", &failure)) { + mongoc_error_append_contents_to_bson (error, + &failure, + MONGOC_ERROR_CONTENT_FLAG_MESSAGE | MONGOC_ERROR_CONTENT_FLAG_CODE | + MONGOC_ERROR_CONTENT_FLAG_DOMAIN); + bson_iter_t iter; + if (bson_iter_init_find (&iter, reply, "errorLabels")) { + bson_append_iter (&failure, bson_iter_key (&iter), bson_iter_key_len (&iter), &iter); + } + bson_append_document_end (bson, &failure); + } + } +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_failure_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + BSON_ASSERT (stage[1].func == _mongoc_structured_log_append_cmd_failure_stage1); + const mongoc_cmd_t *cmd = stage[0].arg1.cmd; + const bson_t *reply = stage[0].arg2.bson; + const bson_error_t *error = stage[1].arg1.error; + + BSON_ASSERT (cmd); + BSON_ASSERT (reply); + BSON_ASSERT (error); + + bool is_sensitive = mongoc_apm_is_sensitive_command_message (cmd->command_name, cmd->command) || + mongoc_apm_is_sensitive_command_message (cmd->command_name, reply); + _mongoc_structured_log_append_redacted_cmd_failure (bson, is_sensitive, reply, error); + return stage + 2; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_failure_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + // Never called, marks the second stage in a two-stage cmd_failure + BSON_UNUSED (bson); + BSON_UNUSED (stage); + BSON_UNUSED (opts); + BSON_ASSERT (false); + return NULL; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_failure_stage0 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + BSON_ASSERT (stage[1].func == _mongoc_structured_log_append_cmd_name_failure_stage1); + const char *cmd_name = stage[0].arg1.utf8; + const bson_t *reply = stage[0].arg2.bson; + const bson_error_t *error = stage[1].arg1.error; + + BSON_ASSERT (cmd_name); + BSON_ASSERT (reply); + BSON_ASSERT (error); + + bool is_sensitive = mongoc_apm_is_sensitive_command_message (cmd_name, reply); + _mongoc_structured_log_append_redacted_cmd_failure (bson, is_sensitive, reply, error); + return stage + 2; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_cmd_name_failure_stage1 (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + // Never called, marks the second stage in a two-stage cmd_name_failure + BSON_UNUSED (bson); + BSON_UNUSED (stage); + BSON_UNUSED (opts); + BSON_ASSERT (false); + return NULL; +} + +const mongoc_structured_log_builder_stage_t * +_mongoc_structured_log_append_server_description (bson_t *bson, + const mongoc_structured_log_builder_stage_t *stage, + const mongoc_structured_log_opts_t *opts) +{ + BSON_UNUSED (opts); + const mongoc_server_description_t *sd = stage->arg1.server_description; + const mongoc_server_description_content_flags_t flags = stage->arg2.server_description_flags; + mongoc_server_description_append_contents_to_bson (sd, bson, flags); + return stage + 1; +} diff --git a/src/libmongoc/src/mongoc/mongoc-structured-log.h b/src/libmongoc/src/mongoc/mongoc-structured-log.h new file mode 100644 index 00000000000..468e408d415 --- /dev/null +++ b/src/libmongoc/src/mongoc/mongoc-structured-log.h @@ -0,0 +1,106 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongoc-prelude.h" + +#ifndef MONGOC_STRUCTURED_LOG_H +#define MONGOC_STRUCTURED_LOG_H + +#include + +#include "mongoc-macros.h" + +BSON_BEGIN_DECLS + +typedef enum { + MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY = 0, + MONGOC_STRUCTURED_LOG_LEVEL_ALERT = 1, + MONGOC_STRUCTURED_LOG_LEVEL_CRITICAL = 2, + MONGOC_STRUCTURED_LOG_LEVEL_ERROR = 3, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING = 4, + MONGOC_STRUCTURED_LOG_LEVEL_NOTICE = 5, + MONGOC_STRUCTURED_LOG_LEVEL_INFO = 6, + MONGOC_STRUCTURED_LOG_LEVEL_DEBUG = 7, + MONGOC_STRUCTURED_LOG_LEVEL_TRACE = 8, +} mongoc_structured_log_level_t; + +typedef enum { + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND = 0, + MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY = 1, + MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION = 2, + MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION = 3, +} mongoc_structured_log_component_t; + +typedef struct mongoc_structured_log_entry_t mongoc_structured_log_entry_t; + +typedef struct mongoc_structured_log_opts_t mongoc_structured_log_opts_t; + +typedef void (*mongoc_structured_log_func_t) (const mongoc_structured_log_entry_t *entry, void *user_data); + +MONGOC_EXPORT (mongoc_structured_log_opts_t *) +mongoc_structured_log_opts_new (void); + +MONGOC_EXPORT (void) +mongoc_structured_log_opts_destroy (mongoc_structured_log_opts_t *opts); + +MONGOC_EXPORT (void) +mongoc_structured_log_opts_set_handler (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_func_t log_func, + void *user_data); + +MONGOC_EXPORT (bool) +mongoc_structured_log_opts_set_max_level_for_component (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component, + mongoc_structured_log_level_t level); + +MONGOC_EXPORT (bool) +mongoc_structured_log_opts_set_max_level_for_all_components (mongoc_structured_log_opts_t *opts, + mongoc_structured_log_level_t level); + +MONGOC_EXPORT (bool) +mongoc_structured_log_opts_set_max_levels_from_env (mongoc_structured_log_opts_t *opts); + +MONGOC_EXPORT (mongoc_structured_log_level_t) +mongoc_structured_log_opts_get_max_level_for_component (const mongoc_structured_log_opts_t *opts, + mongoc_structured_log_component_t component); + +MONGOC_EXPORT (bson_t *) +mongoc_structured_log_entry_message_as_bson (const mongoc_structured_log_entry_t *entry); + +MONGOC_EXPORT (mongoc_structured_log_level_t) +mongoc_structured_log_entry_get_level (const mongoc_structured_log_entry_t *entry); + +MONGOC_EXPORT (mongoc_structured_log_component_t) +mongoc_structured_log_entry_get_component (const mongoc_structured_log_entry_t *entry); + +MONGOC_EXPORT (const char *) +mongoc_structured_log_entry_get_message_string (const mongoc_structured_log_entry_t *entry); + +MONGOC_EXPORT (const char *) +mongoc_structured_log_get_level_name (mongoc_structured_log_level_t level); + +MONGOC_EXPORT (bool) +mongoc_structured_log_get_named_level (const char *name, mongoc_structured_log_level_t *out); + +MONGOC_EXPORT (const char *) +mongoc_structured_log_get_component_name (mongoc_structured_log_component_t component); + +MONGOC_EXPORT (bool) +mongoc_structured_log_get_named_component (const char *name, mongoc_structured_log_component_t *out); + +BSON_END_DECLS + +#endif /* MONGOC_STRUCTURED_LOG_H */ diff --git a/src/libmongoc/src/mongoc/mongoc-topology-description-private.h b/src/libmongoc/src/mongoc/mongoc-topology-description-private.h index 870635707f5..12c5c125b86 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-description-private.h +++ b/src/libmongoc/src/mongoc/mongoc-topology-description-private.h @@ -165,7 +165,7 @@ mongoc_topology_description_reconcile (mongoc_topology_description_t *td, mongoc * @param td The topology description that will be updated. * @param server_id The ID of the server to invalidate. * @param service_id A service ID for load-balanced deployments. Use - * kZeroServiceID if not applicable. + * kZeroObjectId if not applicable. * * @note Not applicable to single-threaded clients, which only maintain a * single connection per server and therefore have no connection pool. diff --git a/src/libmongoc/src/mongoc/mongoc-topology-private.h b/src/libmongoc/src/mongoc/mongoc-topology-private.h index a94b5b76788..0ec05e1afb8 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology-private.h +++ b/src/libmongoc/src/mongoc/mongoc-topology-private.h @@ -30,6 +30,7 @@ #include "mongoc-ts-pool-private.h" #include "mongoc-shared-private.h" #include "mongoc-sleep.h" +#include "mongoc-structured-log-private.h" #include #define MONGOC_TOPOLOGY_MIN_HEARTBEAT_FREQUENCY_MS 500 @@ -209,6 +210,8 @@ typedef struct _mongoc_topology_t { mongoc_set_t *rtt_monitors; bson_mutex_t apm_mutex; + mongoc_structured_log_instance_t *structured_log; + /* This is overridable for SRV polling tests to mock DNS records. */ _mongoc_rr_resolver_fn rr_resolver; @@ -237,6 +240,10 @@ mongoc_topology_set_apm_callbacks (mongoc_topology_t *topology, mongoc_apm_callbacks_t const *callbacks, void *context); +void +mongoc_topology_set_structured_log_opts (mongoc_topology_t *topology, const mongoc_structured_log_opts_t *opts); + + void mongoc_topology_destroy (mongoc_topology_t *topology); @@ -411,7 +418,7 @@ typedef enum { * @param generation The generation of the server description the caller was * using. * @param service_id A service ID for a load-balanced deployment. If not - * applicable, pass kZeroServiceID. + * applicable, pass kZeroObjectId. * @return true If the topology was updated and the pool was cleared. * @return false If no modifications were made and the error was ignored. * @@ -470,7 +477,7 @@ _mongoc_topology_get_srv_polling_rescan_interval_ms (mongoc_topology_t const *to * @param td The topology that contains the server * @param server_id The ID of the server to inspect * @param service_id The service ID of the connection if applicable, or - * kZeroServiceID. + * kZeroObjectId. * @returns uint32_t A generation counter for the given server, or zero if the * server does not exist in the topology. */ diff --git a/src/libmongoc/src/mongoc/mongoc-topology.c b/src/libmongoc/src/mongoc/mongoc-topology.c index be86f365cd8..800f1f0fca5 100644 --- a/src/libmongoc/src/mongoc/mongoc-topology.c +++ b/src/libmongoc/src/mongoc/mongoc-topology.c @@ -38,6 +38,7 @@ #include #include #include +#include static void _topology_collect_errors (const mongoc_topology_description_t *topology, bson_error_t *error_out); @@ -172,7 +173,7 @@ _mongoc_topology_scanner_cb ( /* Server monitoring: When a server check fails due to a network error * (including a network timeout), the client MUST clear its connection * pool for the server */ - _mongoc_topology_description_clear_connection_pool (td, id, &kZeroServiceId); + _mongoc_topology_description_clear_connection_pool (td, id, &kZeroObjectId); } /* Server Discovery and Monitoring Spec: "Once a server is connected, the @@ -400,6 +401,11 @@ mongoc_topology_new (const mongoc_uri_t *uri, bool single_threaded) topology->session_pool = mongoc_server_session_pool_new_with_params ( _server_session_init, _server_session_destroy, _server_session_should_prune, topology); + // Capture default structured log options from the environment + mongoc_structured_log_opts_t *structured_log_opts = mongoc_structured_log_opts_new (); + topology->structured_log = mongoc_structured_log_instance_new (structured_log_opts); + mongoc_structured_log_opts_destroy (structured_log_opts); + topology->valid = false; const int32_t heartbeat_default = single_threaded ? MONGOC_TOPOLOGY_HEARTBEAT_FREQUENCY_MS_SINGLE_THREADED @@ -660,6 +666,35 @@ mongoc_topology_set_apm_callbacks (mongoc_topology_t *topology, topology->scanner->apm_context = context; } +/* + *------------------------------------------------------------------------- + * + * mongoc_topology_set_structured_log_opts -- + * + * Replace the topology's structured logging options. Options are copied. + * The structured log instance used by a client or client pool in the public + * API is internally owned by a mongoc_topology_t. + * + * This is only safe to call when no other threads may be accessing the + * structured log. On a single-threaded topology it can be used on the + * thread that owns that topology. On a multi-threaded topology it can + * only be used during initialization, before clients have been created. + * This limitation is enforced by mongoc_client_pool_set_structured_log_opts. + * + *------------------------------------------------------------------------- + */ + +void +mongoc_topology_set_structured_log_opts (mongoc_topology_t *topology, const mongoc_structured_log_opts_t *opts) +{ + BSON_ASSERT_PARAM (topology); + BSON_OPTIONAL_PARAM (opts); + + mongoc_structured_log_instance_destroy (topology->structured_log); + topology->structured_log = mongoc_structured_log_instance_new (opts); +} + + /* *------------------------------------------------------------------------- * @@ -713,6 +748,7 @@ mongoc_topology_destroy (mongoc_topology_t *topology) mongoc_topology_scanner_destroy (topology->scanner); mongoc_server_session_pool_free (topology->session_pool); bson_free (topology->clientSideEncryption.autoOptions.extraOptions.cryptSharedLibPath); + mongoc_structured_log_instance_destroy (topology->structured_log); mongoc_cond_destroy (&topology->cond_client); bson_mutex_destroy (&topology->tpld_modification_mtx); diff --git a/src/libmongoc/src/mongoc/mongoc.h b/src/libmongoc/src/mongoc/mongoc.h index 6dab2fc3dda..cccedc54286 100644 --- a/src/libmongoc/src/mongoc/mongoc.h +++ b/src/libmongoc/src/mongoc/mongoc.h @@ -56,6 +56,7 @@ #include "mongoc-stream-file.h" #include "mongoc-stream-gridfs.h" #include "mongoc-stream-socket.h" +#include "mongoc-structured-log.h" #include "mongoc-uri.h" #include "mongoc-write-concern.h" #include "mongoc-version.h" diff --git a/src/libmongoc/tests/bsonutil/bson-match.c b/src/libmongoc/tests/bsonutil/bson-match.c index 269cef20fe3..adcfef5b180 100644 --- a/src/libmongoc/tests/bsonutil/bson-match.c +++ b/src/libmongoc/tests/bsonutil/bson-match.c @@ -23,7 +23,7 @@ typedef struct _special_functor_t { special_fn fn; - void *ctx; + void *user_data; char *keyword; struct _special_functor_t *next; } special_functor_t; @@ -32,7 +32,7 @@ struct _bson_matcher_t { special_functor_t *specials; }; -#define MATCH_ERR(format, ...) test_set_error (error, "match error at path: '%s': " format, path, __VA_ARGS__) +#define MATCH_ERR(format, ...) test_set_error (error, "match error at path: '%s': " format, context->path, __VA_ARGS__) static char * get_first_key (const bson_t *bson) @@ -62,18 +62,16 @@ is_special_match (const bson_t *bson) /* implements $$placeholder */ static bool -special_placeholder (bson_matcher_t *matcher, +special_placeholder (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { - BSON_UNUSED (matcher); + BSON_UNUSED (context); BSON_UNUSED (assertion); BSON_UNUSED (actual); - BSON_UNUSED (ctx); - BSON_UNUSED (path); + BSON_UNUSED (user_data); BSON_UNUSED (error); /* Nothing to do (not an operator, just a reserved key value). The meaning @@ -83,19 +81,18 @@ special_placeholder (bson_matcher_t *matcher, /* implements $$exists */ static bool -special_exists (bson_matcher_t *matcher, +special_exists (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; bson_iter_t iter; bool should_exist; - BSON_UNUSED (matcher); - BSON_UNUSED (ctx); + BSON_UNUSED (context); + BSON_UNUSED (user_data); bson_iter_init (&iter, assertion); BSON_ASSERT (bson_iter_next (&iter)); @@ -122,18 +119,17 @@ special_exists (bson_matcher_t *matcher, /* implements $$type */ static bool -special_type (bson_matcher_t *matcher, +special_type (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; bson_iter_t iter; - BSON_UNUSED (matcher); - BSON_UNUSED (ctx); + BSON_UNUSED (context); + BSON_UNUSED (user_data); bson_iter_init (&iter, assertion); BSON_ASSERT (bson_iter_next (&iter)); @@ -187,18 +183,17 @@ special_type (bson_matcher_t *matcher, /* implements $$unsetOrMatches */ static bool -special_unset_or_matches (bson_matcher_t *matcher, +special_unset_or_matches (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; bson_iter_t iter; bson_val_t *expected = NULL; - BSON_UNUSED (ctx); + BSON_UNUSED (user_data); bson_iter_init (&iter, assertion); BSON_ASSERT (bson_iter_next (&iter)); @@ -209,7 +204,7 @@ special_unset_or_matches (bson_matcher_t *matcher, goto done; } - if (!bson_matcher_match (matcher, expected, actual, path, false, error)) { + if (!bson_matcher_match (context, expected, actual, error)) { goto done; } @@ -221,11 +216,10 @@ special_unset_or_matches (bson_matcher_t *matcher, /* implements $$matchesHexBytes */ static bool -special_matches_hex_bytes (bson_matcher_t *matcher, +special_matches_hex_bytes (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; @@ -237,8 +231,8 @@ special_matches_hex_bytes (bson_matcher_t *matcher, char *actual_bytes_string = NULL; bson_iter_t iter; - BSON_UNUSED (matcher); - BSON_UNUSED (ctx); + BSON_UNUSED (context); + BSON_UNUSED (user_data); bson_iter_init (&iter, assertion); BSON_ASSERT (bson_iter_next (&iter)); @@ -288,14 +282,102 @@ special_matches_hex_bytes (bson_matcher_t *matcher, bson_free (actual_bytes_string); ret = true; - goto done; done: return ret; } +/* implements $$matchAsDocument */ static bool -evaluate_special ( - bson_matcher_t *matcher, const bson_t *assertion, const bson_val_t *actual, const char *path, bson_error_t *error) +special_match_as_document (const bson_matcher_context_t *context, + const bson_t *assertion, + const bson_val_t *actual, + void *user_data, + bson_error_t *error) +{ + bool ret = false; + bson_t actual_as_bson = BSON_INITIALIZER; + BSON_UNUSED (user_data); + + bson_iter_t iter; + bson_iter_init (&iter, assertion); + BSON_ASSERT (bson_iter_next (&iter)); + if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) { + MATCH_ERR ("%s", "$$matchAsDocument does not contain a document"); + goto done; + } + + if (!actual) { + MATCH_ERR ("%s", "does not exist but should"); + goto done; + } + + if (bson_val_type (actual) != BSON_TYPE_UTF8) { + MATCH_ERR ("%s", "value type is not utf8"); + goto done; + } + const char *actual_json = bson_val_to_utf8 (actual); + if (!bson_init_from_json (&actual_as_bson, actual_json, -1, error)) { + MATCH_ERR ("%s", "value can't be parsed as JSON"); + goto done; + } + + bson_val_t *expected_val = bson_val_from_iter (&iter); + bson_val_t *actual_val = bson_val_from_bson (&actual_as_bson); + ret = bson_matcher_match (context, expected_val, actual_val, error); + bson_val_destroy (actual_val); + bson_val_destroy (expected_val); + bson_destroy (&actual_as_bson); + +done: + + return ret; +} + +/* implements $$matchAsRoot */ +static bool +special_match_as_root (const bson_matcher_context_t *context, + const bson_t *assertion, + const bson_val_t *actual, + void *user_data, + bson_error_t *error) +{ + bool ret = false; + BSON_UNUSED (user_data); + + bson_iter_t iter; + bson_iter_init (&iter, assertion); + BSON_ASSERT (bson_iter_next (&iter)); + if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) { + MATCH_ERR ("%s", "$$matchAsRoot does not contain a document"); + goto done; + } + + if (!actual) { + MATCH_ERR ("%s", "does not exist but should"); + goto done; + } + + if (bson_val_type (actual) != BSON_TYPE_DOCUMENT) { + MATCH_ERR ("%s", "value is not a document"); + goto done; + } + + bson_matcher_context_t as_root_context = *context; + as_root_context.is_root = true; + + bson_val_t *expected_val = bson_val_from_iter (&iter); + ret = bson_matcher_match (&as_root_context, expected_val, actual, error); + bson_val_destroy (expected_val); + +done: + return ret; +} + +static bool +evaluate_special (const bson_matcher_context_t *context, + const bson_t *assertion, + const bson_val_t *actual, + bson_error_t *error) { bson_iter_t iter; const char *assertion_key; @@ -305,10 +387,10 @@ evaluate_special ( BSON_ASSERT (bson_iter_next (&iter)); assertion_key = bson_iter_key (&iter); - LL_FOREACH (matcher->specials, special_iter) + LL_FOREACH (context->matcher->specials, special_iter) { if (0 == strcmp (assertion_key, special_iter->keyword)) { - return special_iter->fn (matcher, assertion, actual, special_iter->ctx, path, error); + return special_iter->fn (context, assertion, actual, special_iter->user_data, error); } } @@ -327,12 +409,14 @@ bson_matcher_new (void) bson_matcher_add_special (matcher, "$$type", special_type, NULL); bson_matcher_add_special (matcher, "$$unsetOrMatches", special_unset_or_matches, NULL); bson_matcher_add_special (matcher, "$$matchesHexBytes", special_matches_hex_bytes, NULL); + bson_matcher_add_special (matcher, "$$matchAsDocument", special_match_as_document, NULL); + bson_matcher_add_special (matcher, "$$matchAsRoot", special_match_as_root, NULL); return matcher; } /* Add a hook function for matching a special $$ operator */ void -bson_matcher_add_special (bson_matcher_t *matcher, const char *keyword, special_fn special, void *ctx) +bson_matcher_add_special (bson_matcher_t *matcher, const char *keyword, special_fn special, void *user_data) { special_functor_t *functor; @@ -343,7 +427,7 @@ bson_matcher_add_special (bson_matcher_t *matcher, const char *keyword, special_ functor = bson_malloc (sizeof (special_functor_t)); functor->keyword = bson_strdup (keyword); functor->fn = special; - functor->ctx = ctx; + functor->user_data = user_data; LL_PREPEND (matcher->specials, functor); } @@ -365,15 +449,12 @@ bson_matcher_destroy (bson_matcher_t *matcher) } bool -bson_matcher_match (bson_matcher_t *matcher, +bson_matcher_match (const bson_matcher_context_t *context, const bson_val_t *expected, const bson_val_t *actual, - const char *path, - bool array_of_root_docs, bson_error_t *error) { bool ret = false; - bool is_root = (0 == strcmp (path, "")); if (bson_val_type (expected) == BSON_TYPE_DOCUMENT) { bson_iter_t expected_iter; @@ -384,7 +465,7 @@ bson_matcher_match (bson_matcher_t *matcher, /* handle special operators (e.g. $$type) */ if (is_special_match (expected_bson)) { - ret = evaluate_special (matcher, expected_bson, actual, path, error); + ret = evaluate_special (context, expected_bson, actual, error); goto done; } @@ -401,7 +482,6 @@ bson_matcher_match (bson_matcher_t *matcher, bson_val_t *expected_val = NULL; bson_val_t *actual_val = NULL; bson_iter_t actual_iter; - char *path_child = NULL; key = bson_iter_key (&expected_iter); expected_val = bson_val_from_iter (&expected_iter); @@ -412,9 +492,10 @@ bson_matcher_match (bson_matcher_t *matcher, if (bson_val_type (expected_val) == BSON_TYPE_DOCUMENT && is_special_match (bson_val_to_document (expected_val))) { - bool special_ret; - path_child = bson_strdup_printf ("%s.%s", path, key); - special_ret = evaluate_special (matcher, bson_val_to_document (expected_val), actual_val, path, error); + char *path_child = bson_strdup_printf ("%s.%s", context->path, key); + bson_matcher_context_t special_context = {.matcher = context->matcher, .path = path_child}; + bool special_ret = + evaluate_special (&special_context, bson_val_to_document (expected_val), actual_val, error); bson_free (path_child); bson_val_destroy (expected_val); bson_val_destroy (actual_val); @@ -431,16 +512,18 @@ bson_matcher_match (bson_matcher_t *matcher, goto done; } - path_child = bson_strdup_printf ("%s.%s", path, key); - if (!bson_matcher_match (matcher, expected_val, actual_val, path_child, false, error)) { - bson_val_destroy (expected_val); - bson_val_destroy (actual_val); - bson_free (path_child); - goto done; - } + char *path_child = bson_strdup_printf ("%s.%s", context->path, key); + bson_matcher_context_t document_child_context = { + .matcher = context->matcher, + .path = path_child, + }; + bool document_child_ret = bson_matcher_match (&document_child_context, expected_val, actual_val, error); bson_val_destroy (expected_val); bson_val_destroy (actual_val); bson_free (path_child); + if (!document_child_ret) { + goto done; + } } expected_keys = bson_count_keys (expected_bson); @@ -451,8 +534,9 @@ bson_matcher_match (bson_matcher_t *matcher, * not present in the expected document."" * * This logic must also handle the case where `expected` is one of any - * number of root documents within an array (i.e. cursor result). */ - if (!(is_root || array_of_root_docs) && expected_keys < actual_keys) { + * number of root documents within an array (i.e. cursor result); see + * array_child_context below. */ + if (!context->is_root && expected_keys < actual_keys) { MATCH_ERR ("expected %" PRIu32 " keys in document, got: %" PRIu32, expected_keys, actual_keys); goto done; } @@ -465,7 +549,6 @@ bson_matcher_match (bson_matcher_t *matcher, bson_iter_t expected_iter; const bson_t *expected_bson = bson_val_to_array (expected); const bson_t *actual_bson = NULL; - char *path_child = NULL; uint32_t expected_keys = bson_count_keys (expected_bson); uint32_t actual_keys; @@ -499,17 +582,19 @@ bson_matcher_match (bson_matcher_t *matcher, actual_val = bson_val_from_iter (&actual_iter); - path_child = bson_strdup_printf ("%s.%s", path, key); - if (!bson_matcher_match ( - matcher, expected_val, actual_val, path_child, (is_root && array_of_root_docs), error)) { - bson_val_destroy (expected_val); - bson_val_destroy (actual_val); - bson_free (path_child); - goto done; - } + char *path_child = bson_strdup_printf ("%s.%s", context->path, key); + bson_matcher_context_t array_child_context = { + .matcher = context->matcher, + .path = path_child, + .is_root = context->is_root && context->array_of_root_docs, + }; + bool array_child_ret = bson_matcher_match (&array_child_context, expected_val, actual_val, error); bson_val_destroy (expected_val); bson_val_destroy (actual_val); bson_free (path_child); + if (!array_child_ret) { + goto done; + } } ret = true; goto done; @@ -522,7 +607,7 @@ bson_matcher_match (bson_matcher_t *matcher, ret = true; done: - if (!ret && is_root) { + if (!ret && context->is_root) { /* Append the error with more context at the root match. */ bson_error_t tmp_error; @@ -541,9 +626,14 @@ bson_matcher_match (bson_matcher_t *matcher, bool bson_match (const bson_val_t *expected, const bson_val_t *actual, bool array_of_root_docs, bson_error_t *error) { - bson_matcher_t *matcher = bson_matcher_new (); - bool matched = bson_matcher_match (matcher, expected, actual, "", array_of_root_docs, error); - bson_matcher_destroy (matcher); + bson_matcher_context_t root_context = { + .matcher = bson_matcher_new (), + .path = "", + .is_root = true, + .array_of_root_docs = array_of_root_docs, + }; + bool matched = bson_matcher_match (&root_context, expected, actual, error); + bson_matcher_destroy (root_context.matcher); return matched; } @@ -557,22 +647,45 @@ typedef struct { static void test_match (void) { - testcase_t tests[] = {{"int32 ==", "{'a': 1}", "{'a': 1}", true}, - {"int32 !=", "{'a': 1}", "{'a': 0}", false}, - {"$$exists", "{'a': {'$$exists': true}}", "{'a': 0}", true}, - {"$$exists fail", "{'a': {'$$exists': true}}", "{'b': 0}", false}, - {"does not $$exists", "{'a': {'$$exists': false}}", "{'b': 0}", true}, - {"$$unsetOrMatches match", "{'a': {'$$unsetOrMatches': 1}}", "{'a': 1}", true}, - {"$$unsetOrMatches unset", "{'a': {'$$unsetOrMatches': 1}}", "{}", true}, - {"$$unsetOrMatches mismatch", "{'a': {'$$unsetOrMatches': 'abc'}}", "{'a': 1}", false}, - {"$$type match", "{'a': {'$$type': 'string'}}", "{'a': 'abc'}", true}, - {"$$type mismatch", "{'a': {'$$type': 'string'}}", "{'a': 1}", false}, - {"$$type array match", "{'a': {'$$type': ['string', 'int']}}", "{'a': 1}", true}, - {"$$type array mismatch", "{'a': {'$$type': ['string', 'int']}}", "{'a': 1.2}", false}, - {"extra keys in root ok", "{'a': 1}", "{'a': 1, 'b': 2}", true}, - {"extra keys in subdoc not ok", "{'a': {'b': 1}}", "{'a': {'b': 1, 'c': 2}}", false}, - {"numeric type mismatch is ok", "{'a': 1}", "{'a': 1.0}", true}, - {"comparing number to string is an error", "{'a': 1}", "{'a': 'foo'}", false}}; + testcase_t tests[] = { + {"int32 ==", "{'a': 1}", "{'a': 1}", true}, + {"int32 !=", "{'a': 1}", "{'a': 0}", false}, + {"$$exists", "{'a': {'$$exists': true}}", "{'a': 0}", true}, + {"$$exists fail", "{'a': {'$$exists': true}}", "{'b': 0}", false}, + {"does not $$exists", "{'a': {'$$exists': false}}", "{'b': 0}", true}, + {"$$unsetOrMatches match", "{'a': {'$$unsetOrMatches': 1}}", "{'a': 1}", true}, + {"$$unsetOrMatches unset", "{'a': {'$$unsetOrMatches': 1}}", "{}", true}, + {"$$unsetOrMatches mismatch", "{'a': {'$$unsetOrMatches': 'abc'}}", "{'a': 1}", false}, + {"$$type match", "{'a': {'$$type': 'string'}}", "{'a': 'abc'}", true}, + {"$$type mismatch", "{'a': {'$$type': 'string'}}", "{'a': 1}", false}, + {"$$type array match", "{'a': {'$$type': ['string', 'int']}}", "{'a': 1}", true}, + {"$$type array mismatch", "{'a': {'$$type': ['string', 'int']}}", "{'a': 1.2}", false}, + {"extra keys in root ok", "{'a': 1}", "{'a': 1, 'b': 2}", true}, + {"extra keys in subdoc not ok", "{'a': {'b': 1}}", "{'a': {'b': 1, 'c': 2}}", false}, + {"extra keys in subdoc allowed explicitly", + "{'a': {'$$matchAsRoot': {'b': 1}}}", + "{'a': {'b': 1, 'c': 2}}", + true}, + {"missing key in matchAsRoot subdoc", + "{'a': {'$$matchAsRoot': {'b': 1, 'd': 1}}}", + "{'a': {'b': 1, 'c': 2}}", + false}, + {"$$matchAsDocument match", "{'a': {'$$matchAsDocument': {'b': 'abc'}}}", "{'a': '{\\'b\\':\\'abc\\'}'}", true}, + {"$$matchAsDocument mismatch", + "{'a': {'$$matchAsDocument': {'b': 'abc'}}}", + "{'a': '{\\'b\\':\\'xyz\\'}'}", + false}, + {"$$matchAsDocument parse error", "{'a': {'$$matchAsDocument': {'b': 'abc'}}}", "{'a': 'nope'}", false}, + {"$$matchAsDocument extra keys not ok", + "{'a': {'$$matchAsDocument': {'b': 'abc'}}}", + "{'a': '{\\'b\\':\\'abc\\',\\'c\\':1}'}", + false}, + {"$$matchAsDocument and $$matchAsRoot, extra keys ok", + "{'a': {'$$matchAsDocument': {'$$matchAsRoot': {'b': 'abc'}}}}", + "{'a': '{\\'b\\':\\'abc\\',\\'c\\':1}'}", + true}, + {"numeric type mismatch is ok", "{'a': 1}", "{'a': 1.0}", true}, + {"comparing number to string is an error", "{'a': 1}", "{'a': 'foo'}", false}}; int i; for (i = 0; i < sizeof (tests) / sizeof (testcase_t); i++) { diff --git a/src/libmongoc/tests/bsonutil/bson-match.h b/src/libmongoc/tests/bsonutil/bson-match.h index 703154ca5d3..3f9aa2639d6 100644 --- a/src/libmongoc/tests/bsonutil/bson-match.h +++ b/src/libmongoc/tests/bsonutil/bson-match.h @@ -30,13 +30,22 @@ typedef struct _bson_matcher_t bson_matcher_t; bson_matcher_t * bson_matcher_new (void); -typedef bool (*special_fn) (bson_matcher_t *matcher, +/* Current state of an ongoing match operation, of interest to custom operators. + * Each recursion level has its own instance on the stack. */ +typedef struct bson_matcher_context_t { + bson_matcher_t *matcher; + const char *path; + bool is_root; + bool array_of_root_docs; +} bson_matcher_context_t; + +typedef bool (*special_fn) (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error); + /* Adds a handler function for matching a special $$ operator. * * Example: @@ -45,14 +54,12 @@ typedef bool (*special_fn) (bson_matcher_t *matcher, * expectation. */ void -bson_matcher_add_special (bson_matcher_t *matcher, const char *keyword, special_fn special, void *ctx); +bson_matcher_add_special (bson_matcher_t *matcher, const char *keyword, special_fn special, void *user_data); bool -bson_matcher_match (bson_matcher_t *matcher, +bson_matcher_match (const bson_matcher_context_t *context, const bson_val_t *expected, const bson_val_t *actual, - const char *path, - bool array_of_root_docs, bson_error_t *error); void diff --git a/src/libmongoc/tests/json-test-operations.c b/src/libmongoc/tests/json-test-operations.c index 92e80ded567..d080d8543a0 100644 --- a/src/libmongoc/tests/json-test-operations.c +++ b/src/libmongoc/tests/json-test-operations.c @@ -27,6 +27,7 @@ #include "mongoc/mongoc-topology-private.h" #include "mongoc/mongoc-uri-private.h" #include "mongoc/mongoc-util-private.h" +#include #include "json-test-operations.h" #include "json-test.h" @@ -2009,7 +2010,7 @@ _get_total_pool_cleared_event (json_test_ctx_t *ctx) const mongoc_server_description_t *sd; sd = mongoc_set_get_item_const (mc_tpld_servers_const (td.ptr), i); - total += mc_tpl_sd_get_generation (sd, &kZeroServiceId); + total += mc_tpl_sd_get_generation (sd, &kZeroObjectId); } mc_tpld_drop_ref (&td); return total; diff --git a/src/libmongoc/tests/json-test.c b/src/libmongoc/tests/json-test.c index ec9b7e3fd47..016f5a298ed 100644 --- a/src/libmongoc/tests/json-test.c +++ b/src/libmongoc/tests/json-test.c @@ -21,6 +21,7 @@ #include "mongoc/mongoc-util-private.h" #include "mongoc/mongoc-uri-private.h" #include "mongoc/mongoc-client-side-encryption.h" +#include #include "json-test.h" #include "json-test-operations.h" @@ -265,7 +266,7 @@ process_sdam_test_hello_responses (bson_t *phase, mongoc_topology_t *topology) generation = bson_iter_int32 (&app_error_field_iter); } else { /* Default to the current generation. */ - generation = mc_tpl_sd_get_generation (sd, &kZeroServiceId); + generation = mc_tpl_sd_get_generation (sd, &kZeroObjectId); } BSON_ASSERT (bson_iter_init_find (&app_error_field_iter, &app_error, "maxWireVersion")); @@ -304,7 +305,7 @@ process_sdam_test_hello_responses (bson_t *phase, mongoc_topology_t *topology) memset (&err, 0, sizeof (bson_error_t)); _mongoc_topology_handle_app_error ( - topology, sd->id, handshake_complete, type, &response, &err, max_wire_version, generation, &kZeroServiceId); + topology, sd->id, handshake_complete, type, &response, &err, max_wire_version, generation, &kZeroObjectId); mc_tpld_drop_ref (&td); } } diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/command.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/command.json new file mode 100644 index 00000000000..d2970df692f --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/command.json @@ -0,0 +1,215 @@ +{ + "description": "command-logging", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "collectionName": "logging-tests-collection", + "databaseName": "logging-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful command", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "ping", + "command": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ping": 1, + "$db": "logging-tests" + } + } + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "databaseName": "logging-tests", + "commandName": "ping", + "reply": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + } + ] + } + ] + }, + { + "description": "A failed command", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "find", + "command": { + "$$type": "string" + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "databaseName": "logging-tests", + "commandName": "find", + "failure": { + "$$exists": true + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/driver-connection-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/driver-connection-id.json new file mode 100644 index 00000000000..40db98d6fae --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/driver-connection-id.json @@ -0,0 +1,146 @@ +{ + "description": "driver-connection-id", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "collectionName": "logging-tests-collection", + "databaseName": "logging-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful command", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "ping", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "ping", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + }, + { + "description": "A failed command", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "find", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "find", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-handshake-messages.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-handshake-messages.json new file mode 100644 index 00000000000..a61e208798c --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-handshake-messages.json @@ -0,0 +1,94 @@ +{ + "description": "no-handshake-command-logs", + "schemaVersion": "1.13", + "tests": [ + { + "description": "Handshake commands should not generate log messages", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + }, + "observeEvents": [ + "connectionCreatedEvent", + "connectionReadyEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionCreatedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionReadyEvent": {} + }, + "count": 1 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "ping" + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-heartbeat-messages.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-heartbeat-messages.json new file mode 100644 index 00000000000..525be9171df --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/no-heartbeat-messages.json @@ -0,0 +1,91 @@ +{ + "description": "no-heartbeat-command-logs", + "schemaVersion": "1.13", + "runOnRequirements": [ + { + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "tests": [ + { + "description": "Heartbeat commands should not generate log messages", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + }, + "observeEvents": [ + "serverDescriptionChangedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": {} + }, + "count": 1 + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "ping" + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/operation-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/operation-id.json new file mode 100644 index 00000000000..b1a3cec3d91 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/operation-id.json @@ -0,0 +1,198 @@ +{ + "description": "operation-id", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "collectionName": "logging-tests-collection", + "databaseName": "logging-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "Successful bulk write command log messages include operationIds", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "x": 1 + } + } + }, + { + "deleteOne": { + "filter": { + "x": 1 + } + } + } + ] + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "insert", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "delete", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "delete", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + }, + { + "description": "Failed bulk write command log message includes operationId", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "x": 1 + }, + "update": [ + { + "$invalidOperator": true + } + ] + } + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "update", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "update", + "operationId": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/pre-42-server-connection-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/pre-42-server-connection-id.json new file mode 100644 index 00000000000..d5ebd865906 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/pre-42-server-connection-id.json @@ -0,0 +1,119 @@ +{ + "description": "pre-42-server-connection-id", + "schemaVersion": "1.13", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-server-connection-id-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "databaseName": "logging-server-connection-id-tests", + "collectionName": "logging-tests-collection", + "documents": [] + } + ], + "tests": [ + { + "description": "command log messages do not include server connection id", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "insert", + "serverConnectionId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "serverConnectionId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "find", + "serverConnectionId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "find", + "serverConnectionId": { + "$$exists": false + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/redacted-commands.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/redacted-commands.json new file mode 100644 index 00000000000..43b9ff74f29 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/redacted-commands.json @@ -0,0 +1,1438 @@ +{ + "description": "redacted-commands", + "schemaVersion": "1.13", + "runOnRequirements": [ + { + "minServerVersion": "5.0", + "auth": false + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-redaction-tests" + } + } + ], + "tests": [ + { + "description": "authenticate command and resulting server-generated error are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "authenticate", + "command": { + "authenticate": 1, + "mechanism": "MONGODB-X509", + "user": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", + "db": "$external" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "authenticate", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "authenticate", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to authenticate is not redacted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "authenticate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "authenticate", + "command": { + "authenticate": 1, + "mechanism": "MONGODB-X509", + "user": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "authenticate", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "authenticate", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "saslStart command and resulting server-generated error are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslStart", + "command": { + "saslStart": 1, + "payload": "definitely-invalid-payload", + "db": "admin" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "saslStart", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "saslStart", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to saslStart is not redacted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslStart" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslStart", + "command": { + "saslStart": 1, + "payload": "ZmFrZXNhc2xwYXlsb2Fk", + "mechanism": "MONGODB-X509" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "saslStart", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "saslStart", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "saslContinue command and resulting server-generated error are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslContinue", + "command": { + "saslContinue": 1, + "conversationId": 0, + "payload": "definitely-invalid-payload" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "saslContinue", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "saslContinue", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to saslContinue is not redacted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslContinue", + "command": { + "saslContinue": 1, + "conversationId": 0, + "payload": "ZmFrZXNhc2xwYXlsb2Fk" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "saslContinue", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "saslContinue", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "getnonce command and server reply are redacted", + "runOnRequirements": [ + { + "maxServerVersion": "6.1.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "getnonce", + "command": { + "getnonce": 1 + } + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "getnonce", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "getnonce", + "reply": { + "$$matchAsDocument": {} + } + } + } + ] + } + ] + }, + { + "description": "network error in response to getnonce is not redacted", + "runOnRequirements": [ + { + "maxServerVersion": "6.1.99" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "getnonce" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "getnonce", + "command": { + "getnonce": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "getnonce", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "getnonce", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "createUser command and resulting server-generated error are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "createUser", + "command": { + "createUser": "private", + "pwd": {}, + "roles": [] + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "createUser", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "createUser", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to createUser is not redacted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "createUser" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "createUser", + "command": { + "createUser": "private", + "pwd": "pwd", + "roles": [] + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "createUser", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "createUser", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "updateUser command and resulting server-generated error are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "updateUser", + "command": { + "updateUser": "private", + "pwd": {}, + "roles": [] + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "updateUser", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "updateUser", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to updateUser is not redacted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "updateUser" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "updateUser", + "command": { + "updateUser": "private", + "pwd": "pwd", + "roles": [] + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "updateUser", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "updateUser", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "copydbgetnonce command and resulting server-generated error are redacted", + "runOnRequirements": [ + { + "maxServerVersion": "3.6.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": "private" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydbgetnonce", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "copydbgetnonce", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to copydbgetnonce is not redacted", + "runOnRequirements": [ + { + "maxServerVersion": "3.6.99" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "copydbgetnonce" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": "private" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydbgetnonce", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "copydbgetnonce", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "copydbsaslstart command and resulting server-generated error are redacted", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": "private" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydbsaslstart", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "copydbsaslstart", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to copydbsaslstart is not redacted", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "copydbsaslstart" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": "private" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydbgetnonce", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "copydbgetnonce", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "copydb command and resulting server-generated error are redacted", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydb", + "command": { + "copydb": "private" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydb", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": true, + "data": { + "message": "Command failed", + "commandName": "copydb", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "network error in response to copydb is not redacted", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "copydb" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydb", + "command": { + "copydb": "private" + } + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "copydb", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "failureIsRedacted": false, + "data": { + "message": "Command failed", + "commandName": "copydb", + "failure": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "hello with speculative authenticate command and server reply are redacted", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "hello", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "hello", + "reply": { + "$$matchAsDocument": {} + } + } + } + ] + } + ] + }, + { + "description": "legacy hello with speculative authenticate command and server reply are redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "ismaster", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "ismaster", + "reply": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "isMaster", + "command": { + "$$matchAsDocument": {} + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "isMaster", + "reply": { + "$$matchAsDocument": {} + } + } + } + ] + } + ] + }, + { + "description": "hello without speculative authenticate command and server reply are not redacted", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": 1 + } + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "hello", + "command": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "hello": 1 + } + } + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "hello", + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1, + "isWritablePrimary": true + } + } + } + } + } + ] + } + ] + }, + { + "description": "legacy hello without speculative authenticate command and server reply are not redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": 1 + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": 1 + } + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "ismaster", + "command": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ismaster": 1 + } + } + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "ismaster", + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1, + "ismaster": true + } + } + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-redaction-tests", + "commandName": "isMaster", + "command": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "isMaster": 1 + } + } + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "isMaster", + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1, + "ismaster": true + } + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/server-connection-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/server-connection-id.json new file mode 100644 index 00000000000..abbbbc74421 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/server-connection-id.json @@ -0,0 +1,131 @@ +{ + "description": "server-connection-id", + "schemaVersion": "1.13", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-server-connection-id-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "databaseName": "logging-server-connection-id-tests", + "collectionName": "logging-tests-collection", + "documents": [] + } + ], + "tests": [ + { + "description": "command log messages include server connection id", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "insert", + "serverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "serverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "find", + "serverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "find", + "serverConnectionId": { + "$$type": [ + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/service-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/service-id.json new file mode 100644 index 00000000000..ea39d612315 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/service-id.json @@ -0,0 +1,207 @@ +{ + "description": "service-id", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-server-connection-id-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection" + } + } + ], + "initialData": [ + { + "databaseName": "logging-server-connection-id-tests", + "collectionName": "logging-tests-collection", + "documents": [] + } + ], + "tests": [ + { + "description": "command log messages include serviceId when in LB mode", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "insert", + "serviceId": { + "$$type": "string" + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "serviceId": { + "$$type": "string" + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "find", + "serviceId": { + "$$type": "string" + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "find", + "serviceId": { + "$$type": "string" + } + } + } + ] + } + ] + }, + { + "description": "command log messages omit serviceId when not in LB mode", + "runOnRequirements": [ + { + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "insert", + "serviceId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "serviceId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "commandName": "find", + "serviceId": { + "$$exists": false + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command failed", + "commandName": "find", + "serviceId": { + "$$exists": false + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/logging/unacknowledged-write.json b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/unacknowledged-write.json new file mode 100644 index 00000000000..0d33c020d54 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/logging/unacknowledged-write.json @@ -0,0 +1,151 @@ +{ + "description": "unacknowledged-write", + "schemaVersion": "1.16", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeLogMessages": { + "command": "debug" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "logging-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "logging-tests-collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + } + } + } + ], + "initialData": [ + { + "collectionName": "logging-tests-collection", + "databaseName": "logging-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ], + "tests": [ + { + "description": "An unacknowledged write generates a succeeded log message with ok: 1 reply", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "messages": [ + { + "level": "debug", + "component": "command", + "data": { + "message": "Command started", + "databaseName": "logging-tests", + "commandName": "insert", + "command": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "insert": "logging-tests-collection", + "$db": "logging-tests" + } + } + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "command", + "data": { + "message": "Command succeeded", + "commandName": "insert", + "reply": { + "$$matchAsDocument": { + "ok": 1 + } + }, + "requestId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/bulkWrite.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/bulkWrite.json new file mode 100644 index 00000000000..49c728442e0 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/bulkWrite.json @@ -0,0 +1,154 @@ +{ + "description": "bulkWrite", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "A successful mixed bulk write", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 333 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 3 + }, + "u": { + "$set": { + "x": 333 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/command.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/command.json new file mode 100644 index 00000000000..c28af95fed2 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/command.json @@ -0,0 +1,83 @@ +{ + "description": "command", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful command", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "command": { + "ping": 1 + }, + "commandName": "ping" + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "commandName": "ping", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1 + }, + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteMany.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteMany.json new file mode 100644 index 00000000000..78ebad1f98c --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteMany.json @@ -0,0 +1,162 @@ +{ + "description": "deleteMany", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "A successful deleteMany", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "limit": 0 + } + ], + "ordered": true + }, + "commandName": "delete", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 2 + }, + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "A successful deleteMany with write errors", + "operations": [ + { + "name": "deleteMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$unsupported": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$unsupported": 1 + } + }, + "limit": 0 + } + ], + "ordered": true + }, + "commandName": "delete", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "delete" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteOne.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteOne.json new file mode 100644 index 00000000000..2420794fe50 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/deleteOne.json @@ -0,0 +1,162 @@ +{ + "description": "deleteOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "A successful deleteOne", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "limit": 1 + } + ], + "ordered": true + }, + "commandName": "delete", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "delete" + } + } + ] + } + ] + }, + { + "description": "A successful deleteOne with write errors", + "operations": [ + { + "name": "deleteOne", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$unsupported": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$unsupported": 1 + } + }, + "limit": 1 + } + ], + "ordered": true + }, + "commandName": "delete", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "delete" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/find.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/find.json new file mode 100644 index 00000000000..bc9668499b3 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/find.json @@ -0,0 +1,558 @@ +{ + "description": "find", + "schemaVersion": "1.15", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "_yamlAnchors": { + "namespace": "command-monitoring-tests.test" + }, + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "A successful find with no options", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": { + "_id": 1 + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": 0, + "ns": "command-monitoring-tests.test", + "firstBatch": [ + { + "_id": 1, + "x": 11 + } + ] + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + }, + { + "description": "A successful find with options", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "x": -1 + }, + "projection": { + "_id": 0, + "x": 1 + }, + "skip": 2, + "comment": "test", + "hint": { + "_id": 1 + }, + "max": { + "_id": 6 + }, + "maxTimeMS": 6000, + "min": { + "_id": 0 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "x": -1 + }, + "projection": { + "_id": 0, + "x": 1 + }, + "skip": 2, + "comment": "test", + "hint": { + "_id": 1 + }, + "max": { + "_id": 6 + }, + "maxTimeMS": 6000, + "min": { + "_id": 0 + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": 0, + "ns": "command-monitoring-tests.test", + "firstBatch": [ + { + "x": 33 + }, + { + "x": 22 + } + ] + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + }, + { + "description": "A successful find with showRecordId and returnKey", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "showRecordId": true, + "returnKey": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "showRecordId": true, + "returnKey": true + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": 0, + "ns": "command-monitoring-tests.test", + "firstBatch": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + }, + { + "description": "A successful find with a getMore", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gte": 1 + } + }, + "sort": { + "_id": 1 + }, + "batchSize": 3 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": { + "_id": { + "$gte": 1 + } + }, + "sort": { + "_id": 1 + }, + "batchSize": 3 + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": { + "$$type": [ + "int", + "long" + ] + }, + "ns": "command-monitoring-tests.test", + "firstBatch": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3 + }, + "commandName": "getMore", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": 0, + "ns": "command-monitoring-tests.test", + "nextBatch": [ + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + }, + "commandName": "getMore", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + }, + { + "description": "A successful find event with a getmore and the server kills the cursor (<= 4.4)", + "runOnRequirements": [ + { + "minServerVersion": "3.1", + "maxServerVersion": "4.4.99", + "topologies": [ + "single", + "replicaset" + ] + } + ], + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gte": 1 + } + }, + "sort": { + "_id": 1 + }, + "batchSize": 3, + "limit": 4 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": { + "_id": { + "$gte": 1 + } + }, + "sort": { + "_id": 1 + }, + "batchSize": 3, + "limit": 4 + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": { + "$$type": [ + "int", + "long" + ] + }, + "ns": "command-monitoring-tests.test", + "firstBatch": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 1 + }, + "commandName": "getMore", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "cursor": { + "id": 0, + "ns": "command-monitoring-tests.test", + "nextBatch": [ + { + "_id": 4, + "x": 44 + } + ] + } + }, + "commandName": "getMore", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + }, + { + "description": "A failed find event", + "operations": [ + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "filter": { + "$or": true + } + }, + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "find", + "databaseName": "command-monitoring-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertMany.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertMany.json new file mode 100644 index 00000000000..a80a218c67a --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertMany.json @@ -0,0 +1,148 @@ +{ + "description": "insertMany", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful insertMany", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2, + "x": 22 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "A successful insertMany with write errors", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1, + "x": 11 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "insert" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertOne.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertOne.json new file mode 100644 index 00000000000..6ff732e41be --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/insertOne.json @@ -0,0 +1,144 @@ +{ + "description": "insertOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful insertOne", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2, + "x": 22 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "A successful insertOne with write errors", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1, + "x": 11 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "insert" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/pre-42-server-connection-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/pre-42-server-connection-id.json new file mode 100644 index 00000000000..141fbe584f7 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/pre-42-server-connection-id.json @@ -0,0 +1,101 @@ +{ + "description": "pre-42-server-connection-id", + "schemaVersion": "1.6", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "server-connection-id-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "databaseName": "server-connection-id-tests", + "collectionName": "coll", + "documents": [] + } + ], + "tests": [ + { + "description": "command events do not include server connection id", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "hasServerConnectionId": false + } + }, + { + "commandSucceededEvent": { + "commandName": "insert", + "hasServerConnectionId": false + } + }, + { + "commandStartedEvent": { + "commandName": "find", + "hasServerConnectionId": false + } + }, + { + "commandFailedEvent": { + "commandName": "find", + "hasServerConnectionId": false + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/redacted-commands.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/redacted-commands.json new file mode 100644 index 00000000000..4302ba89004 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/redacted-commands.json @@ -0,0 +1,679 @@ +{ + "description": "redacted-commands", + "schemaVersion": "1.5", + "runOnRequirements": [ + { + "minServerVersion": "5.0", + "auth": false + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ], + "observeSensitiveCommands": true + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + } + ], + "tests": [ + { + "description": "authenticate", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "authenticate", + "command": { + "authenticate": 1, + "mechanism": "MONGODB-X509", + "user": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", + "db": "$external" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "authenticate", + "command": { + "authenticate": { + "$$exists": false + }, + "mechanism": { + "$$exists": false + }, + "user": { + "$$exists": false + }, + "db": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "saslStart", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslStart", + "command": { + "saslStart": 1, + "payload": "definitely-invalid-payload", + "db": "admin" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "saslStart", + "command": { + "saslStart": { + "$$exists": false + }, + "payload": { + "$$exists": false + }, + "db": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "saslContinue", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "saslContinue", + "command": { + "saslContinue": 1, + "conversationId": 0, + "payload": "definitely-invalid-payload" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "saslContinue", + "command": { + "saslContinue": { + "$$exists": false + }, + "conversationId": { + "$$exists": false + }, + "payload": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "getnonce", + "runOnRequirements": [ + { + "maxServerVersion": "6.1.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "getnonce", + "command": { + "getnonce": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "getnonce", + "command": { + "getnonce": { + "$$exists": false + } + } + } + }, + { + "commandSucceededEvent": { + "commandName": "getnonce", + "reply": { + "ok": { + "$$exists": false + }, + "nonce": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "createUser", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "createUser", + "command": { + "createUser": "private", + "pwd": {}, + "roles": [] + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "createUser", + "command": { + "createUser": { + "$$exists": false + }, + "pwd": { + "$$exists": false + }, + "roles": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateUser", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "updateUser", + "command": { + "updateUser": "private", + "pwd": {}, + "roles": [] + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "updateUser", + "command": { + "updateUser": { + "$$exists": false + }, + "pwd": { + "$$exists": false + }, + "roles": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydbgetnonce", + "runOnRequirements": [ + { + "maxServerVersion": "3.6.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydbgetnonce", + "command": { + "copydbgetnonce": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydbsaslstart", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydbsaslstart", + "command": { + "copydbsaslstart": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "copydb", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "copydb", + "command": { + "copydb": "private" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "copydb", + "command": { + "copydb": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "hello with speculative authenticate", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "hello", + "command": { + "hello": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + }, + { + "commandSucceededEvent": { + "commandName": "hello", + "reply": { + "isWritablePrimary": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "legacy hello with speculative authenticate", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": 1, + "speculativeAuthenticate": { + "saslStart": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "ismaster", + "command": { + "ismaster": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + }, + { + "commandSucceededEvent": { + "commandName": "ismaster", + "reply": { + "ismaster": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "isMaster", + "command": { + "isMaster": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + }, + { + "commandSucceededEvent": { + "commandName": "isMaster", + "reply": { + "ismaster": { + "$$exists": false + }, + "speculativeAuthenticate": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "hello without speculative authenticate is not redacted", + "runOnRequirements": [ + { + "minServerVersion": "4.9" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "hello", + "command": { + "hello": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "hello", + "command": { + "hello": 1 + } + } + }, + { + "commandSucceededEvent": { + "commandName": "hello", + "reply": { + "isWritablePrimary": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "legacy hello without speculative authenticate is not redacted", + "operations": [ + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ismaster", + "command": { + "ismaster": 1 + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "isMaster", + "command": { + "isMaster": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "ismaster", + "command": { + "ismaster": 1 + } + } + }, + { + "commandSucceededEvent": { + "commandName": "ismaster", + "reply": { + "ismaster": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "isMaster", + "command": { + "isMaster": 1 + } + } + }, + { + "commandSucceededEvent": { + "commandName": "isMaster", + "reply": { + "ismaster": { + "$$exists": true + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/server-connection-id.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/server-connection-id.json new file mode 100644 index 00000000000..a8f27637fc0 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/server-connection-id.json @@ -0,0 +1,101 @@ +{ + "description": "server-connection-id", + "schemaVersion": "1.6", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "server-connection-id-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "databaseName": "server-connection-id-tests", + "collectionName": "coll", + "documents": [] + } + ], + "tests": [ + { + "description": "command events include server connection id", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "x": 1 + } + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$or": true + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "hasServerConnectionId": true + } + }, + { + "commandSucceededEvent": { + "commandName": "insert", + "hasServerConnectionId": true + } + }, + { + "commandStartedEvent": { + "commandName": "find", + "hasServerConnectionId": true + } + }, + { + "commandFailedEvent": { + "commandName": "find", + "hasServerConnectionId": true + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json new file mode 100644 index 00000000000..78ddde767ff --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json @@ -0,0 +1,117 @@ +{ + "description": "unacknowledgedBulkWrite", + "schemaVersion": "1.7", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + } + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A successful unordered bulk write with an unacknowledged write concern", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": "unorderedBulkWriteInsertW0", + "x": 44 + } + } + } + ], + "ordered": false + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": "unorderedBulkWriteInsertW0", + "x": 44 + } + ], + "ordered": false, + "writeConcern": { + "w": 0 + } + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": { + "$$exists": false + } + }, + "commandName": "insert" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateMany.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateMany.json new file mode 100644 index 00000000000..b15434226c8 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateMany.json @@ -0,0 +1,188 @@ +{ + "description": "updateMany", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "A successful updateMany", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": true + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 2 + }, + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "A successful updateMany with write errors", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$unsupported": { + "x": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$unsupported": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": true + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "update" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateOne.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateOne.json new file mode 100644 index 00000000000..a0ae99e88db --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/updateOne.json @@ -0,0 +1,260 @@ +{ + "description": "updateOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "A successful updateOne", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "A successful updateOne with upsert where the upserted id is not an ObjectId", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": true, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1, + "upserted": [ + { + "index": 0, + "_id": 4 + } + ] + }, + "commandName": "update" + } + } + ] + } + ] + }, + { + "description": "A successful updateOne with write errors", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$unsupported": { + "x": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$unsupported": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true + }, + "commandName": "update", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 0, + "writeErrors": { + "$$type": "array" + } + }, + "commandName": "update" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/writeConcernError.json b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/writeConcernError.json new file mode 100644 index 00000000000..455e5422b72 --- /dev/null +++ b/src/libmongoc/tests/json/command-logging-and-monitoring/monitoring/writeConcernError.json @@ -0,0 +1,155 @@ +{ + "description": "writeConcernError", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "A retryable write with write concern errors publishes success event", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91 + } + } + } + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2, + "x": 22 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1, + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91 + } + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2, + "x": 22 + } + ], + "ordered": true + }, + "commandName": "insert", + "databaseName": "command-monitoring-tests" + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "insert" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/test-libmongoc-main.c b/src/libmongoc/tests/test-libmongoc-main.c index 7eaa348c180..20b25c40cfa 100644 --- a/src/libmongoc/tests/test-libmongoc-main.c +++ b/src/libmongoc/tests/test-libmongoc-main.c @@ -48,6 +48,7 @@ main (int argc, char *argv[]) TEST_INSTALL (test_bson_cmp_install); TEST_INSTALL (test_mcommon_cmp_install); TEST_INSTALL (test_mcommon_atomic_install); + TEST_INSTALL (test_mcommon_oid_install); /* libmongoc */ @@ -82,6 +83,7 @@ main (int argc, char *argv[]) TEST_INSTALL (test_linux_distro_scanner_install); TEST_INSTALL (test_list_install); TEST_INSTALL (test_log_install); + TEST_INSTALL (test_structured_log_install); TEST_INSTALL (test_long_namespace_install); TEST_INSTALL (test_matcher_install); TEST_INSTALL (test_mongos_pinning_install); diff --git a/src/libmongoc/tests/test-libmongoc.c b/src/libmongoc/tests/test-libmongoc.c index 22497bdb1ea..e6c9ad9a78d 100644 --- a/src/libmongoc/tests/test-libmongoc.c +++ b/src/libmongoc/tests/test-libmongoc.c @@ -31,6 +31,7 @@ #include "test-conveniences.h" #include "test-libmongoc.h" #include +#include #ifdef BSON_HAVE_STRINGS_H #include @@ -58,11 +59,36 @@ typedef struct { static bson_mutex_t captured_logs_mutex; static mongoc_array_t captured_logs; static bool capturing_logs; +static int suppress_structured_logs_counter_atomic; #ifdef MONGOC_ENABLE_SSL static mongoc_ssl_opt_t gSSLOptions; #endif +bool +test_is_suppressing_structured_logs (void) +{ + int c = mcommon_atomic_int_fetch (&suppress_structured_logs_counter_atomic, mcommon_memory_order_seq_cst); + BSON_ASSERT (c >= 0); + return c > 0; +} + +// Ignore logs generated by test-internal operations. Can be nested. Structured logs only; see capturing_logs too. +void +test_begin_suppressing_structured_logs (void) +{ + mcommon_atomic_int_fetch_add (&suppress_structured_logs_counter_atomic, 1, mcommon_memory_order_seq_cst); +} + +void +test_end_suppressing_structured_logs (void) +{ + int previous = + mcommon_atomic_int_fetch_sub (&suppress_structured_logs_counter_atomic, 1, mcommon_memory_order_seq_cst); + BSON_ASSERT (previous > 0); +} + + static log_entry_t * log_entry_create (mongoc_log_level_t level, const char *msg) { @@ -842,6 +868,8 @@ call_hello_with_host_and_port (const char *host_and_port, bson_t *reply) mongoc_client_t *client; bson_error_t error; + test_begin_suppressing_structured_logs (); + if (test_framework_get_user_password (&user, &password)) { uri_str = bson_strdup_printf ( "mongodb://%s:%s@%s%s", user, password, host_and_port, test_framework_get_ssl () ? "/?ssl=true" : ""); @@ -888,6 +916,7 @@ call_hello_with_host_and_port (const char *host_and_port, bson_t *reply) mongoc_client_destroy (client); mongoc_uri_destroy (uri); bson_free (uri_str); + test_end_suppressing_structured_logs (); } /* @@ -2573,6 +2602,7 @@ test_libmongoc_destroy (TestSuite *suite) TestSuite_Destroy (suite); capture_logs (false); /* clear entries */ _mongoc_array_destroy (&captured_logs); + BSON_ASSERT (!test_is_suppressing_structured_logs ()); mongoc_cleanup (); } diff --git a/src/libmongoc/tests/test-libmongoc.h b/src/libmongoc/tests/test-libmongoc.h index 67259739faf..468211e90e0 100644 --- a/src/libmongoc/tests/test-libmongoc.h +++ b/src/libmongoc/tests/test-libmongoc.h @@ -36,6 +36,12 @@ char * gen_collection_name (const char *prefix); mongoc_collection_t * get_test_collection (mongoc_client_t *client, const char *prefix); +bool +test_is_suppressing_structured_logs (void); +void +test_begin_suppressing_structured_logs (void); +void +test_end_suppressing_structured_logs (void); void capture_logs (bool capture); void diff --git a/src/libmongoc/tests/test-mongoc-client.c b/src/libmongoc/tests/test-mongoc-client.c index 6bedc8e10c5..29270e9257a 100644 --- a/src/libmongoc/tests/test-mongoc-client.c +++ b/src/libmongoc/tests/test-mongoc-client.c @@ -24,6 +24,7 @@ #include "mock_server/mock-server.h" #include "mock_server/mock-rs.h" #include // BEGIN_IGNORE_DEPRECATIONS +#include #ifdef BSON_HAVE_STRINGS_H @@ -3717,7 +3718,7 @@ test_mongoc_client_recv_network_error (void) /* The server should be a standalone. */ sd = mongoc_topology_description_server_by_id_const (mc_tpld_unsafe_get_const (client->topology), 1, &error); ASSERT_OR_PRINT (sd, error); - generation = mc_tpl_sd_get_generation (sd, &kZeroServiceId); + generation = mc_tpl_sd_get_generation (sd, &kZeroObjectId); BSON_ASSERT (sd->type == MONGOC_SERVER_STANDALONE); mock_server_destroy (server); @@ -3733,7 +3734,7 @@ test_mongoc_client_recv_network_error (void) td = mc_tpld_take_ref (client->topology); sd = mongoc_topology_description_server_by_id_const (td.ptr, 1, &error); ASSERT_OR_PRINT (sd, error); - ASSERT_CMPINT (mc_tpl_sd_get_generation (sd, &kZeroServiceId), ==, generation + 1); + ASSERT_CMPINT (mc_tpl_sd_get_generation (sd, &kZeroObjectId), ==, generation + 1); BSON_ASSERT (sd->type == MONGOC_SERVER_UNKNOWN); mongoc_client_destroy (client); diff --git a/src/libmongoc/tests/test-mongoc-cluster.c b/src/libmongoc/tests/test-mongoc-cluster.c index de74d49f9af..c3a929dcf2d 100644 --- a/src/libmongoc/tests/test-mongoc-cluster.c +++ b/src/libmongoc/tests/test-mongoc-cluster.c @@ -5,6 +5,7 @@ #include "mongoc/mongoc-client-pool-private.h" #include "mongoc/mongoc-topology-background-monitoring-private.h" #include "mongoc/mongoc-uri-private.h" +#include #include "mock_server/mock-server.h" #include "mock_server/future.h" @@ -1669,8 +1670,7 @@ test_cluster_stream_invalidation_single (void) ASSERT_OR_PRINT (stream, error); BSON_ASSERT (mongoc_cluster_stream_valid (&client->cluster, stream)); tdmod = mc_tpld_modify_begin (client->topology); - _mongoc_topology_description_clear_connection_pool ( - tdmod.new_td, mongoc_server_description_id (sd), &kZeroServiceId); + _mongoc_topology_description_clear_connection_pool (tdmod.new_td, mongoc_server_description_id (sd), &kZeroObjectId); mc_tpld_modify_commit (tdmod); BSON_ASSERT (!mongoc_cluster_stream_valid (&client->cluster, stream)); mongoc_server_stream_cleanup (stream); @@ -1720,8 +1720,7 @@ test_cluster_stream_invalidation_pooled (void) ASSERT_OR_PRINT (stream, error); BSON_ASSERT (mongoc_cluster_stream_valid (&client->cluster, stream)); tdmod = mc_tpld_modify_begin (client->topology); - _mongoc_topology_description_clear_connection_pool ( - tdmod.new_td, mongoc_server_description_id (sd), &kZeroServiceId); + _mongoc_topology_description_clear_connection_pool (tdmod.new_td, mongoc_server_description_id (sd), &kZeroObjectId); mc_tpld_modify_commit (tdmod); BSON_ASSERT (!mongoc_cluster_stream_valid (&client->cluster, stream)); mongoc_server_stream_cleanup (stream); diff --git a/src/libmongoc/tests/test-mongoc-command-monitoring.c b/src/libmongoc/tests/test-mongoc-command-monitoring.c index ce1b1b9f9c3..e0a491a0179 100644 --- a/src/libmongoc/tests/test-mongoc-command-monitoring.c +++ b/src/libmongoc/tests/test-mongoc-command-monitoring.c @@ -97,6 +97,7 @@ test_command_monitoring_cb (void *scenario) static void test_all_spec_tests (TestSuite *suite) { + // Newer versions of the 'unified' tests have migrated to command-logging-and-monitoring run_unified_tests (suite, JSON_DIR, "command_monitoring/unified"); install_json_test_suite (suite, JSON_DIR, "command_monitoring/legacy", &test_command_monitoring_cb); } diff --git a/src/libmongoc/tests/test-mongoc-exhaust.c b/src/libmongoc/tests/test-mongoc-exhaust.c index b6645dcf3e6..dcdff3a9fdc 100644 --- a/src/libmongoc/tests/test-mongoc-exhaust.c +++ b/src/libmongoc/tests/test-mongoc-exhaust.c @@ -13,6 +13,7 @@ #include "mock_server/future-functions.h" #include "mock_server/mock-server.h" #include // BEGIN_IGNORE_DEPRECATIONS +#include #undef MONGOC_LOG_DOMAIN @@ -81,7 +82,7 @@ get_generation (mongoc_client_t *client, mongoc_cursor_t *cursor) sd = mongoc_topology_description_server_by_id_const (td.ptr, server_id, &error); ASSERT_OR_PRINT (sd, error); - generation = mc_tpl_sd_get_generation (sd, &kZeroServiceId); + generation = mc_tpl_sd_get_generation (sd, &kZeroObjectId); mc_tpld_drop_ref (&td); return generation; diff --git a/src/libmongoc/tests/test-mongoc-sdam.c b/src/libmongoc/tests/test-mongoc-sdam.c index 87e41d02047..3b26f682b30 100644 --- a/src/libmongoc/tests/test-mongoc-sdam.c +++ b/src/libmongoc/tests/test-mongoc-sdam.c @@ -1,6 +1,7 @@ #include #include +#include #include "json-test.h" @@ -84,7 +85,7 @@ _topology_has_description (const mongoc_topology_description_t *topology, bson_t BSON_ASSERT (bson_iter_recurse (&server_iter, &iter)); BSON_ASSERT (bson_iter_find (&iter, "generation") && BSON_ITER_HOLDS_INT32 (&iter)); expected_generation = bson_iter_int32 (&iter); - ASSERT_CMPINT32 (expected_generation, ==, mc_tpl_sd_get_generation (sd, &kZeroServiceId)); + ASSERT_CMPINT32 (expected_generation, ==, mc_tpl_sd_get_generation (sd, &kZeroObjectId)); } else if (strcmp ("logicalSessionTimeoutMinutes", bson_iter_key (&server_iter)) == 0) { if (BSON_ITER_HOLDS_NULL (&server_iter)) { if (sd->session_timeout_minutes != MONGOC_NO_SESSIONS) { diff --git a/src/libmongoc/tests/test-mongoc-structured-log.c b/src/libmongoc/tests/test-mongoc-structured-log.c new file mode 100644 index 00000000000..fa32b1d35c6 --- /dev/null +++ b/src/libmongoc/tests/test-mongoc-structured-log.c @@ -0,0 +1,717 @@ +/* + * Copyright 2009-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "mongoc/mongoc-structured-log-private.h" +#include "TestSuite.h" + +typedef struct log_assumption { + mongoc_structured_log_envelope_t expected_envelope; + bson_t *expected_bson; + int expected_calls; + int calls; +} log_assumption; + +static void +structured_log_func (const mongoc_structured_log_entry_t *entry, void *user_data) +{ + struct log_assumption *assumption = (struct log_assumption *) user_data; + + int calls = ++assumption->calls; + ASSERT_CMPINT (calls, <=, assumption->expected_calls); + + ASSERT_CMPINT (entry->envelope.level, ==, assumption->expected_envelope.level); + ASSERT_CMPINT (entry->envelope.component, ==, assumption->expected_envelope.component); + ASSERT_CMPSTR (entry->envelope.message, assumption->expected_envelope.message); + + ASSERT_CMPSTR (entry->envelope.message, mongoc_structured_log_entry_get_message_string (entry)); + ASSERT_CMPINT (entry->envelope.level, ==, mongoc_structured_log_entry_get_level (entry)); + ASSERT_CMPINT (entry->envelope.component, ==, mongoc_structured_log_entry_get_component (entry)); + + // Each call to message_as_bson allocates an identical copy + bson_t *bson_1 = mongoc_structured_log_entry_message_as_bson (entry); + bson_t *bson_2 = mongoc_structured_log_entry_message_as_bson (entry); + + // Compare for exact bson equality *after* comparing json strings, to give a more user friendly error on most + // failures + char *json_actual = bson_as_relaxed_extended_json (bson_1, NULL); + char *json_expected = bson_as_relaxed_extended_json (assumption->expected_bson, NULL); + ASSERT_CMPSTR (json_actual, json_expected); + + ASSERT (bson_equal (bson_1, assumption->expected_bson)); + ASSERT (bson_equal (bson_2, assumption->expected_bson)); + bson_destroy (bson_2); + bson_destroy (bson_1); + bson_free (json_actual); + bson_free (json_expected); +} +void + +test_structured_log_opts (void) +{ + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, (mongoc_structured_log_component_t) 12345)); + + ASSERT (!mongoc_structured_log_opts_set_max_level_for_all_components (opts, (mongoc_structured_log_level_t) -1)); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_INFO)); + + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, (mongoc_structured_log_component_t) 12345)); + + ASSERT (!mongoc_structured_log_opts_set_max_level_for_component ( + opts, (mongoc_structured_log_component_t) -1, MONGOC_STRUCTURED_LOG_LEVEL_WARNING)); + ASSERT (!mongoc_structured_log_opts_set_max_level_for_component ( + opts, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, (mongoc_structured_log_level_t) -1)); + ASSERT (mongoc_structured_log_opts_set_max_level_for_component ( + opts, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, MONGOC_STRUCTURED_LOG_LEVEL_WARNING)); + + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND)); + ASSERT_CMPINT ( + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + ==, + mongoc_structured_log_opts_get_max_level_for_component (opts, MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION)); + + mongoc_structured_log_opts_destroy (opts); +} + +void +test_structured_log_plain (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Plain log entry", + .expected_bson = BCON_NEW ("message", BCON_UTF8 ("Plain log entry")), + .expected_calls = 1, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log ( + instance, MONGOC_STRUCTURED_LOG_LEVEL_WARNING, MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, "Plain log entry"); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_plain_with_extra_data (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Plain log entry with extra data", + .expected_bson = BCON_NEW ("message", BCON_UTF8 ("Plain log entry with extra data"), "extra", BCON_INT32 (1)), + .expected_calls = 1, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Plain log entry with extra data", + int32 ("extra", 1)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_basic_data_types (void) +{ + const char non_terminated_test_string[] = {0, 1, 2, 3, 'a', '\\'}; + bson_t *bson_str_n = bson_new (); + bson_append_utf8 (bson_str_n, "kStrN1", -1, non_terminated_test_string, sizeof non_terminated_test_string); + bson_append_utf8 (bson_str_n, "kStrN2", -1, non_terminated_test_string, sizeof non_terminated_test_string); + + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with all basic data types", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with all basic data types"), + "kStr", + BCON_UTF8 ("string value"), + "kNullStr", + BCON_NULL, + BCON (bson_str_n), + "kNullStrN1", + BCON_NULL, + "kNullStrN2", + BCON_NULL, + "kNullStrN3", + BCON_NULL, + "kInt32", + BCON_INT32 (-12345), + "kInt64", + BCON_INT64 (0x76543210aabbccdd), + "kTrue", + BCON_BOOL (true), + "kFalse", + BCON_BOOL (false)), + .expected_calls = 1, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with all basic data types", + utf8 ("kStr", "string value"), + utf8 ("kNullStr", NULL), + utf8 (NULL, NULL), + utf8_nn ("kStrN1ZZZ", 6, non_terminated_test_string, sizeof non_terminated_test_string), + utf8_n ("kStrN2", non_terminated_test_string, sizeof non_terminated_test_string), + utf8_nn ("kNullStrN1ZZZ", 10, NULL, 12345), + utf8_nn ("kNullStrN2", -1, NULL, 12345), + utf8_nn (NULL, 999, NULL, 999), + utf8_n ("kNullStrN3", NULL, 12345), + int32 ("kInt32", -12345), + int32 (NULL, 9999), + int64 ("kInt64", 0x76543210aabbccdd), + int64 (NULL, -1), + boolean ("kTrue", true), + boolean ("kFalse", false), + boolean (NULL, true)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); + bson_destroy (bson_str_n); +} + +void +test_structured_log_json (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with deferred BSON-to-JSON", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with deferred BSON-to-JSON"), + "kJSON", + BCON_UTF8 ("{ \"k\" : \"v\" }"), + "kNull", + BCON_NULL), + .expected_calls = 1, + }; + + bson_t *json_doc = BCON_NEW ("k", BCON_UTF8 ("v")); + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with deferred BSON-to-JSON", + bson_as_json ("kJSON", json_doc), + bson_as_json ("kNull", NULL), + bson_as_json (NULL, NULL)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); + bson_destroy (json_doc); +} + +void +test_structured_log_oid (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with deferred OID-to-hex conversion", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with deferred OID-to-hex conversion"), + "kOID", + BCON_UTF8 ("112233445566778899aabbcc"), + "kNull", + BCON_NULL), + .expected_calls = 1, + }; + + bson_oid_t oid; + bson_oid_init_from_string (&oid, "112233445566778899aabbcc"); + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with deferred OID-to-hex conversion", + oid_as_hex ("kOID", &oid), + oid_as_hex ("kNull", NULL), + oid_as_hex (NULL, NULL)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_error (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_INFO, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION, + .expected_envelope.message = "Log entry with bson_error_t values", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with bson_error_t values"), + "failure", + "{", + "code", + BCON_INT32 (0xabab5555), + "domain", + BCON_INT32 (0x87654321), + "message", + BCON_UTF8 ("Some Text"), + "}", + "null", + BCON_NULL), + .expected_calls = 1, + }; + + const bson_error_t err = { + .domain = 0x87654321, + .code = 0xabab5555, + .message = "Some Text", + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_INFO)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_INFO, + MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION, + "Log entry with bson_error_t values", + error ("failure", &err), + error (NULL, NULL), + error ("null", NULL)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_server_description (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with server description", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with server description"), + "serverHost", + BCON_UTF8 ("db1.example.com"), + "serverHost", + BCON_UTF8 ("db2.example.com"), + "serverPort", + BCON_INT32 (2340), + "serverConnectionId", + BCON_INT64 (0x3deeff00112233f0), + "serverHost", + BCON_UTF8 ("db1.example.com"), + "serverPort", + BCON_INT32 (2340), + "serverHost", + BCON_UTF8 ("db1.example.com"), + "serverPort", + BCON_INT32 (2340), + "serverConnectionId", + BCON_INT64 (0x3deeff00112233f0), + "serviceId", + BCON_UTF8 ("2233445566778899aabbccdd"), + "serverHost", + BCON_UTF8 ("db2.example.com"), + "serverPort", + BCON_INT32 (2341), + "serverConnectionId", + BCON_INT64 (0x3deeff00112233f1)), + .expected_calls = 1, + }; + + mongoc_server_description_t server_description_1 = { + .host.host = "db1.example.com", + .host.port = 2340, + .server_connection_id = 0x3deeff00112233f0, + }; + bson_oid_init_from_string (&server_description_1.service_id, "2233445566778899aabbccdd"); + + mongoc_server_description_t server_description_2 = { + .host.host = "db2.example.com", + .host.port = 2341, + .server_connection_id = 0x3deeff00112233f1, + .service_id = {{0}}, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log ( + instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with server description", + server_description (&server_description_1, SERVER_HOST), + server_description (&server_description_2, SERVICE_ID), + server_description (&server_description_2, SERVER_HOST), + server_description (&server_description_1, SERVER_PORT), + server_description (&server_description_1, SERVER_CONNECTION_ID), + server_description (&server_description_1, SERVER_HOST, SERVER_PORT), + server_description (&server_description_1, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID), + server_description (&server_description_2, SERVER_HOST, SERVER_PORT, SERVER_CONNECTION_ID, SERVICE_ID)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_command (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with command and reply fields", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with command and reply fields"), + "commandName", + BCON_UTF8 ("Not a command"), + "databaseName", + BCON_UTF8 ("Some database"), + "commandName", + BCON_UTF8 ("Not a command"), + "operationId", + BCON_INT64 (0x12345678eeff0011), + "command", + BCON_UTF8 ("{ \"c\" : \"d\" }"), + "reply", // Un-redacted successful reply (not-a-command) + BCON_UTF8 ("{ \"r\" : \"s\", \"code\" : 1 }"), + "reply", // Un-redacted successful reply (ping) + BCON_UTF8 ("{ \"r\" : \"s\", \"code\" : 1 }"), + "reply", // Redacted successful reply (auth) + BCON_UTF8 ("{}"), + "failure", // Un-redacted server side error (not-a-command) + "{", + "r", + BCON_UTF8 ("s"), + "code", + BCON_INT32 (1), + "}", + "failure", // Un-redacted server side error (ping) + "{", + "r", + BCON_UTF8 ("s"), + "code", + BCON_INT32 (1), + "}", + "failure", // Redacted server side error (auth) + "{", + "code", + BCON_INT32 (1), + "}", + "failure", // Client side error + "{", + "code", + BCON_INT32 (123), + "domain", + BCON_INT32 (456), + "message", + BCON_UTF8 ("oh no"), + "}"), + .expected_calls = 1, + }; + + bson_t *cmd_doc = BCON_NEW ("c", BCON_UTF8 ("d")); + bson_t *reply_doc = BCON_NEW ("r", BCON_UTF8 ("s"), "code", BCON_INT32 (1)); + + const bson_error_t server_error = { + .domain = MONGOC_ERROR_SERVER, + .code = 99, + .message = "unused", + }; + const bson_error_t client_error = { + .domain = 456, + .code = 123, + .message = "oh no", + }; + + mongoc_cmd_t cmd = { + .db_name = "Some database", + .command_name = "Not a command", + .operation_id = 0x12345678eeff0011, + .command = cmd_doc, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with command and reply fields", + cmd (&cmd, COMMAND_NAME), + cmd (&cmd, DATABASE_NAME, COMMAND_NAME, OPERATION_ID, COMMAND), + cmd_reply (&cmd, reply_doc), + cmd_name_reply ("ping", reply_doc), + cmd_name_reply ("authenticate", reply_doc), + cmd_failure (&cmd, reply_doc, &server_error), + cmd_name_failure ("ping", reply_doc, &server_error), + cmd_name_failure ("authenticate", reply_doc, &server_error), + cmd_name_failure ("authenticate", reply_doc, &client_error)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); + bson_destroy (cmd_doc); + bson_destroy (reply_doc); +} + +void +test_structured_log_duration (void) +{ + struct log_assumption assumption = { + .expected_envelope.level = MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + .expected_envelope.component = MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + .expected_envelope.message = "Log entry with duration", + .expected_bson = BCON_NEW ("message", + BCON_UTF8 ("Log entry with duration"), + "durationMS", + BCON_INT32 (1), + "durationMicros", + BCON_INT64 (1999), + "durationMS", + BCON_INT32 (0), + "durationMicros", + BCON_INT64 (10), + "durationMS", + BCON_INT32 (10000000), + "durationMicros", + BCON_INT64 (10000000999)), + .expected_calls = 1, + }; + + mongoc_structured_log_opts_t *opts = mongoc_structured_log_opts_new (); + mongoc_structured_log_opts_set_handler (opts, structured_log_func, &assumption); + ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components (opts, MONGOC_STRUCTURED_LOG_LEVEL_DEBUG)); + + mongoc_structured_log_instance_t *instance = mongoc_structured_log_instance_new (opts); + mongoc_structured_log_opts_destroy (opts); + + mongoc_structured_log (instance, + MONGOC_STRUCTURED_LOG_LEVEL_WARNING, + MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, + "Log entry with duration", + monotonic_time_duration (1999), + monotonic_time_duration (10), + monotonic_time_duration (10000000999)); + + mongoc_structured_log_instance_destroy (instance); + ASSERT_CMPINT (assumption.calls, ==, 1); + bson_destroy (assumption.expected_bson); +} + +void +test_structured_log_level_names (void) +{ + mongoc_structured_log_level_t level = (mongoc_structured_log_level_t) -1; + + // Alias, off = 0 + ASSERT (mongoc_structured_log_get_named_level ("off", &level)); + ASSERT_CMPINT (0, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Emergency"); + + ASSERT (mongoc_structured_log_get_named_level ("emergency", &level)); + ASSERT_CMPINT (0, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Emergency"); + + ASSERT (mongoc_structured_log_get_named_level ("alert", &level)); + ASSERT_CMPINT (1, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_ALERT, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Alert"); + + ASSERT (mongoc_structured_log_get_named_level ("critical", &level)); + ASSERT_CMPINT (2, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_CRITICAL, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Critical"); + + ASSERT (mongoc_structured_log_get_named_level ("error", &level)); + ASSERT_CMPINT (3, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_ERROR, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Error"); + + // Alias, warn = Warning + ASSERT (mongoc_structured_log_get_named_level ("warn", &level)); + ASSERT_CMPINT (4, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_WARNING, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Warning"); + + ASSERT (mongoc_structured_log_get_named_level ("warning", &level)); + ASSERT_CMPINT (4, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_WARNING, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Warning"); + + ASSERT (mongoc_structured_log_get_named_level ("notice", &level)); + ASSERT_CMPINT (5, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_NOTICE, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Notice"); + + // Alias, info = Informational + ASSERT (mongoc_structured_log_get_named_level ("info", &level)); + ASSERT_CMPINT (6, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_INFO, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Informational"); + + ASSERT (mongoc_structured_log_get_named_level ("informational", &level)); + ASSERT_CMPINT (6, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_INFO, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Informational"); + + ASSERT (mongoc_structured_log_get_named_level ("debug", &level)); + ASSERT_CMPINT (7, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_DEBUG, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Debug"); + + ASSERT (mongoc_structured_log_get_named_level ("trace", &level)); + ASSERT_CMPINT (8, ==, level); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_LEVEL_TRACE, ==, level); + ASSERT_CMPSTR (mongoc_structured_log_get_level_name (level), "Trace"); +} + +void +test_structured_log_component_names (void) +{ + mongoc_structured_log_component_t component = (mongoc_structured_log_component_t) -1; + + ASSERT (mongoc_structured_log_get_named_component ("Command", &component)); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_COMPONENT_COMMAND, ==, component); + ASSERT_CMPSTR (mongoc_structured_log_get_component_name (component), "command"); + + ASSERT (mongoc_structured_log_get_named_component ("Topology", &component)); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_COMPONENT_TOPOLOGY, ==, component); + ASSERT_CMPSTR (mongoc_structured_log_get_component_name (component), "topology"); + + ASSERT (mongoc_structured_log_get_named_component ("ServerSelection", &component)); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_COMPONENT_SERVER_SELECTION, ==, component); + ASSERT_CMPSTR (mongoc_structured_log_get_component_name (component), "serverSelection"); + + ASSERT (mongoc_structured_log_get_named_component ("Connection", &component)); + ASSERT_CMPINT (MONGOC_STRUCTURED_LOG_COMPONENT_CONNECTION, ==, component); + ASSERT_CMPSTR (mongoc_structured_log_get_component_name (component), "connection"); +} + +void +test_structured_log_install (TestSuite *suite) +{ + TestSuite_Add (suite, "/structured_log/opts", test_structured_log_opts); + TestSuite_Add (suite, "/structured_log/plain", test_structured_log_plain); + TestSuite_Add (suite, "/structured_log/plain_with_extra_data", test_structured_log_plain_with_extra_data); + TestSuite_Add (suite, "/structured_log/basic_data_types", test_structured_log_basic_data_types); + TestSuite_Add (suite, "/structured_log/json", test_structured_log_json); + TestSuite_Add (suite, "/structured_log/oid", test_structured_log_oid); + TestSuite_Add (suite, "/structured_log/error", test_structured_log_error); + TestSuite_Add (suite, "/structured_log/server_description", test_structured_log_server_description); + TestSuite_Add (suite, "/structured_log/command", test_structured_log_command); + TestSuite_Add (suite, "/structured_log/duration", test_structured_log_duration); + TestSuite_Add (suite, "/structured_log/level_names", test_structured_log_level_names); + TestSuite_Add (suite, "/structured_log/component_names", test_structured_log_component_names); +} diff --git a/src/libmongoc/tests/test-mongoc-topology-description.c b/src/libmongoc/tests/test-mongoc-topology-description.c index 4351416a8f1..ca1e403e7c2 100644 --- a/src/libmongoc/tests/test-mongoc-topology-description.c +++ b/src/libmongoc/tests/test-mongoc-topology-description.c @@ -2,6 +2,7 @@ #include "mongoc/mongoc-set-private.h" #include "mongoc/mongoc-client-pool-private.h" #include "mongoc/mongoc-client-private.h" +#include #include "TestSuite.h" #include "test-libmongoc.h" @@ -258,11 +259,11 @@ test_topology_pool_clear (void) topology = mongoc_topology_new (uri, true); tdmod = mc_tpld_modify_begin (topology); - ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 1, &kZeroServiceId)); - ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 2, &kZeroServiceId)); - _mongoc_topology_description_clear_connection_pool (tdmod.new_td, 1, &kZeroServiceId); - ASSERT_CMPUINT32 (1, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 1, &kZeroServiceId)); - ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 2, &kZeroServiceId)); + ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 1, &kZeroObjectId)); + ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 2, &kZeroObjectId)); + _mongoc_topology_description_clear_connection_pool (tdmod.new_td, 1, &kZeroObjectId); + ASSERT_CMPUINT32 (1, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 1, &kZeroObjectId)); + ASSERT_CMPUINT32 (0, ==, _mongoc_topology_get_connection_pool_generation (tdmod.new_td, 2, &kZeroObjectId)); mongoc_uri_destroy (uri); mc_tpld_modify_drop (tdmod); diff --git a/src/libmongoc/tests/test-mongoc-topology.c b/src/libmongoc/tests/test-mongoc-topology.c index f60485306cd..0e38db11e1e 100644 --- a/src/libmongoc/tests/test-mongoc-topology.c +++ b/src/libmongoc/tests/test-mongoc-topology.c @@ -1,6 +1,7 @@ #include #include #include +#include #include "mongoc/mongoc-client-private.h" #include "mongoc/mongoc-server-api-private.h" @@ -595,12 +596,11 @@ test_invalid_cluster_node (void *ctx) ASSERT_OR_PRINT (sd, error); /* Both generations match, and are the first generation. */ ASSERT_CMPINT32 (cluster_node->handshake_sd->generation, ==, 0); - ASSERT_CMPINT32 (mc_tpl_sd_get_generation (sd, &kZeroServiceId), ==, 0); + ASSERT_CMPINT32 (mc_tpl_sd_get_generation (sd, &kZeroObjectId), ==, 0); /* update the server's generation, simulating a connection pool clearing */ tdmod = mc_tpld_modify_begin (client->topology); - mc_tpl_sd_increment_generation (mongoc_topology_description_server_by_id (tdmod.new_td, id, &error), - &kZeroServiceId); + mc_tpl_sd_increment_generation (mongoc_topology_description_server_by_id (tdmod.new_td, id, &error), &kZeroObjectId); mc_tpld_modify_commit (tdmod); /* cluster discards node and creates new one with the current generation */ @@ -656,7 +656,7 @@ test_max_wire_version_race_condition (void *ctx) tdmod = mc_tpld_modify_begin (client->topology); sd = mongoc_set_get (mc_tpld_servers (tdmod.new_td), id); BSON_ASSERT (sd); - mc_tpl_sd_increment_generation (sd, &kZeroServiceId); + mc_tpl_sd_increment_generation (sd, &kZeroObjectId); mongoc_server_description_reset (sd); mc_tpld_modify_commit (tdmod); diff --git a/src/libmongoc/tests/unified/entity-map.c b/src/libmongoc/tests/unified/entity-map.c index edf9fb1eb64..686f6757244 100644 --- a/src/libmongoc/tests/unified/entity-map.c +++ b/src/libmongoc/tests/unified/entity-map.c @@ -43,6 +43,8 @@ struct _entity_findcursor_t { mongoc_cursor_t *cursor; }; +typedef void (*event_serialize_func_t) (bson_t *bson, const void *event); + static void entity_destroy (entity_t *entity); @@ -131,31 +133,71 @@ uri_apply_options (mongoc_uri_t *uri, bson_t *opts, bson_error_t *error) return ret; } -event_t * -event_new (const char *type) +static event_t * +event_new (const char *type, bson_t *serialized, bool is_sensitive_command) { - event_t *event = NULL; + BSON_ASSERT_PARAM (type); + BSON_ASSERT_PARAM (serialized); + + const int64_t usecs = usecs_since_epoch (); + const double secs = (double) usecs / 1000000.0; - event = bson_malloc0 (sizeof (event_t)); - event->type = bson_strdup (type); + // Append required common fields + BSON_APPEND_UTF8 (serialized, "name", type); + BSON_APPEND_DOUBLE (serialized, "observedAt", secs); + + MONGOC_DEBUG ("new event: %s %s (%s)", + type, + tmp_json (serialized), + is_sensitive_command ? "marked SENSITIVE" : "not sensitive"); + + event_t *event = bson_malloc0 (sizeof *event); + event->type = type; // Borrowed + event->serialized = serialized; // Takes ownership + event->is_sensitive_command = is_sensitive_command; return event; } -void +static void event_destroy (event_t *event) { if (!event) { return; } - bson_free (event->command_name); - bson_free (event->database_name); - bson_destroy (event->command); - bson_destroy (event->reply); - bson_free (event->type); + bson_destroy (event->serialized); bson_free (event); } +static log_message_t * +log_message_new (const mongoc_structured_log_entry_t *entry) +{ + log_message_t *log_message = NULL; + + log_message = bson_malloc0 (sizeof (log_message_t)); + log_message->component = mongoc_structured_log_entry_get_component (entry); + log_message->level = mongoc_structured_log_entry_get_level (entry); + log_message->message = mongoc_structured_log_entry_message_as_bson (entry); + + MONGOC_DEBUG ("new structured log: %s %s %s", + mongoc_structured_log_get_level_name (log_message->level), + mongoc_structured_log_get_component_name (log_message->component), + tmp_json (log_message->message)); + + return log_message; +} + +static void +log_message_destroy (log_message_t *log_message) +{ + if (!log_message) { + return; + } + + bson_destroy (log_message->message); + bson_free (log_message); +} + static entity_t * entity_new (entity_map_t *em, const char *type) { @@ -165,31 +207,45 @@ entity_new (entity_map_t *em, const char *type) entity->entity_map = em; _mongoc_array_init (&entity->observe_events, sizeof (observe_event_t)); _mongoc_array_init (&entity->store_events, sizeof (store_event_t)); + bson_mutex_init (&entity->log_messages_mutex); return entity; } -static bool -is_sensitive_command (event_t *event) +static void +structured_log_cb (const mongoc_structured_log_entry_t *entry, void *user_data) { - const bson_t *body = event->reply ? event->reply : event->command; - BSON_ASSERT (body); - return mongoc_apm_is_sensitive_command_message (event->command_name, body); + BSON_ASSERT_PARAM (entry); + BSON_ASSERT_PARAM (user_data); + if (!test_is_suppressing_structured_logs ()) { + entity_t *entity = (entity_t *) user_data; + log_message_t *log_message = log_message_new (entry); + bson_mutex_lock (&entity->log_messages_mutex); + LL_APPEND (entity->log_messages, log_message); + bson_mutex_unlock (&entity->log_messages_mutex); + } } bool -should_ignore_event (entity_t *client_entity, event_t *event) +should_observe_event (entity_t *client_entity, event_t *event) { - bson_iter_t iter; - - if (0 == strcmp (event->command_name, "configureFailPoint")) { - return true; - } + { + bson_iter_t event_iter; + const char *event_command_name = bson_iter_init_find (&event_iter, event->serialized, "commandName") + ? bson_iter_utf8 (&event_iter, NULL) + : NULL; + if (event_command_name) { + if (0 == strcmp (event_command_name, "configureFailPoint")) { + return false; + } - if (client_entity->ignore_command_monitoring_events) { - BSON_FOREACH (client_entity->ignore_command_monitoring_events, iter) - { - if (0 == strcmp (event->command_name, bson_iter_utf8 (&iter, NULL))) { - return true; + if (client_entity->ignore_command_monitoring_events) { + bson_iter_t ignore_iter; + BSON_FOREACH (client_entity->ignore_command_monitoring_events, ignore_iter) + { + if (0 == strcmp (event_command_name, bson_iter_utf8 (&ignore_iter, NULL))) { + return false; + } + } } } } @@ -208,342 +264,140 @@ should_ignore_event (entity_t *client_entity, event_t *event) } if (!is_observed) { - return true; + return false; } } - if (client_entity->observe_sensitive_commands && *client_entity->observe_sensitive_commands) { - return false; - } - - /* Sensitive commands need to be ignored */ - return is_sensitive_command (event); -} - - -typedef void *(*apm_func_void_t) (const void *); -typedef const bson_t *(*apm_func_bson_t) (const void *); -typedef const char *(*apm_func_utf8_t) (const void *); -typedef int64_t (*apm_func_int64_t) (const void *); -typedef const bson_oid_t *(*apm_func_bson_oid_t) (const void *); -typedef int32_t (*apm_func_int32_t) (const void *); -typedef const mongoc_host_list_t *(*apm_func_host_list_t) (const void *); -typedef void (*apm_func_serialize_t) (bson_t *, const void *); - -typedef struct command_callback_funcs_t { - apm_func_void_t get_context; - apm_func_bson_t get_command; - apm_func_bson_t get_reply; - apm_func_utf8_t get_command_name; - apm_func_utf8_t get_database_name; - apm_func_int64_t get_request_id; - apm_func_int64_t get_operation_id; - apm_func_bson_oid_t get_service_id; - apm_func_host_list_t get_host; - apm_func_int64_t get_server_connection_id; - apm_func_serialize_t serialize; -} command_callback_funcs_t; - -#define _apm_func(kind, fn) (mongoc_apm_command_##kind##_##fn##_cb) -#define _apm_func_defn(kind, fn, type) \ - static type mongoc_apm_command_##kind##_##fn##_cb (const void *command) \ - { \ - return (mongoc_apm_command_##kind##_##fn) (command); \ - } \ - struct _force_semicolon - -_apm_func_defn (started, get_context, void *); -_apm_func_defn (started, get_command, const bson_t *); -_apm_func_defn (started, get_command_name, const char *); -_apm_func_defn (started, get_database_name, const char *); -_apm_func_defn (started, get_request_id, int64_t); -_apm_func_defn (started, get_operation_id, int64_t); -_apm_func_defn (started, get_service_id, const bson_oid_t *); -_apm_func_defn (started, get_host, const mongoc_host_list_t *); -_apm_func_defn (started, get_server_connection_id_int64, int64_t); - -_apm_func_defn (failed, get_context, void *); -_apm_func_defn (failed, get_reply, const bson_t *); -_apm_func_defn (failed, get_command_name, const char *); -_apm_func_defn (failed, get_database_name, const char *); -_apm_func_defn (failed, get_request_id, int64_t); -_apm_func_defn (failed, get_operation_id, int64_t); -_apm_func_defn (failed, get_service_id, const bson_oid_t *); -_apm_func_defn (failed, get_host, const mongoc_host_list_t *); -_apm_func_defn (failed, get_server_connection_id_int64, int64_t); - -_apm_func_defn (succeeded, get_context, void *); -_apm_func_defn (succeeded, get_reply, const bson_t *); -_apm_func_defn (succeeded, get_command_name, const char *); -_apm_func_defn (succeeded, get_database_name, const char *); -_apm_func_defn (succeeded, get_request_id, int64_t); -_apm_func_defn (succeeded, get_operation_id, int64_t); -_apm_func_defn (succeeded, get_service_id, const bson_oid_t *); -_apm_func_defn (succeeded, get_host, const mongoc_host_list_t *); -_apm_func_defn (succeeded, get_server_connection_id_int64, int64_t); - -#undef _apm_func_defn - - -static void -observe_event (entity_t *entity, command_callback_funcs_t funcs, const char *type, const void *apm_command) -{ - BSON_ASSERT_PARAM (type); - BSON_ASSERT_PARAM (apm_command); - - BSON_ASSERT (funcs.get_context); - event_t *const event = event_new (type); - - if (funcs.get_command) { - event->command = bson_copy (funcs.get_command (apm_command)); - } - - if (funcs.get_reply) { - event->reply = bson_copy (funcs.get_reply (apm_command)); - } - - BSON_ASSERT (funcs.get_command_name); - event->command_name = bson_strdup (funcs.get_command_name (apm_command)); - - if (funcs.get_database_name) { - event->database_name = bson_strdup (funcs.get_database_name (apm_command)); - } - - BSON_ASSERT (funcs.get_service_id); - const bson_oid_t *const service_id = funcs.get_service_id (apm_command); - if (service_id) { - bson_oid_copy (service_id, &event->service_id); - } - - BSON_ASSERT (funcs.get_server_connection_id); - event->server_connection_id = funcs.get_server_connection_id (apm_command); - - if (should_ignore_event (entity, event)) { - event_destroy (event); - return; - } - - LL_APPEND (entity->events, event); + // Sensitive command events are only observed if explicitly requested + return !event->is_sensitive_command || + (client_entity->observe_sensitive_commands && *client_entity->observe_sensitive_commands); } - static void -store_event_serialize_started (bson_t *doc, const void *apm_command_vp) +event_store_or_destroy (entity_t *entity, event_t *event) { - // Spec: The test runner MAY omit the command field for CommandStartedEvent - // and reply field for CommandSucceededEvent. - // BSON_APPEND_DOCUMENT ( - // doc, "command", mongoc_apm_command_started_get_command (apm_command)); - - const mongoc_apm_command_started_t *const apm_command = apm_command_vp; - - BSON_APPEND_UTF8 (doc, "databaseName", mongoc_apm_command_started_get_database_name (apm_command)); - - BSON_APPEND_UTF8 (doc, "commandName", mongoc_apm_command_started_get_command_name (apm_command)); - - BSON_APPEND_INT64 (doc, "requestId", mongoc_apm_command_started_get_request_id (apm_command)); - - BSON_APPEND_INT64 (doc, "operationId", mongoc_apm_command_started_get_operation_id (apm_command)); - - BSON_APPEND_UTF8 (doc, "connectionId", mongoc_apm_command_started_get_host (apm_command)->host_and_port); + BSON_ASSERT_PARAM (entity); + BSON_ASSERT_PARAM (event); // Takes ownership - BSON_APPEND_INT64 ( - doc, "serverConnectionId", mongoc_apm_command_started_get_server_connection_id_int64 (apm_command)); + BSON_ASSERT (entity->entity_map); + entity_map_t *const em = entity->entity_map; + // Make additional copies as requested by storeEventsAsEntities { - const bson_oid_t *const service_id = mongoc_apm_command_started_get_service_id (apm_command); - - if (service_id) { - BSON_APPEND_OID (doc, "serviceId", service_id); + store_event_t *const begin = (store_event_t *) entity->store_events.data; + store_event_t *const end = begin + entity->store_events.len; + bson_error_t error = {0}; + for (store_event_t *iter = begin; iter != end; ++iter) { + if (bson_strcasecmp (iter->type, event->type) == 0) { + mongoc_array_t *arr = entity_map_get_bson_array (em, iter->entity_id, &error); + ASSERT_OR_PRINT (arr, error); + bson_t *serialized_copy = bson_copy (event->serialized); + _mongoc_array_append_val (arr, serialized_copy); // Transfer ownership. + } } } -} - - -static void -store_event_serialize_failed (bson_t *doc, const void *apm_command_vp) -{ - const mongoc_apm_command_failed_t *const apm_command = apm_command_vp; - - BSON_APPEND_INT64 (doc, "duration", mongoc_apm_command_failed_get_duration (apm_command)); - - BSON_APPEND_UTF8 (doc, "commandName", mongoc_apm_command_failed_get_command_name (apm_command)); - - BSON_APPEND_UTF8 (doc, "databaseName", mongoc_apm_command_failed_get_database_name (apm_command)); - { - bson_error_t error; - mongoc_apm_command_failed_get_error (apm_command, &error); - BSON_APPEND_UTF8 (doc, "failure", error.message); - } - - BSON_APPEND_INT64 (doc, "requestId", mongoc_apm_command_failed_get_request_id (apm_command)); - - BSON_APPEND_INT64 (doc, "operationId", mongoc_apm_command_failed_get_operation_id (apm_command)); - - BSON_APPEND_UTF8 (doc, "connectionId", mongoc_apm_command_failed_get_host (apm_command)->host_and_port); - - BSON_APPEND_INT64 ( - doc, "serverConnectionId", mongoc_apm_command_failed_get_server_connection_id_int64 (apm_command)); - - { - const bson_oid_t *const service_id = mongoc_apm_command_failed_get_service_id (apm_command); - - if (service_id) { - BSON_APPEND_OID (doc, "serviceId", service_id); - } + if (should_observe_event (entity, event)) { + // Transfer ownership of observed serialized events to the event list + LL_APPEND (entity->events, event); + } else { + // Discard serialized events we are not observing + event_destroy (event); } } - static void -store_event_serialize_succeeded (bson_t *doc, const void *apm_command_vp) +command_started (const mongoc_apm_command_started_t *started) { - const mongoc_apm_command_succeeded_t *const apm_command = apm_command_vp; - - BSON_APPEND_INT64 (doc, "duration", mongoc_apm_command_succeeded_get_duration (apm_command)); - - // Spec: The test runner MAY omit the command field for CommandStartedEvent - // and reply field for CommandSucceededEvent. - // BSON_APPEND_DOCUMENT ( - // doc, "reply", mongoc_apm_command_succeeded_get_reply (apm_command)); - - BSON_APPEND_UTF8 (doc, "commandName", mongoc_apm_command_succeeded_get_command_name (apm_command)); + entity_t *entity = (entity_t *) mongoc_apm_command_started_get_context (started); + const bson_oid_t *const service_id = mongoc_apm_command_started_get_service_id (started); + const bool is_sensitive = mongoc_apm_is_sensitive_command_message ( + mongoc_apm_command_started_get_command_name (started), mongoc_apm_command_started_get_command (started)); - BSON_APPEND_UTF8 (doc, "databaseName", mongoc_apm_command_succeeded_get_database_name (apm_command)); - - BSON_APPEND_INT64 (doc, "requestId", mongoc_apm_command_succeeded_get_request_id (apm_command)); - - BSON_APPEND_INT64 (doc, "operationId", mongoc_apm_command_succeeded_get_operation_id (apm_command)); - - BSON_APPEND_UTF8 (doc, "connectionId", mongoc_apm_command_succeeded_get_host (apm_command)->host_and_port); - - BSON_APPEND_INT64 ( - doc, "serverConnectionId", mongoc_apm_command_succeeded_get_server_connection_id_int64 (apm_command)); - - { - const bson_oid_t *const service_id = mongoc_apm_command_succeeded_get_service_id (apm_command); + bson_t *serialized = bson_new (); + bsonBuildAppend ( + *serialized, + kv ("databaseName", cstr (mongoc_apm_command_started_get_database_name (started))), + kv ("commandName", cstr (mongoc_apm_command_started_get_command_name (started))), + kv ("requestId", int64 (mongoc_apm_command_started_get_request_id (started))), + kv ("operationId", int64 (mongoc_apm_command_started_get_operation_id (started))), + kv ("connectionId", cstr (mongoc_apm_command_started_get_host (started)->host_and_port)), + kv ("serverConnectionId", int64 (mongoc_apm_command_started_get_server_connection_id_int64 (started))), + if (service_id, then (kv ("serviceId", oid (service_id)))), + kv ("command", bson (*mongoc_apm_command_started_get_command (started)))); - if (service_id) { - BSON_APPEND_OID (doc, "serviceId", service_id); - } - } + event_store_or_destroy (entity, event_new ("commandStartedEvent", serialized, is_sensitive)); } - static void -store_event_to_entities (entity_t *entity, command_callback_funcs_t funcs, const char *type, const void *apm_command) +command_failed (const mongoc_apm_command_failed_t *failed) { - BSON_ASSERT_PARAM (entity); - BSON_ASSERT_PARAM (type); - - BSON_ASSERT (entity->entity_map); - - entity_map_t *const em = entity->entity_map; - - store_event_t *const begin = (store_event_t *) entity->store_events.data; - store_event_t *const end = begin + entity->store_events.len; - - const int64_t usecs = usecs_since_epoch (); - const double secs = (double) usecs / 1000000.0; - - bson_error_t error = {0}; - - for (store_event_t *iter = begin; iter != end; ++iter) { - if (bson_strcasecmp (iter->type, type) == 0) { - mongoc_array_t *arr = entity_map_get_bson_array (em, iter->entity_id, &error); - ASSERT_OR_PRINT (arr, error); - - bson_t *doc = bson_new (); - - // Spec: the following fields MUST be stored with each event document: - BSON_APPEND_UTF8 (doc, "name", type); - BSON_APPEND_DOUBLE (doc, "observedAt", secs); - - // The event subscriber MUST serialize the events it receives into a - // document, using the documented properties of the event as field - // names, and append the document to the list stored in the specified - // entity. - funcs.serialize (doc, apm_command); - - _mongoc_array_append_val (arr, doc); // Transfer ownership. - } - } + entity_t *entity = (entity_t *) mongoc_apm_command_failed_get_context (failed); + const bson_oid_t *const service_id = mongoc_apm_command_failed_get_service_id (failed); + const bool is_sensitive = mongoc_apm_is_sensitive_command_message ( + mongoc_apm_command_failed_get_command_name (failed), mongoc_apm_command_failed_get_reply (failed)); + bson_error_t error; + mongoc_apm_command_failed_get_error (failed, &error); + + bson_t *serialized = bson_new (); + bsonBuildAppend ( + *serialized, + kv ("duration", int64 (mongoc_apm_command_failed_get_duration (failed))), + kv ("commandName", cstr (mongoc_apm_command_failed_get_command_name (failed))), + kv ("databaseName", cstr (mongoc_apm_command_failed_get_database_name (failed))), + kv ("requestId", int64 (mongoc_apm_command_failed_get_request_id (failed))), + kv ("operationId", int64 (mongoc_apm_command_failed_get_operation_id (failed))), + kv ("connectionId", cstr (mongoc_apm_command_failed_get_host (failed)->host_and_port)), + kv ("serverConnectionId", int64 (mongoc_apm_command_failed_get_server_connection_id_int64 (failed))), + if (service_id, then (kv ("serviceId", oid (service_id)))), + kv ("failure", cstr (error.message))); + + event_store_or_destroy (entity, event_new ("commandFailedEvent", serialized, is_sensitive)); } - static void -apm_command_callback (command_callback_funcs_t funcs, const char *type, const void *apm_command) +command_succeeded (const mongoc_apm_command_succeeded_t *succeeded) { - BSON_ASSERT_PARAM (type); - BSON_ASSERT_PARAM (apm_command); + entity_t *entity = (entity_t *) mongoc_apm_command_succeeded_get_context (succeeded); + const bson_oid_t *const service_id = mongoc_apm_command_succeeded_get_service_id (succeeded); + const bool is_sensitive = mongoc_apm_is_sensitive_command_message ( + mongoc_apm_command_succeeded_get_command_name (succeeded), mongoc_apm_command_succeeded_get_reply (succeeded)); - BSON_ASSERT (funcs.get_context); - entity_t *const entity = (entity_t *) funcs.get_context (apm_command); + bson_t *serialized = bson_new (); + bsonBuildAppend ( + *serialized, + kv ("duration", int64 (mongoc_apm_command_succeeded_get_duration (succeeded))), + kv ("commandName", cstr (mongoc_apm_command_succeeded_get_command_name (succeeded))), + kv ("databaseName", cstr (mongoc_apm_command_succeeded_get_database_name (succeeded))), + kv ("requestId", int64 (mongoc_apm_command_succeeded_get_request_id (succeeded))), + kv ("operationId", int64 (mongoc_apm_command_succeeded_get_operation_id (succeeded))), + kv ("connectionId", cstr (mongoc_apm_command_succeeded_get_host (succeeded)->host_and_port)), + kv ("serverConnectionId", int64 (mongoc_apm_command_succeeded_get_server_connection_id_int64 (succeeded))), + if (service_id, then (kv ("serviceId", oid (service_id)))), + kv ("reply", bson (*mongoc_apm_command_succeeded_get_reply (succeeded)))); - observe_event (entity, funcs, type, apm_command); - store_event_to_entities (entity, funcs, type, apm_command); + event_store_or_destroy (entity, event_new ("commandSucceededEvent", serialized, is_sensitive)); } - static void -command_started (const mongoc_apm_command_started_t *started) +server_changed (const mongoc_apm_server_changed_t *changed) { - command_callback_funcs_t funcs = { - .get_context = _apm_func (started, get_context), - .get_command = _apm_func (started, get_command), - .get_reply = NULL, - .get_command_name = _apm_func (started, get_command_name), - .get_database_name = _apm_func (started, get_database_name), - .get_request_id = _apm_func (started, get_request_id), - .get_operation_id = _apm_func (started, get_operation_id), - .get_service_id = _apm_func (started, get_service_id), - .get_host = _apm_func (started, get_host), - .get_server_connection_id = _apm_func (started, get_server_connection_id_int64), - .serialize = store_event_serialize_started, - }; + entity_t *entity = (entity_t *) mongoc_apm_server_changed_get_context (changed); + bson_oid_t topology_id; + mongoc_apm_server_changed_get_topology_id (changed, &topology_id); - apm_command_callback (funcs, "commandStartedEvent", started); -} + bson_t *serialized = bson_new (); + bsonBuildAppend ( + *serialized, + kv ("address", cstr (mongoc_apm_server_changed_get_host (changed)->host_and_port)), + kv ("topologyId", oid (&topology_id)), + kv ("previousDescription", + bson ( + *mongoc_server_description_hello_response (mongoc_apm_server_changed_get_previous_description (changed)))), + kv ("newDescription", + bson (*mongoc_server_description_hello_response (mongoc_apm_server_changed_get_new_description (changed))))); -static void -command_failed (const mongoc_apm_command_failed_t *failed) -{ - command_callback_funcs_t funcs = { - .get_context = _apm_func (failed, get_context), - .get_command = NULL, - .get_reply = _apm_func (failed, get_reply), - .get_command_name = _apm_func (failed, get_command_name), - .get_database_name = _apm_func (failed, get_database_name), - .get_request_id = _apm_func (failed, get_request_id), - .get_operation_id = _apm_func (failed, get_operation_id), - .get_service_id = _apm_func (failed, get_service_id), - .get_host = _apm_func (failed, get_host), - .get_server_connection_id = _apm_func (failed, get_server_connection_id_int64), - .serialize = store_event_serialize_failed, - }; - - apm_command_callback (funcs, "commandFailedEvent", failed); -} - -static void -command_succeeded (const mongoc_apm_command_succeeded_t *succeeded) -{ - command_callback_funcs_t funcs = { - .get_context = _apm_func (succeeded, get_context), - .get_command = NULL, - .get_reply = _apm_func (succeeded, get_reply), - .get_command_name = _apm_func (succeeded, get_command_name), - .get_database_name = _apm_func (succeeded, get_database_name), - .get_request_id = _apm_func (succeeded, get_request_id), - .get_operation_id = _apm_func (succeeded, get_operation_id), - .get_service_id = _apm_func (succeeded, get_service_id), - .get_host = _apm_func (succeeded, get_host), - .get_server_connection_id = _apm_func (succeeded, get_server_connection_id_int64), - .serialize = store_event_serialize_succeeded, - }; - - apm_command_callback (funcs, "commandSucceededEvent", succeeded); + event_store_or_destroy (entity, event_new ("serverDescriptionChangedEvent", serialized, false)); } static void @@ -564,11 +418,19 @@ set_command_succeeded_cb (mongoc_apm_callbacks_t *callbacks) mongoc_apm_set_command_succeeded_cb (callbacks, command_succeeded); } -// Note: multiple invocations of this function is okay, since all it does -// is set the appropriate pointer in `callbacks`, and the callback function(s) -// being used is always the same for a given type. static void -set_command_callback (mongoc_apm_callbacks_t *callbacks, const char *type) +set_server_changed_cb (mongoc_apm_callbacks_t *callbacks) +{ + mongoc_apm_set_server_changed_cb (callbacks, server_changed); +} + +/* Set a callback for the indicated event type in a mongoc_apm_callbacks_t. + * Safe to call multiple times for the same event: callbacks for a specific + * event type are always the same. Returns 'true' if the event is known and + * 'false' if unknown. If 'callbacks' is NULL, validates the 'type' without + * taking any other action. */ +static bool +set_event_callback (mongoc_apm_callbacks_t *callbacks, const char *type) { typedef void (*set_func_t) (mongoc_apm_callbacks_t *); @@ -577,38 +439,45 @@ set_command_callback (mongoc_apm_callbacks_t *callbacks, const char *type) set_func_t set; } command_to_cb_t; - const command_to_cb_t commands[] = { + static const command_to_cb_t commands[] = { {.type = "commandStartedEvent", .set = set_command_started_cb}, {.type = "commandFailedEvent", .set = set_command_failed_cb}, {.type = "commandSucceededEvent", .set = set_command_succeeded_cb}, + {.type = "serverDescriptionChangedEvent", .set = set_server_changed_cb}, {.type = NULL, .set = NULL}, }; for (const command_to_cb_t *iter = commands; iter->type; ++iter) { if (bson_strcasecmp (type, iter->type) == 0) { - iter->set (callbacks); - return; + if (callbacks) { + iter->set (callbacks); + } + return true; } } + return false; +} + +static bool +is_supported_event_type (const char *type) +{ + return set_event_callback (NULL, type); } static void add_observe_event (entity_t *entity, const char *type) { - observe_event_t event = {.type = type}; - + observe_event_t event = {.type = bson_strdup (type)}; _mongoc_array_append_val (&entity->observe_events, event); } static void add_store_event (entity_t *entity, const char *type, const char *entity_id) { - store_event_t event = {.type = type, .entity_id = entity_id}; - + store_event_t event = {.type = bson_strdup (type), .entity_id = bson_strdup (entity_id)}; _mongoc_array_append_val (&entity->store_events, event); } - entity_t * entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) { @@ -618,6 +487,7 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) bool ret = false; mongoc_apm_callbacks_t *callbacks = NULL; bson_t *uri_options = NULL; + mongoc_structured_log_opts_t *log_opts = mongoc_structured_log_opts_new (); bool use_multiple_mongoses = false; bool use_multiple_mongoses_set = false; bool can_reduce_heartbeat = false; @@ -650,12 +520,9 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) // Ensure all elements are strings: when (not(type (utf8)), error ("Every 'observeEvents' element must be a string")), // Dispatch based on the event name: - when (anyOf (iStrEqual ("commandStartedEvent"), - iStrEqual ("commandFailedEvent"), - iStrEqual ("commandSucceededEvent")), - do ({ + when (eval (is_supported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), do ({ const char *const type = bson_iter_utf8 (&bsonVisitIter, NULL); - set_command_callback (callbacks, type); + set_event_callback (callbacks, type); add_observe_event (entity, type); })), // Unsupported (but known) event names: @@ -699,34 +566,56 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) *p = bsonAs (boolean); })), // Which events should be available as entities: - find (key ("storeEventsAsEntities"), - if (not(type (array)), then (error ("'storeEventsAsEntities' must be an array"))), - visitEach (parse ( - find (keyWithType ("id", utf8), storeStrRef (store_entity_id), do ({ - if (!entity_map_add_bson_array (em, store_entity_id, error)) { - test_error ("failed to create storeEventsAsEntities " - "entity '%s': %s", - store_entity_id, - error->message); - } - })), - find (keyWithType ("events", array), - visitEach (case (when (not(type (utf8)), - error ("Every 'storeEventsAsEntities.events' " - "element must be a string")), - when (anyOf (iStrEqual ("commandStartedEvent"), - iStrEqual ("commandFailedEvent"), - iStrEqual ("commandSucceededEvent")), - do ({ - const char *const type = bson_iter_utf8 (&bsonVisitIter, NULL); - set_command_callback (callbacks, type); - add_store_event (entity, type, store_entity_id); - })), - when (eval (is_unsupported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), - do (MONGOC_DEBUG ("Skipping unsupported event type '%s'", bsonAs (cstr)))), - else (do (test_error ("Unknown event type '%s'", bsonAs (cstr))))))), - visitOthers ( - errorf (err, "Unexpected field '%s' in storeEventsAsEntities", bson_iter_key (&bsonVisitIter)))))), + find ( + key ("storeEventsAsEntities"), + if (not(type (array)), then (error ("'storeEventsAsEntities' must be an array"))), + visitEach (parse ( + find (keyWithType ("id", utf8), storeStrRef (store_entity_id), do ({ + if (!entity_map_add_bson_array (em, store_entity_id, error)) { + test_error ("failed to create storeEventsAsEntities " + "entity '%s': %s", + store_entity_id, + error->message); + } + })), + find (keyWithType ("events", array), + visitEach (case (when (not(type (utf8)), + error ("Every 'storeEventsAsEntities.events' " + "element must be a string")), + when (eval (is_supported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), do ({ + const char *const type = bson_iter_utf8 (&bsonVisitIter, NULL); + set_event_callback (callbacks, type); + add_store_event (entity, type, store_entity_id); + })), + when (eval (is_unsupported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), + do (MONGOC_DEBUG ("Skipping unsupported event type '%s'", bsonAs (cstr)))), + else (do (test_error ("Unknown event type '%s'", bsonAs (cstr))))))), + visitOthers ( + errorf (err, "Unexpected field '%s' in storeEventsAsEntities", bson_iter_key (&bsonVisitIter)))))), + // Log messages to observe: + find (key ("observeLogMessages"), + if (not(type (doc)), then (error ("'observeLogMessages' must be a document"))), + do ({ + // Initialize all components to the lowest available level, and install a handler. + BSON_ASSERT (mongoc_structured_log_opts_set_max_level_for_all_components ( + log_opts, MONGOC_STRUCTURED_LOG_LEVEL_EMERGENCY)); + mongoc_structured_log_opts_set_handler (log_opts, structured_log_cb, entity); + }), + visitEach ( + if (not(type (utf8)), then (error ("Every value in 'observeLogMessages' must be a log level string"))), + do ({ + const char *const component_name = bson_iter_key (&bsonVisitIter); + mongoc_structured_log_component_t component; + if (!mongoc_structured_log_get_named_component (component_name, &component)) { + test_error ("Unknown log component '%s' given in 'observeLogMessages'", component_name); + } + const char *const level_name = bson_iter_utf8 (&bsonVisitIter, NULL); + mongoc_structured_log_level_t level; + if (!mongoc_structured_log_get_named_level (level_name, &level)) { + test_error ("Unknown log level '%s' given in 'observeLogMessages'", component_name); + } + BSON_ASSERT (mongoc_structured_log_opts_set_max_level_for_component (log_opts, component, level)); + }))), visitOthers ( dupPath (errpath), errorf (err, "At [%s]: Unknown key '%s' given in entity options", errpath, bson_iter_key (&bsonVisitIter)))); @@ -779,6 +668,7 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) mongoc_client_set_error_api (client, MONGOC_ERROR_API_VERSION_2); entity->value = client; mongoc_client_set_apm_callbacks (client, callbacks, entity); + BSON_ASSERT (mongoc_client_set_structured_log_opts (client, log_opts)); if (can_reduce_heartbeat && em->reduced_heartbeat) { client->topology->min_heartbeat_frequency_msec = REDUCED_MIN_HEARTBEAT_FREQUENCY_MS; @@ -789,6 +679,7 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) mongoc_uri_destroy (uri); mongoc_apm_callbacks_destroy (callbacks); mongoc_server_api_destroy (api); + mongoc_structured_log_opts_destroy (log_opts); bson_destroy (uri_options); if (!ret) { entity_destroy (entity); @@ -1711,7 +1602,6 @@ static void entity_destroy (entity_t *entity) { event_t *event = NULL; - event_t *tmp = NULL; if (!entity) { return; @@ -1781,13 +1671,41 @@ entity_destroy (entity_t *entity) test_error ("Attempting to destroy unrecognized entity type: %s, id: %s", entity->type, entity->id); } - LL_FOREACH_SAFE (entity->events, event, tmp) { - event_destroy (event); + event_t *tmp; + LL_FOREACH_SAFE (entity->events, event, tmp) + { + event_destroy (event); + } + } + { + // No reason to take the log_messages_mutex here; log handlers are stopped above when we delete clients. + log_message_t *log_message, *tmp; + LL_FOREACH_SAFE (entity->log_messages, log_message, tmp) + { + log_message_destroy (log_message); + } } - _mongoc_array_destroy (&entity->observe_events); - _mongoc_array_destroy (&entity->store_events); + { + observe_event_t *const begin = (observe_event_t *) entity->observe_events.data; + observe_event_t *const end = begin + entity->observe_events.len; + for (observe_event_t *iter = begin; iter != end; ++iter) { + bson_free (iter->type); + } + _mongoc_array_destroy (&entity->observe_events); + } + { + store_event_t *const begin = (store_event_t *) entity->store_events.data; + store_event_t *const end = begin + entity->store_events.len; + for (store_event_t *iter = begin; iter != end; ++iter) { + bson_free (iter->type); + bson_free (iter->entity_id); + } + _mongoc_array_destroy (&entity->store_events); + } + + bson_mutex_destroy (&entity->log_messages_mutex); bson_destroy (entity->ignore_command_monitoring_events); bson_free (entity->type); bson_free (entity->id); @@ -2058,18 +1976,17 @@ entity_map_add_size_t (entity_map_t *em, const char *id, size_t *value, bson_err /* implement $$sessionLsid */ static bool -special_session_lsid (bson_matcher_t *matcher, +special_session_lsid (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; const char *id; bson_val_t *session_val = NULL; bson_t *lsid = NULL; - entity_map_t *em = (entity_map_t *) ctx; + entity_map_t *em = (entity_map_t *) user_data; bson_iter_t iter; bson_iter_init (&iter, assertion); @@ -2087,11 +2004,10 @@ special_session_lsid (bson_matcher_t *matcher, } session_val = bson_val_from_bson (lsid); - if (!bson_matcher_match (matcher, session_val, actual, path, false, error)) { + if (!bson_matcher_match (context, session_val, actual, error)) { goto done; } - ret = true; done: bson_val_destroy (session_val); @@ -2100,16 +2016,15 @@ special_session_lsid (bson_matcher_t *matcher, /* implement $$matchesEntity */ bool -special_matches_entity (bson_matcher_t *matcher, +special_matches_entity (const bson_matcher_context_t *context, const bson_t *assertion, const bson_val_t *actual, - void *ctx, - const char *path, + void *user_data, bson_error_t *error) { bool ret = false; bson_iter_t iter; - entity_map_t *em = (entity_map_t *) ctx; + entity_map_t *em = (entity_map_t *) user_data; bson_val_t *entity_val = NULL; const char *id; @@ -2127,7 +2042,7 @@ special_matches_entity (bson_matcher_t *matcher, goto done; } - if (!bson_matcher_match (matcher, entity_val, actual, path, false, error)) { + if (!bson_matcher_match (context, entity_val, actual, error)) { goto done; } @@ -2140,14 +2055,16 @@ bool entity_map_match ( entity_map_t *em, const bson_val_t *expected, const bson_val_t *actual, bool array_of_root_docs, bson_error_t *error) { - bson_matcher_t *matcher; - bool ret; - - matcher = bson_matcher_new (); - bson_matcher_add_special (matcher, "$$sessionLsid", special_session_lsid, em); - bson_matcher_add_special (matcher, "$$matchesEntity", special_matches_entity, em); - ret = bson_matcher_match (matcher, expected, actual, "", array_of_root_docs, error); - bson_matcher_destroy (matcher); + bson_matcher_context_t root_context = { + .matcher = bson_matcher_new (), + .path = "", + .is_root = true, + .array_of_root_docs = array_of_root_docs, + }; + bson_matcher_add_special (root_context.matcher, "$$sessionLsid", special_session_lsid, em); + bson_matcher_add_special (root_context.matcher, "$$matchesEntity", special_matches_entity, em); + bool ret = bson_matcher_match (&root_context, expected, actual, error); + bson_matcher_destroy (root_context.matcher); return ret; } @@ -2160,20 +2077,11 @@ event_list_to_string (event_t *events) str = mcommon_string_new (""); LL_FOREACH (events, eiter) { - mcommon_string_append_printf (str, "- %s:", eiter->type); - if (eiter->command_name) { - mcommon_string_append_printf (str, " cmd=%s", eiter->command_name); - } - if (eiter->database_name) { - mcommon_string_append_printf (str, " db=%s", eiter->database_name); - } - if (eiter->command) { - mcommon_string_append_printf (str, " sent %s", tmp_json (eiter->command)); - } - if (eiter->reply) { - mcommon_string_append_printf (str, " received %s", tmp_json (eiter->reply)); - } - mcommon_string_append (str, "\n"); + mcommon_string_append_printf (str, + "- %s: %s (%s)\n", + eiter->type, + tmp_json (eiter->serialized), + eiter->is_sensitive_command ? "marked SENSITIVE" : "not sensitive"); } return mcommon_string_free (str, false); } diff --git a/src/libmongoc/tests/unified/entity-map.h b/src/libmongoc/tests/unified/entity-map.h index 56035640774..ac4ffb99e5a 100644 --- a/src/libmongoc/tests/unified/entity-map.h +++ b/src/libmongoc/tests/unified/entity-map.h @@ -20,27 +20,31 @@ #include "bson/bson.h" #include "mongoc/mongoc.h" #include "mongoc-array-private.h" +#include "common-thread-private.h" #include "bsonutil/bson-match.h" #include "test-diagnostics.h" typedef struct _event_t { - char *type; - char *command_name; - char *database_name; - bson_t *command; - bson_t *reply; - bson_oid_t service_id; - int64_t server_connection_id; struct _event_t *next; + const char *type; // Non-owning + bson_t *serialized; + bool is_sensitive_command; } event_t; +typedef struct _log_message_t { + struct _log_message_t *next; + mongoc_structured_log_component_t component; + mongoc_structured_log_level_t level; + bson_t *message; +} log_message_t; + typedef struct _observe_event_t { - const char *type; // Non-owning. Type of event to observe. + char *type; // Type of event to observe. } observe_event_t; typedef struct _store_event_t { - const char *entity_id; // Non-owning. Target entity to store event. - const char *type; // Non-owning. Type of event to store. + char *entity_id; // Target entity to store event. + char *type; // Type of event to store. } store_event_t; typedef struct _entity_t { @@ -51,6 +55,8 @@ typedef struct _entity_t { bool *observe_sensitive_commands; struct _entity_t *next; event_t *events; + bson_mutex_t log_messages_mutex; + log_message_t *log_messages; struct _entity_map_t *entity_map; // Parent entity map. mongoc_array_t observe_events; // observe_event_t [N]. mongoc_array_t store_events; // store_event_t [N]. diff --git a/src/libmongoc/tests/unified/operation.c b/src/libmongoc/tests/unified/operation.c index 0e8e7018928..039b49eab61 100644 --- a/src/libmongoc/tests/unified/operation.c +++ b/src/libmongoc/tests/unified/operation.c @@ -2514,7 +2514,9 @@ operation_failpoint (test_t *test, operation_t *op, result_t *result, bson_error rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY); bson_destroy (&op_reply); + test_begin_suppressing_structured_logs (); mongoc_client_command_simple (client, "admin", failpoint, rp, &op_reply, &op_error); + test_end_suppressing_structured_logs (); result_from_val_and_reply (result, NULL /* value */, &op_reply, &op_error); /* Add failpoint to list of test_t's known failpoints */ @@ -2572,7 +2574,9 @@ operation_targeted_failpoint (test_t *test, operation_t *op, result_t *result, b rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY); bson_destroy (&op_reply); + test_begin_suppressing_structured_logs (); mongoc_client_command_simple_with_server_id (client, "admin", failpoint, rp, server_id, &op_reply, &op_error); + test_end_suppressing_structured_logs (); result_from_val_and_reply (result, NULL /* value */, &op_reply, &op_error); /* Add failpoint to list of test_t's known failpoints */ @@ -2818,20 +2822,20 @@ assert_lsid_on_last_two_commands (test_t *test, operation_t *op, result_t *resul goto done; } - if (!bson_iter_init_find (&iter, a->command, "lsid")) { - test_set_error (error, "unable to find lsid in second to last commandStartedEvent: %s", tmp_json (a->command)); + if (bson_iter_init (&iter, a->serialized) && bson_iter_find_descendant (&iter, "command.lsid", &iter)) { + bson_iter_bson (&iter, &a_lsid); + } else { + test_set_error (error, "unable to find lsid in second to last commandStartedEvent: %s", tmp_json (a->serialized)); goto done; } - bson_iter_bson (&iter, &a_lsid); - - if (!bson_iter_init_find (&iter, b->command, "lsid")) { - test_set_error (error, "unable to find lsid in second to last commandStartedEvent: %s", tmp_json (b->command)); + if (bson_iter_init (&iter, b->serialized) && bson_iter_find_descendant (&iter, "command.lsid", &iter)) { + bson_iter_bson (&iter, &b_lsid); + } else { + test_set_error (error, "unable to find lsid in last commandStartedEvent: %s", tmp_json (b->serialized)); goto done; } - bson_iter_bson (&iter, &b_lsid); - if (check_same != bson_equal (&a_lsid, &b_lsid)) { test_set_error (error, "expected $lsid's to be%s equal, but got: %s and %s", @@ -3809,6 +3813,121 @@ operation_updateSearchIndex (test_t *test, operation_t *op, result_t *result, bs return ret; } +static bool +operation_create_entities (test_t *test, operation_t *op, result_t *result, bson_error_t *error) +{ + bool ret = false; + + bson_parser_t *bp = bson_parser_new (); + bson_t *entities; + bson_parser_array_optional (bp, "entities", &entities); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + bson_iter_t entity_iter; + BSON_FOREACH (entities, entity_iter) + { + bson_t entity; + bson_iter_bson (&entity_iter, &entity); + bool create_ret = entity_map_create (test->entity_map, &entity, error); + bson_destroy (&entity); + if (!create_ret) { + goto done; + } + } + + result_from_ok (result); + ret = true; +done: + bson_parser_destroy_with_parsed_fields (bp); + return ret; +} + +#define WAIT_FOR_EVENT_TIMEOUT_MS (10 * 1000) // Specified by test runner spec +#define WAIT_FOR_EVENT_TICK_MS 10 // Same tick size as used in non-unified json test + +static bool +operation_wait_for_event (test_t *test, operation_t *op, result_t *result, bson_error_t *error) +{ + bool ret = false; + int64_t start_time = bson_get_monotonic_time (); + + bson_parser_t *bp = bson_parser_new (); + char *client_id; + bson_t *expected_event; + int64_t *expected_count; + bson_parser_utf8 (bp, "client", &client_id); + bson_parser_doc (bp, "event", &expected_event); + bson_parser_int (bp, "count", &expected_count); + if (!bson_parser_parse (bp, op->arguments, error)) { + goto done; + } + + entity_t *client = entity_map_get (test->entity_map, client_id, error); + if (!client) { + goto done; + } + + // @todo CDRIVER-3525 test support for CMAP events once supported by libmongoc + bson_iter_t iter; + bson_iter_init (&iter, expected_event); + bson_iter_next (&iter); + const char *expected_event_type = bson_iter_key (&iter); + if (is_unsupported_event_type (expected_event_type)) { + MONGOC_DEBUG ("Skipping wait for unsupported event type '%s'", expected_event_type); + result_from_ok (result); + ret = true; + goto done; + } + + while (true) { + int64_t count; + if (!test_count_matching_events_for_client (test, client, expected_event, error, &count)) { + goto done; + } + if (count >= *expected_count) { + break; + } + + int64_t duration = bson_get_monotonic_time () - start_time; + if (duration >= (int64_t) WAIT_FOR_EVENT_TIMEOUT_MS * 1000) { + char *event_list_string = event_list_to_string (client->events); + test_diagnostics_error_info ("all captured events for client:\n%s", event_list_string); + bson_free (event_list_string); + test_diagnostics_error_info ("checking for expected event: %s\n", tmp_json (expected_event)); + test_set_error (error, + "waitForEvent timed out with %" PRId64 " of %" PRId64 + " matches needed. waited %dms (max %dms)", + count, + *expected_count, + (int) (duration / 1000), + (int) WAIT_FOR_EVENT_TIMEOUT_MS); + goto done; + }; + + _mongoc_usleep (WAIT_FOR_EVENT_TICK_MS * 1000); + + // @todo Re-examine this once we have support for connection pools in the unified test + // runner. Without pooling, all events we could be waiting on would be coming + // from single-threaded (blocking) topology scans, or from lazily opening the topology + // description when it's first used. Request server selection after blocking, to + // handle either of these cases. + { + mongoc_client_t *mc_client = entity_map_get_client (test->entity_map, client_id, error); + if (mc_client) { + mongoc_topology_select_server_id (mc_client->topology, MONGOC_SS_READ, NULL, NULL, NULL, error); + } + } + } + + result_from_ok (result); + ret = true; +done: + bson_parser_destroy_with_parsed_fields (bp); + return ret; +} + typedef struct { const char *op; bool (*fn) (test_t *, operation_t *, result_t *, bson_error_t *); @@ -3827,6 +3946,7 @@ operation_run (test_t *test, bson_t *op_bson, bson_error_t *error) {"listDatabases", operation_list_databases}, {"listDatabaseNames", operation_list_database_names}, {"clientBulkWrite", operation_client_bulkwrite}, + {"waitForEvent", operation_wait_for_event}, /* ClientEncryption operations */ {"createDataKey", operation_create_datakey}, @@ -3903,13 +4023,15 @@ operation_run (test_t *test, bson_t *op_bson, bson_error_t *error) {"download", operation_download}, {"upload", operation_upload}, - /* Session operations. */ + /* Session operations */ {"endSession", operation_end_session}, {"startTransaction", operation_start_transaction}, {"commitTransaction", operation_commit_transaction}, {"withTransaction", operation_with_transaction}, {"abortTransaction", operation_abort_transaction}, + /* Entity operations */ + {"createEntities", operation_create_entities}, }; bool ret = false; diff --git a/src/libmongoc/tests/unified/runner.c b/src/libmongoc/tests/unified/runner.c index 62c4f89eb61..f0c5df59b47 100644 --- a/src/libmongoc/tests/unified/runner.c +++ b/src/libmongoc/tests/unified/runner.c @@ -26,6 +26,7 @@ #include "util.h" #include #include +#include typedef struct { const char *file_description; @@ -49,6 +50,7 @@ skipped_unified_test_t SKIPPED_TESTS[] = { {"assertNumberConnectionsCheckedOut", SKIP_ALL_TESTS}, {"entity-client-cmap-events", SKIP_ALL_TESTS}, {"expectedEventsForClient-eventType", SKIP_ALL_TESTS}, + {"driver-connection-id", SKIP_ALL_TESTS}, // CDRIVER-4115: listCollections does not support batchSize. {"cursors are correctly pinned to connections for load-balanced clusters", "listCollections pins the cursor to a connection"}, // CDRIVER-4116: listIndexes does not support batchSize. @@ -140,6 +142,8 @@ cleanup_failpoints (test_t *test, bson_error_t *error) failpoint_t *iter = NULL; mongoc_read_prefs_t *rp = NULL; + test_begin_suppressing_structured_logs (); + rp = mongoc_read_prefs_new (MONGOC_READ_PRIMARY); LL_FOREACH (test->failpoints, iter) @@ -168,6 +172,7 @@ cleanup_failpoints (test_t *test, bson_error_t *error) ret = true; done: mongoc_read_prefs_destroy (rp); + test_end_suppressing_structured_logs (); return ret; } @@ -267,6 +272,8 @@ test_runner_terminate_open_transactions (test_runner_t *test_runner, bson_error_ bool cmd_ret = false; bson_error_t cmd_error = {0}; + test_begin_suppressing_structured_logs (); + if (test_framework_getenv_bool ("MONGOC_TEST_ATLAS")) { // Not applicable when running as test-atlas-executor. return true; @@ -319,6 +326,7 @@ test_runner_terminate_open_transactions (test_runner_t *test_runner, bson_error_ ret = true; done: + test_end_suppressing_structured_logs (); return ret; } @@ -449,6 +457,7 @@ test_new (test_file_t *test_file, bson_t *bson) bson_parser_utf8_optional (parser, "skipReason", &test->skip_reason); bson_parser_array (parser, "operations", &test->operations); bson_parser_array_optional (parser, "expectEvents", &test->expect_events); + bson_parser_array_optional (parser, "expectLogMessages", &test->expect_log_messages); bson_parser_array_optional (parser, "outcome", &test->outcome); bson_parser_parse_or_assert (parser, bson); bson_parser_destroy (parser); @@ -470,6 +479,7 @@ test_destroy (test_t *test) entity_map_destroy (test->entity_map); bson_destroy (test->outcome); bson_destroy (test->expect_events); + bson_destroy (test->expect_log_messages); bson_destroy (test->operations); bson_destroy (test->run_on_requirements); bson_free (test->description); @@ -986,6 +996,11 @@ test_check_event (test_t *test, bson_t *expected, event_t *actual, bson_error_t bool *expected_has_service_id = NULL; bool *expected_has_server_connection_id = NULL; + BSON_ASSERT_PARAM (test); + BSON_ASSERT_PARAM (expected); + BSON_ASSERT_PARAM (actual); + BSON_ASSERT_PARAM (error); + if (bson_count_keys (expected) != 1) { test_set_error (error, "expected 1 key in expected event, but got: %s", tmp_json (expected)); goto done; @@ -1017,73 +1032,115 @@ test_check_event (test_t *test, bson_t *expected, event_t *actual, bson_error_t } if (expected_command) { - bson_val_t *expected_val; - bson_val_t *actual_val; - - if (!actual || !actual->command) { - test_set_error (error, "Expected a value but got NULL"); + if (!bson_iter_init_find (&iter, actual->serialized, "command")) { + test_set_error (error, "event.command expected but missing"); goto done; } - - expected_val = bson_val_from_bson (expected_command); - actual_val = bson_val_from_bson (actual->command); - - if (!entity_map_match (test->entity_map, expected_val, actual_val, false, error)) { - bson_val_destroy (expected_val); - bson_val_destroy (actual_val); + if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) { + test_set_error (error, "Unexpected type for event.command, should be document"); goto done; } + bson_val_t *expected_val = bson_val_from_bson (expected_command); + bson_val_t *actual_val = bson_val_from_iter (&iter); + bool is_match = entity_map_match (test->entity_map, expected_val, actual_val, false, error); bson_val_destroy (expected_val); bson_val_destroy (actual_val); + if (!is_match) { + goto done; + } } - if (expected_command_name && 0 != strcmp (expected_command_name, actual->command_name)) { - test_set_error (error, "expected commandName: %s, but got: %s", expected_command_name, actual->command_name); - goto done; + if (expected_command_name) { + if (!bson_iter_init_find (&iter, actual->serialized, "commandName")) { + test_set_error (error, "event.commandName expected but missing"); + goto done; + } + if (!BSON_ITER_HOLDS_UTF8 (&iter)) { + test_set_error (error, "Unexpected type for event.commandName, should be string"); + goto done; + } + const char *actual_command_name = bson_iter_utf8 (&iter, NULL); + if (0 != strcmp (expected_command_name, actual_command_name)) { + test_set_error (error, "expected commandName: %s, but got: %s", expected_command_name, actual_command_name); + goto done; + } } - if (expected_database_name && 0 != strcmp (expected_database_name, actual->database_name)) { - test_set_error (error, "expected databaseName: %s, but got: %s", expected_database_name, actual->database_name); - goto done; + if (expected_database_name) { + if (!bson_iter_init_find (&iter, actual->serialized, "databaseName")) { + test_set_error (error, "event.databaseName expected but missing"); + goto done; + } + if (!BSON_ITER_HOLDS_UTF8 (&iter)) { + test_set_error (error, "Unexpected type for event.databaseName, should be string"); + goto done; + } + const char *actual_database_name = bson_iter_utf8 (&iter, NULL); + if (0 != strcmp (expected_database_name, actual_database_name)) { + test_set_error (error, "expected databaseName: %s, but got: %s", expected_database_name, actual_database_name); + goto done; + } } if (expected_reply) { - bson_val_t *expected_val = bson_val_from_bson (expected_reply); - bson_val_t *actual_val = bson_val_from_bson (actual->reply); - if (!entity_map_match (test->entity_map, expected_val, actual_val, false, error)) { - bson_val_destroy (expected_val); - bson_val_destroy (actual_val); + if (!bson_iter_init_find (&iter, actual->serialized, "reply")) { + test_set_error (error, "event.reply expected but missing"); goto done; } + if (!BSON_ITER_HOLDS_DOCUMENT (&iter)) { + test_set_error (error, "Unexpected type for event.reply, should be document"); + goto done; + } + bson_val_t *expected_val = bson_val_from_bson (expected_reply); + bson_val_t *actual_val = bson_val_from_iter (&iter); + bool is_match = entity_map_match (test->entity_map, expected_val, actual_val, false, error); bson_val_destroy (expected_val); bson_val_destroy (actual_val); + if (!is_match) { + goto done; + } } if (expected_has_service_id) { - char oid_str[25] = {0}; - bool has_service_id = false; + if (!bson_iter_init_find (&iter, actual->serialized, "serviceId")) { + test_set_error (error, "event.serviceId field expected but missing"); + goto done; + } + if (!BSON_ITER_HOLDS_OID (&iter)) { + test_set_error (error, "Unexpected type for event.serviceId, should be ObjectId"); + goto done; + } - bson_oid_to_string (&actual->service_id, oid_str); - has_service_id = 0 != bson_oid_compare (&actual->service_id, &kZeroServiceId); + const bson_oid_t *actual_oid = bson_iter_oid (&iter); + bool actual_has_service_id = !mcommon_oid_is_zero (actual_oid); + char actual_oid_str[25]; + bson_oid_to_string (actual_oid, actual_oid_str); - if (*expected_has_service_id && !has_service_id) { - test_error ("expected serviceId, but got none"); + if (*expected_has_service_id && !actual_has_service_id) { + test_error ("expected nonzero serviceId, but found zero"); } - - if (!*expected_has_service_id && has_service_id) { - test_error ("expected no serviceId, but got %s", oid_str); + if (!*expected_has_service_id && actual_has_service_id) { + test_error ("expected zeroed serviceId, but found nonzero value: %s", actual_oid_str); } } if (expected_has_server_connection_id) { - const bool has_server_connection_id = actual->server_connection_id != MONGOC_NO_SERVER_CONNECTION_ID; + if (!bson_iter_init_find (&iter, actual->serialized, "serverConnectionId")) { + test_set_error (error, "event.serverConnectionId expected but missing"); + goto done; + } + if (!BSON_ITER_HOLDS_INT64 (&iter)) { + test_set_error (error, "Unexpected type for event.serverConnectionId, should be int64"); + goto done; + } + int64_t actual_server_connection_id = bson_iter_int64 (&iter); + const bool has_server_connection_id = actual_server_connection_id != MONGOC_NO_SERVER_CONNECTION_ID; if (*expected_has_server_connection_id && !has_server_connection_id) { - test_error ("expected server connectionId, but got none"); + test_error ("expected server connectionId, but got MONGOC_NO_SERVER_CONNECTION_ID"); } - if (!*expected_has_server_connection_id && has_server_connection_id) { - test_error ("expected no server connectionId, but got %" PRId64, actual->server_connection_id); + test_error ("expected MONGOC_NO_SERVER_CONNECTION_ID, but got %" PRId64, actual_server_connection_id); } } @@ -1093,21 +1150,38 @@ test_check_event (test_t *test, bson_t *expected, event_t *actual, bson_error_t return ret; } +bool +test_count_matching_events_for_client ( + test_t *test, entity_t *client, bson_t *expected_event, bson_error_t *error, int64_t *count_out) +{ + int64_t count = 0; + + event_t *eiter; + LL_FOREACH (client->events, eiter) + { + if (test_check_event (test, expected_event, eiter, error)) { + count++; + } + } + + *count_out = count; + return true; +} + static bool test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for_client, bson_error_t *error) { bool ret = false; - bson_parser_t *bp = NULL; - char *client_id = NULL; - bson_t *expected_events = NULL; - bool just_false = false; - bool *ignore_extra_events = &just_false; + bson_parser_t *bp; + char *client_id; + bson_t *expected_events; + bool *ignore_extra_events; entity_t *entity = NULL; bson_iter_t iter; - event_t *eiter = NULL; + event_t *eiter; uint32_t expected_num_events; - uint32_t actual_num_events = 0; - char *event_type = NULL; + uint32_t actual_num_events; + char *event_type; bp = bson_parser_new (); bson_parser_utf8 (bp, "client", &client_id); @@ -1173,9 +1247,7 @@ test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for done: if (!ret) { if (entity && entity->events) { - char *event_list_string = NULL; - - event_list_string = event_list_to_string (entity->events); + char *event_list_string = event_list_to_string (entity->events); test_diagnostics_error_info ("all captured events:\n%s", event_list_string); bson_free (event_list_string); } @@ -1200,11 +1272,280 @@ test_check_expected_events (test_t *test, bson_error_t *error) bson_t expected_events_for_client; bson_iter_bson (&iter, &expected_events_for_client); if (!test_check_expected_events_for_client (test, &expected_events_for_client, error)) { - test_diagnostics_error_info ("checking expectations: %s", tmp_json (&expected_events_for_client)); + test_diagnostics_error_info ("checking expected events: %s", tmp_json (&expected_events_for_client)); + goto done; + } + } + + ret = true; +done: + return ret; +} + +static bool +check_failure_is_redacted (const bson_iter_t *failure_iter, bson_error_t *error) +{ + if (BSON_ITER_HOLDS_UTF8 (failure_iter)) { + test_diagnostics_error_info ("%s", "expected redacted 'failure', found string message (not allowed)"); + return false; + } + if (!BSON_ITER_HOLDS_DOCUMENT (failure_iter)) { + test_diagnostics_error_info ("%s", "expected redacted 'failure' document, found unexpected type"); + return false; + } + + bson_t failure; + bson_iter_bson (failure_iter, &failure); + + bson_parser_t *bp = bson_parser_new (); + int64_t *failure_code; + char *failure_code_name; + bson_t *failure_error_labels; + bson_parser_int_optional (bp, "code", &failure_code); + bson_parser_utf8_optional (bp, "codeName", &failure_code_name); + bson_parser_array_optional (bp, "errorLabels", &failure_error_labels); + bool parse_result = bson_parser_parse (bp, &failure, error); + bson_parser_destroy_with_parsed_fields (bp); + + bson_destroy (&failure); + return parse_result; +} + +static bool +check_failure_is_detailed (const bson_iter_t *failure_iter, bson_error_t *error) +{ + if (BSON_ITER_HOLDS_UTF8 (failure_iter)) { + // Strings are fine, that's enough proof that the failure was not redacted + return true; + } + if (!BSON_ITER_HOLDS_DOCUMENT (failure_iter)) { + test_diagnostics_error_info ("%s", "expected non-redacted 'failure' document, found unexpected type"); + return false; + } + + // Look for keys that indicate an un-redacted message + bson_iter_t child; + BSON_ASSERT (bson_iter_recurse (failure_iter, &child)); + while (bson_iter_next (&child)) { + const char *key = bson_iter_key (&child); + if (!strcmp (key, "message") || !strcmp (key, "details")) { + return true; + } + } + return false; +} + +static bool +test_check_log_message (test_t *test, bson_t *expected, log_message_t *actual, bson_error_t *error) +{ + bool ret = false; + + bson_parser_t *bp = bson_parser_new (); + char *expected_level_str; + char *expected_component_str; + bool *failure_is_redacted; + bson_t *expected_message_doc; + bson_parser_utf8 (bp, "level", &expected_level_str); + bson_parser_utf8 (bp, "component", &expected_component_str); + bson_parser_bool_optional (bp, "failureIsRedacted", &failure_is_redacted); + bson_parser_doc (bp, "data", &expected_message_doc); + if (!bson_parser_parse (bp, expected, error)) { + goto done; + } + + mongoc_structured_log_level_t expected_level; + if (!mongoc_structured_log_get_named_level (expected_level_str, &expected_level)) { + test_set_error (error, "expected log level '%s' is not recognized", expected_level_str); + goto done; + } + mongoc_structured_log_component_t expected_component; + if (!mongoc_structured_log_get_named_component (expected_component_str, &expected_component)) { + test_set_error (error, "expected log component '%s' is not recognized", expected_component_str); + goto done; + } + + if (expected_level != actual->level) { + test_set_error (error, + "expected log level: %s, but got: %s", + mongoc_structured_log_get_level_name (expected_level), + mongoc_structured_log_get_level_name (actual->level)); + goto done; + } + + if (expected_component != actual->component) { + test_set_error (error, + "expected log component: %s, but got: %s", + mongoc_structured_log_get_component_name (expected_component), + mongoc_structured_log_get_component_name (actual->component)); + goto done; + } + + if (failure_is_redacted) { + bson_iter_t failure_iter; + if (!bson_iter_init_find (&failure_iter, actual->message, "failure")) { + test_set_error (error, "expected log 'failure' to exist"); + goto done; + }; + if (*failure_is_redacted) { + if (!check_failure_is_redacted (&failure_iter, error)) { + test_diagnostics_error_info ("actual log message: %s", tmp_json (actual->message)); + test_set_error (error, "expected log 'failure' to be redacted"); + goto done; + } + } else { + if (!check_failure_is_detailed (&failure_iter, error)) { + test_diagnostics_error_info ("actual log message: %s", tmp_json (actual->message)); + test_set_error (error, "expected a complete un-redacted 'failure'"); + goto done; + } + } + } + + bson_val_t *expected_val = bson_val_from_bson (expected_message_doc); + bson_val_t *actual_val = bson_val_from_bson (actual->message); + bool is_match = bson_match (expected_val, actual_val, false, error); + bson_val_destroy (actual_val); + bson_val_destroy (expected_val); + if (!is_match) { + test_set_error ( + error, "expected log message: %s, but got: %s", tmp_json (expected_message_doc), tmp_json (actual->message)); + goto done; + } + + ret = true; +done: + bson_parser_destroy_with_parsed_fields (bp); + return ret; +} + +static bool +test_log_message_should_be_ignored (test_t *test, + log_message_t *message, + bson_t *optional_ignore_list, + bson_error_t *error) +{ + if (optional_ignore_list) { + bson_iter_t iter; + BSON_FOREACH (optional_ignore_list, iter) + { + bson_t expected; + bson_iter_bson (&iter, &expected); + bool is_match = test_check_log_message (test, &expected, message, error); + bson_destroy (&expected); + if (is_match) { + return true; + } + } + } + return false; +} + +static bool +test_check_expected_log_messages_for_client (test_t *test, + bson_t *expected_log_messages_for_client, + bson_error_t *error) +{ + bool ret = false; + bson_mutex_t *locked = NULL; + + bson_parser_t *bp = bson_parser_new (); + char *client_id; + bson_t *expected_messages; + bson_t *ignore_messages; + bool *ignore_extra_messages; + bson_parser_utf8 (bp, "client", &client_id); + bson_parser_array (bp, "messages", &expected_messages); + bson_parser_array_optional (bp, "ignoreMessages", &ignore_messages); + bson_parser_bool_optional (bp, "ignoreExtraMessages", &ignore_extra_messages); + if (!bson_parser_parse (bp, expected_log_messages_for_client, error)) { + goto done; + } + + entity_t *entity = entity_map_get (test->entity_map, client_id, error); + if (0 != strcmp (entity->type, "client")) { + test_set_error (error, "expected entity %s to be client, got: %s", entity->id, entity->type); + goto done; + } + + locked = &entity->log_messages_mutex; + bson_mutex_lock (locked); + + log_message_t *actual_message_iter = entity->log_messages; + bson_iter_t expected_message_iter; + bool expected_message_iter_ok = + bson_iter_init (&expected_message_iter, expected_messages) && bson_iter_next (&expected_message_iter); + + while (actual_message_iter || expected_message_iter_ok) { + if (actual_message_iter && + test_log_message_should_be_ignored (test, actual_message_iter, ignore_messages, error)) { + actual_message_iter = actual_message_iter->next; + continue; + } + if (!actual_message_iter) { + bson_t expected_message; + bson_iter_bson (&expected_message_iter, &expected_message); + test_diagnostics_error_info ("missing expected log message: %s", tmp_json (&expected_message)); + test_set_error (error, "additional log messages expected beyond those collected"); + bson_destroy (&expected_message); + goto done; + } + if (!expected_message_iter_ok) { + if (ignore_extra_messages && *ignore_extra_messages) { + break; + } else { + test_diagnostics_error_info ("extra log message: %s", tmp_json (actual_message_iter->message)); + test_set_error (error, "unexpected extra log messages"); + goto done; + } + } + bson_t expected_message; + bson_iter_bson (&expected_message_iter, &expected_message); + bool is_match = test_check_log_message (test, &expected_message, actual_message_iter, error); + if (!is_match) { + test_diagnostics_error_info ("expected log message: %s\nactual log message: %s, %s, %s", + tmp_json (&expected_message), + mongoc_structured_log_get_level_name (actual_message_iter->level), + mongoc_structured_log_get_component_name (actual_message_iter->component), + tmp_json (actual_message_iter->message)); + } + bson_destroy (&expected_message); + if (!is_match) { goto done; } + actual_message_iter = actual_message_iter->next; + expected_message_iter_ok = bson_iter_next (&expected_message_iter); } + ret = true; +done: + if (locked) { + bson_mutex_unlock (locked); + } + bson_parser_destroy_with_parsed_fields (bp); + return ret; +} + +static bool +test_check_expected_log_messages (test_t *test, bson_error_t *error) +{ + bool ret = false; + bson_iter_t iter; + + if (!test->expect_log_messages) { + ret = true; + goto done; + } + + BSON_FOREACH (test->expect_log_messages, iter) + { + bson_t expected_log_messages_for_client; + bson_iter_bson (&iter, &expected_log_messages_for_client); + if (!test_check_expected_log_messages_for_client (test, &expected_log_messages_for_client, error)) { + test_diagnostics_error_info ("checking expected log messages: %s", + tmp_json (&expected_log_messages_for_client)); + goto done; + } + } ret = true; done: @@ -1617,7 +1958,12 @@ test_run (test_t *test, bson_error_t *error) entity_map_disable_event_listeners (test->entity_map); if (!test_check_expected_events (test, error)) { - test_diagnostics_error_info ("%s", "checking expectations"); + test_diagnostics_error_info ("%s", "checking expected events"); + goto done; + } + + if (!test_check_expected_log_messages (test, error)) { + test_diagnostics_error_info ("%s", "checking expected log messages"); goto done; } @@ -1733,5 +2079,5 @@ test_install_unified (TestSuite *suite) run_unified_tests (suite, JSON_DIR, "index-management"); - run_unified_tests (suite, JSON_DIR, "command-logging-and-monitoring/monitoring"); + run_unified_tests (suite, JSON_DIR, "command-logging-and-monitoring"); } diff --git a/src/libmongoc/tests/unified/runner.h b/src/libmongoc/tests/unified/runner.h index 62cc3a7c7f0..a6e9ee7fae6 100644 --- a/src/libmongoc/tests/unified/runner.h +++ b/src/libmongoc/tests/unified/runner.h @@ -58,6 +58,7 @@ typedef struct { char *skip_reason; bson_t *operations; bson_t *expect_events; + bson_t *expect_log_messages; bson_t *outcome; entity_map_t *entity_map; failpoint_t *failpoints; @@ -68,6 +69,10 @@ typedef struct { void register_failpoint (test_t *test, char *failpoint, char *client_id, uint32_t server_id); +bool +test_count_matching_events_for_client ( + test_t *test, entity_t *client, bson_t *expected_event, bson_error_t *error, int64_t *count_out); + /* Run a directory of test files through the unified test runner. */ void run_unified_tests (TestSuite *suite, const char *base, const char *subdir);