diff --git a/subsys/logging/backends/CMakeLists.txt b/subsys/logging/backends/CMakeLists.txt index 7ab40c8cb30..d7090e7c5f7 100644 --- a/subsys/logging/backends/CMakeLists.txt +++ b/subsys/logging/backends/CMakeLists.txt @@ -35,6 +35,11 @@ zephyr_sources_ifdef( log_backend_net.c ) +zephyr_sources_ifdef( + CONFIG_LOG_BACKEND_WS + log_backend_ws.c +) + zephyr_sources_ifdef( CONFIG_LOG_BACKEND_RTT log_backend_rtt.c diff --git a/subsys/logging/backends/Kconfig b/subsys/logging/backends/Kconfig index 960aeb20c2a..0368f369b08 100644 --- a/subsys/logging/backends/Kconfig +++ b/subsys/logging/backends/Kconfig @@ -10,6 +10,7 @@ rsource "Kconfig.efi_console" rsource "Kconfig.fs" rsource "Kconfig.native_posix" rsource "Kconfig.net" +rsource "Kconfig.ws" rsource "Kconfig.rtt" rsource "Kconfig.spinel" rsource "Kconfig.swo" diff --git a/subsys/logging/backends/Kconfig.ws b/subsys/logging/backends/Kconfig.ws new file mode 100644 index 00000000000..497cacac74b --- /dev/null +++ b/subsys/logging/backends/Kconfig.ws @@ -0,0 +1,44 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config LOG_BACKEND_WS + bool "Websocket backend" + depends on WEBSOCKET_CONSOLE + select LOG_OUTPUT + default y + help + Send console messages to websocket console. + +if LOG_BACKEND_WS + +config LOG_BACKEND_WS_MAX_BUF_SIZE + int "Max message size" + range 64 1500 + default 512 + help + Maximum size of the output string that is sent via websocket. + +config LOG_BACKEND_WS_TX_RETRY_CNT + int "Number of TX retries" + default 2 + help + Number of TX retries before dropping the full line of data. + +config LOG_BACKEND_WS_TX_RETRY_DELAY_MS + int "Delay between TX retries in milliseconds" + default 50 + help + Sleep period between TX retry attempts. + +config LOG_BACKEND_WS_AUTOSTART + bool "Automatically start websocket backend" + default y if NET_CONFIG_NEED_IPV4 || NET_CONFIG_NEED_IPV6 + help + When enabled automatically start the websocket backend on + application start. + +backend = WS +backend-str = websocket +source "subsys/logging/Kconfig.template.log_format_config" + +endif # LOG_BACKEND_WS diff --git a/subsys/logging/backends/log_backend_ws.c b/subsys/logging/backends/log_backend_ws.c new file mode 100644 index 00000000000..03dc995d48d --- /dev/null +++ b/subsys/logging/backends/log_backend_ws.c @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(log_backend_ws, CONFIG_LOG_DEFAULT_LEVEL); + +#include +#include +#include +#include +#include +#include +#include + +/* Set this to 1 if you want to see what is being sent to server */ +#define DEBUG_PRINTING 0 + +#define DBG(fmt, ...) IF_ENABLED(DEBUG_PRINTING, (printk(fmt, ##__VA_ARGS__))) + +static bool ws_init_done; +static bool panic_mode; +static uint32_t log_format_current = CONFIG_LOG_BACKEND_WS_OUTPUT_DEFAULT; +static uint8_t output_buf[CONFIG_LOG_BACKEND_WS_MAX_BUF_SIZE]; +static size_t pos; + +static struct log_backend_ws_ctx { + int sock; +} ctx = { + .sock = -1, +}; + +static void wait(void) +{ + k_msleep(CONFIG_LOG_BACKEND_WS_TX_RETRY_DELAY_MS); +} + +static int ws_send_all(int sock, const char *output, size_t len) +{ + int ret; + + while (len > 0) { + ret = zsock_send(sock, output, len, ZSOCK_MSG_DONTWAIT); + if ((ret < 0) && (errno == EAGAIN)) { + return -EAGAIN; + } + + if (ret < 0) { + ret = -errno; + return ret; + } + + output += ret; + len -= ret; + } + + return 0; +} + +static int ws_console_out(struct log_backend_ws_ctx *ctx, int c) +{ + static int max_cnt = CONFIG_LOG_BACKEND_WS_TX_RETRY_CNT; + bool printnow = false; + unsigned int cnt = 0; + int ret; + + if (pos >= (sizeof(output_buf) - 1)) { + printnow = true; + } else { + if ((c != '\n') && (c != '\r')) { + output_buf[pos++] = c; + } else { + printnow = true; + } + } + + if (printnow) { + while (ctx->sock >= 0 && cnt < max_cnt) { + ret = ws_send_all(ctx->sock, output_buf, pos); + if (ret < 0) { + if (ret == -EAGAIN) { + wait(); + cnt++; + continue; + } + } + + break; + } + + if (ctx->sock >= 0 && ret == 0) { + /* We could send data */ + pos = 0; + } else { + /* If the line is full and we cannot send, then + * ignore the output data in buffer. + */ + if (pos >= (sizeof(output_buf) - 1)) { + pos = 0; + } + } + } + + return cnt; +} + +static int line_out(uint8_t *data, size_t length, void *output_ctx) +{ + struct log_backend_ws_ctx *ctx = (struct log_backend_ws_ctx *)output_ctx; + int ret = -ENOMEM; + + if (ctx == NULL || ctx->sock == -1) { + return length; + } + + for (int i = 0; i < length; i++) { + ret = ws_console_out(ctx, data[i]); + if (ret < 0) { + goto fail; + } + } + + length = ret; + + DBG(data); +fail: + return length; +} + +LOG_OUTPUT_DEFINE(log_output_ws, line_out, output_buf, sizeof(output_buf)); + +static int do_ws_init(struct log_backend_ws_ctx *ctx) +{ + log_output_ctx_set(&log_output_ws, ctx); + + return 0; +} + +static void process(const struct log_backend *const backend, + union log_msg_generic *msg) +{ + uint32_t flags = LOG_OUTPUT_FLAG_FORMAT_SYSLOG | + LOG_OUTPUT_FLAG_TIMESTAMP | + LOG_OUTPUT_FLAG_THREAD; + log_format_func_t log_output_func; + + if (panic_mode) { + return; + } + + if (!ws_init_done && do_ws_init(&ctx) == 0) { + ws_init_done = true; + } + + log_output_func = log_format_func_t_get(log_format_current); + + log_output_func(&log_output_ws, &msg->log, flags); +} + +static int format_set(const struct log_backend *const backend, uint32_t log_type) +{ + log_format_current = log_type; + return 0; +} + +void log_backend_ws_start(void) +{ + const struct log_backend *backend = log_backend_ws_get(); + + if (!log_backend_is_active(backend)) { + log_backend_activate(backend, backend->cb->ctx); + } +} + +int log_backend_ws_register(int fd) +{ + struct log_backend_ws_ctx *ctx = log_output_ws.control_block->ctx; + + ctx->sock = fd; + + return 0; +} + +int log_backend_ws_unregister(int fd) +{ + struct log_backend_ws_ctx *ctx = log_output_ws.control_block->ctx; + + if (ctx->sock != fd) { + DBG("Websocket sock mismatch (%d vs %d)", ctx->sock, fd); + } + + ctx->sock = -1; + + return 0; +} + +static void init_ws(struct log_backend const *const backend) +{ + ARG_UNUSED(backend); + + log_backend_deactivate(log_backend_ws_get()); +} + +static void panic(struct log_backend const *const backend) +{ + panic_mode = true; +} + +const struct log_backend_api log_backend_ws_api = { + .panic = panic, + .init = init_ws, + .process = process, + .format_set = format_set, +}; + +/* Note that the backend can be activated only after we have networking + * subsystem ready so we must not start it immediately. + */ +LOG_BACKEND_DEFINE(log_backend_ws, log_backend_ws_api, + IS_ENABLED(CONFIG_LOG_BACKEND_WS_AUTOSTART)); + +const struct log_backend *log_backend_ws_get(void) +{ + return &log_backend_ws; +}