From b9f7e269b6be514ac520d061d8c9f6a0b1bbaded Mon Sep 17 00:00:00 2001 From: Paul Elder Date: Sat, 13 Jul 2019 00:09:51 +0900 Subject: libcamera: logging: add syslog, stream, and nowhere logging targets Allow logging to syslog, or any given ostream, or to nowhere. The logging API is updated to accomodate these new logging destinations. LogMessage is modified to allow this. Signed-off-by: Paul Elder Reviewed-by: Laurent Pinchart --- include/libcamera/logging.h | 13 +- src/libcamera/include/log.h | 8 + src/libcamera/log.cpp | 465 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 404 insertions(+), 82 deletions(-) diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h index a8495cf6..2b6dd3f4 100644 --- a/include/libcamera/logging.h +++ b/include/libcamera/logging.h @@ -9,8 +9,17 @@ namespace libcamera { -void logSetFile(const char *file); -int logSetLevel(const char *category, const char *level); +enum LoggingTarget { + LoggingTargetNone, + LoggingTargetSyslog, + LoggingTargetFile, + LoggingTargetStream, +}; + +int logSetFile(const char *path); +int logSetStream(std::ostream *stream); +int logSetTarget(LoggingTarget target); +void logSetLevel(const char *category, const char *level); } /* namespace libcamera */ diff --git a/src/libcamera/include/log.h b/src/libcamera/include/log.h index 802836d2..9b203f97 100644 --- a/src/libcamera/include/log.h +++ b/src/libcamera/include/log.h @@ -60,12 +60,20 @@ public: std::ostream &stream() { return msgStream_; } + const struct timespec ×tamp() const { return timestamp_; } + LogSeverity severity() const { return severity_; } + const LogCategory &category() const { return category_; } + const std::string &fileInfo() const { return fileInfo_; } + const std::string msg() const { return msgStream_.str(); } + private: void init(const char *fileName, unsigned int line); std::ostringstream msgStream_; const LogCategory &category_; LogSeverity severity_; + struct timespec timestamp_; + std::string fileInfo_; }; class Loggable diff --git a/src/libcamera/log.cpp b/src/libcamera/log.cpp index 709c669c..91f7c3ee 100644 --- a/src/libcamera/log.cpp +++ b/src/libcamera/log.cpp @@ -15,8 +15,11 @@ #include #include #include +#include #include +#include + #include "utils.h" /** @@ -48,8 +51,179 @@ * to stderr. */ +/** + * \file logging.h + * \brief Logging management + * + * API to change the logging output destination and log levels programatically. + */ + namespace libcamera { +static int log_severity_to_syslog(LogSeverity severity) +{ + switch (severity) { + case LogDebug: + return LOG_DEBUG; + case LogInfo: + return LOG_INFO; + case LogWarning: + return LOG_WARNING; + case LogError: + return LOG_ERR; + case LogFatal: + return LOG_ALERT; + default: + return LOG_NOTICE; + } +} + +static std::string log_timespec_to_string(const struct timespec ×tamp) +{ + std::ostringstream ossTimestamp; + ossTimestamp.fill('0'); + ossTimestamp << "[" << timestamp.tv_sec / (60 * 60) << ":" + << std::setw(2) << (timestamp.tv_sec / 60) % 60 << ":" + << std::setw(2) << timestamp.tv_sec % 60 << "." + << std::setw(9) << timestamp.tv_nsec << "]"; + return ossTimestamp.str(); +} + +static const char *log_severity_name(LogSeverity severity) +{ + static const char *const names[] = { + " DBG", + " INFO", + " WARN", + " ERR", + "FATAL", + }; + + if (static_cast(severity) < ARRAY_SIZE(names)) + return names[severity]; + else + return "UNKWN"; +} + +/** + * \brief Log output + * + * The LogOutput class models a log output destination + */ +class LogOutput +{ +public: + LogOutput(const char *path); + LogOutput(std::ostream *stream); + LogOutput(); + ~LogOutput(); + + bool isValid() const; + void write(const LogMessage &msg); + +private: + void writeSyslog(const LogMessage &msg); + void writeStream(const LogMessage &msg); + + std::ostream *stream_; + LoggingTarget target_; +}; + +/** + * \brief Construct a log output based on a file + * \param[in] path Full path to log file + */ +LogOutput::LogOutput(const char *path) + : target_(LoggingTargetFile) +{ + stream_ = new std::ofstream(path); +} + +/** + * \brief Construct a log output based on a stream + * \param[in] stream Stream to send log output to + */ +LogOutput::LogOutput(std::ostream *stream) + : stream_(stream), target_(LoggingTargetStream) +{ +} + +/** + * \brief Construct a log output to syslog + */ +LogOutput::LogOutput() + : stream_(nullptr), target_(LoggingTargetSyslog) +{ + openlog("libcamera", LOG_PID, 0); +} + +LogOutput::~LogOutput() +{ + switch (target_) { + case LoggingTargetFile: + delete stream_; + break; + case LoggingTargetSyslog: + closelog(); + break; + default: + break; + } +} + +/** + * \brief Check if the log output is valid + * \return True if the log output is valid + */ +bool LogOutput::isValid() const +{ + switch (target_) { + case LoggingTargetFile: + return stream_->good(); + case LoggingTargetStream: + return stream_ != nullptr; + default: + return true; + } +} + +/** + * \brief Write message to log output + * \param[in] msg Message to write + */ +void LogOutput::write(const LogMessage &msg) +{ + switch (target_) { + case LoggingTargetSyslog: + writeSyslog(msg); + break; + case LoggingTargetStream: + case LoggingTargetFile: + writeStream(msg); + break; + default: + break; + } +} + +void LogOutput::writeSyslog(const LogMessage &msg) +{ + std::string str = std::string(log_severity_name(msg.severity())) + " " + + msg.category().name() + " " + msg.fileInfo() + " " + + msg.msg(); + syslog(log_severity_to_syslog(msg.severity()), "%s", str.c_str()); +} + +void LogOutput::writeStream(const LogMessage &msg) +{ + std::string str = std::string(log_timespec_to_string(msg.timestamp()) + + log_severity_name(msg.severity()) + " " + + msg.category().name() + " " + msg.fileInfo() + " " + + msg.msg()); + stream_->write(str.c_str(), str.size()); + stream_->flush(); +} + /** * \brief Message logger * @@ -60,7 +234,12 @@ class Logger public: static Logger *instance(); - void write(const std::string &msg); + void write(const LogMessage &msg); + + int logSetFile(const char *path); + int logSetStream(std::ostream *stream); + int logSetTarget(LoggingTarget target); + void logSetLevel(const char *category, const char *level); private: Logger(); @@ -69,52 +248,91 @@ private: void parseLogLevels(); static LogSeverity parseLogLevel(const std::string &level); - friend int logSetFile(const char *file); - friend void logSetLevel(const char *category, const char *level); - friend LogCategory; void registerCategory(LogCategory *category); void unregisterCategory(LogCategory *category); + void writeSyslog(const LogMessage &msg); + void writeStream(const LogMessage &msg); + std::unordered_set categories_; std::list> levels_; - std::ofstream file_; - std::ostream *output_; + std::shared_ptr output_; }; /** - * \brief Set the log file - * \param[in] file Full path to the log file + * \enum LoggingTarget + * \brief Log destination type + * \var LoggingTargetNone + * \brief No logging destination + * \sa Logger::logSetTarget + * \var LoggingTargetSyslog + * \brief Log to syslog + * \sa Logger::logSetTarget + * \var LoggingTargetFile + * \brief Log to file + * \sa Logger::logSetFile + * \var LoggingTargetStream + * \brief Log to stream + * \sa Logger::logSetStream + */ + +/** + * \brief Direct logging to a file + * \param[in] path Full path to the log file + * + * This function directs the log output to the file identified by \a path. The + * previous log target, if any, is closed, and all new log messages will be + * written to the new log file. + * + * If the function returns an error, the log target is not changed. * - * This function sets the logging output file to \a file. The previous log file, + * \return Zero on success, or a negative error code otherwise + */ +int logSetFile(const char *path) +{ + return Logger::instance()->logSetFile(path); +} + +/** + * \brief Direct logging to a stream + * \param[in] stream Stream to send log output to + * + * This function directs the log output to \a stream. The previous log target, * if any, is closed, and all new log messages will be written to the new log - * file. + * stream. * - * If \a file is a null pointer, the log is directed to stderr. If the - * function returns an error, the log file is not changed. + * If the function returns an error, the log file is not changed * * \return Zero on success, or a negative error code otherwise. */ -int logSetFile(const char *file) +int logSetStream(std::ostream *stream) { - Logger *logger = Logger::instance(); - - if (!file) { - logger->output_ = &std::cerr; - logger->file_.close(); - return 0; - } - - std::ofstream logFile(file); - if (!logFile.good()) - return -EINVAL; + return Logger::instance()->logSetStream(stream); +} - if (logger->output_ != &std::cerr) - logger->file_.close(); - logger->file_ = std::move(logFile); - logger->output_ = &logger->file_; - return 0; +/** + * \brief Set the logging target + * \param[in] target Logging destination + * + * This function sets the logging output to the target specified by \a target. + * The allowed values of \a target are LoggingTargetNone and + * LoggingTargetSyslog. LoggingTargetNone will send the log output to nowhere, + * and LoggingTargetSyslog will send the log output to syslog. The previous + * log target, if any, is closed, and all new log messages will be written to + * the new log destination. + * + * LoggingTargetFile and LoggingTargetStream are not valid values for \a target. + * Use logSetFile() and logSetStream() instead, respectively. + * + * If the function returns an error, the log file is not changed. + * + * \return Zero on success, or a negative error code otherwise. + */ +int logSetTarget(LoggingTarget target) +{ + return Logger::instance()->logSetTarget(target); } /** @@ -134,15 +352,7 @@ int logSetFile(const char *file) */ void logSetLevel(const char *category, const char *level) { - Logger *logger = Logger::instance(); - - LogSeverity severity = Logger::parseLogLevel(level); - if (severity == LogInvalid) - return; - - for (LogCategory *c : logger->categories_) - if (!strcmp(c->name(), category)) - c->setSeverity(severity); + Logger::instance()->logSetLevel(category, level); } /** @@ -161,19 +371,103 @@ Logger *Logger::instance() /** * \brief Write a message to the configured logger output - * \param[in] msg The message string + * \param[in] msg The message object + */ +void Logger::write(const LogMessage &msg) +{ + std::shared_ptr output = std::atomic_load(&output_); + if (!output) + return; + + output->write(msg); +} + +/** + * \brief Set the log file + * \param[in] path Full path to the log file + * + * \sa libcamera::logSetFile() + * + * \return Zero on success, or a negative error code otherwise. + */ +int Logger::logSetFile(const char *path) +{ + std::shared_ptr output = std::make_shared(path); + if (!output->isValid()) + return -EINVAL; + + std::atomic_store(&output_, output); + return 0; +} + +/** + * \brief Set the log stream + * \param[in] stream Stream to send log output to + * + * \sa libcamera::logSetStream() + * + * \return Zero on success, or a negative error code otherwise. */ -void Logger::write(const std::string &msg) +int Logger::logSetStream(std::ostream *stream) { - output_->write(msg.c_str(), msg.size()); - output_->flush(); + std::shared_ptr output = std::make_shared(stream); + std::atomic_store(&output_, output); + return 0; +} + +/** + * \brief Set the log target + * \param[in] target Log destination + * + * \sa libcamera::logSetTarget() + * + * \return Zero on success, or a negative error code otherwise. + */ +int Logger::logSetTarget(enum LoggingTarget target) +{ + std::shared_ptr output; + + switch (target) { + case LoggingTargetSyslog: + output = std::make_shared(); + std::atomic_store(&output_, output); + break; + case LoggingTargetNone: + output = nullptr; + std::atomic_store(&output_, std::shared_ptr()); + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * \brief Set the log level + * \param[in] category Logging category + * \param[in] level Log level + * + * \sa libcamera::logSetLevel() + */ +void Logger::logSetLevel(const char *category, const char *level) +{ + LogSeverity severity = parseLogLevel(level); + if (severity == LogInvalid) + return; + + for (LogCategory *c : categories_) { + if (!strcmp(c->name(), category)) { + c->setSeverity(severity); + break; + } + } } /** * \brief Construct a logger */ Logger::Logger() - : output_(&std::cerr) { parseLogFile(); parseLogLevels(); @@ -183,18 +477,24 @@ Logger::Logger() * \brief Parse the log output file from the environment * * If the LIBCAMERA_LOG_FILE environment variable is set, open the file it - * points to and redirect the logger output to it. Errors are silently ignored - * and don't affect the logger output (set to stderr). + * points to and redirect the logger output to it. If the environment variable + * is set to "syslog", then the logger output will be directed to syslog. Errors + * are silently ignored and don't affect the logger output (set to stderr). */ void Logger::parseLogFile() { const char *file = utils::secure_getenv("LIBCAMERA_LOG_FILE"); - if (!file) + if (!file) { + logSetStream(&std::cerr); return; + } + + if (!strcmp(file, "syslog")) { + logSetTarget(LoggingTargetSyslog); + return; + } - file_.open(file); - if (file_.good()) - output_ = &file_; + logSetFile(file); } /** @@ -408,22 +708,6 @@ const LogCategory &LogCategory::defaultCategory() return category; } -static const char *log_severity_name(LogSeverity severity) -{ - static const char *const names[] = { - " DBG", - " INFO", - " WARN", - " ERR", - "FATAL", - }; - - if (static_cast(severity) < ARRAY_SIZE(names)) - return names[severity]; - else - return "UNKWN"; -} - /** * \class LogMessage * \brief Internal log message representation. @@ -493,18 +777,11 @@ LogMessage::LogMessage(LogMessage &&other) void LogMessage::init(const char *fileName, unsigned int line) { /* Log the timestamp, severity and file information. */ - struct timespec timestamp; - clock_gettime(CLOCK_MONOTONIC, ×tamp); - msgStream_.fill('0'); - msgStream_ << "[" << timestamp.tv_sec / (60 * 60) << ":" - << std::setw(2) << (timestamp.tv_sec / 60) % 60 << ":" - << std::setw(2) << timestamp.tv_sec % 60 << "." - << std::setw(9) << timestamp.tv_nsec << "]"; - msgStream_.fill(' '); + clock_gettime(CLOCK_MONOTONIC, ×tamp_); - msgStream_ << " " << log_severity_name(severity_); - msgStream_ << " " << category_.name(); - msgStream_ << " " << utils::basename(fileName) << ":" << line << " "; + std::ostringstream ossFileInfo; + ossFileInfo << utils::basename(fileName) << ":" << line; + fileInfo_ = ossFileInfo.str(); } LogMessage::~LogMessage() @@ -515,10 +792,8 @@ LogMessage::~LogMessage() msgStream_ << std::endl; - if (severity_ >= category_.severity()) { - std::string msg(msgStream_.str()); - Logger::instance()->write(msg); - } + if (severity_ >= category_.severity()) + Logger::instance()->write(*this); if (severity_ == LogSeverity::LogFatal) std::abort(); @@ -534,6 +809,36 @@ LogMessage::~LogMessage() * \return A reference to the log message stream */ +/** + * \fn LogMessage::timestamp() + * \brief Retrieve the timestamp of the log message + * \return The timestamp of the message + */ + +/** + * \fn LogMessage::severity() + * \brief Retrieve the severity of the log message + * \return The severity of the message + */ + +/** + * \fn LogMessage::category() + * \brief Retrieve the category of the log message + * \return The category of the message + */ + +/** + * \fn LogMessage::fileInfo() + * \brief Retrieve the file info of the log message + * \return The file info of the message + */ + +/** + * \fn LogMessage::msg() + * \brief Retrieve the message text of the log message + * \return The message text of the message, as a string + */ + /** * \class Loggable * \brief Base class to support log message extensions -- cgit v1.2.1