[libcamera-devel] [PATCH v2 1/2] libcamera: base: log: Add coloring to the log output

Laurent Pinchart laurent.pinchart at ideasonboard.com
Thu Jun 2 09:34:04 CEST 2022


Extend the logger to support coloring messages. The log level is
colorized with per-level colors, and the category with a fixed color.
This makes the log output more readable.

Coloring is enabled by default when logging to std::cerr, and can be
disabled by setting the LIBCAMERA_LOG_NO_COLOR environment variable.
When logging to a file with LIBCAMERA_LOG_FILE, coloring is disabled. It
can be enabled for file logging using the logSetFile() function.

Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
Reviewed-by: Umang Jain <umang.jain at ideasonboard.com>
---
Changes since v1:

- Use '[1;3xm' instead of '[9xm' for bright colors
- Rename msgColor to resetColor
- Add link to https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
- Drop debug leftovers
- Fix typo
---
 Documentation/environment_variables.rst |  21 +++--
 include/libcamera/logging.h             |   4 +-
 src/libcamera/base/log.cpp              | 110 ++++++++++++++++++------
 3 files changed, 104 insertions(+), 31 deletions(-)

diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst
index fa703a726845..0a7760cbea42 100644
--- a/Documentation/environment_variables.rst
+++ b/Documentation/environment_variables.rst
@@ -19,6 +19,9 @@ LIBCAMERA_LOG_LEVELS
 
    Example value: ``*:DEBUG``
 
+LIBCAMERA_LOG_NO_COLOR
+   Disable coloring of log messages (`more <Notes about debugging_>`__).
+
 LIBCAMERA_IPA_CONFIG_PATH
    Define custom search locations for IPA configurations (`more <IPA configuration_>`__).
 
@@ -40,12 +43,20 @@ Further details
 Notes about debugging
 ~~~~~~~~~~~~~~~~~~~~~
 
-The environment variables ``LIBCAMERA_LOG_FILE`` and ``LIBCAMERA_LOG_LEVELS``
-are used to modify the destination and verbosity of messages provided by
-libcamera.
+The environment variables ``LIBCAMERA_LOG_FILE``, ``LIBCAMERA_LOG_LEVELS`` and
+``LIBCAMERA_LOG_NO_COLOR`` are used to modify the default configuration of the
+libcamera logger.
 
-The ``LIBCAMERA_LOG_LEVELS`` variable accepts a comma-separated list of
-'category:level' pairs.
+By default, libcamera logs all messages to the standard error (std::cerr).
+Messages are colored by default depending on the log level. Coloring can be
+disabled by setting the ``LIBCAMERA_LOG_NO_COLOR`` environment variable.
+
+The default log destination can also be directed to a file by setting the
+``LIBCAMERA_LOG_FILE`` environment variable to the log file name. This also
+disables coloring.
+
+Log levels are controlled through the ``LIBCAMERA_LOG_LEVELS`` variable, which
+accepts a comma-separated list of 'category:level' pairs.
 
 The `level <Log levels_>`__ part is mandatory and can either be specified by
 name or by numerical index associated with each level.
diff --git a/include/libcamera/logging.h b/include/libcamera/logging.h
index c36882b91974..cd842f67d553 100644
--- a/include/libcamera/logging.h
+++ b/include/libcamera/logging.h
@@ -16,8 +16,8 @@ enum LoggingTarget {
 	LoggingTargetStream,
 };
 
-int logSetFile(const char *path);
-int logSetStream(std::ostream *stream);
+int logSetFile(const char *path, bool color = false);
+int logSetStream(std::ostream *stream, bool color = false);
 int logSetTarget(LoggingTarget target);
 void logSetLevel(const char *category, const char *level);
 
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
index 26f1420703b9..eefdda4ba177 100644
--- a/src/libcamera/base/log.cpp
+++ b/src/libcamera/base/log.cpp
@@ -104,8 +104,8 @@ static const char *log_severity_name(LogSeverity severity)
 class LogOutput
 {
 public:
-	LogOutput(const char *path);
-	LogOutput(std::ostream *stream);
+	LogOutput(const char *path, bool color);
+	LogOutput(std::ostream *stream, bool color);
 	LogOutput();
 	~LogOutput();
 
@@ -119,14 +119,16 @@ private:
 
 	std::ostream *stream_;
 	LoggingTarget target_;
+	bool color_;
 };
 
 /**
  * \brief Construct a log output based on a file
  * \param[in] path Full path to log file
+ * \param[in] color True to output colored messages
  */
