diff --git a/src/libbson/src/bson/bson-json-private.h b/src/libbson/src/bson/bson-json-private.h
index 9f51dcbd7eb..003171b2a55 100644
--- a/src/libbson/src/bson/bson-json-private.h
+++ b/src/libbson/src/bson/bson-json-private.h
@@ -23,6 +23,9 @@
 struct _bson_json_opts_t {
    bson_json_mode_t mode;
    int32_t max_len;
+   const char *initial_indent;
+   const char *subsequent_indent;
+   const char *level_indent;
 };
 
 
diff --git a/src/libbson/src/bson/bson-json.c b/src/libbson/src/bson/bson-json.c
index 474b3e91174..1ed284dfad5 100644
--- a/src/libbson/src/bson/bson-json.c
+++ b/src/libbson/src/bson/bson-json.c
@@ -387,13 +387,12 @@ _noop (void)
    }
 
 
-
 bson_json_opts_t *
 bson_json_opts_new (bson_json_mode_t mode, int32_t max_len)
 {
    bson_json_opts_t *opts;
 
-   opts = (bson_json_opts_t *) bson_malloc (sizeof *opts);
+   opts = (bson_json_opts_t *) bson_malloc0 (sizeof *opts);
    opts->mode = mode;
    opts->max_len = max_len;
 
diff --git a/src/libbson/src/bson/bson.c b/src/libbson/src/bson/bson.c
index 5af710e4ddb..ef9d180ea34 100644
--- a/src/libbson/src/bson/bson.c
+++ b/src/libbson/src/bson/bson.c
@@ -62,9 +62,9 @@ typedef struct {
    ssize_t *err_offset;
    uint32_t depth;
    bson_string_t *str;
-   bson_json_mode_t mode;
    int32_t max_len;
    bool max_len_reached;
+   const bson_json_opts_t *opts;
 } bson_json_state_t;
 
 
@@ -84,8 +84,7 @@ _bson_as_json_visit_document (const bson_iter_t *iter,
 static char *
 _bson_as_json_visit_all (const bson_t *bson,
                          size_t *length,
-                         bson_json_mode_t mode,
-                         int32_t max_len);
+                         const bson_json_opts_t *opts);
 
 /*
  * Globals.
@@ -2510,6 +2509,25 @@ bson_equal (const bson_t *bson, const bson_t *other)
 }
 
 
+static void
+_bson_json_newline_indent (const bson_json_state_t *state)
+{
+   if (!state->opts->level_indent) {
+      return;
+   }
+   bson_string_append (state->str, "\n");
+   if (state->opts->subsequent_indent) {
+      bson_string_append (state->str, state->opts->subsequent_indent);
+   }
+   if (state->opts->level_indent) {
+      int i = 0;
+      for (i = 0; i < state->depth + 1; ++i) {
+         bson_string_append (state->str, state->opts->level_indent);
+      }
+   }
+}
+
+
 static bool
 _bson_as_json_visit_utf8 (const bson_iter_t *iter,
                           const char *key,
@@ -2542,7 +2560,7 @@ _bson_as_json_visit_int32 (const bson_iter_t *iter,
 {
    bson_json_state_t *state = data;
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL) {
       bson_string_append_printf (
          state->str, "{ \"$numberInt\" : \"%" PRId32 "\" }", v_int32);
    } else {
@@ -2561,7 +2579,7 @@ _bson_as_json_visit_int64 (const bson_iter_t *iter,
 {
    bson_json_state_t *state = data;
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL) {
       bson_string_append_printf (
          state->str, "{ \"$numberLong\" : \"%" PRId64 "\" }", v_int64);
    } else {
@@ -2604,8 +2622,8 @@ _bson_as_json_visit_double (const bson_iter_t *iter,
    /* Determine if legacy (i.e. unwrapped) output should be used. Relaxed mode
     * will use this for nan and inf values, which we check manually since old
     * platforms may not have isinf or isnan. */
-   legacy = state->mode == BSON_JSON_MODE_LEGACY ||
-            (state->mode == BSON_JSON_MODE_RELAXED &&
+   legacy = state->opts->mode == BSON_JSON_MODE_LEGACY ||
+            (state->opts->mode == BSON_JSON_MODE_RELAXED &&
              !(v_double != v_double || v_double * 0 != 0));
 
    if (!legacy) {
@@ -2697,8 +2715,8 @@ _bson_as_json_visit_binary (const bson_iter_t *iter,
    b64 = bson_malloc0 (b64_len);
    BSON_ASSERT (mcommon_b64_ntop (v_binary, v_binary_len, b64, b64_len) != -1);
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL ||
-       state->mode == BSON_JSON_MODE_RELAXED) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL ||
+       state->opts->mode == BSON_JSON_MODE_RELAXED) {
       bson_string_append (state->str, "{ \"$binary\" : { \"base64\" : \"");
       bson_string_append (state->str, b64);
       bson_string_append (state->str, "\", \"subType\" : \"");
@@ -2740,12 +2758,12 @@ _bson_as_json_visit_date_time (const bson_iter_t *iter,
 {
    bson_json_state_t *state = data;
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL ||
-       (state->mode == BSON_JSON_MODE_RELAXED && msec_since_epoch < 0)) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL ||
+       (state->opts->mode == BSON_JSON_MODE_RELAXED && msec_since_epoch < 0)) {
       bson_string_append (state->str, "{ \"$date\" : { \"$numberLong\" : \"");
       bson_string_append_printf (state->str, "%" PRId64, msec_since_epoch);
       bson_string_append (state->str, "\" } }");
-   } else if (state->mode == BSON_JSON_MODE_RELAXED) {
+   } else if (state->opts->mode == BSON_JSON_MODE_RELAXED) {
       bson_string_append (state->str, "{ \"$date\" : \"");
       _bson_iso8601_date_format (msec_since_epoch, state->str);
       bson_string_append (state->str, "\" }");
@@ -2774,8 +2792,8 @@ _bson_as_json_visit_regex (const bson_iter_t *iter,
       return true;
    }
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL ||
-       state->mode == BSON_JSON_MODE_RELAXED) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL ||
+       state->opts->mode == BSON_JSON_MODE_RELAXED) {
       bson_string_append (state->str,
                           "{ \"$regularExpression\" : { \"pattern\" : \"");
       bson_string_append (state->str, escaped);
@@ -2832,8 +2850,8 @@ _bson_as_json_visit_dbpointer (const bson_iter_t *iter,
       return true;
    }
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL ||
-       state->mode == BSON_JSON_MODE_RELAXED) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL ||
+       state->opts->mode == BSON_JSON_MODE_RELAXED) {
       bson_string_append (state->str, "{ \"$dbPointer\" : { \"$ref\" : \"");
       bson_string_append (state->str, escaped);
       bson_string_append (state->str, "\"");
@@ -2909,6 +2927,8 @@ _bson_as_json_visit_before (const bson_iter_t *iter,
       bson_string_append (state->str, ", ");
    }
 
+   _bson_json_newline_indent (state);
+
    if (state->keys) {
       escaped = bson_utf8_escape_for_json (key, -1);
       if (escaped) {
@@ -2947,6 +2967,8 @@ _bson_as_json_visit_after (const bson_iter_t *iter, const char *key, void *data)
       return true;
    }
 
+   _bson_json_newline_indent (state);
+
    return false;
 }
 
@@ -2997,8 +3019,8 @@ _bson_as_json_visit_symbol (const bson_iter_t *iter,
       return true;
    }
 
-   if (state->mode == BSON_JSON_MODE_CANONICAL ||
-       state->mode == BSON_JSON_MODE_RELAXED) {
+   if (state->opts->mode == BSON_JSON_MODE_CANONICAL ||
+       state->opts->mode == BSON_JSON_MODE_RELAXED) {
       bson_string_append (state->str, "{ \"$symbol\" : \"");
       bson_string_append (state->str, escaped);
       bson_string_append (state->str, "\" }");
@@ -3025,7 +3047,6 @@ _bson_as_json_visit_codewscope (const bson_iter_t *iter,
    bson_json_state_t *state = data;
    char *code_escaped;
    char *scope;
-   int32_t max_scope_len = BSON_MAX_LEN_UNLIMITED;
 
    code_escaped = bson_utf8_escape_for_json (v_code, v_code_len);
    if (!code_escaped) {
@@ -3038,12 +3059,7 @@ _bson_as_json_visit_codewscope (const bson_iter_t *iter,
 
    bson_free (code_escaped);
 
-   /* Encode scope with the same mode */
-   if (state->max_len != BSON_MAX_LEN_UNLIMITED) {
-      max_scope_len = BSON_MAX (0, state->max_len - state->str->len);
-   }
-
-   scope = _bson_as_json_visit_all (v_scope, NULL, state->mode, max_scope_len);
+   scope = _bson_as_json_visit_all (v_scope, NULL, state->opts);
 
    if (!scope) {
       return true;
@@ -3090,10 +3106,12 @@ _bson_as_json_visit_document (const bson_iter_t *iter,
       return false;
    }
 
-   if (bson_iter_init (&child, v_document)) {
+   if (bson_empty (v_document)) {
+      child_state.str = bson_string_new ("{}");
+   } else if (bson_iter_init (&child, v_document)) {
       child_state.str = bson_string_new ("{ ");
       child_state.depth = state->depth + 1;
-      child_state.mode = state->mode;
+      child_state.opts = state->opts;
       child_state.max_len = BSON_MAX_LEN_UNLIMITED;
       if (state->max_len != BSON_MAX_LEN_UNLIMITED) {
          child_state.max_len = BSON_MAX (0, state->max_len - state->str->len);
@@ -3114,7 +3132,12 @@ _bson_as_json_visit_document (const bson_iter_t *iter,
          return !child_state.max_len_reached;
       }
 
-      bson_string_append (child_state.str, " }");
+      child_state.depth -= 1;
+      _bson_json_newline_indent (&child_state);
+      bson_string_append (child_state.str,
+                          state->opts->level_indent ? "}" : " }");
+   }
+   if (child_state.str) {
       bson_string_append (state->str, child_state.str->str);
       bson_string_free (child_state.str, true);
    }
@@ -3141,7 +3164,7 @@ _bson_as_json_visit_array (const bson_iter_t *iter,
    if (bson_iter_init (&child, v_array)) {
       child_state.str = bson_string_new ("[ ");
       child_state.depth = state->depth + 1;
-      child_state.mode = state->mode;
+      child_state.opts = state->opts;
       child_state.max_len = BSON_MAX_LEN_UNLIMITED;
       if (state->max_len != BSON_MAX_LEN_UNLIMITED) {
          child_state.max_len = BSON_MAX (0, state->max_len - state->str->len);
@@ -3162,7 +3185,10 @@ _bson_as_json_visit_array (const bson_iter_t *iter,
          return !child_state.max_len_reached;
       }
 
-      bson_string_append (child_state.str, " ]");
+      child_state.depth -= 1;
+      _bson_json_newline_indent (&child_state);
+      bson_string_append (child_state.str,
+                          state->opts->level_indent ? "]" : " ]");
       bson_string_append (state->str, child_state.str->str);
       bson_string_free (child_state.str, true);
    }
@@ -3174,8 +3200,7 @@ _bson_as_json_visit_array (const bson_iter_t *iter,
 static char *
 _bson_as_json_visit_all (const bson_t *bson,
                          size_t *length,
-                         bson_json_mode_t mode,
-                         int32_t max_len)
+                         const bson_json_opts_t *opts)
 {
    bson_json_state_t state;
    bson_iter_t iter;
@@ -3202,11 +3227,16 @@ _bson_as_json_visit_all (const bson_t *bson,
 
    state.count = 0;
    state.keys = true;
-   state.str = bson_string_new ("{ ");
+   state.opts = opts;
+   if (state.opts->initial_indent) {
+      state.str = bson_string_new (state.opts->initial_indent);
+      bson_string_append_c (state.str, '{');
+   } else {
+      state.str = bson_string_new ("{ ");
+   }
    state.depth = 0;
    state.err_offset = &err_offset;
-   state.mode = mode;
-   state.max_len = max_len;
+   state.max_len = opts->max_len;
    state.max_len_reached = false;
 
    if ((bson_iter_visit_all (&iter, &bson_as_json_visitors, &state) ||
@@ -3222,11 +3252,16 @@ _bson_as_json_visit_all (const bson_t *bson,
       return NULL;
    }
 
-   /* Append closing space and } separately, in case we hit the max in between. */
+   /* Append closing space and } separately, in case we hit the max in between.
+    */
    remaining = state.max_len - state.str->len;
-   if (state.max_len == BSON_MAX_LEN_UNLIMITED ||
-       remaining > 1) {
-      bson_string_append (state.str, " }");
+   if (state.max_len == BSON_MAX_LEN_UNLIMITED || remaining > 1) {
+      if (state.opts->level_indent && state.opts->subsequent_indent) {
+         bson_string_append_printf (
+            state.str, "\n%s}", state.opts->subsequent_indent);
+      } else {
+         bson_string_append (state.str, " }");
+      }
    } else if (remaining == 1) {
       bson_string_append (state.str, " ");
    }
@@ -3244,7 +3279,7 @@ bson_as_json_with_opts (const bson_t *bson,
                         size_t *length,
                         const bson_json_opts_t *opts)
 {
-   return _bson_as_json_visit_all (bson, length, opts->mode, opts->max_len);
+   return _bson_as_json_visit_all (bson, length, opts);
 }
 
 
@@ -3286,7 +3321,7 @@ bson_array_as_json (const bson_t *bson, size_t *length)
 
    if (length) {
       *length = 0;
-    }
+   }
 
    if (bson_empty0 (bson)) {
       if (length) {
@@ -3305,9 +3340,12 @@ bson_array_as_json (const bson_t *bson, size_t *length)
    state.str = bson_string_new ("[ ");
    state.depth = 0;
    state.err_offset = &err_offset;
-   state.mode = BSON_JSON_MODE_LEGACY;
    state.max_len = BSON_MAX_LEN_UNLIMITED;
    state.max_len_reached = false;
+   bson_json_opts_t opts = {0};
+   opts.mode = BSON_JSON_MODE_LEGACY;
+   opts.max_len = BSON_MAX_LEN_UNLIMITED;
+   state.opts = &opts;
 
    if ((bson_iter_visit_all (&iter, &bson_as_json_visitors, &state) ||
         err_offset != -1) &&
diff --git a/src/libmongoc/tests/json-test-monitoring.c b/src/libmongoc/tests/json-test-monitoring.c
index 1a22bcda0dc..10f972fc1a6 100644
--- a/src/libmongoc/tests/json-test-monitoring.c
+++ b/src/libmongoc/tests/json-test-monitoring.c
@@ -24,6 +24,7 @@
 #include "mongoc/mongoc-topology-private.h"
 #include "mongoc/mongoc-util-private.h"
 #include "mongoc/mongoc-util-private.h"
+#include <bson/bson-json-private.h>
 
 #include "TestSuite.h"
 #include "test-conveniences.h"
@@ -425,22 +426,33 @@ apm_match_visitor (match_ctx_t *ctx,
    return MATCH_ACTION_CONTINUE;
 }
 
+static void
+_print_bson_array_as_json (FILE *out, const bson_t *arr)
+{
+   bson_iter_t it;
+   for (bson_iter_init (&it, arr); bson_iter_next (&it);) {
+      bson_t elem;
+      bson_iter_bson (&it, &elem);
+
+      bson_json_opts_t opts = {0};
+      opts.initial_indent = "";
+      opts.level_indent = "  ";
+      opts.subsequent_indent = "    ";
+      opts.mode = BSON_JSON_MODE_CANONICAL;
+      opts.max_len = BSON_MAX_LEN_UNLIMITED;
+      char *str = bson_as_json_with_opts (&elem, NULL, &opts);
+      fprintf (out, "  - %s\n", str);
+      bson_free (str);
+   }
+}
 
 static void
 _apm_match_error_context (const bson_t *actual, const bson_t *expectations)
 {
-   char *actual_str;
-   char *expectations_str;
-
-   actual_str = bson_as_canonical_extended_json (actual, NULL);
-   expectations_str = bson_as_canonical_extended_json (expectations, NULL);
-   fprintf (stderr,
-            "Error in APM matching\nFull list of captured events: %s\nFull "
-            "list of expectations: %s",
-            actual_str,
-            expectations_str);
-   bson_free (actual_str);
-   bson_free (expectations_str);
+   fprintf (stderr, "Error in APM matching.\nFull list of captured events:\n");
+   _print_bson_array_as_json (stderr, actual);
+   fprintf (stderr, "Expected events:\n");
+   _print_bson_array_as_json (stderr, expectations);
 }
 
 bool