[libcamera-devel,4/6] libcamera: log: Get log levels from the environment

Message ID 20190121005930.10112-5-laurent.pinchart@ideasonboard.com
State Superseded
Headers show
Series
  • Extend the logger with categories and configuration
Related show

Commit Message

Laurent Pinchart Jan. 21, 2019, 12:59 a.m. UTC
Set the log level for each log category from the environment variable
LIBCAMERA_LOG_LEVELS.

The variable contains a comma-separated list of category:level pairs,
and category names can include wildcards.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 src/libcamera/log.cpp | 202 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 202 insertions(+)

Patch

diff --git a/src/libcamera/log.cpp b/src/libcamera/log.cpp
index 8f3d1c181245..143029f19826 100644
--- a/src/libcamera/log.cpp
+++ b/src/libcamera/log.cpp
@@ -9,7 +9,9 @@ 
 #include <cstdlib>
 #include <ctime>
 #include <iomanip>
+#include <list>
 #include <string.h>
+#include <unordered_set>
 
 #include "log.h"
 #include "utils.h"
@@ -17,10 +19,208 @@ 
 /**
  * \file log.h
  * \brief Logging infrastructure
+ *
+ * libcamera includes a logging infrastructure used through the library that
+ * allows inspection of internal operation in a user-configurable way. The log
+ * messages are grouped in categories that represent areas of libcamera, and
+ * output of messages for each category can be controlled by independent log
+ * levels.
+ *
+ * The levels are configurable through the LIBCAMERA_LOG_LEVELS environment
+ * variable that contains a comma-separated list of 'category=level' pairs.
+ *
+ * The category names are strings and can include a wildcard ('*') character at
+ * the end to match multiple categories.
+ *
+ * The level are either numeric values, or strings containing the log level
+ * name. The available log levels are DEBUG, INFO, WARN, ERROR and FATAL. Log
+ * message with a level higher than or equal to the configured log level for
+ * their category are output to the log, while other messages are silently
+ * discarded.
  */
 
 namespace libcamera {
 
+/**
+ * \brief Message logger
+ *
+ * The Logger class handles log configuration.
+ */
+class Logger
+{
+public:
+	static Logger *instance();
+
+private:
+	Logger();
+
+	void parseLogLevels();
+	static LogSeverity parseLogLevel(const std::string &level);
+
+	friend LogCategory;
+	void registerCategory(LogCategory *category);
+	void unregisterCategory(LogCategory *category);
+
+	std::unordered_set<LogCategory *> categories_;
+	std::list<std::pair<std::string, LogSeverity>> levels_;
+};
+
+/**
+ * \brief Retrieve the logger instance
+ *
+ * The Logger is a singleton and can't be constructed manually. This function
+ * shall instead be used to retrieve the single global instance of the logger.
+ *
+ * \return The logger instance
+ */
+Logger *Logger::instance()
+{
+	static Logger instance;
+	return &instance;
+}
+
+/**
+ * \brief Construct a logger
+ */
+Logger::Logger()
+{
+	parseLogLevels();
+}
+
+/**
+ * \brief Parse the log levels from the environment
+ *
+ * The logr levels are stored in LIBCAMERA_LOG_LEVELS environement variable as a list
+ * of "category=level" pairs, separated by commas (','). Parse the variable and
+ * store the levels to configure all log categories.
+ */
+void Logger::parseLogLevels()
+{
+	const char *debug = secure_getenv("LIBCAMERA_LOG_LEVELS");
+	if (!debug)
+		return;
+
+	for (const char *pair = debug; *debug != '\0'; pair = debug) {
+		const char *comma = strchrnul(debug, ',');
+		size_t len = comma - pair;
+
+		/* Skip over the comma. */
+		debug = *comma == ',' ? comma + 1 : comma;
+
+		/* Skip to the next pair if the pair is empty. */
+		if (!len)
+			continue;
+
+		std::string category;
+		std::string level;
+
+		const char *colon = static_cast<const char *>(memchr(pair, ':', len));
+		if (!colon) {
+			/* 'x' is a shortcut for '*:x'. */
+			category = "*";
+			level = std::string(pair, len);
+		} else {
+			category = std::string(pair, colon - pair);
+			level = std::string(colon + 1, comma - colon - 1);
+		}
+
+		/* Both the category and the level must be specified. */
+		if (category.empty() || level.empty())
+			continue;
+
+		LogSeverity severity = parseLogLevel(level);
+		if (severity == -1)
+			continue;
+
+		levels_.push_back({category, severity});
+	}
+}
+
+/**
+ * \brief Parse a log level string into a LogSeverity
+ * \param[in] level The log level string
+ *
+ * Log levels can be specified as an integer value in the range from LogDebug to
+ * LogFatal, or as a string corresponding to the severity name in uppercase. Any
+ * other value is invalid.
+ *
+ * \return The log severity, or -1 if the string is invalid
+ */
+LogSeverity Logger::parseLogLevel(const std::string &level)
+{
+	static const char *const names[] = {
+		"DEBUG",
+		"INFO",
+		"WARN",
+		"ERROR",
+		"FATAL",
+	};
+
+	int severity;
+
+	if (std::isdigit(level[0])) {
+		char *endptr;
+		severity = strtoul(level.c_str(), &endptr, 10);
+		if (*endptr != '\0' || severity > LogFatal)
+			severity = -1;
+	} else {
+		severity = -1;
+		for (unsigned int i = 0; i < ARRAY_SIZE(names); ++i) {
+			if (names[i] == level) {
+				severity = i;
+				break;
+			}
+		}
+	}
+
+	return static_cast<LogSeverity>(severity);
+}
+
+/**
+ * \brief Register a log category with the logger
+ * \param[in] category The log category
+ *
+ * Log categories must have unique names. If a category with the same name
+ * already exists this function performs no operation.
+ */
+void Logger::registerCategory(LogCategory *category)
+{
+	categories_.insert(category);
+
+	const std::string &name = category->name();
+	for (const std::pair<std::string, LogSeverity> &level : levels_) {
+		bool match = true;
+
+		for (unsigned int i = 0; i < level.first.size(); ++i) {
+			if (level.first[i] == '*')
+				break;
+
+			if (i >= name.size() ||
+			    name[i] != level.first[i]) {
+				match = false;
+				break;
+			}
+		}
+
+		if (match) {
+			category->setSeverity(level.second);
+			break;
+		}
+	}
+}
+
+/**
+ * \brief Unregister a log category from the logger
+ * \param[in] category The log category
+ *
+ * If the \a category hasn't been registered with the logger this function
+ * performs no operation.
+ */
+void Logger::unregisterCategory(LogCategory *category)
+{
+	categories_.erase(category);
+}
+
 /**
  * \enum LogSeverity
  * Log message severity
@@ -52,10 +252,12 @@  namespace libcamera {
 LogCategory::LogCategory(const char *name)
 	: name_(name), severity_(LogSeverity::LogInfo)
 {
+	Logger::instance()->registerCategory(this);
 }
 
 LogCategory::~LogCategory()
 {
+	Logger::instance()->unregisterCategory(this);
 }
 
 /**