| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <logging/log_output.h> |
| #include <logging/log_ctrl.h> |
| #include <logging/log.h> |
| #include <sys/__assert.h> |
| #include <sys/cbprintf.h> |
| #include <ctype.h> |
| #include <time.h> |
| #include <stdio.h> |
| #include <stdbool.h> |
| |
| #define LOG_COLOR_CODE_DEFAULT "\x1B[0m" |
| #define LOG_COLOR_CODE_RED "\x1B[1;31m" |
| #define LOG_COLOR_CODE_YELLOW "\x1B[1;33m" |
| |
| #define HEXDUMP_BYTES_IN_LINE 16 |
| |
| #define DROPPED_COLOR_PREFIX \ |
| Z_LOG_EVAL(CONFIG_LOG_BACKEND_SHOW_COLOR, (LOG_COLOR_CODE_RED), ()) |
| |
| #define DROPPED_COLOR_POSTFIX \ |
| Z_LOG_EVAL(CONFIG_LOG_BACKEND_SHOW_COLOR, (LOG_COLOR_CODE_DEFAULT), ()) |
| |
| static const char *const severity[] = { |
| NULL, |
| "err", |
| "wrn", |
| "inf", |
| "dbg" |
| }; |
| |
| static const char *const colors[] = { |
| NULL, |
| LOG_COLOR_CODE_RED, /* err */ |
| LOG_COLOR_CODE_YELLOW, /* warn */ |
| NULL, /* info */ |
| NULL /* dbg */ |
| }; |
| |
| static uint32_t freq; |
| static uint32_t timestamp_div; |
| |
| extern void log_output_msg_syst_process(const struct log_output *output, |
| struct log_msg *msg, uint32_t flag); |
| extern void log_output_string_syst_process(const struct log_output *output, |
| struct log_msg_ids src_level, |
| const char *fmt, va_list ap, uint32_t flag); |
| extern void log_output_hexdump_syst_process(const struct log_output *output, |
| struct log_msg_ids src_level, |
| const uint8_t *data, uint32_t length, uint32_t flag); |
| |
| /* The RFC 5424 allows very flexible mapping and suggest the value 0 being the |
| * highest severity and 7 to be the lowest (debugging level) severity. |
| * |
| * 0 Emergency System is unusable |
| * 1 Alert Action must be taken immediately |
| * 2 Critical Critical conditions |
| * 3 Error Error conditions |
| * 4 Warning Warning conditions |
| * 5 Notice Normal but significant condition |
| * 6 Informational Informational messages |
| * 7 Debug Debug-level messages |
| */ |
| static int level_to_rfc5424_severity(uint32_t level) |
| { |
| uint8_t ret; |
| |
| switch (level) { |
| case LOG_LEVEL_NONE: |
| ret = 7U; |
| break; |
| case LOG_LEVEL_ERR: |
| ret = 3U; |
| break; |
| case LOG_LEVEL_WRN: |
| ret = 4U; |
| break; |
| case LOG_LEVEL_INF: |
| ret = 6U; |
| break; |
| case LOG_LEVEL_DBG: |
| ret = 7U; |
| break; |
| default: |
| ret = 7U; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int out_func(int c, void *ctx) |
| { |
| const struct log_output *out_ctx = (const struct log_output *)ctx; |
| int idx; |
| |
| if (IS_ENABLED(CONFIG_LOG_IMMEDIATE)) { |
| /* Backend must be thread safe in synchronous operation. */ |
| out_ctx->func((uint8_t *)&c, 1, out_ctx->control_block->ctx); |
| return 0; |
| } |
| |
| if (out_ctx->control_block->offset == out_ctx->size) { |
| log_output_flush(out_ctx); |
| } |
| |
| idx = atomic_inc(&out_ctx->control_block->offset); |
| out_ctx->buf[idx] = (uint8_t)c; |
| |
| __ASSERT_NO_MSG(out_ctx->control_block->offset <= out_ctx->size); |
| |
| return 0; |
| } |
| |
| static int cr_out_func(int c, void *ctx) |
| { |
| out_func(c, ctx); |
| if (c == '\n') { |
| out_func((int)'\r', ctx); |
| } |
| |
| return 0; |
| } |
| |
| static int print_formatted(const struct log_output *output, |
| const char *fmt, ...) |
| { |
| va_list args; |
| int length = 0; |
| |
| va_start(args, fmt); |
| length = cbvprintf(out_func, (void *)output, fmt, args); |
| va_end(args); |
| |
| return length; |
| } |
| |
| static void buffer_write(log_output_func_t outf, uint8_t *buf, size_t len, |
| void *ctx) |
| { |
| int processed; |
| |
| do { |
| processed = outf(buf, len, ctx); |
| len -= processed; |
| buf += processed; |
| } while (len != 0); |
| } |
| |
| |
| void log_output_flush(const struct log_output *output) |
| { |
| buffer_write(output->func, output->buf, |
| output->control_block->offset, |
| output->control_block->ctx); |
| |
| output->control_block->offset = 0; |
| } |
| |
| static int timestamp_print(const struct log_output *output, |
| uint32_t flags, uint32_t timestamp) |
| { |
| int length; |
| bool format = |
| (flags & LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP) | |
| (flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG); |
| |
| |
| if (!format) { |
| length = print_formatted(output, "[%08lu] ", timestamp); |
| } else if (freq != 0U) { |
| uint32_t total_seconds; |
| uint32_t remainder; |
| uint32_t seconds; |
| uint32_t hours; |
| uint32_t mins; |
| uint32_t ms; |
| uint32_t us; |
| |
| timestamp /= timestamp_div; |
| total_seconds = timestamp / freq; |
| seconds = total_seconds; |
| hours = seconds / 3600U; |
| seconds -= hours * 3600U; |
| mins = seconds / 60U; |
| seconds -= mins * 60U; |
| |
| remainder = timestamp % freq; |
| ms = (remainder * 1000U) / freq; |
| us = (1000 * (remainder * 1000U - (ms * freq))) / freq; |
| |
| if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) { |
| #if defined(CONFIG_NEWLIB_LIBC) |
| char time_str[sizeof("1970-01-01T00:00:00")]; |
| struct tm *tm; |
| time_t time; |
| |
| time = total_seconds; |
| tm = gmtime(&time); |
| |
| strftime(time_str, sizeof(time_str), "%FT%T", tm); |
| |
| length = print_formatted(output, "%s.%06uZ ", |
| time_str, ms * 1000U + us); |
| #else |
| length = print_formatted(output, |
| "1970-01-01T%02u:%02u:%02u.%06uZ ", |
| hours, mins, seconds, ms * 1000U + us); |
| #endif |
| } else { |
| length = print_formatted(output, |
| "[%02u:%02u:%02u.%03u,%03u] ", |
| hours, mins, seconds, ms, us); |
| } |
| } else { |
| length = 0; |
| } |
| |
| return length; |
| } |
| |
| static void color_print(const struct log_output *output, |
| bool color, bool start, uint32_t level) |
| { |
| if (color) { |
| const char *log_color = start && (colors[level] != NULL) ? |
| colors[level] : LOG_COLOR_CODE_DEFAULT; |
| print_formatted(output, "%s", log_color); |
| } |
| } |
| |
| static void color_prefix(const struct log_output *output, |
| bool color, uint32_t level) |
| { |
| color_print(output, color, true, level); |
| } |
| |
| static void color_postfix(const struct log_output *output, |
| bool color, uint32_t level) |
| { |
| color_print(output, color, false, level); |
| } |
| |
| |
| static int ids_print(const struct log_output *output, bool level_on, |
| bool func_on, uint32_t domain_id, int16_t source_id, |
| uint32_t level) |
| { |
| int total = 0; |
| |
| if (level_on) { |
| total += print_formatted(output, "<%s> ", severity[level]); |
| } |
| |
| if (source_id >= 0) { |
| total += print_formatted(output, |
| (func_on && |
| ((1 << level) & LOG_FUNCTION_PREFIX_MASK)) ? |
| "%s." : "%s: ", |
| log_source_name_get(domain_id, source_id)); |
| } |
| |
| return total; |
| } |
| |
| static void newline_print(const struct log_output *ctx, uint32_t flags) |
| { |
| if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) { |
| return; |
| } |
| |
| if ((flags & LOG_OUTPUT_FLAG_CRLF_NONE) != 0U) { |
| return; |
| } |
| |
| if ((flags & LOG_OUTPUT_FLAG_CRLF_LFONLY) != 0U) { |
| print_formatted(ctx, "\n"); |
| } else { |
| print_formatted(ctx, "\r\n"); |
| } |
| } |
| |
| static void std_print(struct log_msg *msg, |
| const struct log_output *output) |
| { |
| const char *str = log_msg_str_get(msg); |
| uint32_t nargs = log_msg_nargs_get(msg); |
| log_arg_t *args = alloca(sizeof(log_arg_t)*nargs); |
| int i; |
| |
| for (i = 0; i < nargs; i++) { |
| args[i] = log_msg_arg_get(msg, i); |
| } |
| |
| switch (log_msg_nargs_get(msg)) { |
| case 0: |
| print_formatted(output, str); |
| break; |
| case 1: |
| print_formatted(output, str, args[0]); |
| break; |
| case 2: |
| print_formatted(output, str, args[0], args[1]); |
| break; |
| case 3: |
| print_formatted(output, str, args[0], args[1], args[2]); |
| break; |
| case 4: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3]); |
| break; |
| case 5: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4]); |
| break; |
| case 6: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5]); |
| break; |
| case 7: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6]); |
| break; |
| case 8: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7]); |
| break; |
| case 9: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8]); |
| break; |
| case 10: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9]); |
| break; |
| case 11: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9], args[10]); |
| break; |
| case 12: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9], args[10], args[11]); |
| break; |
| case 13: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9], args[10], args[11], args[12]); |
| break; |
| case 14: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9], args[10], args[11], args[12], |
| args[13]); |
| break; |
| case 15: |
| print_formatted(output, str, args[0], args[1], args[2], |
| args[3], args[4], args[5], args[6], args[7], |
| args[8], args[9], args[10], args[11], args[12], |
| args[13], args[14]); |
| break; |
| default: |
| /* Unsupported number of arguments. */ |
| __ASSERT_NO_MSG(true); |
| break; |
| } |
| } |
| |
| static void hexdump_line_print(const struct log_output *output, |
| const uint8_t *data, uint32_t length, |
| int prefix_offset, uint32_t flags) |
| { |
| newline_print(output, flags); |
| |
| for (int i = 0; i < prefix_offset; i++) { |
| print_formatted(output, " "); |
| } |
| |
| for (int i = 0; i < HEXDUMP_BYTES_IN_LINE; i++) { |
| if (i > 0 && !(i % 8)) { |
| print_formatted(output, " "); |
| } |
| |
| if (i < length) { |
| print_formatted(output, "%02x ", data[i]); |
| } else { |
| print_formatted(output, " "); |
| } |
| } |
| |
| print_formatted(output, "|"); |
| |
| for (int i = 0; i < HEXDUMP_BYTES_IN_LINE; i++) { |
| if (i > 0 && !(i % 8)) { |
| print_formatted(output, " "); |
| } |
| |
| if (i < length) { |
| char c = (char)data[i]; |
| |
| print_formatted(output, "%c", |
| isprint((int)c) ? c : '.'); |
| } else { |
| print_formatted(output, " "); |
| } |
| } |
| } |
| |
| static void hexdump_print(struct log_msg *msg, |
| const struct log_output *output, |
| int prefix_offset, uint32_t flags) |
| { |
| uint32_t offset = 0U; |
| uint8_t buf[HEXDUMP_BYTES_IN_LINE]; |
| size_t length; |
| |
| print_formatted(output, "%s", log_msg_str_get(msg)); |
| |
| do { |
| length = sizeof(buf); |
| log_msg_hexdump_data_get(msg, buf, &length, offset); |
| |
| if (length) { |
| hexdump_line_print(output, buf, length, |
| prefix_offset, flags); |
| offset += length; |
| } else { |
| break; |
| } |
| } while (true); |
| } |
| |
| static void log_msg2_hexdump(const struct log_output *output, |
| uint8_t *data, uint32_t len, |
| int prefix_offset, uint32_t flags) |
| { |
| size_t length; |
| |
| do { |
| length = MIN(len, HEXDUMP_BYTES_IN_LINE); |
| |
| hexdump_line_print(output, data, length, |
| prefix_offset, flags); |
| data += length; |
| len -= length; |
| } while (len); |
| } |
| |
| |
| static void raw_string_print(struct log_msg *msg, |
| const struct log_output *output) |
| { |
| __ASSERT_NO_MSG(output->size); |
| |
| size_t offset = 0; |
| size_t length; |
| bool eol = false; |
| |
| do { |
| length = output->size; |
| /* Sting is stored in a hexdump message. */ |
| log_msg_hexdump_data_get(msg, output->buf, &length, offset); |
| output->control_block->offset = length; |
| |
| if (length != 0) { |
| eol = (output->buf[length - 1] == '\n'); |
| } |
| |
| log_output_flush(output); |
| offset += length; |
| } while (length > 0); |
| |
| if (eol) { |
| print_formatted(output, "\r"); |
| } |
| } |
| |
| static uint32_t prefix_print(const struct log_output *output, |
| uint32_t flags, bool func_on, uint32_t timestamp, uint8_t level, |
| uint8_t domain_id, int16_t source_id) |
| { |
| uint32_t length = 0U; |
| |
| bool stamp = flags & LOG_OUTPUT_FLAG_TIMESTAMP; |
| bool colors_on = flags & LOG_OUTPUT_FLAG_COLORS; |
| bool level_on = flags & LOG_OUTPUT_FLAG_LEVEL; |
| |
| if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) { |
| /* TODO: As there is no way to figure out the |
| * facility at this point, use a pre-defined value. |
| * Change this to use the real facility of the |
| * logging call when that info is available. |
| */ |
| static const int facility = 16; /* local0 */ |
| |
| length += print_formatted( |
| output, |
| "<%d>1 ", |
| facility * 8 + |
| level_to_rfc5424_severity(level)); |
| } |
| |
| if (stamp) { |
| length += timestamp_print(output, flags, timestamp); |
| } |
| |
| if (IS_ENABLED(CONFIG_LOG_BACKEND_NET) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYSLOG) { |
| length += print_formatted( |
| output, "%s - - - - ", |
| output->control_block->hostname ? |
| output->control_block->hostname : |
| "zephyr"); |
| } else { |
| color_prefix(output, colors_on, level); |
| } |
| |
| length += ids_print(output, level_on, func_on, |
| domain_id, source_id, level); |
| |
| |
| return length; |
| } |
| |
| static void postfix_print(const struct log_output *output, |
| uint32_t flags, uint8_t level) |
| { |
| color_postfix(output, (flags & LOG_OUTPUT_FLAG_COLORS), |
| level); |
| newline_print(output, flags); |
| } |
| |
| void log_output_msg_process(const struct log_output *output, |
| struct log_msg *msg, |
| uint32_t flags) |
| { |
| bool std_msg = log_msg_is_std(msg); |
| uint32_t timestamp = log_msg_timestamp_get(msg); |
| uint8_t level = (uint8_t)log_msg_level_get(msg); |
| uint8_t domain_id = (uint8_t)log_msg_domain_id_get(msg); |
| int16_t source_id = (int16_t)log_msg_source_id_get(msg); |
| bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING); |
| int prefix_offset; |
| |
| if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYST) { |
| log_output_msg_syst_process(output, msg, flags); |
| return; |
| } |
| |
| prefix_offset = raw_string ? |
| 0 : prefix_print(output, flags, std_msg, timestamp, |
| level, domain_id, source_id); |
| |
| if (log_msg_is_std(msg)) { |
| std_print(msg, output); |
| } else if (raw_string) { |
| raw_string_print(msg, output); |
| } else { |
| hexdump_print(msg, output, prefix_offset, flags); |
| } |
| |
| if (!raw_string) { |
| postfix_print(output, flags, level); |
| } |
| |
| log_output_flush(output); |
| } |
| |
| void log_output_msg2_process(const struct log_output *output, |
| struct log_msg2 *msg, uint32_t flags) |
| { |
| log_timestamp_t timestamp = log_msg2_get_timestamp(msg); |
| uint8_t level = log_msg2_get_level(msg); |
| bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING); |
| uint32_t prefix_offset; |
| |
| if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYST) { |
| __ASSERT_NO_MSG(0); |
| /* todo not supported |
| * log_output_msg_syst_process(output, msg, flags); |
| */ |
| return; |
| } |
| |
| if (!raw_string) { |
| void *source = (void *)log_msg2_get_source(msg); |
| uint8_t domain_id = log_msg2_get_domain(msg); |
| int16_t source_id = source ? |
| (IS_ENABLED(CONFIG_LOG_RUNTIME_FILTERING) ? |
| log_dynamic_source_id(source) : |
| log_const_source_id(source)) : |
| -1; |
| |
| prefix_offset = prefix_print(output, flags, 0, timestamp, |
| level, domain_id, source_id); |
| } else { |
| prefix_offset = 0; |
| } |
| |
| size_t len; |
| uint8_t *data = log_msg2_get_package(msg, &len); |
| |
| if (len) { |
| int err = cbpprintf(raw_string ? cr_out_func : out_func, |
| (void *)output, data); |
| |
| (void)err; |
| __ASSERT_NO_MSG(err >= 0); |
| } |
| |
| data = log_msg2_get_data(msg, &len); |
| if (len) { |
| log_msg2_hexdump(output, data, len, prefix_offset, flags); |
| } |
| |
| if (!raw_string) { |
| postfix_print(output, flags, level); |
| } |
| |
| log_output_flush(output); |
| } |
| |
| static bool ends_with_newline(const char *fmt) |
| { |
| char c = '\0'; |
| |
| while (*fmt != '\0') { |
| c = *fmt; |
| fmt++; |
| } |
| |
| return (c == '\n'); |
| } |
| |
| void log_output_string(const struct log_output *output, |
| struct log_msg_ids src_level, uint32_t timestamp, |
| const char *fmt, va_list ap, uint32_t flags) |
| { |
| int length; |
| uint8_t level = (uint8_t)src_level.level; |
| uint8_t domain_id = (uint8_t)src_level.domain_id; |
| int16_t source_id = (int16_t)src_level.source_id; |
| bool raw_string = (level == LOG_LEVEL_INTERNAL_RAW_STRING); |
| |
| if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYST) { |
| log_output_string_syst_process(output, |
| src_level, fmt, ap, flags); |
| return; |
| } |
| |
| if (!raw_string) { |
| prefix_print(output, flags, true, timestamp, |
| level, domain_id, source_id); |
| } |
| |
| length = cbvprintf(out_func, (void *)output, fmt, ap); |
| |
| (void)length; |
| |
| if (raw_string) { |
| /* add \r if string ends with newline. */ |
| if (ends_with_newline(fmt)) { |
| print_formatted(output, "\r"); |
| } |
| } else { |
| postfix_print(output, flags, level); |
| } |
| |
| log_output_flush(output); |
| } |
| |
| void log_output_hexdump(const struct log_output *output, |
| struct log_msg_ids src_level, uint32_t timestamp, |
| const char *metadata, const uint8_t *data, |
| uint32_t length, uint32_t flags) |
| { |
| uint32_t prefix_offset; |
| uint8_t level = (uint8_t)src_level.level; |
| uint8_t domain_id = (uint8_t)src_level.domain_id; |
| int16_t source_id = (int16_t)src_level.source_id; |
| |
| if (IS_ENABLED(CONFIG_LOG_MIPI_SYST_ENABLE) && |
| flags & LOG_OUTPUT_FLAG_FORMAT_SYST) { |
| log_output_hexdump_syst_process(output, |
| src_level, data, length, flags); |
| return; |
| } |
| |
| prefix_offset = prefix_print(output, flags, true, timestamp, |
| level, domain_id, source_id); |
| |
| /* Print metadata */ |
| print_formatted(output, "%s", metadata); |
| |
| while (length != 0U) { |
| uint32_t part_len = length > HEXDUMP_BYTES_IN_LINE ? |
| HEXDUMP_BYTES_IN_LINE : length; |
| |
| hexdump_line_print(output, data, part_len, |
| prefix_offset, flags); |
| |
| data += part_len; |
| length -= part_len; |
| } |
| |
| postfix_print(output, flags, level); |
| log_output_flush(output); |
| } |
| |
| void log_output_dropped_process(const struct log_output *output, uint32_t cnt) |
| { |
| char buf[5]; |
| int len; |
| static const char prefix[] = DROPPED_COLOR_PREFIX "--- "; |
| static const char postfix[] = |
| " messages dropped ---\r\n" DROPPED_COLOR_POSTFIX; |
| log_output_func_t outf = output->func; |
| |
| cnt = MIN(cnt, 9999); |
| len = snprintk(buf, sizeof(buf), "%d", cnt); |
| |
| buffer_write(outf, (uint8_t *)prefix, sizeof(prefix) - 1, |
| output->control_block->ctx); |
| buffer_write(outf, buf, len, output->control_block->ctx); |
| buffer_write(outf, (uint8_t *)postfix, sizeof(postfix) - 1, |
| output->control_block->ctx); |
| } |
| |
| void log_output_timestamp_freq_set(uint32_t frequency) |
| { |
| timestamp_div = 1U; |
| /* There is no point to have frequency higher than 1MHz (ns are not |
| * printed) and too high frequency leads to overflows in calculations. |
| */ |
| while (frequency > 1000000) { |
| frequency /= 2U; |
| timestamp_div *= 2U; |
| } |
| |
| freq = frequency; |
| } |
| |
| uint64_t log_output_timestamp_to_us(uint32_t timestamp) |
| { |
| timestamp /= timestamp_div; |
| |
| return ((uint64_t) timestamp * 1000000U) / freq; |
| } |