/* * EMS-ESP - https://github.com/emsesp/EMS-ESP * Copyright 2020 Paul Derbyshire * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "emsesp.h" using namespace std::placeholders; namespace emsesp { WebLogService::WebLogService(AsyncWebServer * server, SecurityManager * securityManager) : _events(EVENT_SOURCE_LOG_PATH) , _setLevel(LOG_SETTINGS_PATH, std::bind(&WebLogService::setLevel, this, _1, _2), 256) { // for POSTS _events.setFilter(securityManager->filterRequest(AuthenticationPredicates::IS_ADMIN)); server->addHandler(&_events); server->on(EVENT_SOURCE_LOG_PATH, HTTP_GET, std::bind(&WebLogService::forbidden, this, _1)); // for bring back the whole log server->on(FETCH_LOG_PATH, HTTP_GET, std::bind(&WebLogService::fetchLog, this, _1)); // get when page is loaded server->on(LOG_SETTINGS_PATH, HTTP_GET, std::bind(&WebLogService::getLevel, this, _1)); // for setting a level server->addHandler(&_setLevel); // start event source service start(); } void WebLogService::forbidden(AsyncWebServerRequest * request) { request->send(403); } void WebLogService::start() { uuid::log::Logger::register_handler(this, uuid::log::Level::INFO); // default is INFO } uuid::log::Level WebLogService::log_level() const { return uuid::log::Logger::get_log_level(this); } void WebLogService::log_level(uuid::log::Level level) { uuid::log::Logger::register_handler(this, level); } size_t WebLogService::maximum_log_messages() const { return maximum_log_messages_; } void WebLogService::maximum_log_messages(size_t count) { maximum_log_messages_ = std::max((size_t)1, count); while (log_messages_.size() > maximum_log_messages_) { log_messages_.pop_front(); } } WebLogService::QueuedLogMessage::QueuedLogMessage(unsigned long id, std::shared_ptr && content) : id_(id) , content_(std::move(content)) { } void WebLogService::operator<<(std::shared_ptr message) { if (log_messages_.size() >= maximum_log_messages_) { log_messages_.pop_front(); } log_messages_.emplace_back(log_message_id_++, std::move(message)); } void WebLogService::loop() { if (!_events.count() || log_messages_.empty()) { return; } // put a small delay in const uint64_t now = uuid::get_uptime_ms(); if (now < last_transmit_ || now - last_transmit_ < 100) { return; } // see if we've advanced if (log_messages_.back().id_ > log_message_id_tail_) { transmit(log_messages_.back()); log_message_id_tail_ = log_messages_.back().id_; last_transmit_ = uuid::get_uptime_ms(); } } // send to web eventsource void WebLogService::transmit(const QueuedLogMessage & message) { DynamicJsonDocument jsonDocument = DynamicJsonDocument(EMSESP_JSON_SIZE_SMALL); JsonObject logEvent = jsonDocument.to(); logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3); logEvent["l"] = message.content_->level; logEvent["n"] = message.content_->name; logEvent["m"] = message.content_->text; size_t len = measureJson(jsonDocument); char * buffer = new char[len + 1]; if (buffer) { serializeJson(jsonDocument, buffer, len + 1); _events.send(buffer, "message", millis()); } delete[] buffer; } // send the current log buffer to the API void WebLogService::fetchLog(AsyncWebServerRequest * request) { MsgpackAsyncJsonResponse * response = new MsgpackAsyncJsonResponse(false, EMSESP_JSON_SIZE_XXLARGE_DYN); // 8kb buffer JsonObject root = response->getRoot(); JsonArray log = root.createNestedArray("events"); for (const auto & msg : log_messages_) { JsonObject logEvent = log.createNestedObject(); auto message = std::move(msg); logEvent["t"] = uuid::log::format_timestamp_ms(message.content_->uptime_ms, 3); logEvent["l"] = message.content_->level; logEvent["n"] = message.content_->name; logEvent["m"] = message.content_->text; } log_message_id_tail_ = log_messages_.back().id_; response->setLength(); request->send(response); } // sets the level after a POST void WebLogService::setLevel(AsyncWebServerRequest * request, JsonVariant & json) { if (not json.is()) { return; } auto && body = json.as(); uuid::log::Level level = body["level"]; log_level(level); // send the value back AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); JsonObject root = response->getRoot(); root["level"] = log_level(); response->setLength(); request->send(response); } // return the current log level after a GET void WebLogService::getLevel(AsyncWebServerRequest * request) { AsyncJsonResponse * response = new AsyncJsonResponse(false, EMSESP_JSON_SIZE_SMALL); JsonObject root = response->getRoot(); root["level"] = log_level(); root["max_messages"] = maximum_log_messages(); response->setLength(); request->send(response); } } // namespace emsesp