From patchwork Tue Jul 16 07:05:05 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul Elder X-Patchwork-Id: 1707 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id E234160C23 for ; Tue, 16 Jul 2019 09:05:19 +0200 (CEST) Received: from emerald.amanokami.net (unknown [IPv6:2a00:79e1:abc:3602:b57a:2dda:be67:ac6e]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A741A564; Tue, 16 Jul 2019 09:05:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1563260719; bh=yyP/Z0YQT3BezTem6N81/14JgN3D5U9TQDjD/Zu4G9Y=; h=From:To:Cc:Subject:Date:From; b=P+9oK3vlx+yi2+seTVR9oiOaVGPAhE/3Vne1vF94P/C78tAlGofSmV8pLt0jiRdjH lMMCZ5Et+CKPnB3cSjbZQAhtS3rHGLyOoZJh7Lg4UGtCG/KYXXNf+dXJiNFVcr67Py aRfakwRav9XAKb0amfYbKHfqgPdMB4HgLVG/85f0= From: Paul Elder To: libcamera-devel@lists.libcamera.org Date: Tue, 16 Jul 2019 16:05:05 +0900 Message-Id: <20190716070508.24589-1-paul.elder@ideasonboard.com> X-Mailer: git-send-email 2.17.1 Subject: [libcamera-devel] [PATCH v2 1/4] libcamera: logging: add syslog, stream, and nowhere logging targets X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 16 Jul 2019 07:05:20 -0000 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 --- Changes in v2: - make LogMessage's message component getters return the data directly, and not as a string (eg. severity, category, timestamp) - move LogMessage's message component formatting (converting to string) to Logger - add LogOutput to abstract away log output destinations from the Logger, and to prevent anticipated concurrency issues (changing the log output between commencing and completing a previous write) - upgrade documentation - other minor changes include/libcamera/logging.h | 13 +- src/libcamera/include/log.h | 8 + src/libcamera/log.cpp | 458 +++++++++++++++++++++++++++++------- 3 files changed, 397 insertions(+), 82 deletions(-) diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h index a8495cf..2b6dd3f 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 802836d..9b203f9 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 709c669..3108ef3 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,172 @@ * 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 << "]"; + ossTimestamp.fill(' '); + 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 desination + */ +class LogOutput +{ +public: + LogOutput(const char *path); + LogOutput(std::ostream *stream); + LogOutput(); + ~LogOutput(); + + bool good() 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 log output is a valid stream + */ +bool LogOutput::good() const +{ + return stream_ && stream_->good(); +} + +/** + * \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 +227,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 +241,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 sets the logging output file to \a file. The previous log file, - * if any, is closed, and all new log messages will be written to the new 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 \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 target is not changed. * * \return Zero on success, or a negative error code otherwise. */ -int logSetFile(const char *file) +int logSetFile(const char *path) { - Logger *logger = Logger::instance(); - - if (!file) { - logger->output_ = &std::cerr; - logger->file_.close(); - return 0; - } + return Logger::instance()->logSetFile(path); +} - std::ofstream logFile(file); - if (!logFile.good()) - return -EINVAL; +/** + * \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 + * stream. + * + * If the function returns an error, the log file is not changed. + * + * \return Zero on success, or a negative error code otherwise. + */ +int logSetStream(std::ostream *stream) +{ + 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 +345,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 +364,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->good()) + 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_, output); + 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 +470,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 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 +701,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 +770,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 +785,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 +802,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 Getter for 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