The branch, master has been updated
via 788af2540eb WHATSNEW document JSON audit logging changes
via 35cb872ab44 lib:audit_logging JSON remove leading spaces
via b86bcc58b0e lib:util:debug tests for JSON logging macros
via 3c30c4740f4 lib:util:debug Logging macros for JSON output
from 24dc455362f s3:rpc_client: Fix memory leak opening local named pipe
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 788af2540eb289fae6780ce7684172c5a3487166
Author: Gary Lockyer <[email protected]>
Date: Wed Jan 28 10:39:19 2026 +1300
WHATSNEW document JSON audit logging changes
Document the JSON audit logging changes.
- leading spaces before opening '{' removed
- any embedded '\n' chars replaced with spaces
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15898
Signed-off-by: Gary Lockyer <[email protected]>
Reviewed-by: Volker Lendecke <[email protected]>
Autobuild-User(master): Douglas Bagnall <[email protected]>
Autobuild-Date(master): Thu Jan 29 00:33:43 UTC 2026 on atb-devel-224
commit 35cb872ab44ba9fe0782164a979c112705374cee
Author: Gary Lockyer <[email protected]>
Date: Fri Jan 23 09:33:10 2026 +1300
lib:audit_logging JSON remove leading spaces
Remove the leading spaces in JSON audit lines.
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15898
Signed-off-by: Gary Lockyer <[email protected]>
Reviewed-by: Volker Lendecke <[email protected]>
commit b86bcc58b0ea43c4ab85e0030a9522c686877043
Author: Gary Lockyer <[email protected]>
Date: Fri Jan 23 09:27:54 2026 +1300
lib:util:debug tests for JSON logging macros
Add unit tests for the new JSON debugging macros DBGJSON and DBGJSONC
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15898
Signed-off-by: Gary Lockyer <[email protected]>
Reviewed-by: Volker Lendecke <[email protected]>
commit 3c30c4740f40f633b5a71c772374e6c1283ec13c
Author: Gary Lockyer <[email protected]>
Date: Fri Jan 23 09:25:13 2026 +1300
lib:util:debug Logging macros for JSON output
Add new debug macros for outputting JSON log lines
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15898
Signed-off-by: Gary Lockyer <[email protected]>
Reviewed-by: Volker Lendecke <[email protected]>
-----------------------------------------------------------------------
Summary of changes:
WHATSNEW.txt | 6 +
lib/audit_logging/audit_logging.c | 10 +-
lib/util/debug.c | 46 ++++++
lib/util/debug.h | 13 ++
lib/util/tests/test_json_logging.c | 285 +++++++++++++++++++++++++++++++++++++
lib/util/wscript_build | 6 +
selftest/tests.py | 2 +
7 files changed, 361 insertions(+), 7 deletions(-)
create mode 100644 lib/util/tests/test_json_logging.c
Changeset truncated at 500 lines:
diff --git a/WHATSNEW.txt b/WHATSNEW.txt
index addd3a5932a..d845d16cbe7 100644
--- a/WHATSNEW.txt
+++ b/WHATSNEW.txt
@@ -16,6 +16,12 @@ UPGRADING
NEW FEATURES/CHANGES
====================
+JSON Audit logging
+------------------
+
+The two leading spaces before the opening '{' on JSON audit log lines have been
+removed. And any embedded new line characters '\n' are converted to spaces.
+
REMOVED FEATURES
================
diff --git a/lib/audit_logging/audit_logging.c
b/lib/audit_logging/audit_logging.c
index e2fe4b0edb7..58f32df1a25 100644
--- a/lib/audit_logging/audit_logging.c
+++ b/lib/audit_logging/audit_logging.c
@@ -132,14 +132,10 @@ void audit_log_json(struct json_object* message,
return;
}
/*
- * This is very strange, but we call this routine to get a log
- * output without the header. JSON logs all have timestamps
- * so this only makes parsing harder.
- *
- * We push out the raw JSON blob without a prefix, consumers
- * can find such lines by the leading {
+ * Output a single line to the logs, with no prefix
+ * or time stamps, any embedded '\n' chars will be removed
*/
- DEBUGADDC(debug_class, debug_level, ("%s\n", s));
+ DEBUGJSONC(debug_class, debug_level, (s));
TALLOC_FREE(frame);
}
diff --git a/lib/util/debug.c b/lib/util/debug.c
index f79b8811a4b..2a1620898fb 100644
--- a/lib/util/debug.c
+++ b/lib/util/debug.c
@@ -46,6 +46,10 @@
/*
* format_bufr[FORMAT_BUFR_SIZE - 1] should always be reserved
* for a terminating null byte.
+ *
+ * Note: The json logging unit tests lib/util/tests/test_json_logging.c
+ * assume this value is 4096, they'll need to be updated if
+ * this is changed
*/
#define FORMAT_BUFR_SIZE 4096
@@ -1687,6 +1691,48 @@ static void format_debug_text( const char *msg )
format_bufr[format_pos] = '\0';
}
+/***************************************************************************
+ Output a single line of JSON to the logs
+
+ Input: msg - text to be output
+
+ Output: none.
+
+ Notes: - msg is output without any added leading white space
+ - Any embedded "\n" characters are replaced with spaces
+ - A terminating "\n" is output.
+**************************************************************************/
+
+bool dbgjson( const char *msg )
+{
+ size_t i;
+ const char eol[] = "\n";
+
+ debug_init();
+
+ for( i = 0; msg[i]; i++ ) {
+ /* If the buffer is full output it */
+ if (format_pos >= FORMAT_BUFR_SIZE - 1) {
+ bufr_print();
+ }
+ /* replace any new lines with spaces*/
+ if( '\n' == msg[i] ) {
+ format_bufr[format_pos++] = ' ';
+ } else {
+ format_bufr[format_pos++] = msg[i];
+ }
+
+ }
+ if (format_pos > 0) {
+ bufr_print();
+ }
+ (void)Debug1(eol , sizeof(eol) - 1);
+
+ /* Just to be safe... */
+ format_bufr[format_pos] = '\0';
+ return true;
+}
+
/***************************************************************************
Flush debug output, including the format buffer content.
diff --git a/lib/util/debug.h b/lib/util/debug.h
index 2a56c7d48fe..6f59c7f194e 100644
--- a/lib/util/debug.h
+++ b/lib/util/debug.h
@@ -237,6 +237,19 @@ void debuglevel_set_class(size_t idx, int level);
&& (dbgsetclass(level, dbgc_class)) \
&& (dbgtext body) )
+bool dbgjson( const char *msg );
+#define DEBUGJSON( level, line) \
+ (void) ( ((level) <= MAX_DEBUG_LEVEL) && \
+ unlikely(debuglevel_get_class(DBGC_CLASS) >= (level)) \
+ && (dbgsetclass(level, DBGC_CLASS)) \
+ && (dbgjson line) )
+
+#define DEBUGJSONC( dbgc_class, level, line) \
+ (void)( ((level) <= MAX_DEBUG_LEVEL) && \
+ unlikely((debuglevel_get_class(dbgc_class) >= (level))) \
+ && (dbgsetclass(level, dbgc_class)) \
+ && (dbgjson line) )
+
/* Print a separator to the debug log. */
#define DEBUGSEP(level)\
DEBUG((level),("===============================================================\n"))
diff --git a/lib/util/tests/test_json_logging.c
b/lib/util/tests/test_json_logging.c
new file mode 100644
index 00000000000..42e6206a041
--- /dev/null
+++ b/lib/util/tests/test_json_logging.c
@@ -0,0 +1,285 @@
+/*
+ * cmocka unit tests for the DEBUGJSON and DEBUGJSONC debug macros
+ *
+ * Copyright (C) Gary Lockyer 2026 <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * from cmocka.c:
+ * These headers or their equivalents should be included prior to
+ * including
+ * this header file.
+ *
+ * #include <stdarg.h>
+ * #include <stddef.h>
+ * #include <setjmp.h>
+ *
+ * This allows test applications to use custom definitions of C standard
+ * library functions and types.
+ *
+ */
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "lib/util/debug.h"
+#include <talloc.h>
+#include <string.h>
+
+#define MAX_CALLS 4
+
+/*
+ * This needs to be the same as the value set in lib/util/debug.c
+ */
+#define FORMAT_BUFR_SIZE 4096
+
+#define FORMAT_STRING_SIZE (FORMAT_BUFR_SIZE - 1)
+
+/*
+ * Test context
+ */
+struct test_ctx {
+ size_t calls; /* number of times callback fn called */
+ char **data; /* data passed in each call */
+};
+
+/*
+* debug logging call back function.
+*
+* NOTE: The debug code calling this function debug.c:debug_callback_log
+* will replace a trailing '\n' with a '\0'
+*/
+static void debug_callback(void *state, int level, const char *msg) {
+ struct test_ctx *test_ctx = talloc_get_type_abort(state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ assert_int_not_equal(MAX_CALLS, test_ctx->calls);
+ test_ctx->data[test_ctx->calls] = talloc_strdup(test_ctx, msg);
+ assert_non_null(test_ctx->data[test_ctx->calls]);
+
+ test_ctx->calls++;
+}
+
+/*
+* Test set-up.
+*
+* creates and initializes the test context
+*/
+static int setup(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->data = talloc_array(test_ctx, char *, MAX_CALLS);
+
+ debug_set_callback(test_ctx, debug_callback);
+ debuglevel_set_class(DBGC_ALL, DBGLVL_NOTICE);
+ debuglevel_set_class(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO);
+
+ *state = test_ctx;
+ return 0;
+}
+
+/*
+* Test clean up
+*
+* deallocate any memory used by the test context
+*
+*/
+static int teardown(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+
+ talloc_free(test_ctx);
+ return 0;
+}
+/*
+* Test DEBUGJSONC with an empty message
+*/
+static void test_empty_message(void **state)
+{
+ const char *message = "";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO, (message));
+ assert_int_equal(1, test_ctx->calls);
+ assert_string_equal("", test_ctx->data[0]);
+}
+
+/*
+* Test DEBUGJSONC with a short message
+*/
+static void test_short_message(void **state)
+{
+ const char *message = "A message";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO, (message));
+ assert_int_equal(2, test_ctx->calls);
+ assert_string_equal(message, test_ctx->data[0]);
+ assert_string_equal("", test_ctx->data[1]);
+}
+
+/*
+* Test DEBUGJSONC honours the debug class levels
+*/
+static void test_log_class(void **state)
+{
+ const char *message = "A message";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT, DBGLVL_INFO, (message));
+ assert_int_equal(0, test_ctx->calls);
+}
+
+/*
+* Test DEBUGJSONC honours its debug level
+*/
+static void test_log_level(void **state)
+{
+ const char *message = "A message";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_DEBUG, (message));
+ assert_int_equal(0, test_ctx->calls);
+}
+/*
+* Test DEBUGJSONC with a message larger than the buffer.
+*/
+static void test_large_message(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ char *message = talloc_zero_size(test_ctx, 5112);
+ memset(message, 'x', 5111);
+
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO, (message));
+ assert_int_equal(3, test_ctx->calls);
+ assert_int_equal(4095, strlen(test_ctx->data[0]));
+ assert_int_equal(1016, strlen(test_ctx->data[1]));
+ assert_int_equal(5111, (4095+1016));
+ assert_string_equal("", test_ctx->data[2]);
+}
+
+/*
+* Test DEBUGJSONC with a message equal to the buffer size.
+*/
+static void test_buffer_size_message(void **state)
+{
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ char *message = talloc_zero_size(test_ctx, FORMAT_BUFR_SIZE);
+ memset(message, 'x', FORMAT_STRING_SIZE);
+
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO, (message));
+ assert_int_equal(2, test_ctx->calls);
+ assert_int_equal(FORMAT_STRING_SIZE, strlen(test_ctx->data[0]));
+ assert_string_equal("", test_ctx->data[1]);
+}
+
+/*
+* Test DEBUGJSONC replaces '\n' with spaces
+*/
+static void test_embedded_new_lines(void **state)
+{
+ const char *message = "A \nmessage\n with new lines\n";
+ const char *expected_message = "A message with new lines ";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSONC(DBGC_DSDB_AUDIT_JSON, DBGLVL_INFO, (message));
+ assert_int_equal(2, test_ctx->calls);
+ assert_string_equal(expected_message, test_ctx->data[0]);
+ assert_string_equal("", test_ctx->data[1]);
+}
+
+/*
+* Test DEBUGJSON with a short message
+*/
+static void test_debugjson(void **state)
+{
+ const char *message = "A message";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSON(DBGLVL_NOTICE, (message));
+ assert_int_equal(2, test_ctx->calls);
+ assert_string_equal(message, test_ctx->data[0]);
+ assert_string_equal("", test_ctx->data[1]);
+}
+
+/*
+* Test DEBUGJSON honours the DBGC_ALL log level
+*/
+static void test_debugjson_log_level(void **state)
+{
+ const char *message = "A message";
+ struct test_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct test_ctx);
+ assert_non_null(test_ctx);
+
+ DEBUGJSON(DBGLVL_INFO, (message));
+ assert_int_equal(0, test_ctx->calls);
+}
+
+int main(int argc, const char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_empty_message, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_short_message, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_large_message, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_buffer_size_message, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_embedded_new_lines, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_log_class, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_log_level, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_debugjson, setup, teardown),
+ cmocka_unit_test_setup_teardown(
+ test_debugjson_log_level, setup, teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+
+}
diff --git a/lib/util/wscript_build b/lib/util/wscript_build
index 21a94f60cac..0f25fbd2160 100644
--- a/lib/util/wscript_build
+++ b/lib/util/wscript_build
@@ -399,6 +399,12 @@ else:
local_include=False,
for_selftest=True)
+ bld.SAMBA_BINARY('test_json_logging',
+ source='tests/test_json_logging.c',
+ deps='cmocka samba-util',
+ local_include=False,
+ for_selftest=True)
+
bld.SAMBA_BINARY('test_s4_logging',
source='tests/test_logging.c',
deps=' '.join(['CMDLINE_S4',
diff --git a/selftest/tests.py b/selftest/tests.py
index 7eace3cbced..0695ac67ea1 100644
--- a/selftest/tests.py
+++ b/selftest/tests.py
@@ -556,6 +556,8 @@ plantestsuite("samba.unittests.memcache", "none",
[os.path.join(bindir(), "default/lib/util/test_memcache")])
plantestsuite("samba.unittests.sys_rw", "none",
[os.path.join(bindir(), "default/lib/util/test_sys_rw")])
+plantestsuite("samba.unittests.json_logging", "none",
+ [os.path.join(bindir(), "default/lib/util/test_json_logging")])
plantestsuite("samba.unittests.stable_sort", "none",
[os.path.join(bindir(), "default/lib/util/test_stable_sort")])
plantestsuite("samba.unittests.ntlm_check", "none",
--
Samba Shared Repository