diff --git a/src/libcamera/base/global_configuration.cpp b/src/libcamera/base/global_configuration.cpp
index 3350a26a..8d823b98 100644
--- a/src/libcamera/base/global_configuration.cpp
+++ b/src/libcamera/base/global_configuration.cpp
@@ -22,8 +22,15 @@ namespace GlobalConfiguration {
 
 namespace {
 
+LOG_DEFINE_CATEGORY(Configuration)
+
 std::unique_ptr<YamlObject> yamlConfiguration = std::make_unique<YamlObject>();
 
+/*
+ * Care is needed here to not log anything before the configuration is
+ * loaded otherwise the logger would be initialized with empty configuration.
+ */
+
 bool loadFile(const std::filesystem::path &fileName)
 {
 	File file(fileName);
@@ -31,12 +38,18 @@ bool loadFile(const std::filesystem::path &fileName)
 		return false;
 	}
 
-	if (!file.open(File::OpenModeFlag::ReadOnly))
+	if (!file.open(File::OpenModeFlag::ReadOnly)) {
+		LOG(Configuration, Warning)
+			<< "Failed to open configuration file " << fileName;
 		return true;
+	}
 
 	auto root = YamlParser::parse(file);
-	if (!root)
+	if (!root) {
+		LOG(Configuration, Warning)
+			<< "Failed to parse configuration file " << fileName;
 		return true;
+	}
 	yamlConfiguration = std::move(root);
 
 	return true;
@@ -83,7 +96,11 @@ Configuration get()
 /**
  * \brief Initialize the global configuration
  *
- * This must be called before global configuration is accessed.
+ * This function is expected to be called only once, before the configuration is
+ * queried for the first time.
+ *
+ * \note Most notably, the function must be called before any logging, because
+ * logging queries the configuration.
  */
 void initialize()
 {
@@ -97,6 +114,13 @@ void initialize()
  * The configuration file is a YAML file and the configuration itself is stored
  * under `configuration' top-level item.
  *
+ * Example configuration file content:
+ * \code{.yaml}
+ * configuration:
+ *   log:
+ *     levels: 'IPAManager:DEBUG'
+ * \endcode
+ *
  * The configuration file is looked up in user's home directory first and if it
  * is not found then in system-wide configuration directories. If multiple
  * configuration files exist then only the first one found is used and no
diff --git a/src/libcamera/base/log.cpp b/src/libcamera/base/log.cpp
index 3a656b8f..0f676710 100644
--- a/src/libcamera/base/log.cpp
+++ b/src/libcamera/base/log.cpp
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <string>
 #include <syslog.h>
 #include <time.h>
 #include <unordered_set>
@@ -25,6 +26,8 @@
 #include <libcamera/base/thread.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
+
 /**
  * \file base/log.h
  * \brief Logging infrastructure
@@ -626,8 +629,13 @@ void Logger::parseLogFile()
 void Logger::parseLogLevels()
 {
 	const char *debug = utils::secure_getenv("LIBCAMERA_LOG_LEVELS");
-	if (!debug)
-		return;
+	if (!debug) {
+		const std::optional<std::string> confDebug =
+			GlobalConfiguration::configuration()["log"]["levels"].get<std::string>();
+		if (!confDebug.has_value())
+			return;
+		debug = confDebug.value().c_str();
+	}
 
 	for (const char *pair = debug; *debug != '\0'; pair = debug) {
 		const char *comma = strchrnul(debug, ',');
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index cfc24d38..33ae74e8 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -16,6 +16,7 @@
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 #include "libcamera/internal/ipa_proxy.h"
 #include "libcamera/internal/pipeline_handler.h"
@@ -103,6 +104,8 @@ LOG_DEFINE_CATEGORY(IPAManager)
  */
 IPAManager::IPAManager()
 {
+	GlobalConfiguration::initialize();
+
 #if HAVE_IPA_PUBKEY
 	if (!pubKey_.isValid())
 		LOG(IPAManager, Warning) << "Public key not valid";
