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

This commit is contained in:
2026-03-12 16:20:01 +01:00
parent ccf4569969
commit 6e88ce1137
7 changed files with 316 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View 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

View File

@@ -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

View File

@@ -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);