assert: Add an option for a custom assert header

Similar to logging and shell, allow application to override and
extend the ASSERT macros globally. This enables intercepting log strings
at the macro level for things like string tokenizations.

Signed-off-by: Kevin Zeng <zengk@google.com>
diff --git a/doc/kernel/services/other/fatal.rst b/doc/kernel/services/other/fatal.rst
index dc34ef3..8fe0f9f 100644
--- a/doc/kernel/services/other/fatal.rst
+++ b/doc/kernel/services/other/fatal.rst
@@ -117,6 +117,13 @@
 information provided to assist the user in diagnosing the problem; its use is
 discouraged.
 
+Custom Header
+=============
+
+In some cases, asserts need to be redirected at the macro level. For these cases,
+:kconfig:option:`CONFIG_ASSERT_CUSTOM_HEADER` can be used to inject an application provided
+header named ``zephyr_custom_assert.h`` at the end of :zephyr_file:`include/zephyr/sys/__assert.h`.
+
 Build Assertions
 ================
 
diff --git a/include/zephyr/sys/__assert.h b/include/zephyr/sys/__assert.h
index 9f1ab97..b6b1482 100644
--- a/include/zephyr/sys/__assert.h
+++ b/include/zephyr/sys/__assert.h
@@ -149,4 +149,9 @@
 #define __ASSERT_POST_ACTION() { }
 #endif
 
+#ifdef CONFIG_ASSERT_CUSTOM_HEADER
+/* This include must always be at the end of __assert.h */
+#include <zephyr_custom_assert.h>
+#endif
+
 #endif /* ZEPHYR_INCLUDE_SYS___ASSERT_H_ */
diff --git a/subsys/debug/Kconfig b/subsys/debug/Kconfig
index a5a80bd..ef4e50e 100644
--- a/subsys/debug/Kconfig
+++ b/subsys/debug/Kconfig
@@ -215,6 +215,16 @@
 	  the lock has been held is less than the configured value. Requires
 	  the timer driver sys_clock_get_cycles_32() be lock free.
 
+config ASSERT_CUSTOM_HEADER
+	bool "Include Custom Assert Header [EXPERIMENTAL]"
+	select EXPERIMENTAL
+	help
+	  When enabled, a custom application provided header, named
+	  "zephyr_custom_assert.h", is included at the end of __assert.h. This enables
+	  extension of the assert APIs at the macro level. Please use cautiously!
+	  The internal assert API may change in future releases.
+
+
 endif # ASSERT
 
 config FORCE_NO_ASSERT
diff --git a/tests/subsys/debug/assert_custom_header/CMakeLists.txt b/tests/subsys/debug/assert_custom_header/CMakeLists.txt
new file mode 100644
index 0000000..45663b6
--- /dev/null
+++ b/tests/subsys/debug/assert_custom_header/CMakeLists.txt
@@ -0,0 +1,10 @@
+#SPDX - License - Identifier : Apache - 2.0
+
+cmake_minimum_required(VERSION 3.20.0)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+project(assert_custom_header)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
+
+zephyr_include_directories(src)
diff --git a/tests/subsys/debug/assert_custom_header/prj.conf b/tests/subsys/debug/assert_custom_header/prj.conf
new file mode 100644
index 0000000..b5e182f
--- /dev/null
+++ b/tests/subsys/debug/assert_custom_header/prj.conf
@@ -0,0 +1,5 @@
+CONFIG_ASSERT=y
+CONFIG_ASSERT_CUSTOM_HEADER=y
+CONFIG_LOG=n
+CONFIG_ZTEST=y
+CONFIG_TEST_LOGGING_DEFAULTS=n
diff --git a/tests/subsys/debug/assert_custom_header/src/main.c b/tests/subsys/debug/assert_custom_header/src/main.c
new file mode 100644
index 0000000..ac2720d
--- /dev/null
+++ b/tests/subsys/debug/assert_custom_header/src/main.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025 Google, Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** @file
+ *  @brief Custom header assert test suite
+ *
+ */
+
+#include <zephyr/kernel.h>
+#include <zephyr/ztest.h>
+
+#include <zephyr/sys/__assert.h>
+
+static unsigned int last_assert_line;
+
+void assert_post_action(const char *file, unsigned int line)
+{
+	last_assert_line = line;
+}
+
+ZTEST_SUITE(assert, NULL, NULL, NULL, NULL, NULL);
+
+ZTEST(assert, test_assert_call)
+{
+	/* Because CONFIG_ASSERT_TEST is not enabled in this test, and we're using a
+	 * custom assert macro, this should not crash and the test should proceed.
+	 */
+	__ASSERT(false, "This is a custom assert test");
+
+	zassert_true(last_assert_line != 0, "Assert was called on line 0");
+}
diff --git a/tests/subsys/debug/assert_custom_header/src/zephyr_custom_assert.h b/tests/subsys/debug/assert_custom_header/src/zephyr_custom_assert.h
new file mode 100644
index 0000000..ccd5512
--- /dev/null
+++ b/tests/subsys/debug/assert_custom_header/src/zephyr_custom_assert.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2025 Google, Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef __ZEPHYR_CUSTOM_ASSERT_H
+#define __ZEPHYR_CUSTOM_ASSERT_H
+
+#undef __ASSERT
+
+#define __ASSERT(test, fmt, ...)                                                                   \
+	do {                                                                                       \
+		if (!(test)) {                                                                     \
+			__ASSERT_POST_ACTION();                                                    \
+		}                                                                                  \
+	} while (false)
+
+#endif /* __ZEPHYR_CUSTOM_ASSERT_H */
diff --git a/tests/subsys/debug/assert_custom_header/testcase.yaml b/tests/subsys/debug/assert_custom_header/testcase.yaml
new file mode 100644
index 0000000..70bb040
--- /dev/null
+++ b/tests/subsys/debug/assert_custom_header/testcase.yaml
@@ -0,0 +1,9 @@
+common:
+  integration_platforms:
+    - native_sim
+
+tests:
+  debug.assert_custom_header:
+    tags:
+      - assert_custom_header
+      - assert