diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 7c39c969a..42c60417e 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -11,6 +11,8 @@
 #include <optional>
 #include <vector>
 
+#include <libcamera/base/utils.h>
+
 #include "libcamera/internal/yaml_parser.h"
 
 namespace libcamera {
@@ -22,7 +24,30 @@ public:
 
 	static unsigned int version();
 	static Configuration configuration();
-	static std::optional<std::string> option(const std::string &confPath);
+
+	template<typename T,
+		 std::enable_if_t<
+			 std::is_same_v<bool, T> ||
+			 std::is_same_v<double, T> ||
+			 std::is_same_v<int8_t, T> ||
+			 std::is_same_v<uint8_t, T> ||
+			 std::is_same_v<int16_t, T> ||
+			 std::is_same_v<uint16_t, T> ||
+			 std::is_same_v<int32_t, T> ||
+			 std::is_same_v<uint32_t, T> ||
+			 std::is_same_v<std::string, T> ||
+			 std::is_same_v<Size, T>> * = nullptr>
+	static std::optional<T> option(const std::string &confPath)
+	{
+		YamlObject *c = &const_cast<YamlObject &>(configuration());
+		for (auto part : utils::details::StringSplitter(confPath, "."))
+			if (c->contains(part))
+				c = &const_cast<YamlObject &>((*c)[part]);
+			else
+				return std::optional<T>();
+		return c->get<T>();
+	}
+
 	static std::optional<std::string> envOption(const char *const envVariable,
 						    const std::string &confPath);
 
diff --git a/src/libcamera/base/global_configuration.cpp b/src/libcamera/base/global_configuration.cpp
index b64bf28e2..f18ca28bc 100644
--- a/src/libcamera/base/global_configuration.cpp
+++ b/src/libcamera/base/global_configuration.cpp
@@ -15,7 +15,6 @@
 
 #include <libcamera/base/file.h>
 #include <libcamera/base/log.h>
-#include <libcamera/base/utils.h>
 
 #include "libcamera/internal/yaml_parser.h"
 
@@ -146,23 +145,14 @@ GlobalConfiguration::Configuration GlobalConfiguration::get()
 }
 
 /**
+ * \fn static std::optional<T> GlobalConfiguration::option(const char *const confPath)
  * \brief Return value of the configuration option identified by \a confPath
+ * \tparam T The type of the retrieved configuration value
  * \param[in] confPath Sequence of the YAML section names (excluding
  * `configuration') leading to the requested option separated by dots
- * \return A value if an item corresponding to \a confPath exists in the
- * configuration file, no value otherwise
+ * \return A value of type \a T if an item corresponding to \a confPath exists
+ * in the configuration file and matches type \a T, no value otherwise
  */
-std::optional<std::string> GlobalConfiguration::option(
-	const std::string &confPath)
-{
-	YamlObject *c = &const_cast<YamlObject &>(configuration());
-	for (auto part : utils::details::StringSplitter(confPath, "."))
-		if (c->contains(part))
-			c = &const_cast<YamlObject &>((*c)[part]);
-		else
-			return std::optional<std::string>();
-	return c->get<std::string>();
-}
 
 /**
  * \brief Return value of the configuration option from a file or environment
@@ -188,7 +178,7 @@ std::optional<std::string> GlobalConfiguration::envOption(
 	const char *envValue = utils::secure_getenv(envVariable);
 	if (envValue)
 		return std::optional{ std::string{ envValue } };
-	return option(confPath);
+	return option<std::string>(confPath);
 }
 
 /**
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
index 07352f6c1..d522e895e 100644
--- a/src/libcamera/base/log.cpp
+++ b/src/libcamera/base/log.cpp
@@ -590,6 +590,8 @@ void Logger::logSetLevel(const char *category, const char *level)
 Logger::Logger()
 {
 	bool color = !utils::secure_getenv("LIBCAMERA_LOG_NO_COLOR");
+	if (color)
+		color = GlobalConfiguration::option<bool>("log.color").value_or(true);
 	logSetStream(&std::cerr, color);
 
 	parseLogFile();
