diff --git a/server/core/internal/logger.hh b/server/core/internal/logger.hh new file mode 100644 index 000000000..eef0f09f4 --- /dev/null +++ b/server/core/internal/logger.hh @@ -0,0 +1,171 @@ +#pragma once +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include + +#include +#include +#include + +#include + +namespace maxscale +{ + +// Minimal logger interface +class Logger +{ +public: + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + + virtual ~Logger() + { + } + + /** + * Write a message to the log + * + * @param msg Message to write + * @param len Length of message + * + * @return True on success + */ + virtual bool write(const char* msg, int len) = 0; + + /** + * Rotate the logfile + * + * @return True if the log was rotated + */ + virtual bool rotate() = 0; + + /** + * Get the name of the log file + * + * @return The name of the log file + */ + const char* filename() const + { + return m_filename.c_str(); + } + +protected: + Logger(const std::string& filename): + m_filename(filename) + { + } + + std::string m_filename; +}; + +class FileLogger: public Logger +{ +public: + FileLogger(const FileLogger&) = delete; + FileLogger& operator=(const FileLogger&) = delete; + + /** + * Create a new logger that writes to a file + * + * @param logdir Log file to open + * + * @return New logger instance or an empty unique_ptr on error + */ + static std::unique_ptr create(const std::string& filename); + + /** + * Close the log + * + * A footer is written to the log and the file is closed. + */ + ~FileLogger(); + + /** + * Write a message to the log + * + * @param msg Message to write + * @param len Length of message + * + * @return True on success + */ + bool write(const char* msg, int len); + + /** + * Rotate the logfile by reopening it + * + * @return True if the log was rotated. False if the opening of the new file + * descriptor failed in which case the old file descriptor will be used. + */ + bool rotate(); + +private: + int m_fd; + std::mutex m_lock; + + FileLogger(int fd, const std::string& filename); + bool write_header(); + bool write_footer(const char* suffix); + void close(const char* msg); +}; + +class StdoutLogger: public Logger +{ +public: + StdoutLogger(const StdoutLogger&) = delete; + StdoutLogger& operator=(const StdoutLogger&) = delete; + + /** + * Create a new logger that writes to stdout + * + * @param logdir Log file to open, has no functional effect on this logger + * + * @return New logger instance or an empty unique_ptr on error + */ + static std::unique_ptr create(const std::string& filename) + { + return std::unique_ptr(new StdoutLogger(filename)); + } + + /** + * Write a message to stdout + * + * @param msg Message to write + * @param len Length of message + * + * @return True on success + */ + bool write(const char* msg, int len) + { + return ::write(STDOUT_FILENO, msg, len) != -1; + } + + /** + * Rotate the "logfile" + * + * @return Always true + */ + bool rotate() + { + return true; + }; + +private: + StdoutLogger(const std::string& filename): + Logger(filename) + { + } +}; + +} diff --git a/server/core/logger.cc b/server/core/logger.cc new file mode 100644 index 000000000..1260dd257 --- /dev/null +++ b/server/core/logger.cc @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2016 MariaDB Corporation Ab + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file and at www.mariadb.com/bsl11. + * + * Change Date: 2022-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2 or later of the General + * Public License. + */ + +#include "internal/logger.hh" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +/** + * Error logging for the logger itself. + * + * For obvious reasons, it cannot use its own functions for reporting errors. + */ +#define LOG_ERROR(format, ...) do { fprintf(stderr, format, ##__VA_ARGS__); } while (false) + +// +// Helper functions +// +namespace +{ + +int open_fd(const std::string& filename) +{ + int fd = open(filename.c_str(), O_WRONLY | O_APPEND | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + + if (fd == -1) + { + LOG_ERROR("Failed to open file '%s': %d, %s\n", filename.c_str(), errno, mxs_strerror(errno)); + } + + return fd; +} + +bool should_log_error() +{ + using std::chrono::seconds; + static auto last_write = std::chrono::steady_clock::now() - seconds(61); + auto now = std::chrono::steady_clock::now(); + bool rval = false; + + if ((now - last_write).count() >= 60) + { + last_write = now; + rval = true; + } + + return rval; +} + +} + +namespace maxscale +{ + +// +// Public methods +// + +std::unique_ptr FileLogger::create(const std::string& filename) +{ + std::unique_ptr logger; + int fd = open_fd(filename); + + if (fd != -1) + { + logger.reset(new (std::nothrow) FileLogger(fd, filename)); + + if (logger) + { + logger->write_header(); + } + else + { + ::close(fd); + } + } + + return std::move(logger); +} + +FileLogger::~FileLogger() +{ + std::lock_guard guard(m_lock); + ss_dassert(m_fd != -1); + close("MariaDB MaxScale is shut down."); +} + +bool FileLogger::write(const char* msg, int len) +{ + bool rval = true; + std::lock_guard guard(m_lock); + + while (len > 0) + { + int rc; + do + { + rc = ::write(m_fd, msg, len); + } + while (rc == -1 && errno == EINTR); + + if (rc == -1) + { + if (should_log_error()) // Coarse error suppression + { + LOG_ERROR("Failed to write to log: %d, %s\n", errno, mxs_strerror(errno)); + } + + rval = false; + break; + } + + // If write only writes a part of the message, retry again + len -= rc; + msg += rc; + } + + return rval; +} + +bool FileLogger::rotate() +{ + std::lock_guard guard(m_lock); + int fd = open_fd(m_filename); + + if (fd != -1) + { + close("File closed due to log rotation."); + m_fd = fd; + } + + return fd != -1; +} + +// +// Private methods +// + +FileLogger::FileLogger(int fd, const std::string& filename): + Logger(filename), + m_fd(fd) +{ +} + +void FileLogger::close(const char* msg) +{ + write_footer(msg); + ::close(m_fd); + m_fd = -1; +} + +// Nearly identical to the one in log_manager.cc +bool FileLogger::write_header() +{ + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + + const char PREFIX[] = "MariaDB MaxScale "; // sizeof(PREFIX) includes the NULL. + char time_string[32]; // 26 would be enough, according to "man asctime". + asctime_r(&tm, time_string); + + size_t size = sizeof(PREFIX) + m_filename.length() + 2 * sizeof(' ') + strlen(time_string); + + char header[size + 2]; // For the 2 newlines. + sprintf(header, "\n\n%s%s %s", PREFIX, m_filename.c_str(), time_string); + + char line[sizeof(header) - 1]; + memset(line, '-', sizeof(line) - 1); + line[sizeof(line) - 1] = '\n'; + + bool ok = ::write(m_fd, header, sizeof(header) - 1) != -1 && + ::write(m_fd, line, sizeof(line)) != -1; + + if (!ok) + { + LOG_ERROR("Error: Writing log header failed due to %d, %s\n", + errno, mxs_strerror(errno)); + } + + return ok; +} + +// Nearly identical to the one in log_manager.cc +bool FileLogger::write_footer(const char* suffix) +{ + time_t t = time(NULL); + struct tm tm; + localtime_r(&t, &tm); + + const char FORMAT[] = "%04d-%02d-%02d %02d:%02d:%02d"; + char time_string[20]; // 19 chars + NULL. + + sprintf(time_string, FORMAT, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + size_t size = sizeof(time_string) + 3 * sizeof(' ') + strlen(suffix) + sizeof('\n'); + + char header[size]; + sprintf(header, "%s %s\n", time_string, suffix); + + char line[sizeof(header) - 1]; + memset(line, '-', sizeof(line) - 1); + line[sizeof(line) - 1] = '\n'; + + bool ok = ::write(m_fd, header, sizeof(header) - 1) != -1 && + ::write(m_fd, line, sizeof(line)) != -1; + + if (!ok) + { + LOG_ERROR("Error: Writing log footer failed due to %d, %s\n", + errno, mxs_strerror(errno)); + } + + return ok; +} + +}