[{"id":2253,"web_url":"https://patchwork.libcamera.org/comment/2253/","msgid":"<20190714075644.GB6043@pendragon.ideasonboard.com>","date":"2019-07-14T07:56:44","subject":"Re: [libcamera-devel] [PATCH 1/4] libcamera: logging: add syslog,\n\tstream, and nowhere logging targets","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Paul,\n\nThank you for the patch.\n\nOn Sat, Jul 13, 2019 at 05:16:17AM +0900, Paul Elder wrote:\n> Allow logging to syslog, or any given ostream, or to nowhere. The\n> logging API is updated to accomodate these new logging destinations.\n> LogMessage is modified to allow this.\n\nIt's fine for this time, but review would have been easier if you had\nsplit this patch in three: rework of the internal API to pass the\nLogMessage to the Logger, addition of ostream log support and addition\nof syslog support.\n\n> Signed-off-by: Paul Elder <paul.elder@ideasonboard.com>\n> ---\n>  include/libcamera/logging.h |  13 +-\n>  src/libcamera/include/log.h |  11 ++\n>  src/libcamera/log.cpp       | 345 +++++++++++++++++++++++++++++-------\n>  3 files changed, 306 insertions(+), 63 deletions(-)\n> \n> diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h\n> index a8495cf..a6fa8dc 100644\n> --- a/include/libcamera/logging.h\n> +++ b/include/libcamera/logging.h\n> @@ -9,8 +9,17 @@\n>  \n>  namespace libcamera {\n>  \n> -void logSetFile(const char *file);\n> -int logSetLevel(const char *category, const char *level);\n> +enum LoggingTarget {\n> +\tLoggingTargetNone,\n> +\tLoggingTargetSyslog,\n> +\tLoggingTargetFile,\n> +\tLoggingTargetStream,\n> +};\n> +\n> +int logSetFile(const char *path);\n> +int logSetStream(std::ostream &stream);\n\nWe're going to keep a reference to the ostream internally, so I would\npass it as a pointer.\n\n> +int logSetTarget(LoggingTarget target);\n> +void logSetLevel(const char *category, const char *level);\n>  \n>  } /* namespace libcamera */\n>  \n> diff --git a/src/libcamera/include/log.h b/src/libcamera/include/log.h\n> index 802836d..394db83 100644\n> --- a/src/libcamera/include/log.h\n> +++ b/src/libcamera/include/log.h\n> @@ -60,12 +60,23 @@ public:\n>  \n>  \tstd::ostream &stream() { return msgStream_; }\n>  \n> +\tstd::string &timestamp() { return logTimestamp_; }\n> +\tLogSeverity severity() { return severity_; }\n> +\tstd::string &category() { return logCategory_; }\n> +\tstd::string &fileInfo() { return logFileInfo_; }\n> +\tstd::string &msg() { return msg_; }\n\nAll the functions returning a reference should return a const reference,\nand be marked as const.\n\n> +\n>  private:\n>  \tvoid init(const char *fileName, unsigned int line);\n>  \n>  \tstd::ostringstream msgStream_;\n>  \tconst LogCategory &category_;\n>  \tLogSeverity severity_;\n> +\n> +\tstd::string logTimestamp_;\n\nLet's store this as a struct timespec and do the formatting in the\nLogger, with everything else.\n\n> +\tstd::string logCategory_;\n\nWe should expose category_ instead and drop this field.\n\n> +\tstd::string logFileInfo_;\n\nI would call this fileInfo_ as logFileInfo mislead me into thinking it\nwas information about the log file.\n\n> +\tstd::string msg_;\n>  };\n>  \n>  class Loggable\n> diff --git a/src/libcamera/log.cpp b/src/libcamera/log.cpp\n> index 709c669..df63e81 100644\n> --- a/src/libcamera/log.cpp\n> +++ b/src/libcamera/log.cpp\n> @@ -15,8 +15,11 @@\n>  #include <iostream>\n>  #include <list>\n>  #include <string.h>\n> +#include <syslog.h>\n>  #include <unordered_set>\n>  \n> +#include <libcamera/logging.h>\n> +\n>  #include \"utils.h\"\n>  \n>  /**\n> @@ -60,7 +63,12 @@ class Logger\n>  public:\n>  \tstatic Logger *instance();\n>  \n> -\tvoid write(const std::string &msg);\n> +\tvoid write(LogMessage *msg);\n\n\tvoid write(const LogMessage &msg);\n\nas passing a null msg is not valid, and you don't need to modify it.\n\n> +\n> +\tint logSetFile(const char *path);\n> +\tint logSetStream(std::ostream &stream);\n\nPointer here as well.\n\n> +\tint logSetTarget(LoggingTarget target);\n> +\tvoid logSetLevel(const char *category, const char *level);\n>  \n>  private:\n>  \tLogger();\n> @@ -68,9 +76,7 @@ private:\n>  \tvoid parseLogFile();\n>  \tvoid parseLogLevels();\n>  \tstatic LogSeverity parseLogLevel(const std::string &level);\n> -\n> -\tfriend int logSetFile(const char *file);\n> -\tfriend void logSetLevel(const char *category, const char *level);\n> +\tvoid closeLog();\n>  \n>  \tfriend LogCategory;\n>  \tvoid registerCategory(LogCategory *category);\n> @@ -81,40 +87,83 @@ private:\n>  \n>  \tstd::ofstream file_;\n>  \tstd::ostream *output_;\n> +\n> +\tenum LoggingTarget target_;\n\ns/enum //\n\n>  };\n>  \n> +/**\n> + * \\enum LoggingTarget\n> + * \\brief Log destination type\n> + * \\var LoggingTargetNone\n> + * \\brief No logging destination\n> + * \\sa logSetTarget\n\nYou don't need to repeat the \\sa line, let's have one only after \\brief.\n\n> + * \\var LoggingTargetSyslog\n> + * \\brief Log to syslog\n> + * \\sa logSetTarget\n> + * \\var LoggingTargetFile\n> + * \\brief Log to file\n> + * \\sa logSetFile\n> + * \\var LoggingTargetStream\n> + * \\brief Log to stream\n> + * \\sa logSetStream\n> + */\n> +\n>  /**\n>   * \\brief Set the log file\n\nLet's name this \"Direct logging to a file\" and update the other\naccordingly, as it does more than just setting the log file.\n\n> - * \\param[in] file Full path to the log file\n> + * \\param[in] path Full path to the log file\n>   *\n> - * This function sets the logging output file to \\a file. The previous log file,\n> + * This function sets the logging output file to \\a path. The previous log file,\n\n\"This function directs the log output to the file identified by \\a path. The\nprevious log target, if any, is closed, ...\"\n\n>   * if any, is closed, and all new log messages will be written to the new log\n>   * file.\n>   *\n> - * If \\a file is a null pointer, the log is directed to stderr. If the\n> - * function returns an error, the log file is not changed.\n> + * If the function returns an error, the log file is not changed.\n\ns/log file/log target/\n\nThose three comments apply to the other functions (and the corresponding\nmethods of the Logger class).\n\n>   *\n>   * \\return Zero on success, or a negative error code otherwise.\n>   */\n> -int logSetFile(const char *file)\n> +int logSetFile(const char *path)\n>  {\n> -\tLogger *logger = Logger::instance();\n> -\n> -\tif (!file) {\n> -\t\tlogger->output_ = &std::cerr;\n> -\t\tlogger->file_.close();\n> -\t\treturn 0;\n> -\t}\n> +\treturn Logger::instance()->logSetFile(path);\n> +}\n>  \n> -\tstd::ofstream logFile(file);\n> -\tif (!logFile.good())\n> -\t\treturn -EINVAL;\n> +/**\n> + * \\brief Set the log stream\n> + * \\param[in] stream Stream to send log output to\n> + *\n> + * This function sets the logging output stream to \\a stream. The previous log file,\n> + * if any, is closed, and all new log messages will be written to the new log\n> + * stream.\n\nPlease wrap at 80 columns.\n\n> + *\n> + * If the function returns an error, the log file is not changed.\n> + *\n> + * \\return Zero on success, or a negative error code otherwise.\n> + */\n> +int logSetStream(std::ostream &stream)\n> +{\n> +\treturn Logger::instance()->logSetStream(stream);\n> +}\n>  \n> -\tif (logger->output_ != &std::cerr)\n> -\t\tlogger->file_.close();\n> -\tlogger->file_ = std::move(logFile);\n> -\tlogger->output_ = &logger->file_;\n> -\treturn 0;\n> +/**\n> + * \\brief Set the log target\n> + * \\param[in] target Log destination\n> + *\n> + * This function sets the logging output to the target specified by \\a target.\n> + * The allowed values of \\a target are LoggingTargetNone and LoggingTargetSyslog.\n> + * LoggingTargetNone will send the log output to nowhere, and LoggingTargetSyslog\n> + * will send the log output to syslog. The previous log file, if any, is closed,\n> + * and all new log messages will be written to the new log destination.\n\nWrapping here too please.\n\n> + *\n> + * LoggingTargetFile and LoggingTargetStream are not valid values for \\a target.\n> + * Use logSetFile() and logSetStream() instead, respectively.\n> + *\n> + * \\sa LoggingTarget\n\nI think you can drop this as doxygen will link to Logging Target in the\nfunction argument.\n\n> + *\n> + * If the function returns an error, the log file is not changed.\n> + *\n> + * \\return Zero on success, or a negative error code otherwise.\n> + */\n> +int logSetTarget(LoggingTarget target)\n> +{\n> +\treturn Logger::instance()->logSetTarget(target);\n>  }\n>  \n>  /**\n> @@ -134,15 +183,41 @@ int logSetFile(const char *file)\n>   */\n>  void logSetLevel(const char *category, const char *level)\n>  {\n> -\tLogger *logger = Logger::instance();\n> +\tLogger::instance()->logSetLevel(category, level);\n> +}\n>  \n> -\tLogSeverity severity = Logger::parseLogLevel(level);\n> -\tif (severity == LogInvalid)\n> -\t\treturn;\n> +static int log_severity_to_syslog(LogSeverity severity)\n> +{\n> +\tswitch (severity) {\n> +\tcase LogDebug:\n> +\t\treturn LOG_DEBUG;\n> +\tcase LogInfo:\n> +\t\treturn LOG_INFO;\n> +\tcase LogWarning:\n> +\t\treturn LOG_WARNING;\n> +\tcase LogError:\n> +\t\treturn LOG_ERR;\n> +\tcase LogFatal:\n> +\t\treturn LOG_ALERT;\n> +\tdefault:\n> +\t\treturn LOG_NOTICE;\n> +\t}\n> +}\n>  \n> -\tfor (LogCategory *c : logger->categories_)\n> -\t\tif (!strcmp(c->name(), category))\n> -\t\t\tc->setSeverity(severity);\n> +static const char *log_severity_name(LogSeverity severity)\n> +{\n> +\tstatic const char *const names[] = {\n> +\t\t\"  DBG\",\n> +\t\t\" INFO\",\n> +\t\t\" WARN\",\n> +\t\t\"  ERR\",\n> +\t\t\"FATAL\",\n> +\t};\n> +\n> +\tif (static_cast<unsigned int>(severity) < ARRAY_SIZE(names))\n> +\t\treturn names[severity];\n> +\telse\n> +\t\treturn \"UNKWN\";\n>  }\n>  \n>  /**\n> @@ -163,17 +238,119 @@ Logger *Logger::instance()\n>   * \\brief Write a message to the configured logger output\n>   * \\param[in] msg The message string\n\nThis should be updated.\n\n>   */\n> -void Logger::write(const std::string &msg)\n> +void Logger::write(LogMessage *msg)\n> +{\n> +\tstd::string str;\n> +\n> +\tswitch (target_) {\n> +\tcase LoggingTargetNone:\n> +\t\tbreak;\n> +\tcase LoggingTargetSyslog:\n> +\t\tstr = std::string(log_severity_name(msg->severity())) + \" \" +\n> +\t\t      msg->category() + \" \" + msg->fileInfo() + \" \" + msg->msg();\n> +\t\tsyslog(log_severity_to_syslog(msg->severity()), \"%s\", str.c_str());\n\nI would move this to a writeSyslog() method.\n\n> +\t\tbreak;\n> +\tcase LoggingTargetFile:\n> +\tcase LoggingTargetStream:\n> +\t\tstr = msg->timestamp() + log_severity_name(msg->severity()) +\n> +\t\t      \" \" + msg->category() + \" \" + msg->fileInfo() + \" \" +\n> +\t\t      msg->msg();\n> +\t\toutput_->write(str.c_str(), str.size());\n> +\t\toutput_->flush();\n\nAnd this to a writeStream() method.\n\n> +\t\tbreak;\n> +\t}\n> +}\n> +\n> +/**\n> + * \\brief Set the log file\n> + * \\param[in] path Full path to the log file\n> + *\n> + * \\sa logSetFile\n\nCould you verify that this links to the global function, and that you\ndon't need to specify libcamera::logSetFile() ?\n\n> + *\n> + * \\return Zero on success, or a negative error code otherwise.\n> + */\n> +int Logger::logSetFile(const char *path)\n> +{\n> +\tstd::ofstream logFile(path);\n> +\tif (!logFile.good())\n> +\t\treturn -EINVAL;\n> +\n> +\tcloseLog();\n> +\tfile_ = std::move(logFile);\n> +\toutput_ = &file_;\n> +\ttarget_ = LoggingTargetFile;\n> +\n> +\treturn 0;\n> +}\n> +\n> +/**\n> + * \\brief Set the log stream\n> + * \\param[in] stream Stream to send log output to\n> + *\n> + * \\sa logSetStream\n> + *\n> + * \\return Zero on success, or a negative error code otherwise.\n> + */\n> +int Logger::logSetStream(std::ostream &stream)\n>  {\n> -\toutput_->write(msg.c_str(), msg.size());\n> -\toutput_->flush();\n> +\tcloseLog();\n> +\toutput_ = &stream;\n> +\ttarget_ = LoggingTargetStream;\n> +\treturn 0;\n> +}\n> +\n> +/**\n> + * \\brief Set the log target\n> + * \\param[in] target Log destination\n> + *\n> + * \\sa logSetTarget\n> + *\n> + * \\return Zero on success, or a negative error code otherwise.\n> + */\n> +int Logger::logSetTarget(enum LoggingTarget target)\n> +{\n> +\tswitch (target) {\n> +\tcase LoggingTargetNone:\n> +\t\tcloseLog();\n> +\t\toutput_ = nullptr;\n> +\t\ttarget_ = LoggingTargetNone;\n> +\t\tbreak;\n> +\tcase LoggingTargetSyslog:\n> +\t\topenlog(\"libcamera\", LOG_PID, 0);\n> +\t\tcloseLog();\n> +\t\toutput_ = nullptr;\n> +\t\ttarget_ = LoggingTargetSyslog;\n> +\t\tbreak;\n> +\tdefault:\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +/**\n> + * \\brief Set the log level\n> + * \\param[in] category Logging category\n> + * \\param[in] level Log level\n> + *\n> + * \\sa logSetLevel\n> + */\n> +void Logger::logSetLevel(const char *category, const char *level)\n> +{\n> +\tLogSeverity severity = Logger::parseLogLevel(level);\n\nNow that this method is part of the Logger file you can drop the\nLogger:: prefix.\n\n> +\tif (severity == LogInvalid)\n> +\t\treturn;\n> +\n> +\tfor (LogCategory *c : categories_)\n> +\t\tif (!strcmp(c->name(), category))\n> +\t\t\tc->setSeverity(severity);\n\nShould we break here ?\n\n>  }\n>  \n>  /**\n>   * \\brief Construct a logger\n>   */\n>  Logger::Logger()\n> -\t: output_(&std::cerr)\n> +\t: output_(&std::cerr), target_(LoggingTargetStream)\n>  {\n>  \tparseLogFile();\n>  \tparseLogLevels();\n> @@ -184,6 +361,7 @@ Logger::Logger()\n>   *\n>   * If the LIBCAMERA_LOG_FILE environment variable is set, open the file it\n>   * points to and redirect the logger output to it. Errors are silently ignored\n> + *\n\n?\n\n>   * and don't affect the logger output (set to stderr).\n>   */\n>  void Logger::parseLogFile()\n> @@ -192,9 +370,16 @@ void Logger::parseLogFile()\n>  \tif (!file)\n>  \t\treturn;\n>  \n> +\tif (!strcmp(file, \"syslog\")) {\n\nYou should document the magic \"syslog\" value for the LIBCAMERA_LOG_FILE\nenvironment variable.\n\n> +\t\topenlog(\"libcamera\", LOG_PID, 0);\n> +\t\ttarget_ = LoggingTargetSyslog;\n> +\t\treturn;\n> +\t}\n> +\n>  \tfile_.open(file);\n>  \tif (file_.good())\n>  \t\toutput_ = &file_;\n> +\ttarget_ = LoggingTargetFile;\n\nShould this method call setLogSyslog() and setLogFile() instead of\nduplicating the code ?\n\n>  }\n>  \n>  /**\n> @@ -286,6 +471,25 @@ LogSeverity Logger::parseLogLevel(const std::string &level)\n>  \treturn static_cast<LogSeverity>(severity);\n>  }\n>  \n> +/**\n> + * \\brief Close the current log file\n\ns/file/target/\n\n> + */\n> +void Logger::closeLog()\n> +{\n> +\tswitch (target_) {\n> +\tcase LoggingTargetNone:\n> +\t\tbreak;\n> +\tcase LoggingTargetSyslog:\n> +\t\tcloselog();\n> +\t\tbreak;\n> +\tcase LoggingTargetFile:\n> +\t\tfile_.close();\n> +\t\tbreak;\n> +\tcase LoggingTargetStream:\n> +\t\tbreak;\n> +\t}\n\nShould we add\n\n\ttarget_ = LoggingTargetNone;\n\toutput_ = nullptr;\n\n?\n\n> +}\n> +\n>  /**\n>   * \\brief Register a log category with the logger\n>   * \\param[in] category The log category\n> @@ -408,22 +612,6 @@ const LogCategory &LogCategory::defaultCategory()\n>  \treturn category;\n>  }\n>  \n> -static const char *log_severity_name(LogSeverity severity)\n> -{\n> -\tstatic const char *const names[] = {\n> -\t\t\"  DBG\",\n> -\t\t\" INFO\",\n> -\t\t\" WARN\",\n> -\t\t\"  ERR\",\n> -\t\t\"FATAL\",\n> -\t};\n> -\n> -\tif (static_cast<unsigned int>(severity) < ARRAY_SIZE(names))\n> -\t\treturn names[severity];\n> -\telse\n> -\t\treturn \"UNKWN\";\n> -}\n> -\n>  /**\n>   * \\class LogMessage\n>   * \\brief Internal log message representation.\n> @@ -495,16 +683,22 @@ void LogMessage::init(const char *fileName, unsigned int line)\n>  \t/* Log the timestamp, severity and file information. */\n>  \tstruct timespec timestamp;\n>  \tclock_gettime(CLOCK_MONOTONIC, &timestamp);\n> -\tmsgStream_.fill('0');\n> -\tmsgStream_ << \"[\" << timestamp.tv_sec / (60 * 60) << \":\"\n> +\tstd::ostringstream ossTimestamp;\n> +\tossTimestamp.fill('0');\n> +\tossTimestamp << \"[\" << timestamp.tv_sec / (60 * 60) << \":\"\n>  \t\t   << std::setw(2) << (timestamp.tv_sec / 60) % 60 << \":\"\n>  \t\t   << std::setw(2) << timestamp.tv_sec % 60 << \".\"\n>  \t\t   << std::setw(9) << timestamp.tv_nsec << \"]\";\n> -\tmsgStream_.fill(' ');\n> +\tossTimestamp.fill(' ');\n> +\tlogTimestamp_ = ossTimestamp.str();\n> +\n> +\tstd::ostringstream ossCategory;\n> +\tossCategory << category_.name();\n> +\tlogCategory_ = ossCategory.str();\n>  \n> -\tmsgStream_ << \" \" << log_severity_name(severity_);\n> -\tmsgStream_ << \" \" << category_.name();\n> -\tmsgStream_ << \" \" << utils::basename(fileName) << \":\" << line << \" \";\n> +\tstd::ostringstream ossFileInfo;\n> +\tossFileInfo << utils::basename(fileName) << \":\" << line;\n> +\tlogFileInfo_ = ossFileInfo.str();\n>  }\n>  \n>  LogMessage::~LogMessage()\n> @@ -514,11 +708,10 @@ LogMessage::~LogMessage()\n>  \t\treturn;\n>  \n>  \tmsgStream_ << std::endl;\n> +\tmsg_ = msgStream_.str();\n\nShould we implement the LogMessage::msg() accessor as msgStream_.str()\nto avoid storing a copy of the message ?\n\n>  \n> -\tif (severity_ >= category_.severity()) {\n> -\t\tstd::string msg(msgStream_.str());\n> -\t\tLogger::instance()->write(msg);\n> -\t}\n> +\tif (severity_ >= category_.severity())\n> +\t\tLogger::instance()->write(this);\n>  \n>  \tif (severity_ == LogSeverity::LogFatal)\n>  \t\tstd::abort();\n> @@ -534,6 +727,36 @@ LogMessage::~LogMessage()\n>   * \\return A reference to the log message stream\n>   */\n>  \n> +/**\n> + * \\fn LogMessage::timestamp()\n> + * \\brief Getter for the timestamp of the log message\n\ns/Getter for/Retrieve/\n\n> + * \\return The timestamp of the message, as a string\n> + */\n> +\n> +/**\n> + * \\fn LogMessage::severity()\n> + * \\brief Getter for the severity of the log message\n> + * \\return The severity of the message\n> + */\n> +\n> +/**\n> + * \\fn LogMessage::category()\n> + * \\brief Getter for the category of the log message\n> + * \\return The category of the message, as a string\n> + */\n> +\n> +/**\n> + * \\fn LogMessage::fileInfo()\n> + * \\brief Getter for the file info of the log message\n> + * \\return The file info of the message, as a string\n> + */\n> +\n> +/**\n> + * \\fn LogMessage::msg()\n> + * \\brief Getter for the message text of the log message\n> + * \\return The message text of the message, as a string\n> + */\n> +\n>  /**\n>   * \\class Loggable\n>   * \\brief Base class to support log message extensions","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BD88160E3F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun, 14 Jul 2019 09:57:13 +0200 (CEST)","from pendragon.ideasonboard.com (softbank126159224182.bbtec.net\n\t[126.159.224.182])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 13DBD99A;\n\tSun, 14 Jul 2019 09:57:11 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1563091033;\n\tbh=DvkuJukUdEDkpt1XejcYHGzekikmg/1xlytOHJ7DoBc=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=DVZdxWzbomWJn10EejdCwYFP3B6QyBt72rDGDKBt5KeN6Wt3W6tEeqTwtmTfrZvNK\n\tSXYF1cHzBtbzFme3TuQa5nFSGfoqH72slVphGFjIQ/WdQc/X5T9k/Tc7Beh5ZTHMyg\n\tGoYB7o4bXUfyonv3anyoySD2Rk2W20dpcQPrvh34=","Date":"Sun, 14 Jul 2019 10:56:44 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Paul Elder <paul.elder@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190714075644.GB6043@pendragon.ideasonboard.com>","References":"<20190712201620.30457-1-paul.elder@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20190712201620.30457-1-paul.elder@ideasonboard.com>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH 1/4] libcamera: logging: add syslog,\n\tstream, and nowhere logging targets","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Sun, 14 Jul 2019 07:57:13 -0000"}}]