diff --git a/.gitignore b/.gitignore index 65225d8..496ee2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -.DS_Store -.vscode -build -sdkconfig -sdkconfig.old \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..289a70d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +idf_component_register(SRCS "zh_syslog.c" INCLUDE_DIRS "include") \ No newline at end of file diff --git a/README.md b/README.md index 3df3e4d..f05d258 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,117 @@ -# esp_component_template +# ESP32 ESP-IDF component for Syslog server UDP client -esp_component_template \ No newline at end of file +## Tested on + +1. [ESP32 ESP-IDF v5.5.3](https://docs.espressif.com/projects/esp-idf/en/v5.5.3/esp32/index.html) + +## SAST Tools + +[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. + +## Note + +Messages are built using the format defined in [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424). + +## Using + +In an existing project, run the following command to install the component: + +```text +cd ../your_project/components +git clone https://github.com/aZholtikov/zh_syslog.git +``` + +In the application, add the component: + +```c +#include "zh_syslog.h" +``` + +## Example + +Sending messages: + +```c +#include "nvs_flash.h" +#include "esp_netif.h" +#include "esp_wifi.h" +#include "esp_timer.h" +#include "esp_event.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "zh_syslog.h" + +#define WIFI_SSID "ssid" +#define WIFI_PASS "password" +#define WIFI_MAXIMUM_RETRY 5 +#define WIFI_RECONNECT_TIME 5 +#define WIFI_CONNECT_SUCCESS BIT0 + +#define SYSLOG_IP "192.168.1.2" + +esp_timer_handle_t wifi_reconnect_timer = {0}; +uint8_t wifi_reconnect_retry_num = 0; +EventGroupHandle_t event_group_handle = {0}; + +void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); + +void app_main(void) +{ + esp_log_level_set("zh_syslog", ESP_LOG_ERROR); + nvs_flash_init(); + esp_netif_init(); + esp_event_loop_create_default(); + esp_netif_create_default_wifi_sta(); + wifi_init_config_t wifi_init_sta_config = WIFI_INIT_CONFIG_DEFAULT(); + esp_wifi_init(&wifi_init_sta_config); + wifi_config_t wifi_config = { + .sta.ssid = WIFI_SSID, + .sta.password = WIFI_PASS}; + esp_wifi_set_mode(WIFI_MODE_STA); + esp_wifi_set_config(WIFI_IF_STA, &wifi_config); + esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL); + esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL); + esp_wifi_start(); + event_group_handle = xEventGroupCreate(); + xEventGroupWaitBits(event_group_handle, WIFI_CONNECT_SUCCESS, pdTRUE, pdTRUE, portMAX_DELAY); + zh_syslog_init_config_t config = ZH_SYSLOG_INIT_CONFIG_DEFAULT(); + strcpy(config.syslog_ip, SYSLOG_IP); + zh_syslog_init(&config); + for (;;) + { + zh_syslog_send(ZH_USER, ZH_INFO, "my_device", "my_application", "Message"); + vTaskDelay(5000 / portTICK_PERIOD_MS); + } +} + +void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) +{ + switch (event_id) + { + case WIFI_EVENT_STA_START: + esp_wifi_connect(); + break; + case WIFI_EVENT_STA_DISCONNECTED: + if (wifi_reconnect_retry_num < WIFI_MAXIMUM_RETRY) + { + esp_wifi_connect(); + ++wifi_reconnect_retry_num; + } + else + { + wifi_reconnect_retry_num = 0; + esp_timer_create_args_t wifi_reconnect_timer_args = { + .callback = (void *)esp_wifi_connect}; + esp_timer_create(&wifi_reconnect_timer_args, &wifi_reconnect_timer); + esp_timer_start_once(wifi_reconnect_timer, WIFI_RECONNECT_TIME * 1000); + } + break; + case IP_EVENT_STA_GOT_IP: + wifi_reconnect_retry_num = 0; + xEventGroupSetBits(event_group_handle, WIFI_CONNECT_SUCCESS); + break; + default: + break; + } +} +``` diff --git a/component.mk b/component.mk deleted file mode 100644 index e69de29..0000000 diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/zh_syslog.h b/include/zh_syslog.h new file mode 100644 index 0000000..3f16093 --- /dev/null +++ b/include/zh_syslog.h @@ -0,0 +1,120 @@ +/** + * @file zh_syslog.h + */ + +#pragma once + +#include "stdbool.h" +#include "esp_err.h" +#include "esp_log.h" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" + +/** + * @brief Syslog client initial default values. + */ +#define ZH_SYSLOG_INIT_CONFIG_DEFAULT() \ + { \ + .syslog_ip = "192.168.0.1", \ + .syslog_port = 514} + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Enumeration of possible syslog facility codes. + */ + typedef enum + { + ZH_KERN, /*!< Kernel messages. */ + ZH_USER, /*!< User-level messages. */ + ZH_MAIL, /*!< Mail system. */ + ZH_DAEMON, /*!< System daemons. */ + ZH_AUTH, /*!< Security/authorization messages. */ + ZH_SYSLOG, /*!< Messages generated internally by syslogd. */ + ZH_LPR, /*!< Line printer subsystem. */ + ZH_NEWS, /*!< Network news subsystem. */ + ZH_UUCP, /*!< UUCP subsystem. */ + ZH_CRON, /*!< Cron subsystem. */ + ZH_AUTHPRIV, /*!< Security/authorization messages. */ + ZH_FTP, /*!< FTP daemon. */ + ZH_NTP, /*!< NTP subsystem. */ + ZH_SECURITY, /*!< Log audit. */ + ZH_CONSOLE, /*!< Log alert. */ + ZH_SOLARIS_CRON, /*!< Scheduling daemon. */ + ZH_LOCAL0, /*!< Local use 0. */ + ZH_LOCAL1, /*!< Local use 1. */ + ZH_LOCAL2, /*!< Local use 2. */ + ZH_LOCAL3, /*!< Local use 3. */ + ZH_LOCAL4, /*!< Local use 4. */ + ZH_LOCAL5, /*!< Local use 5. */ + ZH_LOCAL6, /*!< Local use 6. */ + ZH_LOCAL7, /*!< Local use 7. */ + } zh_syslog_facility_code_t; + + /** + * @brief Enumeration of possible syslog severity codes. + */ + typedef enum + { + ZH_EMERG, /*!< System is unusable. */ + ZH_ALERT, /*!< Action must be taken immediately. */ + ZH_CRIT, /*!< Critical conditions. */ + ZH_ERR, /*!< Error conditions. */ + ZH_WARNING, /*!< Warning conditions. */ + ZH_NOTICE, /*!< Normal but significant condition. */ + ZH_INFO, /*!< Informational messages. */ + ZH_DEBUG /*!< Debug-level messages. */ + } zh_syslog_severity_code_t; + + /** + * @brief Structure for initial initialization of syslog client. + */ + typedef struct + { + char syslog_ip[16]; /*!< Syslog server IP address. */ + uint32_t syslog_port; /*!< Syslog server port. */ + } zh_syslog_init_config_t; + + /** + * @brief Initialize syslog client. + * + * @param[in] config Pointer to syslog client initialized configuration structure. Can point to a temporary variable. + * + * @attention Connection to the network must be established first. + * + * @note Before initialize the syslog client recommend initialize zh_syslog_init_config_t structure with default values. + * + * @code zh_syslog_init_config_t config = ZH_SYSLOG_INIT_CONFIG_DEFAULT() @endcode + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_syslog_init(const zh_syslog_init_config_t *config); + + /** + * @brief Deinitialize syslog client. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_syslog_deinit(void); + + /** + * @brief Send message to syslog server. + * + * @param[in] facility Syslog facility code. + * @param[in] severity Syslog severity code. + * @param[in] hostname Pointer to device that generated the message. Only alphanumeric characters without spaces. + * @param[in] app_name Pointer to application that generated the message. Only alphanumeric characters without spaces. + * @param[in] message Pointer to message for send. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_syslog_send(const zh_syslog_facility_code_t facility, const zh_syslog_severity_code_t severity, const char *hostname, const char *app_name, const char *message); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/version.txt b/version.txt index e69de29..afaf360 100644 --- a/version.txt +++ b/version.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/zh_syslog.c b/zh_syslog.c new file mode 100644 index 0000000..9c9a907 --- /dev/null +++ b/zh_syslog.c @@ -0,0 +1,60 @@ +#include "zh_syslog.h" + +#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) + +#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \ + if (!(cond)) \ + { \ + ZH_LOGE(msg, err, ##__VA_ARGS__); \ + cleanup; \ + return err; \ + } + +static const char *TAG = "zh_syslog"; + +static zh_syslog_init_config_t _init_config = {0}; +static struct sockaddr_in _syslog_server = {0}; +static int _socket = 0; +static bool _is_initialized = false; + +esp_err_t zh_syslog_init(const zh_syslog_init_config_t *config) +{ + ESP_LOGI(TAG, "Syslog client initialization begin."); + ZH_ERROR_CHECK(config != NULL, ESP_ERR_INVALID_ARG, NULL, "Syslog client initialization fail. Invalid argument."); + _init_config = *config; + _syslog_server.sin_addr.s_addr = inet_addr(_init_config.syslog_ip); + _syslog_server.sin_family = AF_INET; + _syslog_server.sin_port = htons(_init_config.syslog_port); + _socket = socket(AF_INET, SOCK_DGRAM, 0); + ZH_ERROR_CHECK(_socket >= 0, ESP_FAIL, NULL, "Syslog client initialization fail. Create socket error."); + _is_initialized = true; + ESP_LOGI(TAG, "Syslog client initialization success."); + return ESP_OK; +} + +esp_err_t zh_syslog_deinit(void) +{ + ESP_LOGI(TAG, "Syslog client deinitialization begin."); + ZH_ERROR_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "Syslog client deinitialization fail. Syslog client not initialized."); + shutdown(_socket, 0); + close(_socket); + _is_initialized = false; + ESP_LOGI(TAG, "Syslog client deinitialization success."); + return ESP_OK; +} + +esp_err_t zh_syslog_send(const zh_syslog_facility_code_t facility, const zh_syslog_severity_code_t severity, const char *hostname, const char *app_name, const char *message) // -V2008 +{ + ESP_LOGI(TAG, "Syslog client send begin."); + ZH_ERROR_CHECK(hostname != NULL && app_name != NULL && message != NULL, ESP_ERR_INVALID_ARG, NULL, "Syslog client send fail. Invalid argument."); + ZH_ERROR_CHECK(_is_initialized == true, ESP_ERR_INVALID_STATE, NULL, "Syslog client send fail. Syslog client not initialized."); + char *payload = heap_caps_calloc(1, 1024, MALLOC_CAP_8BIT); + ZH_ERROR_CHECK(payload != NULL, ESP_ERR_NO_MEM, NULL, "Syslog client send fail. Memory allocation fail or no free memory in the heap."); + sprintf(payload, "<%d>1 - %s %s - - - %s", ((uint8_t)facility * 8) + (uint8_t)severity, hostname, app_name, message); + int temp = sendto(_socket, payload, strlen(payload), 0, (struct sockaddr *)&_syslog_server, sizeof(_syslog_server)); // -V641 + ZH_ERROR_CHECK(temp >= 0, ESP_FAIL, heap_caps_free(payload), "Syslog client send fail. Socker error."); + heap_caps_free(payload); + ESP_LOGI(TAG, "Syslog client send success."); + return ESP_OK; +} \ No newline at end of file