log: add log query API endpoints and display logs on web interface
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 53s
All checks were successful
Build ESP32 BMC Firmware / build (push) Successful in 53s
This commit is contained in:
@@ -135,6 +135,13 @@ GPIO pins can be configured in `main/config.h`. Modify the `bmc_gpio_defaults` a
|
||||
| POST | `/api/ota/update` | Start OTA update from URL |
|
||||
| POST | `/api/ota/upload` | Upload firmware file directly |
|
||||
|
||||
### Logs Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/logs` | Get system logs |
|
||||
| POST | `/api/logs/clear` | Clear system logs |
|
||||
|
||||
## Web Interface
|
||||
|
||||
Access the web interface by navigating to `http://<ip-address>/` in your browser.
|
||||
@@ -145,6 +152,7 @@ Features:
|
||||
- Serial console with WebSocket support
|
||||
- System information display
|
||||
- OTA firmware update
|
||||
- System logs viewer
|
||||
|
||||
## Example Usage
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ idf_component_register(SRCS "main.c"
|
||||
"ethernet_manager.c"
|
||||
"web_server.c"
|
||||
"ota_handler.c"
|
||||
"log_handler.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_driver_gpio esp_driver_uart esp_driver_spi
|
||||
esp_http_server esp_eth esp_netif lwip spi_flash nvs_flash log
|
||||
|
||||
@@ -688,6 +688,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- System Logs Card -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">📋</span> System Logs</h2>
|
||||
<div class="console-container">
|
||||
<div class="console-output" id="logs-output">
|
||||
<pre>Loading logs...</pre>
|
||||
</div>
|
||||
<div class="console-controls">
|
||||
<button class="btn btn-secondary" onclick="refreshLogs()">Refresh</button>
|
||||
<button class="btn btn-secondary" onclick="clearLogs()">Clear Logs</button>
|
||||
<label><input type="checkbox" id="auto-refresh-logs" onchange="toggleAutoRefreshLogs()" /> Auto-refresh</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
@@ -1218,12 +1235,61 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize logs
|
||||
refreshLogs();
|
||||
|
||||
// Periodic updates
|
||||
setInterval(updateSystemInfo, 5000);
|
||||
setInterval(updateGPIOStates, 2000);
|
||||
setInterval(updatePowerStatus, 2000);
|
||||
setInterval(updateOTAStatus, 5000);
|
||||
});
|
||||
|
||||
// Log viewer functions
|
||||
let logsAutoRefreshInterval = null;
|
||||
|
||||
async function refreshLogs() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/logs`);
|
||||
const logs = await response.text();
|
||||
const logsOutput = document.getElementById('logs-output');
|
||||
logsOutput.innerHTML = `<pre>${escapeHtml(logs)}</pre>`;
|
||||
// Scroll to bottom
|
||||
logsOutput.scrollTop = logsOutput.scrollHeight;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch logs:', error);
|
||||
document.getElementById('logs-output').innerHTML = '<pre>Failed to load logs</pre>';
|
||||
}
|
||||
}
|
||||
|
||||
async function clearLogs() {
|
||||
try {
|
||||
await fetch(`${API_BASE}/api/logs/clear`, { method: 'POST' });
|
||||
refreshLogs();
|
||||
showToast('Logs cleared', 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to clear logs:', error);
|
||||
showToast('Failed to clear logs', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAutoRefreshLogs() {
|
||||
const checkbox = document.getElementById('auto-refresh-logs');
|
||||
if (checkbox.checked) {
|
||||
logsAutoRefreshInterval = setInterval(refreshLogs, 2000);
|
||||
} else {
|
||||
if (logsAutoRefreshInterval) {
|
||||
clearInterval(logsAutoRefreshInterval);
|
||||
logsAutoRefreshInterval = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
138
main/log_handler.c
Normal file
138
main/log_handler.c
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "log_handler.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "LOG_HANDLER";
|
||||
|
||||
// Ring buffer for logs
|
||||
#define LOG_BUFFER_SIZE 8192
|
||||
static char log_buffer[LOG_BUFFER_SIZE];
|
||||
static size_t log_write_pos = 0;
|
||||
static size_t log_read_pos = 0;
|
||||
static size_t log_count = 0;
|
||||
static SemaphoreHandle_t log_mutex = NULL;
|
||||
static bool log_initialized = false;
|
||||
|
||||
// Vprintf callback for capturing logs
|
||||
static int log_vprintf_cb(const char *fmt, va_list args) {
|
||||
// First, call the default vprintf to output to console
|
||||
int ret = vprintf(fmt, args);
|
||||
|
||||
// Then, capture to our ring buffer
|
||||
if (log_mutex && log_initialized) {
|
||||
char temp_buf[256];
|
||||
int len = vsnprintf(temp_buf, sizeof(temp_buf), fmt, args);
|
||||
if (len > 0 && len < (int)sizeof(temp_buf)) {
|
||||
xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
log_buffer[log_write_pos] = temp_buf[i];
|
||||
log_write_pos = (log_write_pos + 1) % LOG_BUFFER_SIZE;
|
||||
|
||||
if (log_count < LOG_BUFFER_SIZE) {
|
||||
log_count++;
|
||||
} else {
|
||||
// Buffer full, advance read position
|
||||
log_read_pos = (log_read_pos + 1) % LOG_BUFFER_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(log_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t log_handler_init(void) {
|
||||
if (log_initialized) {
|
||||
ESP_LOGW(TAG, "Log handler already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing log handler");
|
||||
|
||||
log_mutex = xSemaphoreCreateMutex();
|
||||
if (!log_mutex) {
|
||||
ESP_LOGE(TAG, "Failed to create log mutex");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
log_write_pos = 0;
|
||||
log_read_pos = 0;
|
||||
log_count = 0;
|
||||
memset(log_buffer, 0, LOG_BUFFER_SIZE);
|
||||
|
||||
// Set our custom vprintf function
|
||||
esp_log_set_vprintf(log_vprintf_cb);
|
||||
|
||||
log_initialized = true;
|
||||
ESP_LOGI(TAG, "Log handler initialized");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t log_handler_deinit(void) {
|
||||
if (!log_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Deinitializing log handler");
|
||||
|
||||
// Restore default vprintf
|
||||
esp_log_set_vprintf(vprintf);
|
||||
|
||||
if (log_mutex) {
|
||||
vSemaphoreDelete(log_mutex);
|
||||
log_mutex = NULL;
|
||||
}
|
||||
|
||||
log_initialized = false;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
size_t log_get_buffer(char *buf, size_t buf_len) {
|
||||
if (!buf || buf_len == 0 || !log_mutex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
|
||||
size_t to_copy = (log_count < buf_len) ? log_count : buf_len;
|
||||
size_t copied = 0;
|
||||
|
||||
for (size_t i = 0; i < to_copy; i++) {
|
||||
size_t pos = (log_read_pos + i) % LOG_BUFFER_SIZE;
|
||||
buf[copied++] = log_buffer[pos];
|
||||
}
|
||||
|
||||
xSemaphoreGive(log_mutex);
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
void log_clear_buffer(void) {
|
||||
if (!log_mutex) {
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
log_write_pos = 0;
|
||||
log_read_pos = 0;
|
||||
log_count = 0;
|
||||
xSemaphoreGive(log_mutex);
|
||||
}
|
||||
|
||||
size_t log_get_size(void) {
|
||||
if (!log_mutex) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
xSemaphoreTake(log_mutex, portMAX_DELAY);
|
||||
size_t count = log_count;
|
||||
xSemaphoreGive(log_mutex);
|
||||
|
||||
return count;
|
||||
}
|
||||
42
main/log_handler.h
Normal file
42
main/log_handler.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef LOG_HANDLER_H
|
||||
#define LOG_HANDLER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief Initialize log handler
|
||||
*
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t log_handler_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize log handler
|
||||
*
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t log_handler_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Get log buffer content
|
||||
*
|
||||
* @param buf Buffer to copy logs into
|
||||
* @param buf_len Length of buffer
|
||||
* @return size_t Number of bytes copied
|
||||
*/
|
||||
size_t log_get_buffer(char *buf, size_t buf_len);
|
||||
|
||||
/**
|
||||
* @brief Clear log buffer
|
||||
*/
|
||||
void log_clear_buffer(void);
|
||||
|
||||
/**
|
||||
* @brief Get current log buffer size
|
||||
*
|
||||
* @return size_t Number of bytes in buffer
|
||||
*/
|
||||
size_t log_get_size(void);
|
||||
|
||||
#endif // LOG_HANDLER_H
|
||||
11
main/main.c
11
main/main.c
@@ -9,6 +9,7 @@
|
||||
#include "ethernet_manager.h"
|
||||
#include "web_server.h"
|
||||
#include "ota_handler.h"
|
||||
#include "log_handler.h"
|
||||
|
||||
static const char *TAG = TAG_BMC;
|
||||
|
||||
@@ -41,9 +42,17 @@ void app_main(void) {
|
||||
ESP_LOGI(TAG, "ESP32-S3 BMC Firmware Starting...");
|
||||
ESP_LOGI(TAG, "========================================");
|
||||
|
||||
// Initialize log handler first to capture all logs
|
||||
ESP_LOGI(TAG, "Initializing log handler...");
|
||||
esp_err_t ret = log_handler_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to initialize log handler: %s", esp_err_to_name(ret));
|
||||
// Continue anyway - log capture is optional
|
||||
}
|
||||
|
||||
// Initialize GPIO controller
|
||||
ESP_LOGI(TAG, "Initializing GPIO controller...");
|
||||
esp_err_t ret = gpio_controller_init();
|
||||
ret = gpio_controller_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize GPIO controller: %s", esp_err_to_name(ret));
|
||||
// Continue anyway - some GPIOs might still work
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "uart_handler.h"
|
||||
#include "ethernet_manager.h"
|
||||
#include "ota_handler.h"
|
||||
#include "log_handler.h"
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
@@ -490,6 +491,42 @@ static esp_err_t api_system_status_handler(httpd_req_t *req) {
|
||||
return send_json_success(req, status);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Log API Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t api_logs_get_handler(httpd_req_t *req) {
|
||||
// Get log buffer size
|
||||
size_t log_size = log_get_size();
|
||||
if (log_size == 0) {
|
||||
httpd_resp_set_type(req, "text/plain");
|
||||
httpd_resp_sendstr(req, "No logs available");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Allocate buffer for logs
|
||||
char *log_buf = malloc(log_size + 1);
|
||||
if (!log_buf) {
|
||||
return send_json_error(req, "Failed to allocate memory for logs");
|
||||
}
|
||||
|
||||
// Get logs
|
||||
size_t copied = log_get_buffer(log_buf, log_size);
|
||||
log_buf[copied] = '\0';
|
||||
|
||||
// Send response
|
||||
httpd_resp_set_type(req, "text/plain");
|
||||
httpd_resp_sendstr(req, log_buf);
|
||||
|
||||
free(log_buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t api_logs_clear_handler(httpd_req_t *req) {
|
||||
log_clear_buffer();
|
||||
return send_json_success(req, cJSON_CreateObject());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OTA API Handlers
|
||||
// ============================================================================
|
||||
@@ -797,6 +834,18 @@ static const httpd_uri_t uri_api_ota_upload = {
|
||||
.handler = api_ota_upload_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_logs_get = {
|
||||
.uri = "/api/logs",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_logs_get_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_logs_clear = {
|
||||
.uri = "/api/logs/clear",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_logs_clear_handler,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Public Functions
|
||||
// ============================================================================
|
||||
@@ -846,6 +895,8 @@ esp_err_t web_server_start(void) {
|
||||
httpd_register_uri_handler(server, &uri_api_ota_status);
|
||||
httpd_register_uri_handler(server, &uri_api_ota_update);
|
||||
httpd_register_uri_handler(server, &uri_api_ota_upload);
|
||||
httpd_register_uri_handler(server, &uri_api_logs_get);
|
||||
httpd_register_uri_handler(server, &uri_api_logs_clear);
|
||||
|
||||
// Register UART callback for WebSocket broadcast at startup
|
||||
esp_err_t cb_ret = uart_register_rx_callback(uart_to_ws_callback);
|
||||
|
||||
Reference in New Issue
Block a user