-LogOutput::LogOutput(const char *path)
-	: target_(LoggingTargetFile)
+LogOutput::LogOutput(const char *path, bool color)
+	: target_(LoggingTargetFile), color_(color)
 {
 	stream_ = new std::ofstream(path);
 }
@@ -134,9 +136,10 @@ LogOutput::LogOutput(const char *path)
 /**
  * \brief Construct a log output based on a stream
  * \param[in] stream Stream to send log output to
+ * \param[in] color True to output colored messages
  */
-LogOutput::LogOutput(std::ostream *stream)
-	: stream_(stream), target_(LoggingTargetStream)
+LogOutput::LogOutput(std::ostream *stream, bool color)
+	: stream_(stream), target_(LoggingTargetStream), color_(color)
 {
 }
 
@@ -144,7 +147,7 @@ LogOutput::LogOutput(std::ostream *stream)
  * \brief Construct a log output to syslog
  */
 LogOutput::LogOutput()
-	: stream_(nullptr), target_(LoggingTargetSyslog)
+	: stream_(nullptr), target_(LoggingTargetSyslog), color_(false)
 {
 	openlog("libcamera", LOG_PID, 0);
 }
@@ -179,28 +182,66 @@ bool LogOutput::isValid() const
 	}
 }
 
+namespace {
+
+/*
+ * For more information about ANSI escape codes, see
+ * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors.
+ */
+constexpr const char *kColorReset = "\033[0m";
+constexpr const char *kColorBrightRed = "\033[1;31m";
+constexpr const char *kColorBrightGreen = "\033[1;32m";
+constexpr const char *kColorBrightYellow = "\033[1;33m";
+constexpr const char *kColorBrightBlue = "\033[1;34m";
+constexpr const char *kColorBrightMagenta = "\033[1;35m";
+constexpr const char *kColorBrightCyan = "\033[1;36m";
+constexpr const char *kColorBrightWhite = "\033[1;37m";
+
+} /* namespace */
+
 /**
  * \brief Write message to log output
  * \param[in] msg Message to write
  */
 void LogOutput::write(const LogMessage &msg)
 {
+	static const char *const severityColors[] = {
+		kColorBrightCyan,
+		kColorBrightGreen,
+		kColorBrightYellow,
+		kColorBrightRed,
+		kColorBrightMagenta,
+	};
+
+	const char *categoryColor = color_ ? kColorBrightWhite : "";
+	const char *fileColor = color_ ? kColorBrightBlue : "";
+	const char *resetColor = color_ ? kColorReset : "";
+	const char *severityColor = "";
+	LogSeverity severity = msg.severity();
 	std::string str;
 
+	if (color_) {
+		if (static_cast<unsigned int>(severity) < std::size(severityColors))
+			severityColor = severityColors[severity];
+		else
+			severityColor = kColorBrightWhite;
+	}
+
 	switch (target_) {
 	case LoggingTargetSyslog:
-		str = std::string(log_severity_name(msg.severity())) + " "
+		str = std::string(log_severity_name(severity)) + " "
 		    + msg.category().name() + " " + msg.fileInfo() + " "
 		    + msg.msg();
-		writeSyslog(msg.severity(), str);
+		writeSyslog(severity, str);
 		break;
 	case LoggingTargetStream:
 	case LoggingTargetFile:
 		str = "[" + utils::time_point_to_string(msg.timestamp()) + "] ["
 		    + std::to_string(Thread::currentId()) + "] "
-		    + log_severity_name(msg.severity()) + " "
-		    + msg.category().name() + " " + msg.fileInfo() + " "
-		    + msg.msg();
+		    + severityColor + log_severity_name(severity) + " "
+		    + categoryColor + msg.category().name() + " "
+		    + fileColor + msg.fileInfo() + " "
+		    + resetColor + msg.msg();
 		writeStream(str);
 		break;
 	default:
@@ -253,8 +294,8 @@ public:
 	void write(const LogMessage &msg);
 	void backtrace();
 
-	int logSetFile(const char *path);
-	int logSetStream(std::ostream *stream);
+	int logSetFile(const char *path, bool color);
+	int logSetStream(std::ostream *stream, bool color);
 	int logSetTarget(LoggingTarget target);
 	void logSetLevel(const char *category, const char *level);
 
@@ -298,35 +339,47 @@ bool Logger::destroyed_ = false;
 /**
  * \brief Direct logging to a file
  * \param[in] path Full path to the log file
+ * \param[in] color True to output colored messages
  *
  * 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.
  *
+ * \a color controls whether or not the messages will be colored with standard
+ * ANSI escape codes. This is done regardless of whether \a path refers to a
+ * standard file or a TTY, the caller is responsible for disabling coloring when
+ * not suitable for the log target.
+ *
  * 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 *path)
+int logSetFile(const char *path, bool color)
 {
-	return Logger::instance()->logSetFile(path);
+	return Logger::instance()->logSetFile(path, color);
 }
 
 /**
  * \brief Direct logging to a stream
  * \param[in] stream Stream to send log output to
+ * \param[in] color True to output colored messages
  *
  * 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.
  *
+ * \a color controls whether or not the messages will be colored with standard
+ * ANSI escape codes. This is done regardless of whether \a stream refers to a
+ * standard file or a TTY, the caller is responsible for disabling coloring when
+ * not suitable for the log target.
+ *
  * 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)
+int logSetStream(std::ostream *stream, bool color)
 {
-	return Logger::instance()->logSetStream(stream);
+	return Logger::instance()->logSetStream(stream, color);
 }
 
 /**
@@ -437,14 +490,16 @@ void Logger::backtrace()
 /**
  * \brief Set the log file
  * \param[in] path Full path to the log file
+ * \param[in] color True to output colored messages
  *
  * \sa libcamera::logSetFile()
  *
  * \return Zero on success, or a negative error code otherwise.
  */
-int Logger::logSetFile(const char *path)
+int Logger::logSetFile(const char *path, bool color)
 {
-	std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(path);
+	std::shared_ptr<LogOutput> output =
+		std::make_shared<LogOutput>(path, color);
 	if (!output->isValid())
 		return -EINVAL;
 
@@ -455,14 +510,16 @@ int Logger::logSetFile(const char *path)
 /**
  * \brief Set the log stream
  * \param[in] stream Stream to send log output to
+ * \param[in] color True to output colored messages
  *
  * \sa libcamera::logSetStream()
  *
  * \return Zero on success, or a negative error code otherwise.
  */
-int Logger::logSetStream(std::ostream *stream)
+int Logger::logSetStream(std::ostream *stream, bool color)
 {
-	std::shared_ptr<LogOutput> output = std::make_shared<LogOutput>(stream);
+	std::shared_ptr<LogOutput> output =
+		std::make_shared<LogOutput>(stream, color);
 	std::atomic_store(&output_, output);
 	return 0;
 }
@@ -514,10 +571,15 @@ void Logger::logSetLevel(const char *category, const char *level)
 
 /**
  * \brief Construct a logger
+ *
+ * If the environment variable is not set, log to std::cerr. The log messages
+ * are then colored by default. This can be overridden by setting the
+ * LIBCAMERA_LOG_NO_COLOR environment variable to disable coloring.
  */
 Logger::Logger()
 {
-	logSetStream(&std::cerr);
+	bool color = !utils::secure_getenv("LIBCAMERA_LOG_NO_COLOR");
+	logSetStream(&std::cerr, color);
 
 	parseLogFile();
 	parseLogLevels();
@@ -543,7 +605,7 @@ void Logger::parseLogFile()
 		return;
 	}
 
-	logSetFile(file);
+	logSetFile(file, false);
 }
 
 /**
-- 
Regards,

Laurent Pinchart



More information about the libcamera-devel mailing list