Skip to content

Commit 7408004

Browse files
jukkarnashif
authored andcommitted
logging: backend: Add websocket backend
Allow logging output to websocket console. Requires that websocket shell backend and websocket HTTP server support is also enabled. Signed-off-by: Jukka Rissanen <[email protected]>
1 parent 20cce99 commit 7408004

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed

subsys/logging/backends/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ zephyr_sources_ifdef(
3535
log_backend_net.c
3636
)
3737

38+
zephyr_sources_ifdef(
39+
CONFIG_LOG_BACKEND_WS
40+
log_backend_ws.c
41+
)
42+
3843
zephyr_sources_ifdef(
3944
CONFIG_LOG_BACKEND_RTT
4045
log_backend_rtt.c

subsys/logging/backends/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ rsource "Kconfig.efi_console"
1010
rsource "Kconfig.fs"
1111
rsource "Kconfig.native_posix"
1212
rsource "Kconfig.net"
13+
rsource "Kconfig.ws"
1314
rsource "Kconfig.rtt"
1415
rsource "Kconfig.spinel"
1516
rsource "Kconfig.swo"

subsys/logging/backends/Kconfig.ws

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copyright (c) 2024 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config LOG_BACKEND_WS
5+
bool "Websocket backend"
6+
depends on WEBSOCKET_CONSOLE
7+
select LOG_OUTPUT
8+
default y
9+
help
10+
Send console messages to websocket console.
11+
12+
if LOG_BACKEND_WS
13+
14+
config LOG_BACKEND_WS_MAX_BUF_SIZE
15+
int "Max message size"
16+
range 64 1500
17+
default 512
18+
help
19+
Maximum size of the output string that is sent via websocket.
20+
21+
config LOG_BACKEND_WS_TX_RETRY_CNT
22+
int "Number of TX retries"
23+
default 2
24+
help
25+
Number of TX retries before dropping the full line of data.
26+
27+
config LOG_BACKEND_WS_TX_RETRY_DELAY_MS
28+
int "Delay between TX retries in milliseconds"
29+
default 50
30+
help
31+
Sleep period between TX retry attempts.
32+
33+
config LOG_BACKEND_WS_AUTOSTART
34+
bool "Automatically start websocket backend"
35+
default y if NET_CONFIG_NEED_IPV4 || NET_CONFIG_NEED_IPV6
36+
help
37+
When enabled automatically start the websocket backend on
38+
application start.
39+
40+
backend = WS
41+
backend-str = websocket
42+
source "subsys/logging/Kconfig.template.log_format_config"
43+
44+
endif # LOG_BACKEND_WS
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright (c) 2024 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/logging/log.h>
8+
LOG_MODULE_REGISTER(log_backend_ws, CONFIG_LOG_DEFAULT_LEVEL);
9+
10+
#include <zephyr/sys/util_macro.h>
11+
#include <zephyr/logging/log_backend.h>
12+
#include <zephyr/logging/log_core.h>
13+
#include <zephyr/logging/log_output.h>
14+
#include <zephyr/logging/log_backend_ws.h>
15+
#include <zephyr/net/net_if.h>
16+
#include <zephyr/net/socket.h>
17+
18+
/* Set this to 1 if you want to see what is being sent to server */
19+
#define DEBUG_PRINTING 0
20+
21+
#define DBG(fmt, ...) IF_ENABLED(DEBUG_PRINTING, (printk(fmt, ##__VA_ARGS__)))
22+
23+
static bool ws_init_done;
24+
static bool panic_mode;
25+
static uint32_t log_format_current = CONFIG_LOG_BACKEND_WS_OUTPUT_DEFAULT;
26+
static uint8_t output_buf[CONFIG_LOG_BACKEND_WS_MAX_BUF_SIZE];
27+
static size_t pos;
28+
29+
static struct log_backend_ws_ctx {
30+
int sock;
31+
} ctx = {
32+
.sock = -1,
33+
};
34+
35+
static void wait(void)
36+
{
37+
k_msleep(CONFIG_LOG_BACKEND_WS_TX_RETRY_DELAY_MS);
38+
}
39+
40+
static int ws_send_all(int sock, const char *output, size_t len)
41+
{
42+
int ret;
43+
44+
while (len > 0) {
45+
ret = zsock_send(sock, output, len, ZSOCK_MSG_DONTWAIT);
46+
if ((ret < 0) && (errno == EAGAIN)) {
47+
return -EAGAIN;
48+
}
49+
50+
if (ret < 0) {
51+
ret = -errno;
52+
return ret;
53+
}
54+
55+
output += ret;
56+
len -= ret;
57+
}
58+
59+
return 0;
60+
}
61+
62+
static int ws_console_out(struct log_backend_ws_ctx *ctx, int c)
63+
{
64+
static int max_cnt = CONFIG_LOG_BACKEND_WS_TX_RETRY_CNT;
65+
bool printnow = false;
66+
unsigned int cnt = 0;
67+
int ret;
68+
69+
if (pos >= (sizeof(output_buf) - 1)) {
70+
printnow = true;
71+
} else {
72+
if ((c != '\n') && (c != '\r')) {
73+
output_buf[pos++] = c;
74+
} else {
75+
printnow = true;
76+
}
77+
}
78+
79+
if (printnow) {
80+
while (ctx->sock >= 0 && cnt < max_cnt) {
81+
ret = ws_send_all(ctx->sock, output_buf, pos);
82+
if (ret < 0) {
83+
if (ret == -EAGAIN) {
84+
wait();
85+
cnt++;
86+
continue;
87+
}
88+
}
89+
90+
break;
91+
}
92+
93+
if (ctx->sock >= 0 && ret == 0) {
94+
/* We could send data */
95+
pos = 0;
96+
} else {
97+
/* If the line is full and we cannot send, then
98+
* ignore the output data in buffer.
99+
*/
100+
if (pos >= (sizeof(output_buf) - 1)) {
101+
pos = 0;
102+
}
103+
}
104+
}
105+
106+
return cnt;
107+
}
108+
109+
static int line_out(uint8_t *data, size_t length, void *output_ctx)
110+
{
111+
struct log_backend_ws_ctx *ctx = (struct log_backend_ws_ctx *)output_ctx;
112+
int ret = -ENOMEM;
113+
114+
if (ctx == NULL || ctx->sock == -1) {
115+
return length;
116+
}
117+
118+
for (int i = 0; i < length; i++) {
119+
ret = ws_console_out(ctx, data[i]);
120+
if (ret < 0) {
121+
goto fail;
122+
}
123+
}
124+
125+
length = ret;
126+
127+
DBG(data);
128+
fail:
129+
return length;
130+
}
131+
132+
LOG_OUTPUT_DEFINE(log_output_ws, line_out, output_buf, sizeof(output_buf));
133+
134+
static int do_ws_init(struct log_backend_ws_ctx *ctx)
135+
{
136+
log_output_ctx_set(&log_output_ws, ctx);
137+
138+
return 0;
139+
}
140+
141+
static void process(const struct log_backend *const backend,
142+
union log_msg_generic *msg)
143+
{
144+
uint32_t flags = LOG_OUTPUT_FLAG_FORMAT_SYSLOG |
145+
LOG_OUTPUT_FLAG_TIMESTAMP |
146+
LOG_OUTPUT_FLAG_THREAD;
147+
log_format_func_t log_output_func;
148+
149+
if (panic_mode) {
150+
return;
151+
}
152+
153+
if (!ws_init_done && do_ws_init(&ctx) == 0) {
154+
ws_init_done = true;
155+
}
156+
157+
log_output_func = log_format_func_t_get(log_format_current);
158+
159+
log_output_func(&log_output_ws, &msg->log, flags);
160+
}
161+
162+
static int format_set(const struct log_backend *const backend, uint32_t log_type)
163+
{
164+
log_format_current = log_type;
165+
return 0;
166+
}
167+
168+
void log_backend_ws_start(void)
169+
{
170+
const struct log_backend *backend = log_backend_ws_get();
171+
172+
if (!log_backend_is_active(backend)) {
173+
log_backend_activate(backend, backend->cb->ctx);
174+
}
175+
}
176+
177+
int log_backend_ws_register(int fd)
178+
{
179+
struct log_backend_ws_ctx *ctx = log_output_ws.control_block->ctx;
180+
181+
ctx->sock = fd;
182+
183+
return 0;
184+
}
185+
186+
int log_backend_ws_unregister(int fd)
187+
{
188+
struct log_backend_ws_ctx *ctx = log_output_ws.control_block->ctx;
189+
190+
if (ctx->sock != fd) {
191+
DBG("Websocket sock mismatch (%d vs %d)", ctx->sock, fd);
192+
}
193+
194+
ctx->sock = -1;
195+
196+
return 0;
197+
}
198+
199+
static void init_ws(struct log_backend const *const backend)
200+
{
201+
ARG_UNUSED(backend);
202+
203+
log_backend_deactivate(log_backend_ws_get());
204+
}
205+
206+
static void panic(struct log_backend const *const backend)
207+
{
208+
panic_mode = true;
209+
}
210+
211+
const struct log_backend_api log_backend_ws_api = {
212+
.panic = panic,
213+
.init = init_ws,
214+
.process = process,
215+
.format_set = format_set,
216+
};
217+
218+
/* Note that the backend can be activated only after we have networking
219+
* subsystem ready so we must not start it immediately.
220+
*/
221+
LOG_BACKEND_DEFINE(log_backend_ws, log_backend_ws_api,
222+
IS_ENABLED(CONFIG_LOG_BACKEND_WS_AUTOSTART));
223+
224+
const struct log_backend *log_backend_ws_get(void)
225+
{
226+
return &log_backend_ws;
227+
}

0 commit comments

Comments
 (0)