diff --git a/include/zephyr/logging/log_backend_mqtt.h b/include/zephyr/logging/log_backend_mqtt.h new file mode 100644 index 00000000000..4608e3647b2 --- /dev/null +++ b/include/zephyr/logging/log_backend_mqtt.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Arif Balik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_LOGGING_LOG_BACKEND_MQTT_H_ +#define ZEPHYR_INCLUDE_LOGGING_LOG_BACKEND_MQTT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MQTT log backend API + * @defgroup log_backend_mqtt MQTT log backend API + * @ingroup log_backend + * @{ + */ + +/** + * @brief Set the MQTT client instance to be able to publish application's log messages to broker. + * + * This function allows the application to provide its own initialized + * MQTT client to the log backend. The backend will use this client + * exclusively for publishing log messages via mqtt_publish(). + * + * @param client Pointer to an initialized and connected MQTT client. + * The client must remain valid for the lifetime of the + * log backend usage. Pass NULL to disable MQTT logging. + * + * @return 0 on success, negative error code on failure. + * + * @note The MQTT client must be connected before calling this function. + * @note The backend will not manage the client connection - this is the + * responsibility of the application. + * @note The backend will only use mqtt_publish() and will not perform + * any other operations on the client. + */ +int log_backend_mqtt_client_set(struct mqtt_client *client); + +/** + * @brief Set the MQTT topic to which log messages will be published. + * + * Allows the application to specify the MQTT topic that the log backend + * will use for publishing log messages to. + * + * @param topic Pointer to a null-terminated string containing the MQTT topic. + * The topic must remain valid for the lifetime of the log backend usage. + * + * @return 0 on success, negative error code on failure. + * + * @note The topic must be a valid UTF-8 string, null-terminated and should not exceed + * the maximum length supported by the MQTT broker. + */ +int log_backend_mqtt_topic_set(const char *topic); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_LOGGING_LOG_BACKEND_MQTT_H_ */ diff --git a/subsys/logging/backends/CMakeLists.txt b/subsys/logging/backends/CMakeLists.txt index d7090e7c5f7..0ea59ce481a 100644 --- a/subsys/logging/backends/CMakeLists.txt +++ b/subsys/logging/backends/CMakeLists.txt @@ -25,6 +25,11 @@ zephyr_sources_ifdef( log_backend_fs.c ) +zephyr_sources_ifdef( + CONFIG_LOG_BACKEND_MQTT + log_backend_mqtt.c +) + zephyr_sources_ifdef( CONFIG_LOG_BACKEND_NATIVE_POSIX log_backend_native_posix.c diff --git a/subsys/logging/backends/Kconfig b/subsys/logging/backends/Kconfig index 0368f369b08..98d3217e75f 100644 --- a/subsys/logging/backends/Kconfig +++ b/subsys/logging/backends/Kconfig @@ -8,6 +8,7 @@ rsource "Kconfig.adsp_mtrace" rsource "Kconfig.ble" rsource "Kconfig.efi_console" rsource "Kconfig.fs" +rsource "Kconfig.mqtt" rsource "Kconfig.native_posix" rsource "Kconfig.net" rsource "Kconfig.ws" diff --git a/subsys/logging/backends/Kconfig.mqtt b/subsys/logging/backends/Kconfig.mqtt new file mode 100644 index 00000000000..bd2c695572c --- /dev/null +++ b/subsys/logging/backends/Kconfig.mqtt @@ -0,0 +1,49 @@ +# Copyright (c) 2024 Arif Balik +# SPDX-License-Identifier: Apache-2.0 + +config LOG_BACKEND_MQTT + bool "MQTT backend" + depends on MQTT_LIB && !LOG_MODE_IMMEDIATE + help + Send log messages to an MQTT broker using an external MQTT client. + This backend publishes log messages to a configurable MQTT topic + using an MQTT client provided by the application via the + log_backend_mqtt_client_set() API. The application is responsible + for initializing, connecting, and managing the MQTT client lifecycle. + +if LOG_BACKEND_MQTT + +config LOG_BACKEND_MQTT_TOPIC_DEFAULT + string "Default MQTT topic for log messages" + default "zephyr/logs" + help + Default MQTT topic for log messages. Use log backend MQTT API to change it at runtime. + +config LOG_BACKEND_MQTT_QOS + int "MQTT Quality of Service level" + default 0 + range 0 2 + help + QoS level for published log messages: + 0 - At most once delivery (fire and forget) + 1 - At least once delivery (acknowledged delivery) + 2 - Exactly once delivery (assured delivery) + +config LOG_BACKEND_MQTT_RETAIN + bool "Retain MQTT messages" + help + When enabled, published log messages will be retained by the broker + and delivered to new subscribers immediately upon subscription. + +config LOG_BACKEND_MQTT_MAX_MSG_SIZE + int "Maximum log message size" + default 256 + range 64 1024 + help + Maximum size of a single log message in bytes. + +backend = MQTT +backend-str = mqtt +source "subsys/logging/Kconfig.template.log_format_config" + +endif # LOG_BACKEND_MQTT diff --git a/subsys/logging/backends/log_backend_mqtt.c b/subsys/logging/backends/log_backend_mqtt.c new file mode 100644 index 00000000000..03c1218183a --- /dev/null +++ b/subsys/logging/backends/log_backend_mqtt.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2024 Arif Balik + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(log_backend_mqtt, CONFIG_LOG_DEFAULT_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static bool panic; +static char *mqtt_topic = CONFIG_LOG_BACKEND_MQTT_TOPIC_DEFAULT; +static uint8_t log_buf[CONFIG_LOG_BACKEND_MQTT_MAX_MSG_SIZE]; +static uint32_t log_format_current = CONFIG_LOG_BACKEND_MQTT_OUTPUT_DEFAULT; + +static int log_output_func(uint8_t *data, size_t length, void *output_ctx) +{ + __ASSERT(output_ctx != NULL, "Output context must not be NULL"); + + struct mqtt_client *client = (struct mqtt_client *)output_ctx; + struct mqtt_publish_param param = {0}; + + param.message.topic.topic.utf8 = (uint8_t *)mqtt_topic; + param.message.topic.topic.size = strlen(mqtt_topic); + param.message.topic.qos = CONFIG_LOG_BACKEND_MQTT_QOS; + param.message.payload.data = data; + param.message.payload.len = length; + param.retain_flag = IS_ENABLED(CONFIG_LOG_BACKEND_MQTT_RETAIN); + +#if (CONFIG_LOG_BACKEND_MQTT_QOS > MQTT_QOS_0_AT_MOST_ONCE) + param.message_id = sys_rand32_get(); +#endif + + int ret = mqtt_publish(client, ¶m); + + if (ret != 0) { + return ret; + } + + return length; +} + +LOG_OUTPUT_DEFINE(log_output_mqtt, log_output_func, log_buf, sizeof(log_buf)); + +static void mqtt_backend_process(const struct log_backend *const backend, + union log_msg_generic *msg) +{ + if (panic) { + return; + } + + uint32_t flags = log_backend_std_get_flags(); + + log_format_func_t log_output_func = log_format_func_t_get(log_format_current); + + log_output_ctx_set(&log_output_mqtt, backend->cb->ctx); + + log_output_func(&log_output_mqtt, &msg->log, flags); +} + +static int mqtt_backend_format_set(const struct log_backend *const backend, uint32_t log_type) +{ + ARG_UNUSED(backend); + log_format_current = log_type; + return 0; +} + +static void mqtt_backend_panic(const struct log_backend *const backend) +{ + ARG_UNUSED(backend); + panic = true; +} + +const struct log_backend_api log_backend_mqtt_api = { + .process = mqtt_backend_process, + .format_set = mqtt_backend_format_set, + .panic = mqtt_backend_panic, +}; + +LOG_BACKEND_DEFINE(log_backend_mqtt, log_backend_mqtt_api, false); + +int log_backend_mqtt_client_set(struct mqtt_client *client) +{ + log_backend_disable(&log_backend_mqtt); + + if (client != NULL) { + log_backend_enable(&log_backend_mqtt, (void *)client, CONFIG_LOG_MAX_LEVEL); + } + + return 0; +} + +int log_backend_mqtt_topic_set(const char *topic) +{ + if (topic == NULL || strlen(topic) == 0) { + return -EINVAL; + } + + mqtt_topic = topic; + + return 0; +}