logging: add mechanism for handling transient strings
Extending logger to support logging transient strings (with %s).
With dedicated call (log_strdup), string is duplicated to a buffer
from internal logger pool. Logger implicitly manages the pool.
Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
diff --git a/doc/subsystems/logging/logger.rst b/doc/subsystems/logging/logger.rst
index 46f402d..dcf475e 100644
--- a/doc/subsystems/logging/logger.rst
+++ b/doc/subsystems/logging/logger.rst
@@ -19,6 +19,7 @@
- Additional run time filtering on module instance level.
- Timestamping with user provided function.
- Dedicated API for dumping data
+- Dedicated API for handling transient strings
- Panic support - in panic mode logger switches to blocking, in-place
processing.
- Printk support - printk message can be redirected to the logger.
@@ -120,6 +121,12 @@
message pool. Single message capable of storing standard log with up to 3
arguments or hexdump message with 12 bytes of data take 32 bytes.
+:option:`CONFIG_LOG_STRDUP_MAX_STRING`: Longest string that can be duplicated
+using log_strdup().
+
+:option:`CONFIG_LOG_STRDUP_BUF_COUNT`: Number of buffers in the pool used by
+log_strdup().
+
:option:`CONFIG_LOG_DOMAIN_ID`: Domain ID. Valid in multi-domain systems.
:option:`CONFIG_LOG_BACKEND_UART`: Enabled build-in UART backend.
@@ -347,6 +354,30 @@
is considered processed by the logger, but the message may still be in use by a
backend.
+.. _logger_strings:
+
+Logging strings
+===============
+Logger stores the address of a log message string argument passed to it. Because
+a string variable argument could be transient, allocated on the stack, or
+modifiable, logger provides a mechanism and a dedicated buffer pool to hold
+copies of strings. The buffer size and count is configurable
+(see :option:`CONFIG_LOG_STRDUP_MAX_STRING` and
+:option:`CONFIG_LOG_STRDUP_BUF_COUNT`).
+
+If a string argument is transient, the user must call cpp:func:`log_strdup` to
+duplicate the passed string into a buffer from the pool. See the examples below.
+If a strdup buffer cannot be allocated, a warning message is logged and an error
+code returned indicating :option:`CONFIG_LOG_STRDUP_BUF_COUNT` should be
+increased. Buffers are freed together with the log message.
+
+.. code-block:: c
+
+ char local_str[] = "abc";
+
+ LOG_INF("logging transient string: %s", log_strdup(local_str));
+ local_str[0] = '\0'; /* String can be modified, logger will use duplicate."
+
Logger backends
===============
@@ -403,17 +434,12 @@
Limitations
***********
-The Logger architecture implies following limitations:
+The Logger architecture has the following limitations:
-- Using *%s* for strings which content may be changed before log is processed
- e.g. strings allocated on stack because logger is storing only argument value
- and does not perform any string analysis to detect that argument is a
- pointer. It is recommended to use hexdump in that case. Optionally, user can
- enable in place processing :option:`CONFIG_LOG_INPLACE_PROCESS`. However,
- this feature has many limitations and is not recommended when logger is used
- in multiple contexts.
+- Strings as arguments (*%s*) require special treatment (see
+ :ref:`logger_strings`).
- Logging double floating point variables is not possible because arguments are
32 bit values.
-- Number of arguments in the string is limited to 6.
+- Number of arguments in the string is limited to 9.
diff --git a/include/logging/log.h b/include/logging/log.h
index 4dd8d76..8491bfa 100644
--- a/include/logging/log.h
+++ b/include/logging/log.h
@@ -255,6 +255,19 @@
*/
int log_printk(const char *fmt, va_list ap);
+/** @brief Copy transient string to a buffer from internal, logger pool.
+ *
+ * Function should be used when transient string is intended to be logged.
+ * Logger allocates a buffer and copies input string returning a pointer to the
+ * copy. Logger ensures that buffer is freed when logger message is freed.
+ *
+ * @param str Transient string.
+ *
+ * @return Copy of the string or default string if buffer could not be
+ * allocated. String may be truncated if input string does not fit in
+ * a buffer from the pool (see CONFIG_LOG_STRDUP_MAX_STRING).
+ */
+char *log_strdup(char *str);
#define __DYNAMIC_MODULE_REGISTER(_name)\
struct log_source_dynamic_data LOG_ITEM_DYNAMIC_DATA(_name) \
diff --git a/include/logging/log_core.h b/include/logging/log_core.h
index 03986b0..6cfd8b5 100644
--- a/include/logging/log_core.h
+++ b/include/logging/log_core.h
@@ -472,6 +472,20 @@
*/
void log_generic(struct log_msg_ids src_level, const char *fmt, va_list ap);
+/** @brief Check if address belongs to the memory pool used for transient.
+ *
+ * @param buf Buffer.
+ *
+ * @return True if address within the pool, false otherwise.
+ */
+bool log_is_strdup(void *buf);
+
+/** @brief Free allocated buffer.
+ *
+ * @param buf Buffer.
+ */
+void log_free(void *buf);
+
#ifdef __cplusplus
}
#endif
diff --git a/include/logging/log_msg.h b/include/logging/log_msg.h
index 6d99c4a..6b0dd37 100644
--- a/include/logging/log_msg.h
+++ b/include/logging/log_msg.h
@@ -23,7 +23,7 @@
*/
/** @brief Maximum number of arguments in the standard log entry. */
-#define LOG_MAX_NARGS 6
+#define LOG_MAX_NARGS 9
/** @brief Number of arguments in the log entry which fits in one chunk.*/
#define LOG_MSG_NARGS_SINGLE_CHUNK 3
diff --git a/samples/subsys/logging/logger/src/main.c b/samples/subsys/logging/logger/src/main.c
index d84815c..73e8fbd 100644
--- a/samples/subsys/logging/logger/src/main.c
+++ b/samples/subsys/logging/logger/src/main.c
@@ -152,6 +152,24 @@
}
/**
+ * @brief Function demonstrates how transient strings can be logged.
+ *
+ * Logger ensures that allocated buffers are freed when log message is
+ * processed.
+ */
+static void log_strdup_showcase(void)
+{
+ char transient_str[] = "transient_string";
+
+ printk("String logging showcase.\n");
+
+ LOG_INF("Logging transient string:%s", log_strdup(transient_str));
+
+ /* Overwrite transient string to show that the logger has a copy. */
+ transient_str[0] = '\0';
+}
+
+/**
* @brief Function demonstrates how fast data can be logged.
*
* Messages are logged and counted in a loop for 2 ticks (same clock source as
@@ -236,6 +254,10 @@
severity_levels_showcase();
+ log_strdup_showcase();
+
+ severity_levels_showcase();
+
wait_on_log_flushed();
performance_showcase();
diff --git a/subsys/logging/Kconfig b/subsys/logging/Kconfig
index b427ce6..55ed07e 100644
--- a/subsys/logging/Kconfig
+++ b/subsys/logging/Kconfig
@@ -256,6 +256,20 @@
help
Number of bytes dedicated for the logger internal buffer.
+config LOG_STRDUP_MAX_STRING
+ int "Longest string that can be duplicated using log_strdup()"
+ default 46 if NETWORKING
+ default 32
+ help
+ Longer strings are truncated.
+
+config LOG_STRDUP_BUF_COUNT
+ int "Number of buffers in the pool used by log_strdup()"
+ default 4
+ help
+ When pool is empty log_strdup() returns warning string instead of the
+ copy.
+
config LOG_DOMAIN_ID
int "Domain ID"
default 0
diff --git a/subsys/logging/log_core.c b/subsys/logging/log_core.c
index 1586e19..56f424f 100644
--- a/subsys/logging/log_core.c
+++ b/subsys/logging/log_core.c
@@ -17,6 +17,20 @@
#define CONFIG_LOG_PRINTK_MAX_STRING_LENGTH 1
#endif
+#define LOG_STRBUF_STR_SIZE \
+ (CONFIG_LOG_STRDUP_MAX_STRING + 1) /* additional byte for termination */
+
+#define LOG_STRBUF_BUF_SIZE \
+ ROUND_UP(LOG_STRBUF_STR_SIZE + 1, sizeof(u32_t))
+
+#define LOG_STRDUP_POOL_BUFFER_SIZE \
+ (LOG_STRBUF_BUF_SIZE * CONFIG_LOG_STRDUP_BUF_COUNT)
+
+static const char *log_strdup_fail_msg = "log_strdup pool empty!";
+struct k_mem_slab log_strdup_pool;
+static u8_t __noinit __aligned(sizeof(u32_t))
+ log_strdup_pool_buf[LOG_STRDUP_POOL_BUFFER_SIZE];
+
static struct log_list_t list;
static atomic_t initialized;
static bool panic_mode;
@@ -242,6 +256,10 @@
return;
}
+ k_mem_slab_init(&log_strdup_pool, log_strdup_pool_buf,
+ CONFIG_LOG_STRDUP_MAX_STRING,
+ CONFIG_LOG_STRDUP_BUF_COUNT);
+
/* Set default timestamp. */
timestamp_func = timestamp_get;
log_output_timestamp_freq_set(CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC);
@@ -482,6 +500,55 @@
}
}
+char *log_strdup(char *str)
+{
+ u32_t *dupl;
+ char *sdupl;
+ int err;
+
+ err = k_mem_slab_alloc(&log_strdup_pool, (void **)&dupl, K_NO_WAIT);
+ if (err) {
+ /* failed to allocate */
+ return (char *)log_strdup_fail_msg;
+ }
+
+ /* Set 'allocated' flag. */
+ *dupl = 1;
+ dupl++;
+ sdupl = (char *)dupl;
+
+ strncpy(sdupl, str, CONFIG_LOG_STRDUP_MAX_STRING - 1);
+ sdupl[LOG_STRBUF_STR_SIZE - 1] = '\0';
+ sdupl[LOG_STRBUF_STR_SIZE - 2] = '~';
+
+ return sdupl;
+}
+
+bool log_is_strdup(void *buf)
+{
+ /* Lowest possible address is located at the second word of the first
+ * buffer in the pool. First word is dedicated for 'allocated' flag.
+ *
+ * Highest possible address is the second word of the last buffer in the
+ * pool.
+ */
+ static const void *start = log_strdup_pool_buf + sizeof(u32_t);
+ static const void *end = &log_strdup_pool_buf[LOG_STRDUP_POOL_BUFFER_SIZE
+ - LOG_STRBUF_BUF_SIZE
+ + sizeof(u32_t)];
+ return (buf >= start) && (buf <= end);
+}
+
+void log_free(void *str)
+{
+ u32_t *buf = (u32_t *)str;
+
+ buf--;
+ if (atomic_dec((atomic_t *)buf) == 1) {
+ k_mem_slab_free(&log_strdup_pool, (void **)&buf);
+ }
+}
+
#ifdef CONFIG_LOG_PROCESS_THREAD
static void log_process_thread_func(void *dummy1, void *dummy2, void *dummy3)
{
diff --git a/subsys/logging/log_msg.c b/subsys/logging/log_msg.c
index 45ec86b..02c8aea 100644
--- a/subsys/logging/log_msg.c
+++ b/subsys/logging/log_msg.c
@@ -6,6 +6,7 @@
#include <kernel.h>
#include <logging/log_msg.h>
#include <logging/log_ctrl.h>
+#include <logging/log_core.h>
#include <string.h>
#define MSG_SIZE sizeof(union log_msg_chunk)
@@ -38,9 +39,25 @@
static void msg_free(struct log_msg *msg)
{
+ u32_t nargs = msg->hdr.params.std.nargs;
+
+ /* Free any transient string found in arguments. */
+ if (log_msg_is_std(msg) && nargs) {
+ int i;
+
+ for (i = 0; i < nargs; i++) {
+ void *buf = (void *)log_msg_arg_get(msg, i);
+
+ if (log_is_strdup(buf)) {
+ log_free(buf);
+ }
+ }
+ }
+
if (msg->hdr.params.generic.ext == 1) {
cont_free(msg->payload.ext.next);
}
+
k_mem_slab_free(&log_msg_pool, (void **)&msg);
}
diff --git a/tests/subsys/logging/log_core/prj.conf b/tests/subsys/logging/log_core/prj.conf
index a851e28..519b8c2 100644
--- a/tests/subsys/logging/log_core/prj.conf
+++ b/tests/subsys/logging/log_core/prj.conf
@@ -4,6 +4,6 @@
CONFIG_LOG_PRINTK=n
CONFIG_LOG_RUNTIME_FILTERING=y
CONFIG_LOG_BACKEND_UART=n
-CONFIG_LOG_BUFFER_SIZE=1024
+CONFIG_LOG_BUFFER_SIZE=512
CONFIG_LOG_INPLACE_PROCESS=n
CONFIG_LOG_PROCESS_THREAD=y
\ No newline at end of file