IndustrialDeviceController/Software/HighLevelApp/libutils/llog.c (246 lines of code) (raw):

/* Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. */ #include <pthread.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> // applibs_versions.h defines the API struct versions to use for applibs APIs. #include <init/applibs_versions.h> #include <applibs/log.h> #include <applibs/uart.h> #include <applibs/gpio.h> #include <frozen/frozen.h> #include <init/globals.h> #include <iot/iot.h> #include <utils/llog.h> #include <utils/timer.h> #include <utils/utils.h> #define SERIAL_LOG_PORT BOARD_UART #define SERIAL_LOG_BAUDRATE 115200 static const char LOG_TAGS[] = {'N', 'F', 'E', 'W', 'I', 'D', 'V'}; typedef struct log_entry_t log_entry_t; struct log_entry_t { char *body; log_entry_t *next; }; typedef struct log_chunk_t log_chunk_t; struct log_chunk_t { log_entry_t *head; log_entry_t *tail; int len; }; typedef struct log_t log_t; struct log_t { pthread_mutex_t lock; int endpoint; int level; log_chunk_t chunks; int uart_fd; int tx_enable_fd; }; static log_t s_log; static int printf_logs(struct json_out *out, va_list *ap) { int len = 0; struct log_entry_t *p = va_arg(*ap, struct log_entry_t *); int i = 0; while (p) { if (i > 0) { json_printf(out, ","); } len += json_printf(out, "%s", p->body); p = p->next; i++; } return len; } // add log entry into a fixed size list static void llog_iothub(const char * fmt, va_list args) { char message[DIAG_MAX_LOG_SIZE]; if (vsnprintf(message, sizeof(message), fmt, args) == DIAG_MAX_LOG_SIZE - 1) { // ensure message always end with CRLF message[DIAG_MAX_LOG_SIZE - 2] = '\n'; } log_entry_t *p = NULL; // if we are at capacity, remove the oldest entry and reuse it at the tail. if (s_log.chunks.len >= DIAG_MAX_LOG_HISTORY) { p = s_log.chunks.head; s_log.chunks.head = p->next; if (p->body) { FREE(p->body); } s_log.chunks.len--; } else { p = CALLOC(1, sizeof(log_entry_t)); } p->body = STRNDUP(message, DIAG_MAX_LOG_SIZE); p->next = NULL; s_log.chunks.len++; if (!s_log.chunks.tail) { s_log.chunks.head = s_log.chunks.tail = p; } else { s_log.chunks.tail->next = p; s_log.chunks.tail = p; } } static void disable_iothub_endpoint(void) { while (s_log.chunks.head) { log_entry_t *p = s_log.chunks.head; s_log.chunks.head = p->next; FREE(p->body); FREE(p); } s_log.chunks.head = s_log.chunks.tail = NULL; s_log.chunks.len = 0; } #ifdef ENABLE_SERIAL_LOG static int enable_serial_endpoint(void) { UART_Config config; UART_InitConfig(&config); config.baudRate = SERIAL_LOG_BAUDRATE; if ((s_log.uart_fd = UART_Open(SERIAL_LOG_PORT, &config)) < 0) { perror("failed to open uart"); return -1; } s_log.tx_enable_fd = GPIO_OpenAsOutput(BOARD_UART_TX_ENABLE, GPIO_OutputMode_PushPull, GPIO_Value_Low); if (s_log.tx_enable_fd <=0) { perror("failed to open tx-enable for log"); return -1; } return 0; } static void disable_serial_endpoint(void) { if (s_log.uart_fd >= 0) { close(s_log.uart_fd); } if (s_log.tx_enable_fd >= 0) { close(s_log.tx_enable_fd); } } static void tcdrain(int baudrate, size_t count) { // assume 8N1 uart config uint32_t bytes_per_second = baudrate / 10; uint32_t tx_enable_us = 1e6 * count / bytes_per_second; tx_enable_us += 200; // assume 200us processing time struct timespec delay; delay.tv_sec = tx_enable_us / 1e6; delay.tv_nsec = (tx_enable_us % 1000000) * 1000; nanosleep(&delay, NULL); } static void llog_serial(const char * fmt, va_list args) { char message[DIAG_MAX_LOG_SIZE]; size_t len = vsnprintf(message, sizeof(message), fmt, args); if (len == DIAG_MAX_LOG_SIZE - 1) { // ensure message always end with CRLF message[DIAG_MAX_LOG_SIZE - 2] = '\n'; } fprintf(stderr, "%s", message); GPIO_SetValue(s_log.tx_enable_fd, GPIO_Value_High); write(s_log.uart_fd, message, len); tcdrain(SERIAL_LOG_BAUDRATE, len); GPIO_SetValue(s_log.tx_enable_fd, GPIO_Value_Low); } #endif static void update_endpoint(int endpoint) { if (s_log.endpoint == LOG_ENDPOINT_IOTHUB) { disable_iothub_endpoint(); #ifdef ENABLE_SERIAL_LOG } else if (s_log.endpoint == LOG_ENDPOINT_SERIAL) { disable_serial_endpoint(); } if (endpoint == LOG_ENDPOINT_SERIAL) { if (enable_serial_endpoint() != 0) return; #endif } s_log.endpoint = endpoint; } // ------------------------ public interface ------------------------------ #ifdef TEST void llog(int level, const char *file, const char *func, const char *fmt, ...) { if (func) { printf("%s %c %s: %s:", timespec2str(now()), LOG_TAGS[level], file, func); } else { printf("%s %c %s:", timespec2str(now()), LOG_TAGS[level], file); } va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); printf("\n"); } #else void llog(int level, const char *file, const char *func, const char *fmt, ...) { if ((s_log.endpoint == LOG_ENDPOINT_NULL) || (level > s_log.level)) return; // avoid reentry if (pthread_mutex_trylock(&s_log.lock) != 0) return; char newfmt[DIAG_MAX_LOG_SIZE]; if (func) { snprintf(newfmt, sizeof(newfmt), "%s %c %s: %s: %s\n", timespec2str(now()), LOG_TAGS[level], file, func, fmt); } else { snprintf(newfmt, sizeof(newfmt), "%s %c %s: %s\n", timespec2str(now()), LOG_TAGS[level], file, fmt); } va_list args; va_start(args, fmt); if (s_log.endpoint == LOG_ENDPOINT_CONSOLE) { Log_DebugVarArgs(newfmt, args); } else if (s_log.endpoint == LOG_ENDPOINT_IOTHUB) { llog_iothub(newfmt, args); #ifdef ENABLE_SERIAL_LOG } else if (s_log.endpoint == LOG_ENDPOINT_SERIAL) { llog_serial(newfmt, args); #endif } va_end(args); pthread_mutex_unlock(&s_log.lock); } #endif // TEST int llog_init() { if (pthread_mutex_init(&s_log.lock, NULL) != 0) { perror("mutex init has failed"); return -1; } s_log.endpoint = LOG_ENDPOINT_CONSOLE; s_log.level = LOG_LEVEL; s_log.chunks.head = s_log.chunks.tail = NULL; s_log.chunks.len = 0; return 0; } void llog_deinit(void) { pthread_mutex_destroy(&s_log.lock); } void llog_config(int endpoint, int level) { if (level != s_log.level) { s_log.level = level; } if (endpoint != s_log.endpoint) { update_endpoint(endpoint); } } bool llog_islog(int level) { return level <= s_log.level; } bool llog_remote_log_enabled(void) { return s_log.endpoint == LOG_ENDPOINT_IOTHUB && s_log.level > LOG_NONE; } void llog_upload(void) { if (s_log.chunks.len == 0) return; char *iot_message = json_asprintf("[%M]", printf_logs, s_log.chunks.head); iot_send_message_async(iot_message, IOT_MESSAGE_TYPE_DIAG_DEBUG, NULL, NULL); FREE(iot_message); while (s_log.chunks.head) { log_entry_t *p = s_log.chunks.head; s_log.chunks.head = p->next; FREE(p->body); FREE(p); } s_log.chunks.head = s_log.chunks.tail = NULL; s_log.chunks.len = 0; }