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

Message ID 20190121195606.8526-2-laurent.pinchart@ideasonboard.com
State Accepted
Commit 63675b6dcfc18d156ab293e2bf1d924d25b14ba3
Headers show
Series
  • Extend the logger with categories and configuration
Related show

Commit Message

Laurent Pinchart Jan. 21, 2019, 7:56 p.m. UTC
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@ideasonboard.com>
---
 Documentation/Doxyfile.in   |   2 +-
 src/libcamera/include/log.h |  56 ++++++++++-
 src/libcamera/log.cpp       | 193 +++++++++++++++++++++++++++++-------
 3 files changed, 210 insertions(+), 41 deletions(-)

Comments

Jacopo Mondi Jan. 22, 2019, 8:06 a.m. UTC | #1
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@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?

> +#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@jmondi.org>


>  } /* namespace libcamera */
> --
> Regards,
>
> Laurent Pinchart
>
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Laurent Pinchart Jan. 22, 2019, 1:54 p.m. UTC | #2
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@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@jmondi.org>
> 
> >  } /* namespace libcamera */

Patch

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__
 
 # 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__
+#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__)
+#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.
+ */
+
 } /* namespace libcamera */