| /** |
| * @brief Log manager. |
| * |
| * This file is part of HostLogger project. |
| * |
| * Copyright (c) 2018 YADRO |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "log_manager.hpp" |
| |
| #include "config.hpp" |
| |
| #include <dirent.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| |
| #include <cstring> |
| #include <set> |
| #include <vector> |
| |
| // Path to the Unix Domain socket file used to read host's logs |
| #define HOSTLOG_SOCKET_PATH "\0obmc-console" |
| // Number of connection attempts |
| #define HOSTLOG_SOCKET_ATTEMPTS 60 |
| // Pause between connection attempts in seconds |
| #define HOSTLOG_SOCKET_PAUSE 1 |
| // Max buffer size to read from socket |
| #define MAX_SOCKET_BUFFER_SIZE 512 |
| |
| LogManager::LogManager() : fd_(-1) |
| { |
| } |
| |
| LogManager::~LogManager() |
| { |
| closeHostLog(); |
| } |
| |
| int LogManager::openHostLog() |
| { |
| int rc; |
| |
| do |
| { |
| // Create socket |
| fd_ = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (fd_ == -1) |
| { |
| rc = errno; |
| fprintf(stderr, "Unable to create socket: error [%i] %s\n", rc, |
| strerror(rc)); |
| break; |
| } |
| |
| // Set non-blocking mode for socket |
| int opt = 1; |
| rc = ioctl(fd_, FIONBIO, &opt); |
| if (rc != 0) |
| { |
| rc = errno; |
| fprintf(stderr, |
| "Unable to set non-blocking mode for log socket: error " |
| "[%i] %s\n", |
| rc, strerror(rc)); |
| break; |
| } |
| |
| sockaddr_un sa = {0}; |
| sa.sun_family = AF_UNIX; |
| constexpr int min_path = |
| sizeof(HOSTLOG_SOCKET_PATH) < sizeof(sa.sun_path) |
| ? sizeof(HOSTLOG_SOCKET_PATH) |
| : sizeof(sa.sun_path); |
| memcpy(&sa.sun_path, HOSTLOG_SOCKET_PATH, min_path); |
| |
| // Connect to host's log stream via socket. |
| // The owner of the socket (server) is obmc-console service and |
| // we have a dependency on it written in the systemd unit file, but |
| // we can't guarantee that the socket is initialized at the moment. |
| rc = -1; |
| for (int attempt = 0; rc != 0 && attempt < HOSTLOG_SOCKET_ATTEMPTS; |
| ++attempt) |
| { |
| rc = connect(fd_, reinterpret_cast<const sockaddr*>(&sa), |
| sizeof(sa) - sizeof(sa.sun_path) + |
| sizeof(HOSTLOG_SOCKET_PATH) - 1); |
| sleep(HOSTLOG_SOCKET_PAUSE); |
| } |
| if (rc < 0) |
| { |
| rc = errno; |
| fprintf(stderr, |
| "Unable to connect to host log socket: error [%i] %s\n", rc, |
| strerror(rc)); |
| } |
| } while (false); |
| |
| if (rc != 0) |
| closeHostLog(); |
| |
| return rc; |
| } |
| |
| void LogManager::closeHostLog() |
| { |
| if (fd_ != -1) |
| { |
| ::close(fd_); |
| fd_ = -1; |
| } |
| } |
| |
| int LogManager::getHostLogFd() const |
| { |
| return fd_; |
| } |
| |
| int LogManager::handleHostLog() |
| { |
| int rc = 0; |
| std::vector<char> buff(MAX_SOCKET_BUFFER_SIZE); |
| size_t readLen = MAX_SOCKET_BUFFER_SIZE; |
| |
| // Read all existing data from log stream |
| while (rc == 0 && readLen != 0) |
| { |
| rc = readHostLog(&buff[0], buff.size(), readLen); |
| if (rc == 0 && readLen != 0) |
| storage_.parse(&buff[0], readLen); |
| } |
| |
| return rc; |
| } |
| |
| int LogManager::flush() |
| { |
| int rc; |
| |
| if (storage_.empty()) |
| return 0; // Nothing to save |
| |
| const std::string logFile = prepareLogPath(); |
| if (logFile.empty()) |
| return EIO; |
| |
| rc = storage_.save(logFile.c_str()); |
| if (rc != 0) |
| return rc; |
| |
| storage_.clear(); |
| |
| // Non critical tasks, don't check returned status |
| rotateLogFiles(); |
| |
| return 0; |
| } |
| |
| int LogManager::readHostLog(char* buffer, size_t bufferLen, |
| size_t& readLen) const |
| { |
| int rc = 0; |
| |
| const ssize_t rsz = ::read(fd_, buffer, bufferLen); |
| if (rsz >= 0) |
| readLen = static_cast<size_t>(rsz); |
| else |
| { |
| readLen = 0; |
| if (errno != EAGAIN && errno != EWOULDBLOCK) |
| { |
| rc = errno; |
| fprintf(stderr, "Unable to read host log: error [%i] %s\n", rc, |
| strerror(rc)); |
| } |
| } |
| |
| return rc; |
| } |
| |
| std::string LogManager::prepareLogPath() const |
| { |
| // Create path for logs |
| if (::access(loggerConfig.path, F_OK) != 0) |
| { |
| const std::string logPath(loggerConfig.path); |
| const size_t len = logPath.length(); |
| size_t pos = 0; |
| while (pos < len - 1) |
| { |
| pos = logPath.find('/', pos + 1); |
| const std::string createPath = logPath.substr(0, pos); |
| if (::mkdir(createPath.c_str(), |
| S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && |
| errno != EEXIST) |
| { |
| const int rc = errno; |
| fprintf(stderr, "Unable to create dir %s: error [%i] %s\n", |
| createPath.c_str(), rc, strerror(rc)); |
| return std::string(); |
| } |
| } |
| } |
| |
| // Construct log file name |
| time_t ts; |
| time(&ts); |
| tm lt = {0}; |
| localtime_r(&ts, <); |
| char fileName[64]; |
| snprintf(fileName, sizeof(fileName), "/host_%i%02i%02i_%02i%02i%02i.log.gz", |
| lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, |
| lt.tm_min, lt.tm_sec); |
| |
| return std::string(loggerConfig.path) + fileName; |
| } |
| |
| int LogManager::rotateLogFiles() const |
| { |
| if (loggerConfig.rotationLimit == 0) |
| return 0; // Not applicable |
| |
| int rc = 0; |
| |
| // Get file list to std::set |
| std::set<std::string> logFiles; |
| DIR* dh = opendir(loggerConfig.path); |
| if (!dh) |
| { |
| rc = errno; |
| fprintf(stderr, "Unable to open directory %s: error [%i] %s\n", |
| loggerConfig.path, rc, strerror(rc)); |
| return rc; |
| } |
| dirent* dir; |
| while ((dir = readdir(dh))) |
| { |
| if (dir->d_type != DT_DIR) |
| logFiles.insert(dir->d_name); |
| } |
| closedir(dh); |
| |
| // Log file has a name with a timestamp generated by prepareLogPath(). |
| // The sorted array of names (std::set) will contain the oldest file on the |
| // top. |
| // Remove oldest files. |
| int filesToRemove = |
| static_cast<int>(logFiles.size()) - loggerConfig.rotationLimit; |
| while (rc == 0 && --filesToRemove >= 0) |
| { |
| std::string fileToRemove = loggerConfig.path; |
| fileToRemove += '/'; |
| fileToRemove += *logFiles.begin(); |
| if (::unlink(fileToRemove.c_str()) == -1) |
| { |
| rc = errno; |
| fprintf(stderr, "Unable to delete file %s: error [%i] %s\n", |
| fileToRemove.c_str(), rc, strerror(rc)); |
| } |
| logFiles.erase(fileToRemove); |
| } |
| |
| return rc; |
| } |