diff --git a/BUILD.gn b/BUILD.gn index b3e9a44..61dc87a 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -121,6 +121,7 @@ static_library("libsommelier") { "sommelier-gtk-shell.cc", "sommelier-idle-inhibit-manager.cc", "sommelier-inpututils.cc", + "sommelier-logging.cc", "sommelier-output.cc", "sommelier-pointer-constraints.cc", "sommelier-relative-pointer-manager.cc", diff --git a/meson.build b/meson.build index c3a27be..1dd4408 100644 --- a/meson.build +++ b/meson.build @@ -165,6 +165,12 @@ if get_option('quirks') cpp_args += '-DQUIRKS_SUPPORT' endif +#===========# +# Log level # +#===========# + +cpp_args += '-DLOG_LEVEL=' + get_option('log_level').to_string() + #===========# # Sommelier # #===========# @@ -205,6 +211,7 @@ libsommelier = static_library( 'sommelier-global.cc', 'sommelier-idle-inhibit-manager.cc', 'sommelier-inpututils.cc', + 'sommelier-logging.cc', 'sommelier-output.cc', 'sommelier-pointer-constraints.cc', 'sommelier-relative-pointer-manager.cc', diff --git a/meson_options.txt b/meson_options.txt index 697d857..9b34ac5 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -57,3 +57,9 @@ option('with_tests', value: true, description: 'build the sommelier_test target' ) + +option('log_level', + type: 'integer', + value: 0, + description: 'log level to print, -1 prints everything, default is 0 (up to INFO)' +) diff --git a/sommelier-logging.cc b/sommelier-logging.cc new file mode 100644 index 0000000..b73eaf8 --- /dev/null +++ b/sommelier-logging.cc @@ -0,0 +1,33 @@ +// Copyright 2024 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "sommelier-logging.h" // NOLINT(build/include_directory) + +namespace logging { + +std::string file_name(std::string file_path) { + // Extract file name from file path. + auto found = file_path.find_last_of('/'); + if (found != std::string::npos) { + return file_path.substr(found + 1); + } + return file_path; +} + +std::string log_level_to_string(int level) { + if (level == LOG_LEVEL_VERBOSE) { + return "VERBOSE"; + } else if (level == LOG_LEVEL_INFO) { + return "INFO"; + } else if (level == LOG_LEVEL_WARNING) { + return "WARNING"; + } else if (level == LOG_LEVEL_ERROR) { + return "ERROR"; + } else if (level == LOG_LEVEL_FATAL) { + return "FATAL"; + } + return std::to_string(level); +} + +} // namespace logging diff --git a/sommelier-logging.h b/sommelier-logging.h new file mode 100644 index 0000000..d9c4d45 --- /dev/null +++ b/sommelier-logging.h @@ -0,0 +1,95 @@ +// Copyright 2024 The ChromiumOS Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Very simple logger that is similar to Chromium's logger. Example usage: +// LOG(INFO) << "hello world"; +// LOG(INFO) << window << " says hello world"; + +#ifndef VM_TOOLS_SOMMELIER_SOMMELIER_LOGGING_H_ +#define VM_TOOLS_SOMMELIER_SOMMELIER_LOGGING_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "sommelier-window.h" // NOLINT(build/include_directory) + +// Build Sommelier with -Dlog_level=X to define LOG_LEVEL +#ifndef LOG_LEVEL +// Default log level is INFO +#define LOG_LEVEL 0 +#endif // LOG_LEVEL + +// Predefined log levels +constexpr int LOG_LEVEL_VERBOSE = -1; +constexpr int LOG_LEVEL_INFO = 0; +constexpr int LOG_LEVEL_WARNING = 1; +constexpr int LOG_LEVEL_ERROR = 2; +constexpr int LOG_LEVEL_FATAL = 3; + +#define LOG(level) \ + ::logging::Log(LOG_LEVEL_##level, __func__, ::logging::file_name(__FILE__), \ + __LINE__) + +namespace logging { + +std::string file_name(std::string file_path); +std::string log_level_to_string(int level); + +class Log { + private: + std::stringstream log_content; + + public: + std::string function; + std::string file; + int line; + int log_level; + + explicit Log(int log_level, + std::string function, + std::string file, + int line) { + this->log_level = log_level; + this->function = function; + this->file = file; + this->line = line; + } + + template + Log& operator<<(T const& value) { + if (log_level >= LOG_LEVEL) { + this->log_content << value; + } + return *this; + } + + Log& operator<<(sl_window* window) { + *this << "(" << window->name << "#" << std::hex << window->id << ")" + << std::dec; + return *this; + } + + ~Log() { + // Example expected usage: + // LOG(INFO) << "hello world "; + // Temporary objects are destroyed after the end of the expression, which + // means this destructor is called at 'semicolon', resulting in printing to + // cerr. + if (this->log_content.peek() == EOF) { + return; + } + std::cerr << log_level_to_string(this->log_level) << " <" << this->file + << ":" << this->line << "> " << this->function << ": " + << this->log_content.rdbuf() << std::endl; + } +}; + +} // namespace logging + +#endif // VM_TOOLS_SOMMELIER_SOMMELIER_LOGGING_H_