[libcamera-devel] [PATCH v2 1/4] libcamera: log: Add log categories

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Jan 22 14:54:32 CET 2019


Hi Jacopo,

On Tue, Jan 22, 2019 at 09:06:39AM +0100, Jacopo Mondi wrote:
> Hi Laurent,
>    great, very very nice, kudos!
> 
> On Mon, Jan 21, 2019 at 09:56:03PM +0200, Laurent Pinchart wrote:
> > Log categories are used to group log messages by topic. Introduce
> > support for categories by making the LOG() macro variadic. Support for
> > configuring log level per category will be introduced in a subsequent
> > commit.
> >
> > Signed-off-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> > ---
> >  Documentation/Doxyfile.in   |   2 +-
> >  src/libcamera/include/log.h |  56 ++++++++++-
> >  src/libcamera/log.cpp       | 193 +++++++++++++++++++++++++++++-------
> >  3 files changed, 210 insertions(+), 41 deletions(-)
> >
> > diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
> > index dd74b7295d50..b78fb3a0b0b6 100644
> > --- a/Documentation/Doxyfile.in
> > +++ b/Documentation/Doxyfile.in
> > @@ -2050,7 +2050,7 @@ INCLUDE_FILE_PATTERNS  = *.h
> >  # recursively expanded use the := operator instead of the = operator.
> >  # This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
> >
> > -PREDEFINED             =
> > +PREDEFINED             = __DOXYGEN__
> >
> 
> Here you pre-define __DOXYGEN__
> 
> >  # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
> >  # tag can be used to specify a list of macro names that should be expanded. The
> > diff --git a/src/libcamera/include/log.h b/src/libcamera/include/log.h
> > index cc3f9404a3d4..ad8f8452c333 100644
> > --- a/src/libcamera/include/log.h
> > +++ b/src/libcamera/include/log.h
> > @@ -19,26 +19,74 @@ enum LogSeverity {
> >  	LogFatal,
> >  };
> >
> > +class LogCategory
> > +{
> > +public:
> > +	explicit LogCategory(const char *name);
> > +	~LogCategory();
> > +
> > +	const char *name() const { return name_; }
> > +	LogSeverity severity() const { return severity_; }
> > +	void setSeverity(LogSeverity severity);
> > +
> > +	static const LogCategory &defaultCategory();
> > +
> > +private:
> > +	const char *name_;
> > +	LogSeverity severity_;
> > +};
> > +
> > +#define LOG_DECLARE_CATEGORY(name)					\
> > +extern const LogCategory &_LOG_CATEGORY(name)();
> > +
> > +#define LOG_DEFINE_CATEGORY(name)					\
> > +const LogCategory &_LOG_CATEGORY(name)()				\
> > +{									\
> > +	static LogCategory category(#name);				\
> > +	return category;						\
> > +}
> > +
> >  class LogMessage
> >  {
> >  public:
> >  	LogMessage(const char *fileName, unsigned int line,
> >  		   LogSeverity severity);
> > +	LogMessage(const char *fileName, unsigned int line,
> > +		   const LogCategory &category, LogSeverity severity);
> >  	LogMessage(const LogMessage &) = delete;
> >  	~LogMessage();
> >
> > -	std::ostream &stream() { return msgStream; }
> > +	std::ostream &stream() { return msgStream_; }
> >
> >  private:
> > -	std::ostringstream msgStream;
> > +	void init(const char *fileName, unsigned int line);
> > +
> > +	std::ostringstream msgStream_;
> > +	const LogCategory &category_;
> >  	LogSeverity severity_;
> >  };
> >
> > -#define LOG(severity) LogMessage(__FILE__, __LINE__, Log##severity).stream()
> > +#ifndef __DOXYGEN__
> 
> And we get here only if it is not defined.
> How does that work?

__DOXYGEN__ is only defined when doxygen parses the code. When the code
is compiled, it isn't. The effect is that the _LOG_CATEGORY, _LOG1,
_LOG2 and _LOG_MACRO macros are hidden from doxygen, and the LOG macro
that doxygen sees is a fake one with two parameters in order to let the
documentation describe them with \param.

> > +#define _LOG_CATEGORY(name) logCategory##name
> > +
> > +#define _LOG1(severity) \
> > +	LogMessage(__FILE__, __LINE__, Log##severity).stream()
> > +#define _LOG2(category, severity) \
> > +	LogMessage(__FILE__, __LINE__, _LOG_CATEGORY(category)(), Log##severity).stream()
> > +
> > +/*
> > + * Expand the LOG() macro to _LOG1() or _LOG2() based on the number of
> > + * arguments.
> > + */
> > +#define _LOG_MACRO(_1, _2, NAME, ...) NAME
> > +#define LOG(...) _LOG_MACRO(__VA_ARGS__, _LOG2, _LOG1)(__VA_ARGS__)
> 
> Took me a lot to get around this, and without the comment, it would
> have take even more! Very very nice :)
> 
> > +#else /* __DOXYGEN___ */
> > +#define LOG(category, severity)
> > +#endif /* __DOXYGEN__ */
> >
> >  #ifndef NDEBUG
> >  #define ASSERT(condition) static_cast<void>(({				\
> > -	if (!(condition))							\
> > +	if (!(condition))						\
> >  		LOG(Fatal) << "assertion \"" #condition "\" failed";	\
> >  }))
> >  #else
> > diff --git a/src/libcamera/log.cpp b/src/libcamera/log.cpp
> > index 74cba383363d..20503fdcfcc1 100644
> > --- a/src/libcamera/log.cpp
> > +++ b/src/libcamera/log.cpp
> > @@ -37,32 +37,64 @@ namespace libcamera {
> >   */
> >
> >  /**
> > - * \def LOG(severity)
> > - * \brief Log a message
> > - *
> > - * Return an std::ostream reference to which a message can be logged using the
> > - * iostream API. The \a severity controls whether the message is printed or
> > - * dropped, depending on the global log level.
> > + * \class LogCategory
> > + * \brief A category of log message
> >   *
> > - * If the severity is set to Fatal, execution is aborted and the program
> > - * terminates immediately after printing the message.
> > + * The LogCategory class represents a category of log messages, related to an
> > + * area of the library. It groups all messages belonging to the same category,
> > + * and is used to control the log level per group.
> >   */
> >
> >  /**
> > - * \def ASSERT(condition)
> > - * \brief Abort program execution if assertion fails
> > + * \brief Construct a log category
> > + * \param[in] name The category name
> > + */
> > +LogCategory::LogCategory(const char *name)
> > +	: name_(name), severity_(LogSeverity::LogInfo)
> > +{
> > +}
> > +
> > +LogCategory::~LogCategory()
> > +{
> > +}
> > +
> > +/**
> > + * \fn LogCategory::name()
> > + * \brief Retrieve the log category name
> > + * \return The log category name
> > + */
> > +
> > +/**
> > + * \fn LogCategory::severity()
> > + * \brief Retrieve the severity of the log category
> > + * \sa setSeverity()
> > + * \return Return the severity of the log category
> > + */
> > +
> > +/**
> > + * \brief Set the severity of the log category
> >   *
> > - * If \a condition is false, ASSERT() logs an error message with the Fatal log
> > - * level and aborts program execution.
> > + * Messages of severity higher than or equal to the severity of the log category
> > + * are printed, other messages are discarded.
> > + */
> > +void LogCategory::setSeverity(LogSeverity severity)
> > +{
> > +	severity_ = severity;
> > +}
> > +
> > +/**
> > + * \brief Retrieve the default log category
> >   *
> > - * If the macro NDEBUG is defined before including log.h, ASSERT() generates no
> > - * code.
> > + * The default log category is named "default" and is used by the LOG() macro
> > + * when no log category is specified.
> >   *
> > - * Using conditions that have side effects with ASSERT() is not recommended, as
> > - * these effects would depend on whether NDEBUG is defined or not. Similarly,
> > - * ASSERT() should not be used to check for errors that can occur under normal
> > - * conditions as those checks would then be removed when compiling with NDEBUG.
> > + * \return A pointer to the default log category
> >   */
> > +const LogCategory &LogCategory::defaultCategory()
> > +{
> > +	static LogCategory category("default");
> > +	return category;
> > +}
> >
> >  static const char *log_severity_name(LogSeverity severity)
> >  {
> > @@ -90,10 +122,11 @@ static const char *log_severity_name(LogSeverity severity)
> >   */
> >
> >  /**
> > - * \param fileName The file name where the message is logged from
> > - * \param line The line number where the message is logged from
> > - * \param severity The log message severity, controlling how the message will be
> > - * displayed
> > + * \brief Construct a log message for the default category
> > + * \param[in] fileName The file name where the message is logged from
> > + * \param[in] line The line number where the message is logged from
> > + * \param[in] severity The log message severity, controlling how the message
> > + * will be displayed
> >   *
> >   * Create a log message pertaining to line \a line of file \a fileName. The
> >   * \a severity argument sets the message severity to control whether it will be
> > @@ -101,29 +134,57 @@ static const char *log_severity_name(LogSeverity severity)
> >   */
> >  LogMessage::LogMessage(const char *fileName, unsigned int line,
> >  		       LogSeverity severity)
> > -	: severity_(severity)
> > +	: category_(LogCategory::defaultCategory()), severity_(severity)
> > +{
> > +	init(fileName, line);
> > +}
> > +
> > +/**
> > + * \brief Construct a log message for a given category
> > + * \param[in] fileName The file name where the message is logged from
> > + * \param[in] line The line number where the message is logged from
> > + * \param[in] category The log message category, controlling how the message
> > + * will be displayed
> > + * \param[in] severity The log message severity, controlling how the message
> > + * will be displayed
> > + *
> > + * Create a log message pertaining to line \a line of file \a fileName. The
> > + * \a severity argument sets the message severity to control whether it will be
> > + * output or dropped.
> > + */
> > +LogMessage::LogMessage(const char *fileName, unsigned int line,
> > +		       const LogCategory &category, LogSeverity severity)
> > +	: category_(category), severity_(severity)
> > +{
> > +	init(fileName, line);
> > +}
> > +
> > +void LogMessage::init(const char *fileName, unsigned int line)
> >  {
> >  	/* Log the timestamp, severity and file information. */
> >  	struct timespec timestamp;
> >  	clock_gettime(CLOCK_MONOTONIC, &timestamp);
> > -	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(' ');
> > -
> > -	msgStream << " " << log_severity_name(severity);
> > -	msgStream << " " << basename(fileName) << ":" << line << " ";
> > +	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(' ');
> > +
> > +	msgStream_ << " " << log_severity_name(severity_);
> > +	msgStream_ << " " << category_.name();
> > +	msgStream_ << " " << basename(fileName) << ":" << line << " ";
> >  }
> >
> >  LogMessage::~LogMessage()
> >  {
> > -	msgStream << std::endl;
> > +	msgStream_ << std::endl;
> >
> > -	std::string msg(msgStream.str());
> > -	fwrite(msg.data(), msg.size(), 1, stderr);
> > -	fflush(stderr);
> > +	if (severity_ >= category_.severity()) {
> > +		std::string msg(msgStream_.str());
> > +		fwrite(msg.data(), msg.size(), 1, stderr);
> > +		fflush(stderr);
> > +	}
> >
> >  	if (severity_ == LogSeverity::LogFatal)
> >  		std::abort();
> > @@ -139,4 +200,64 @@ LogMessage::~LogMessage()
> >   * \return A reference to the log message stream
> >   */
> >
> > +/**
> > + * \def LOG_DECLARE_CATEGORY(name)
> > + * \hideinitializer
> > + * \brief Declare a category of log messages
> > + *
> > + * This macro is used to declare a log category defined in another compilation
> > + * unit by the LOG_DEFINE_CATEGORY() macro.
> > + *
> > + * The LOG_DECLARE_CATEGORY() macro must be used in the libcamera namespace.
> > + *
> > + * \sa LogCategory
> > + */
> > +
> > +/**
> > + * \def LOG_DEFINE_CATEGORY(name)
> > + * \hideinitializer
> > + * \brief Define a category of log messages
> > + *
> > + * This macro is used to define a log category that can then be used with the
> > + * LOGC() macro. Category names shall be unique, if a category is shared between
> > + * compilation units, it shall be defined in one compilation unit only and
> > + * declared with LOG_DECLARE_CATEGORY() in the other compilation units.
> > + *
> > + * The LOG_DEFINE_CATEGORY() macro must be used in the libcamera namespace.
> > + *
> > + * \sa LogCategory
> > + */
> > +
> > +/**
> > + * \def LOG(category, severity)
> > + * \hideinitializer
> > + * \brief Log a message
> > + * \param[in] category Category (optional)
> > + * \param[in] severity Severity
> > + *
> > + * Return an std::ostream reference to which a message can be logged using the
> > + * iostream API. The \a category, if specified, sets the message category. When
> > + * absent the default category is used. The  \a severity controls whether the
> > + * message is printed or discarded, depending on the log level for the category.
> > + *
> > + * If the severity is set to Fatal, execution is aborted and the program
> > + * terminates immediately after printing the message.
> > + */
> > +
> > +/**
> > + * \def ASSERT(condition)
> > + * \brief Abort program execution if assertion fails
> > + *
> > + * If \a condition is false, ASSERT() logs an error message with the Fatal log
> > + * level and aborts program execution.
> > + *
> > + * If the macro NDEBUG is defined before including log.h, ASSERT() generates no
> > + * code.
> > + *
> > + * Using conditions that have side effects with ASSERT() is not recommended, as
> > + * these effects would depend on whether NDEBUG is defined or not. Similarly,
> > + * ASSERT() should not be used to check for errors that can occur under normal
> > + * conditions as those checks would then be removed when compiling with NDEBUG.
> > + */
> > +
> 
> Thanks
> Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
> 
> >  } /* namespace libcamera */

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list