This is an automated email from the ASF dual-hosted git repository.

pnoltes pushed a commit to branch feature/improve-configurability-log-admin
in repository https://gitbox.apache.org/repos/asf/celix.git

commit f0835a5facc2a21b6aaf9a61b14887d051f17677
Author: Pepijn Noltes <[email protected]>
AuthorDate: Sun Sep 29 22:02:00 2024 +0200

    Update log admin to support configure log level per logger
---
 bundles/logging/README.md                          |   8 +-
 bundles/logging/log_admin/CMakeLists.txt           |  15 +-
 bundles/logging/log_admin/gtest/CMakeLists.txt     |  15 +-
 .../log_admin/gtest/src/LogAdminTestSuite.cc       | 124 ++++++++-
 .../src/LogAdminWithErrorInjectionTestSuite.cc     | 141 ++++++++++
 bundles/logging/log_admin/src/celix_log_admin.c    | 308 +++++++++++++--------
 bundles/logging/log_admin/src/celix_log_admin.h    |  12 +
 .../log_service_api/include/celix_log_control.h    |  48 +++-
 8 files changed, 537 insertions(+), 134 deletions(-)

diff --git a/bundles/logging/README.md b/bundles/logging/README.md
index 511c556e4..ab2d55680 100644
--- a/bundles/logging/README.md
+++ b/bundles/logging/README.md
@@ -33,16 +33,18 @@ log messages will be printed on stdout/stderr.
 
 
 ## Logging Properties
-Properties shared among the logging bundles
+Config Properties used in the Celix Log Admina and framework (fallback to 
stdout/stderr, when no log admin is present).
 
-    CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL The default active log level for 
created log services. Default is "info".
+    CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL The default active log level for 
created log services. Default is "info". The value should be one of the log 
levels: "trace", "debug", "info", "error", "fatal".
 
 ## Log Admin Properties
-Properties specific for the Celix Log Admin (`Celix::log_admin` bundle)
+Config Properties specific for the Celix Log Admin (`Celix::log_admin` bundle)
 
     CELIX_LOG_ADMIN_FALLBACK_TO_STDOUT If set to true, the log admin will log 
to stdout/stderr if no celix log writers are available. Default is true
     CELIX_LOG_ADMIN_ALWAYS_USE_STDOUT If set to true, the log admin will 
always log to stdout/stderr after forwaring log statements to the available 
celix log writers. Default is false.
     CELIX_LOG_ADMIN_LOG_SINKS_DEFAULT_ENABLED Whether discovered log sink are 
default enabled. Default is true.
+    CELIX_LOG_ADMIN_LOG_SINK_<log_sink_name>_ENABLED If present, the log sink 
with the name <log_sink_name> is enabled/disabled. The <log_sink_name> is the 
name of the log sink in all caps. For example, the log sink with the name 
"celix_syslog" can be enabled/disabled with the property 
CELIX_LOG_ADMIN_LOG_SINK_CELIX_SYSLOG_ENABLED.
+    CELIX_LOG_ADMIN_LOGGER_<logger_name>_ACTIVE_LOG_LEVEL If present, the 
active log level for the log service with the name <logger_name>. The 
<logger_name> is the name of the logger in all caps. For example, the active 
log level for the log service with the name "celix_framework" can be set with 
the property CELIX_LOGGING_CELIX_FRAMEWORK_ACTIVE_LOG_LEVEL.
     
 ## CMake option
     BUILD_LOG_SERVICE=ON
diff --git a/bundles/logging/log_admin/CMakeLists.txt 
b/bundles/logging/log_admin/CMakeLists.txt
index 049206792..541c35e19 100644
--- a/bundles/logging/log_admin/CMakeLists.txt
+++ b/bundles/logging/log_admin/CMakeLists.txt
@@ -15,17 +15,22 @@
 # specific language governing permissions and limitations
 # under the License.
 
+set(LOG_ADMIN_SRC
+       src/celix_log_admin.c
+       src/celix_log_admin_activator.c
+)
+
+set(LOG_ADDMIN_PRIVATE_DEPS Celix::log_service_api Celix::shell_api)
+
 add_celix_bundle(log_admin
        SYMBOLIC_NAME "apache_celix_log_admin"
        NAME "Apache Celix Log Admin"
        GROUP "Celix/Logging"
        VERSION "1.1.0"
-       SOURCES
-               src/celix_log_admin.c
-               src/celix_log_admin_activator.c
+       SOURCES  ${LOG_ADMIN_SRC}
        FILENAME celix_log_admin
 )
-target_link_libraries(log_admin PRIVATE Celix::log_service_api 
Celix::shell_api)
+target_link_libraries(log_admin PRIVATE ${LOG_ADDMIN_PRIVATE_DEPS})
 target_include_directories(log_admin PRIVATE src)
 celix_deprecated_utils_headers(log_admin)
 install_celix_bundle(log_admin EXPORT celix COMPONENT logging)
@@ -34,5 +39,7 @@ install_celix_bundle(log_admin EXPORT celix COMPONENT logging)
 add_library(Celix::log_admin ALIAS log_admin)
 
 if (ENABLE_TESTING)
+       add_library(log_admin_cut STATIC ${LOG_ADMIN_SRC})
+       target_link_libraries(log_admin_cut PRIVATE ${LOG_ADDMIN_PRIVATE_DEPS} 
Celix::framework)
        add_subdirectory(gtest)
 endif()
diff --git a/bundles/logging/log_admin/gtest/CMakeLists.txt 
b/bundles/logging/log_admin/gtest/CMakeLists.txt
index 41f3b9d8b..1178dea87 100644
--- a/bundles/logging/log_admin/gtest/CMakeLists.txt
+++ b/bundles/logging/log_admin/gtest/CMakeLists.txt
@@ -24,7 +24,20 @@ target_link_libraries(test_log_admin PRIVATE 
Celix::framework Celix::log_service
 add_celix_bundle_dependencies(test_log_admin Celix::log_admin)
 target_compile_definitions(test_log_admin PRIVATE 
-DLOG_ADMIN_BUNDLE=\"$<TARGET_PROPERTY:log_admin,BUNDLE_FILE>\")
 
-
 add_test(NAME test_log_admin COMMAND test_log_admin)
 setup_target_for_coverage(test_log_admin SCAN_DIR ..)
 
+
+if (EI_TESTS)
+    add_executable(test_log_admin_with_ei 
src/LogAdminWithErrorInjectionTestSuite.cc)
+    target_link_libraries(test_log_admin_with_ei PRIVATE
+            log_admin_cut
+            Celix::framework Celix::log_service_api Celix::shell_api
+            Celix::malloc_ei Celix::asprintf_ei Celix::properties_ei 
Celix::string_hash_map_ei
+            GTest::gtest GTest::gtest_main
+    )
+    target_include_directories(test_log_admin_with_ei PRIVATE ../src)
+    add_test(NAME test_log_admin_with_ei COMMAND test_log_admin_with_ei)
+    setup_target_for_coverage(test_log_admin_with_ei SCAN_DIR ..)
+endif ()
+
diff --git a/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc 
b/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc
index 4e94ff11f..79fef9a82 100644
--- a/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc
+++ b/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc
@@ -19,9 +19,10 @@
 
 #include <gtest/gtest.h>
 
-#include <thread>
 #include <atomic>
+#include <memory>
 
+#include "celix/FrameworkFactory.h"
 #include "celix_log_sink.h"
 #include "celix_log_control.h"
 #include "celix_bundle_context.h"
@@ -30,11 +31,12 @@
 #include "celix_shell_command.h"
 #include "celix_constants.h"
 
-class LogBundleTestSuite : public ::testing::Test {
+
+class LogAdminTestSuite : public ::testing::Test {
 public:
-    LogBundleTestSuite() {
+    LogAdminTestSuite() {
         auto* properties = celix_properties_create();
-        celix_properties_set(properties, CELIX_FRAMEWORK_CACHE_DIR, 
".cacheLogBundleTestSuite");
+        celix_properties_set(properties, CELIX_FRAMEWORK_CACHE_DIR, 
".cacheLogAdminTestSuite");
 
 
         auto* fwPtr = celix_frameworkFactory_createFramework(properties);
@@ -50,14 +52,14 @@ public:
         opts.filter.versionRange = CELIX_LOG_CONTROL_USE_RANGE;
         opts.callbackHandle = (void*)this;
         opts.set = [](void *handle, void *svc) {
-            auto* self = (LogBundleTestSuite*)handle;
+            auto* self = (LogAdminTestSuite*)handle;
             self->control = 
std::shared_ptr<celix_log_control_t>{(celix_log_control_t*)svc, 
[](celix_log_control_t *){/*nop*/}};
         };
         trkId = celix_bundleContext_trackServicesWithOptions(ctx.get(), &opts);
         EXPECT_TRUE(trkId >= 0);
     }
 
-    ~LogBundleTestSuite() override {
+    ~LogAdminTestSuite() override {
         celix_bundleContext_stopTracker(ctx.get(), trkId);
     }
 
@@ -70,7 +72,7 @@ public:
 
 
 
-TEST_F(LogBundleTestSuite, StartStop) {
+TEST_F(LogAdminTestSuite, StartStop) {
     auto *list = celix_bundleContext_listBundles(ctx.get());
     EXPECT_EQ(1, celix_arrayList_size(list));
     celix_arrayList_destroy(list);
@@ -79,7 +81,7 @@ TEST_F(LogBundleTestSuite, StartStop) {
     EXPECT_TRUE(svcId >= 0);
 }
 
-TEST_F(LogBundleTestSuite, NrOfLogServices) {
+TEST_F(LogAdminTestSuite, NrOfLogServices) {
     ASSERT_TRUE(control);
     EXPECT_EQ(1, control->nrOfLogServices(control->handle, nullptr)); 
//default the framework log services is available
 
@@ -118,7 +120,7 @@ TEST_F(LogBundleTestSuite, NrOfLogServices) {
     celix_bundleContext_stopTracker(ctx.get(), trkId4);
 }
 
-TEST_F(LogBundleTestSuite, NrOfLogSinks) {
+TEST_F(LogAdminTestSuite, NrOfLogSinks) {
     ASSERT_TRUE(control);
     EXPECT_EQ(0, control->nrOfSinks(control->handle, nullptr));
 
@@ -152,7 +154,7 @@ TEST_F(LogBundleTestSuite, NrOfLogSinks) {
     celix_bundleContext_unregisterService(ctx.get(), svcId3);
 }
 
-TEST_F(LogBundleTestSuite, SinkLogControl) {
+TEST_F(LogAdminTestSuite, SinkLogControl) {
     celix_log_sink_t logSink;
     logSink.handle = nullptr;
     logSink.sinkLog = [](void */*handle*/, celix_log_level_e /*level*/, long 
/*logServiceId*/, const char* /*logServiceName*/, const char* /*file*/, const 
char* /*function*/, int /*line*/, const char */*format*/, va_list 
/*formatArgs*/) {
@@ -219,7 +221,7 @@ TEST_F(LogBundleTestSuite, SinkLogControl) {
     celix_bundleContext_unregisterService(ctx.get(), svcId3);
 }
 
-TEST_F(LogBundleTestSuite, LogServiceControl) {
+TEST_F(LogAdminTestSuite, LogServiceControl) {
     //request "default" log service
     long trkId1 = celix_bundleContext_trackServices(ctx.get(), 
CELIX_LOG_SERVICE_NAME);
     celix_framework_waitForEmptyEventQueue(fw.get());
@@ -295,7 +297,7 @@ static void logSinkFunction(void *handle, celix_log_level_e 
level, long logServi
     fprintf(stdout, "\n");
 }
 
-TEST_F(LogBundleTestSuite, LogServiceAndSink) {
+TEST_F(LogAdminTestSuite, LogServiceAndSink) {
     celix_log_sink_t logSink;
     logSink.handle = nullptr;
     logSink.sinkLog = [](void */*handle*/, celix_log_level_e /*level*/, long 
/*logServiceId*/, const char* /*logServiceName*/, const char* /*file*/, const 
char* /*function*/, int /*line*/, const char */*format*/, va_list 
/*formatArgs*/) {
@@ -431,7 +433,7 @@ TEST_F(LogBundleTestSuite, LogServiceAndSink) {
     celix_bundleContext_stopTracker(ctx.get(), trkId);
 }
 
-TEST_F(LogBundleTestSuite, LogAdminCmd) {
+TEST_F(LogAdminTestSuite, LogAdminCmd) {
     celix_log_sink_t logSink;
     logSink.handle = nullptr;
     logSink.sinkLog = [](void */*handle*/, celix_log_level_e /*level*/, long 
/*logServiceId*/, const char* /*logServiceName*/, const char* /*file*/, const 
char* /*function*/, int /*line*/, const char */*format*/, va_list 
/*formatArgs*/) {
@@ -590,4 +592,98 @@ TEST_F(LogBundleTestSuite, LogAdminCmd) {
     };
     called = celix_bundleContext_useServiceWithOptions(ctx.get(), &opts);
     EXPECT_TRUE(called);
-}
\ No newline at end of file
+}
+
+TEST_F(LogAdminTestSuite, LogServiceWithConfigPropertyTest) {
+    // Given a fw with a config property that set the active log level of a 
log service to fatal
+    auto fw = celix::createFramework({
+        {CELIX_FRAMEWORK_CACHE_DIR, ".cacheLogAdminTestSuiteWithConfig"},
+        {"CELIX_LOG_ADMIN_LOGGER_FOO_ACTIVE_LOG_LEVEL", "fatal"},
+        {"CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL", "debug"}});
+    EXPECT_NE(fw.get(), nullptr);
+
+    // And a log admin
+    auto bndId = 
fw->getFrameworkBundleContext()->installBundle(LOG_ADMIN_BUNDLE, true);
+    EXPECT_GE(bndId, 0);
+
+    // When the log service bar is requested
+    auto trk1 = fw->getFrameworkBundleContext()
+                    
->trackServices<celix_log_service_t>(CELIX_LOG_SERVICE_NAME)
+                    .setFilter("(name=bar)")
+                    .build();
+
+    // Then the active log level of the log service bar is debug (default);
+    auto count = fw->getFrameworkBundleContext()
+                     ->useService<celix_log_control_t>(CELIX_LOG_CONTROL_NAME)
+                     .addUseCallback([](auto& svc) {
+                         celix_log_level_e level;
+                         svc.logServiceInfo(svc.handle, "bar", &level);
+                         EXPECT_EQ(CELIX_LOG_LEVEL_DEBUG, level);
+                     })
+                     .build();
+    EXPECT_EQ(1, count);
+
+    // When the log service foo is requested
+    auto trk2 = fw->getFrameworkBundleContext()
+                    
->trackServices<celix_log_service_t>(CELIX_LOG_SERVICE_NAME)
+                    .setFilter("(name=foo)")
+                    .build();
+
+    // Then the active log level of the log service foo is fatal (set by 
config property);
+    count = fw->getFrameworkBundleContext()
+                ->useService<celix_log_control_t>(CELIX_LOG_CONTROL_NAME)
+                .addUseCallback([](auto& svc) {
+                    celix_log_level_e level;
+                    svc.logServiceInfo(svc.handle, "foo", &level);
+                    EXPECT_EQ(CELIX_LOG_LEVEL_FATAL, level);
+                })
+                .build();
+    EXPECT_EQ(1, count);
+}
+
+TEST_F(LogAdminTestSuite, LogSinkWithConfigPropertyTest) {
+    // Given a fw with a config property that set the active log level of a 
log service to fatal
+    auto fw = celix::createFramework({
+        {CELIX_FRAMEWORK_CACHE_DIR, ".cacheLogAdminTestSuiteWithConfig"},
+        {"CELIX_LOG_ADMIN_LOG_SINKS_DEFAULT_ENABLED", "false"},
+        {"CELIX_LOG_ADMIN_LOG_SINK_FOO_ENABLED", "true"}});
+    EXPECT_NE(fw.get(), nullptr);
+
+    // And a log admin
+    auto bndId = 
fw->getFrameworkBundleContext()->installBundle(LOG_ADMIN_BUNDLE, true);
+    EXPECT_GE(bndId, 0);
+
+    // When a log sink bar is registered
+    auto reg1 = 
fw->getFrameworkBundleContext()->registerService<celix_log_sink_t>(std::make_shared<celix_log_sink_t>(),
 CELIX_LOG_SINK_NAME)
+                    .setVersion(CELIX_LOG_SINK_VERSION)
+                    .addProperty("name", "bar")
+                    .build();
+
+    // Then the log sink bar will be disabled (default for all sinks)
+    auto count = fw->getFrameworkBundleContext()
+                     ->useService<celix_log_control_t>(CELIX_LOG_CONTROL_NAME)
+                     .addUseCallback([](auto& svc) {
+                         bool enabled;
+                         svc.sinkInfo(svc.handle, "bar", &enabled);
+                         EXPECT_FALSE(enabled);
+                     })
+                     .build();
+    EXPECT_EQ(1, count);
+
+    // When a log sink foo is registered
+    auto reg2 = 
fw->getFrameworkBundleContext()->registerService<celix_log_sink_t>(std::make_shared<celix_log_sink_t>(),
 CELIX_LOG_SINK_NAME)
+                    .setVersion(CELIX_LOG_SINK_VERSION)
+                    .addProperty("name", "foo")
+                    .build();
+
+    // Then the log sink fpp will be enabled 
(CELIX_LOG_ADMIN_LOG_SINK_FOO_ENABLED=true)
+    count = fw->getFrameworkBundleContext()
+                     ->useService<celix_log_control_t>(CELIX_LOG_CONTROL_NAME)
+                     .addUseCallback([](auto& svc) {
+                         bool enabled;
+                         svc.sinkInfo(svc.handle, "foo", &enabled);
+                         EXPECT_TRUE(enabled);
+                     })
+                     .build();
+    EXPECT_EQ(1, count);
+}
diff --git 
a/bundles/logging/log_admin/gtest/src/LogAdminWithErrorInjectionTestSuite.cc 
b/bundles/logging/log_admin/gtest/src/LogAdminWithErrorInjectionTestSuite.cc
new file mode 100644
index 000000000..fdf393d1b
--- /dev/null
+++ b/bundles/logging/log_admin/gtest/src/LogAdminWithErrorInjectionTestSuite.cc
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "asprintf_ei.h"
+#include "celix/FrameworkFactory.h"
+#include "celix/Constants.h"
+#include "celix_log_admin.h"
+#include "celix_properties_ei.h"
+#include "celix_string_hash_map_ei.h"
+#include "malloc_ei.h"
+
+#include <celix_log_service.h>
+
+class LogAdminWithErrorInjectionTestSuite : public ::testing::Test {
+  public:
+    LogAdminWithErrorInjectionTestSuite() {
+        fw = celix::createFramework();
+
+        celix_ei_expect_calloc(nullptr, 0, nullptr);
+        celix_ei_expect_asprintf(nullptr, 0, -1);
+        celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0);
+        celix_ei_expect_celix_properties_create(nullptr, 0, nullptr);
+    }
+
+    ~LogAdminWithErrorInjectionTestSuite() noexcept override {}
+
+    std::shared_ptr<celix::Framework> fw{};
+};
+
+TEST_F(LogAdminWithErrorInjectionTestSuite, CreateWithErrorTest) {
+    //Given an error injection for calloc
+    celix_ei_expect_calloc((void*)celix_logAdmin_create, 0, nullptr);
+
+    //When creating a log admin
+    auto* admin = celix_logAdmin_create(nullptr);
+
+    //Then the admin is nullptr
+    ASSERT_EQ(nullptr, admin);
+}
+
+TEST_F(LogAdminWithErrorInjectionTestSuite, AddLogSvcWithErrorTest) {
+    //Given a log admin
+    auto* logAdmin = 
celix_logAdmin_create(fw->getFrameworkBundleContext()->getCBundleContext());
+    ASSERT_NE(logAdmin, nullptr);
+
+    //And an error injection for calloc is expected
+    celix_ei_expect_asprintf((void*)celix_logAdmin_addLogSvcForName, 0, -1);
+
+    //When adding a log service for a name
+    celix_logAdmin_addLogSvcForName(logAdmin, "test");
+
+    //Then the loggers count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfLogServices(logAdmin, "test"));
+
+    //When an error injection for calloc is expected
+    celix_ei_expect_calloc( (void*)celix_logAdmin_addLogSvcForName, 0, 
nullptr);
+
+    //And adding a log service for a name
+    celix_logAdmin_addLogSvcForName(logAdmin, "test");
+
+    //Then the loggers count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfLogServices(logAdmin, "test"));
+
+    //When an error injection for celix_stringHashMap_put is expected
+    celix_ei_expect_celix_stringHashMap_put( 
(void*)celix_logAdmin_addLogSvcForName, 0, ENOMEM);
+
+    //And adding a log service for a name
+    celix_logAdmin_addLogSvcForName(logAdmin, "test");
+
+    //Then the loggers count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfLogServices(logAdmin, "test"));
+
+    //When an error injection for celix_properties_create is expected
+    celix_ei_expect_celix_properties_create( 
(void*)celix_logAdmin_addLogSvcForName, 0, nullptr);
+
+    //And adding a log service for a name
+    celix_logAdmin_addLogSvcForName(logAdmin, "test");
+
+    //Then the loggers count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfLogServices(logAdmin, "test"));
+
+    celix_logAdmin_destroy(logAdmin);
+}
+
+TEST_F(LogAdminWithErrorInjectionTestSuite, AddLogSinkWithErrorTest) {
+    //Given a log admin
+    auto* logAdmin = 
celix_logAdmin_create(fw->getFrameworkBundleContext()->getCBundleContext());
+    ASSERT_NE(logAdmin, nullptr);
+
+    //And a service properties
+    celix_autoptr(celix_properties_t) props = celix_properties_create();
+    celix_properties_set(props, "name", "test");
+    celix_properties_setLong(props, celix::SERVICE_ID, 1L);
+
+    //And an error injection for asprintf is expected
+    celix_ei_expect_asprintf((void*)celix_logAdmin_addSink, 0, -1);
+
+    //When adding a log sink
+    celix_logAdmin_addSink(logAdmin, nullptr, props);
+
+    //Then the sinks count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfSinks(logAdmin, "test"));
+
+    //When an error injection for calloc is expected
+    celix_ei_expect_calloc( (void*)celix_logAdmin_addSink, 0, nullptr);
+
+    //And adding a log sink
+    celix_logAdmin_addSink(logAdmin, nullptr, props);
+
+    //Then the sinks count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfSinks(logAdmin, "test"));
+
+    //When an error injection for celix_stringHashMap_put is expected
+    celix_ei_expect_celix_stringHashMap_put( (void*)celix_logAdmin_addSink, 0, 
ENOMEM);
+
+    //And adding a log sink
+    celix_logAdmin_addSink(logAdmin, nullptr, props);
+
+    //Then the sinks count is not increased
+    EXPECT_EQ(0, celix_logAdmin_nrOfSinks(logAdmin, "test"));
+
+    celix_logAdmin_destroy(logAdmin);
+}
diff --git a/bundles/logging/log_admin/src/celix_log_admin.c 
b/bundles/logging/log_admin/src/celix_log_admin.c
index 5ed38d53d..14381ed5c 100644
--- a/bundles/logging/log_admin/src/celix_log_admin.c
+++ b/bundles/logging/log_admin/src/celix_log_admin.c
@@ -22,21 +22,34 @@
 #include <stdlib.h>
 #include <stdarg.h>
 #include <string.h>
+#include <stdio.h>
 
 #include <celix_constants.h>
 #include <celix_log_control.h>
 #include <assert.h>
+#include <strings.h>
 
+#include "celix_bundle_context.h"
+#include "celix_bundle_context_type.h"
+#include "celix_cleanup.h"
 #include "celix_compiler.h"
+#include "celix_errno.h"
+#include "celix_filter.h"
+#include "celix_framework.h"
+#include "celix_log_constants.h"
+#include "celix_log_level.h"
 #include "celix_log_service.h"
 #include "celix_log_sink.h"
-#include "celix_utils.h"
 #include "celix_log_utils.h"
-#include "celix_log_constants.h"
+#include "celix_properties_type.h"
+#include "celix_properties.h"
 #include "celix_shell_command.h"
+#include "celix_string_hash_map.h"
 #include "celix_threads.h"
-#include "hash_map.h"
-#include "celix_framework.h"
+#include "celix_utils.h"
+
+#include <celix_stdlib_cleanup.h>
+#include <ctype.h>
 
 #define CELIX_LOG_ADMIN_DEFAULT_LOG_NAME "default"
 #define CELIX_LOG_ADMIN_FRAMEWORK_LOG_NAME "celix_framework"
@@ -57,8 +70,8 @@ struct celix_log_admin {
     long cmdSvcId;
 
     celix_thread_rwlock_t lock; //protects below
-    hash_map_t *loggers; //key = name, value = celix_log_service_instance_t
-    hash_map_t* sinks; //key = name, value = celix_log_sink_t
+    celix_string_hash_map_t *loggers; //key = name, value = 
celix_log_service_instance_t
+    celix_string_hash_map_t* sinks; //key = name, value = celix_log_sink_t
 };
 
 typedef struct celix_log_service_entry {
@@ -82,6 +95,14 @@ typedef struct celix_log_sink_entry {
     bool enabled;
 } celix_log_sink_entry_t;
 
+static char* celix_logAdmin_allCaps(const char* str) {
+    char* result = strdup(str);
+    for (int i = 0; result && i < strlen(result); ++i) {
+        result[i] = (char)toupper(result[i]);
+    }
+    return result;
+}
+
 static void celix_logAdmin_vlogDetails(void *handle, celix_log_level_e level, 
const char* file, const char* function, int line, const char *format, va_list 
formatArgs) {
     celix_log_service_entry_t* entry = handle;
 
@@ -92,19 +113,18 @@ static void celix_logAdmin_vlogDetails(void *handle, 
celix_log_level_e level, co
 
     celixThreadRwlock_readLock(&entry->admin->lock);
     if (level >= entry->activeLogLevel) {
-        int nrOfLogWriters = hashMap_size(entry->admin->sinks);
-        hash_map_iterator_t iter = 
hashMapIterator_construct(entry->admin->sinks);
-        while (hashMapIterator_hasNext(&iter)) {
-            celix_log_sink_entry_t *sinkEntry = 
hashMapIterator_nextValue(&iter);
-            if (sinkEntry->enabled) {
-                celix_log_sink_t *sink = sinkEntry->sink;
-                va_list argCopy;
-                va_copy(argCopy, formatArgs);
-                sink->sinkLog(sink->handle, level, entry->logSvcId, 
entry->name,
-                              entry->detailed ? file : NULL, entry->detailed ? 
function : NULL, entry->detailed ? line : 0,
-                              format, argCopy);
-                va_end(argCopy);
-            }
+        size_t nrOfLogWriters = celix_stringHashMap_size(entry->admin->sinks);
+        CELIX_STRING_HASH_MAP_ITERATE(entry->admin->sinks, iter) {
+                celix_log_sink_entry_t *sinkEntry = iter.value.ptrValue;
+                if (sinkEntry->enabled) {
+                        celix_log_sink_t *sink = sinkEntry->sink;
+                        va_list argCopy;
+                        va_copy(argCopy, formatArgs);
+                        sink->sinkLog(sink->handle, level, entry->logSvcId, 
entry->name,
+                                  entry->detailed ? file : NULL, 
entry->detailed ? function : NULL, entry->detailed ? line : 0,
+                                  format, argCopy);
+                        va_end(argCopy);
+                }
         }
 
         if (entry->admin->alwaysLogToStdOut || (nrOfLogWriters == 0 && 
entry->admin->fallbackToStdOut)) {
@@ -184,18 +204,44 @@ static void celix_logAdmin_frameworkLogFunction(void* 
handle, celix_log_level_e
     entry->logSvc.vlogDetails(entry->logSvc.handle, level, file, function, 
line, format, formatArgs);
 }
 
-static void celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const 
char* name) {
+void celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const char* 
name) {
     celix_log_service_entry_t* newEntry = NULL;
 
-    celixThreadRwlock_writeLock(&admin->lock);
-    celix_log_service_entry_t* found = hashMap_get(admin->loggers, name);
+    celix_autofree char* logNameInCaps = celix_logAdmin_allCaps(name);
+    celix_autofree char* configKey = NULL;
+    int rc = -1;
+    if (logNameInCaps) {
+        rc = asprintf(&configKey, 
"CELIX_LOG_ADMIN_LOGGER_%s_ACTIVE_LOG_LEVEL", logNameInCaps);
+    }
+    if (rc < 0) {
+        (void)fprintf(stderr, "Error creating config key for log service %s. 
ENOMEM\n", name);
+        return;
+    }
+
+    const char* configuredLogLevel = 
celix_bundleContext_getProperty(admin->ctx, configKey, NULL);
+    celix_log_level_e activeLogLevel = admin->logServicesDefaultActiveLogLevel;
+    if (configuredLogLevel != NULL) {
+        activeLogLevel = celix_logLevel_fromString(configuredLogLevel, 
activeLogLevel);
+    }
+
+
+    celix_auto(celix_rwlock_wlock_guard_t) lock = 
celixRwlockWlockGuard_init(&admin->lock);
+    celix_log_service_entry_t* found = celix_stringHashMap_get(admin->loggers, 
name);
     if (found == NULL) {
         //new
         newEntry = calloc(1, sizeof(*newEntry));
+        char* entryName = celix_utils_strdup(name);
+        if (!newEntry || !entryName) {
+            free(newEntry);
+            free(entryName);
+            (void)fprintf(stderr, "Error creating log service entry for log 
service %s. ENOMEM\n", name);
+            return;
+        }
+        newEntry->logSvcId = -1L;
         newEntry->admin = admin;
-        newEntry->name = celix_utils_strdup(name);
+        newEntry->name = entryName;
         newEntry->count = 1;
-        newEntry->activeLogLevel = admin->logServicesDefaultActiveLogLevel;
+        newEntry->activeLogLevel = activeLogLevel;
         newEntry->detailed = true;
         newEntry->logSvc.handle = newEntry;
         newEntry->logSvc.trace = celix_logAdmin_trace;
@@ -208,16 +254,32 @@ static void 
celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const char
         newEntry->logSvc.logDetails = celix_logAdmin_logDetails;
         newEntry->logSvc.vlog = celix_logAdmin_vlog;
         newEntry->logSvc.vlogDetails = celix_logAdmin_vlogDetails;
-        hashMap_put(admin->loggers, (void*)newEntry->name, newEntry);
-        celixThreadRwlock_unlock(&admin->lock);
+        celix_status_t addHashmapStatus = 
celix_stringHashMap_put(admin->loggers, (void*)newEntry->name, newEntry);
+        if (addHashmapStatus != CELIX_SUCCESS) {
+            (void)fprintf(stderr, "Error adding log service entry for log 
service %s. ENOMEM\n", name);
+            free(newEntry->name);
+            free(newEntry);
+            return;
+        }
 
         {
-            //register new log service async
+            // register new log service async
             celix_properties_t* props = celix_properties_create();
-            celix_properties_set(props, CELIX_LOG_SERVICE_PROPERTY_NAME, 
newEntry->name);
-            if (celix_utils_stringEquals(newEntry->name, 
CELIX_LOG_ADMIN_DEFAULT_LOG_NAME) == 0) {
-                //ensure that the default log service is found when no name 
filter is used.
-                celix_properties_setLong(props, 
CELIX_FRAMEWORK_SERVICE_RANKING, 100);
+            celix_status_t status = CELIX_SUCCESS;
+            if (props) {
+                status = celix_properties_set(props, 
CELIX_LOG_SERVICE_PROPERTY_NAME, newEntry->name);
+                if (celix_utils_stringEquals(newEntry->name, 
CELIX_LOG_ADMIN_DEFAULT_LOG_NAME) == 0) {
+                    // ensure that the default log service is found when no 
name filter is used.
+                    status = CELIX_DO_IF(status, 
celix_properties_setLong(props, CELIX_FRAMEWORK_SERVICE_RANKING, 100));
+                }
+            }
+
+            if (!props || status != CELIX_SUCCESS) {
+                (void)fprintf(stderr, "Error creating properties for log 
service %s. ENOMEM\n", name);
+                celix_stringHashMap_remove(admin->loggers, name);
+                free(newEntry->name);
+                free(newEntry);
+                return;
             }
 
             celix_service_registration_options_t opts = 
CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS;
@@ -234,7 +296,6 @@ static void 
celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const char
         }
     } else {
         found->count += 1;
-        celixThreadRwlock_unlock(&admin->lock);
     }
 }
 
@@ -259,12 +320,12 @@ static void celix_logAdmin_freeLogEntry(void *data) {
 
 static void celix_logAdmin_remLogSvcForName(celix_log_admin_t* admin, const 
char* name) {
     celixThreadRwlock_writeLock(&admin->lock);
-    celix_log_service_entry_t* found = hashMap_get(admin->loggers, name);
+    celix_log_service_entry_t* found = celix_stringHashMap_get(admin->loggers, 
name);
     if (found != NULL) {
         found->count -= 1;
         if (found->count == 0) {
             //remove
-            hashMap_remove(admin->loggers, name);
+            celix_stringHashMap_remove(admin->loggers, name);
             celix_bundleContext_unregisterServiceAsync(admin->ctx, 
found->logSvcId, found, celix_logAdmin_freeLogEntry);
         }
     }
@@ -281,33 +342,60 @@ static void celix_logAdmin_trackerRem(void *handle, const 
celix_service_tracker_
     celix_logAdmin_remLogSvcForName(admin, name);
 }
 
-
-static void celix_logAdmin_addSink(void *handle, void *svc, const 
celix_properties_t* props) {
+void celix_logAdmin_addSink(void* handle, void* svc, const celix_properties_t* 
props) {
     celix_log_admin_t* admin = handle;
     celix_log_sink_t* sink = svc;
 
-    long svcId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, 
-1L);
+    const long svcId = celix_properties_getAsLong(props, 
CELIX_FRAMEWORK_SERVICE_ID, -1L);
     const char* sinkName = celix_properties_get(props, 
CELIX_LOG_SINK_PROPERTY_NAME, NULL);
-    char nameBuf[16];
+    char nameBuf[24];
     if (sinkName == NULL) {
-        snprintf(nameBuf, 16, "LogSink-%li", svcId);
+        (void)snprintf(nameBuf, 24, "LogSink-%li", svcId);
         sinkName = nameBuf;
     }
 
-    celixThreadRwlock_writeLock(&admin->lock);
-    celix_log_sink_entry_t* found = hashMap_get(admin->sinks, sinkName);
+    celix_autofree char* sinkNameCaps = celix_logAdmin_allCaps(sinkName);
+    celix_autofree char* configKey = NULL;
+    int rc = -1;
+    if (sinkNameCaps) {
+        rc = asprintf(&configKey, "CELIX_LOG_ADMIN_LOG_SINK_%s_ENABLED", 
sinkNameCaps);
+    }
+    if (rc < 0) {
+        (void)fprintf(stderr, "Error creating config key for sink %s. 
ENOMEM\n", sinkName);
+        return;
+    }
+
+    bool enabled = celix_bundleContext_getPropertyAsBool(admin->ctx, 
configKey, admin->sinksDefaultEnabled);
+
+    celix_auto(celix_rwlock_wlock_guard_t) lock = 
celixRwlockWlockGuard_init(&admin->lock);
+    celix_log_sink_entry_t* found = celix_stringHashMap_get(admin->sinks, 
sinkName);
     if (found == NULL) {
-        celix_log_sink_entry_t *entry = calloc(1, sizeof(*entry));
-        entry->name = celix_utils_strdup(sinkName);
-        entry->svcId = svcId;
-        entry->enabled = admin->sinksDefaultEnabled;
-        entry->sink = sink;
-        hashMap_put(admin->sinks, entry->name, entry);
+        celix_log_sink_entry_t* entry = calloc(1, sizeof(*entry));
+        char* entryName = celix_utils_strdup(sinkName);
+        if (entry && entryName) {
+            entry->name = entryName;
+            entry->svcId = svcId;
+            entry->enabled = enabled;
+            entry->sink = sink;
+            celix_status_t status = celix_stringHashMap_put(admin->sinks, 
entry->name, entry);
+            if (status != CELIX_SUCCESS) {
+                (void)fprintf(stderr, "Error creating log sink entry for sink 
%s. ENOMEM\n", sinkName);
+                free(entry->name);
+                free(entry);
+            };
+        } else {
+            free(entry);
+            free(entryName);
+            (void)fprintf(stderr, "Error creating log sink entry for sink %s. 
ENOMEM\n", sinkName);
+            return;
+        }
     }
-    celixThreadRwlock_unlock(&admin->lock);
 
     if (found != NULL) {
-        celix_logUtils_logToStdout(CELIX_LOG_ADMIN_DEFAULT_LOG_NAME, 
CELIX_LOG_LEVEL_ERROR, "Cannot add log sink, Log sink with name '%s' already 
present.", sinkName);
+        celix_logUtils_logToStdout(CELIX_LOG_ADMIN_DEFAULT_LOG_NAME,
+                                   CELIX_LOG_LEVEL_ERROR,
+                                   "Cannot add log sink, Log sink with name 
'%s' already present.",
+                                   sinkName);
     }
 }
 
@@ -315,20 +403,20 @@ static void celix_logAdmin_remSink(void *handle, void 
*svc CELIX_UNUSED, const c
     celix_log_admin_t* admin = handle;
     long svcId = celix_properties_getAsLong(props, CELIX_FRAMEWORK_SERVICE_ID, 
-1L);
     const char* sinkName = celix_properties_get(props, 
CELIX_LOG_SINK_PROPERTY_NAME, NULL);
-    char nameBuf[16];
+    char nameBuf[24];
     if (sinkName == NULL) {
-        snprintf(nameBuf, 16, "LogSink-%li", svcId);
+        (void)snprintf(nameBuf, 24, "LogSink-%li", svcId);
         sinkName = nameBuf;
     }
 
     celixThreadRwlock_writeLock(&admin->lock);
-    celix_log_sink_entry_t* entry = hashMap_get(admin->sinks, sinkName);
+    celix_log_sink_entry_t* entry = celix_stringHashMap_get(admin->sinks, 
sinkName);
     if (entry != NULL && entry->svcId != svcId) {
         //no match (note there can be invalid log sinks with the same name, 
but different svc ids.
         entry = NULL;
     }
     if (entry != NULL) {
-        hashMap_remove(admin->sinks, sinkName);
+        celix_stringHashMap_remove(admin->sinks, sinkName);
     }
     celixThreadRwlock_unlock(&admin->lock);
 
@@ -338,19 +426,18 @@ static void celix_logAdmin_remSink(void *handle, void 
*svc CELIX_UNUSED, const c
     }
 }
 
-static size_t celix_logAdmin_nrOfLogServices(void *handle, const char* select) 
{
+size_t celix_logAdmin_nrOfLogServices(void* handle, const char* select) {
     celix_log_admin_t* admin = handle;
     size_t count = 0;
     celixThreadRwlock_readLock(&admin->lock);
     if (select == NULL) {
-        count = hashMap_size(admin->loggers);
+        count = celix_stringHashMap_size(admin->loggers);
     } else {
-        hash_map_iterator_t iter = hashMapIterator_construct(admin->loggers);
-        while (hashMapIterator_hasNext(&iter)) {
-            celix_log_service_entry_t *visit = 
hashMapIterator_nextValue(&iter);
-            char *match = strcasestr(visit->name, select);
+        CELIX_STRING_HASH_MAP_ITERATE(admin->loggers, iter) {
+            const celix_log_service_entry_t* visit = iter.value.ptrValue;
+            const char* match = strcasestr(visit->name, select);
             if (match != NULL && match == visit->name) {
-                //note if select is found in visit->name and visit->name start 
with select
+                // note if select is found in visit->name and visit->name 
start with select
                 count += 1;
             }
         }
@@ -359,16 +446,15 @@ static size_t celix_logAdmin_nrOfLogServices(void 
*handle, const char* select) {
     return count;
 }
 
-static size_t celix_logAdmin_nrOfSinks(void *handle, const char* select) {
+size_t celix_logAdmin_nrOfSinks(void *handle, const char* select) {
     celix_log_admin_t* admin = handle;
     size_t count = 0;
     celixThreadRwlock_readLock(&admin->lock);
     if (select == NULL) {
-        count = hashMap_size(admin->sinks);
+        count = celix_stringHashMap_size(admin->sinks);
     } else {
-        hash_map_iterator_t iter = hashMapIterator_construct(admin->sinks);
-        while (hashMapIterator_hasNext(&iter)) {
-            celix_log_sink_entry_t *visit = hashMapIterator_nextValue(&iter);
+        CELIX_STRING_HASH_MAP_ITERATE(admin->sinks, iter) {
+            celix_log_sink_entry_t *visit = iter.value.ptrValue;
             char *match = strcasestr(visit->name, select);
             if (match != NULL && match == visit->name) {
                 //note if select is found in visit->name and visit->name start 
with select
@@ -384,9 +470,8 @@ static size_t celix_logAdmin_setActiveLogLevels(void 
*handle, const char* select
     celix_log_admin_t* admin = handle;
     size_t count = 0;
     celixThreadRwlock_writeLock(&admin->lock);
-    hash_map_iterator_t iter = hashMapIterator_construct(admin->loggers);
-    while (hashMapIterator_hasNext(&iter)) {
-        celix_log_service_entry_t* visit = hashMapIterator_nextValue(&iter);
+    CELIX_STRING_HASH_MAP_ITERATE(admin->loggers, iter) {
+        celix_log_service_entry_t* visit = iter.value.ptrValue;
         if (select == NULL) {
             visit->activeLogLevel = activeLogLevel;
             count += 1;
@@ -403,20 +488,19 @@ static size_t celix_logAdmin_setActiveLogLevels(void 
*handle, const char* select
     return count;
 }
 
-static size_t celix_logAdmin_setSinkEnabled(void *handle, const char* select, 
bool enabled) {
+static size_t celix_logAdmin_setSinkEnabled(void* handle, const char* select, 
bool enabled) {
     celix_log_admin_t* admin = handle;
     size_t count = 0;
     celixThreadRwlock_writeLock(&admin->lock);
-    hash_map_iterator_t iter = hashMapIterator_construct(admin->sinks);
-    while (hashMapIterator_hasNext(&iter)) {
-        celix_log_sink_entry_t* visit = hashMapIterator_nextValue(&iter);
+    CELIX_STRING_HASH_MAP_ITERATE(admin->sinks, iter) {
+        celix_log_sink_entry_t* visit = iter.value.ptrValue;
         if (select == NULL) {
             visit->enabled = enabled;
             count += 1;
         } else {
-            char *match = strcasestr(visit->name, select);
+            char* match = strcasestr(visit->name, select);
             if (match != NULL && match == visit->name) {
-                //note if select is found in visit->name and visit->name start 
with select
+                // note if select is found in visit->name and visit->name 
start with select
                 visit->enabled = enabled;
                 count += 1;
             }
@@ -431,9 +515,8 @@ static celix_array_list_t* 
celix_logAdmin_currentLogServices(void *handle) {
     celix_log_admin_t* admin = handle;
     celix_array_list_t* loggers = celix_arrayList_createStringArray();
     celixThreadRwlock_readLock(&admin->lock);
-    hash_map_iterator_t iter = hashMapIterator_construct(admin->loggers);
-    while (hashMapIterator_hasNext(&iter)) {
-        celix_log_service_entry_t* visit = hashMapIterator_nextValue(&iter);
+    CELIX_STRING_HASH_MAP_ITERATE(admin->loggers, iter) {
+        const celix_log_service_entry_t* visit = iter.value.ptrValue;
         celix_arrayList_addString(loggers, visit->name);
     }
     celixThreadRwlock_unlock(&admin->lock);
@@ -444,9 +527,8 @@ static celix_array_list_t* celix_logAdmin_currentSinks(void 
*handle) {
     celix_log_admin_t* admin = handle;
     celix_array_list_t* sinks = celix_arrayList_createStringArray();
     celixThreadRwlock_readLock(&admin->lock);
-    hash_map_iterator_t iter = hashMapIterator_construct(admin->sinks);
-    while (hashMapIterator_hasNext(&iter)) {
-        celix_log_sink_entry_t* entry = hashMapIterator_nextValue(&iter);
+    CELIX_STRING_HASH_MAP_ITERATE(admin->sinks, iter) {
+        const celix_log_sink_entry_t* entry = iter.value.ptrValue;
         celix_arrayList_addString(sinks, entry->name);
     }
     celixThreadRwlock_unlock(&admin->lock);
@@ -456,7 +538,7 @@ static celix_array_list_t* celix_logAdmin_currentSinks(void 
*handle) {
 static bool celix_logAdmin_sinkInfo(void *handle, const char* sinkName, bool* 
outEnabled) {
     celix_log_admin_t* admin = handle;
     celixThreadRwlock_readLock(&admin->lock);
-    celix_log_sink_entry_t* found = hashMap_get(admin->sinks, sinkName);
+    celix_log_sink_entry_t* found = celix_stringHashMap_get(admin->sinks, 
sinkName);
     if (found != NULL && outEnabled != NULL) {
         *outEnabled = found->enabled;
     }
@@ -468,9 +550,8 @@ static size_t celix_logAdmin_setDetailed(void *handle, 
const char* select, bool
     celix_log_admin_t* admin = handle;
     size_t count = 0;
     celixThreadRwlock_writeLock(&admin->lock);
-    hash_map_iterator_t iter = hashMapIterator_construct(admin->loggers);
-    while (hashMapIterator_hasNext(&iter)) {
-        celix_log_service_entry_t* visit = hashMapIterator_nextValue(&iter);
+    CELIX_STRING_HASH_MAP_ITERATE(admin->loggers, iter) {
+        celix_log_service_entry_t* visit = iter.value.ptrValue;
         if (select == NULL) {
             visit->detailed = detailed;
             count += 1;
@@ -490,7 +571,7 @@ static size_t celix_logAdmin_setDetailed(void *handle, 
const char* select, bool
 static bool celix_logAdmin_logServiceInfoEx(void* handle, const char* 
logServiceName, celix_log_level_e* outActiveLogLevel, bool* outDetailed) {
     celix_log_admin_t* admin = handle;
     celixThreadRwlock_readLock(&admin->lock);
-    celix_log_service_entry_t* found = hashMap_get(admin->loggers, 
logServiceName);
+    celix_log_service_entry_t* found = celix_stringHashMap_get(admin->loggers, 
logServiceName);
     if (found != NULL) {
         if (outActiveLogLevel != NULL) {
             *outActiveLogLevel = found->activeLogLevel;
@@ -509,12 +590,12 @@ static bool celix_logAdmin_logServiceInfo(void *handle, 
const char* logServiceNa
 
 static void celix_logAdmin_setLogLevelCmd(celix_log_admin_t* admin, const 
char* select, const char* level, FILE* outStream, FILE* errorStream) {
     bool converted;
-    celix_log_level_e logLevel = 
celix_logUtils_logLevelFromStringWithCheck(level, CELIX_LOG_LEVEL_TRACE, 
&converted);
+    celix_log_level_e logLevel = celix_logLevel_fromStringWithCheck(level, 
CELIX_LOG_LEVEL_TRACE, &converted);
     if (converted) {
         size_t count = celix_logAdmin_setActiveLogLevels(admin, select, 
logLevel);
-        fprintf(outStream, "Updated %zu log services to log level %s\n", 
count, celix_logUtils_logLevelToString(logLevel));
+        (void)fprintf(outStream, "Updated %zu log services to log level %s\n", 
count, celix_logLevel_toString(logLevel));
     } else {
-        fprintf(errorStream, "Cannot convert '%s' to a valid celix log 
level.\n", level);
+        (void)fprintf(errorStream, "Cannot convert '%s' to a valid celix log 
level.\n", level);
     }
 }
 
@@ -530,9 +611,9 @@ static void 
celix_logAdmin_setSinkEnabledCmd(celix_log_admin_t* admin, const cha
     }
     if (valid) {
         size_t count = celix_logAdmin_setSinkEnabled(admin, select, 
enableSink);
-        fprintf(outStream, "Updated %zu log sinks to %s.\n", count, enableSink 
? "enabled" : "disabled");
+        (void)fprintf(outStream, "Updated %zu log sinks to %s.\n", count, 
enableSink ? "enabled" : "disabled");
     } else {
-        fprintf(errorStream, "Cannot convert '%s' to a boolean value.\n", 
enabled);
+        (void)fprintf(errorStream, "Cannot convert '%s' to a boolean 
value.\n", enabled);
     }
 }
 
@@ -542,31 +623,31 @@ static void celix_logAdmin_InfoCmd(celix_log_admin_t* 
admin, FILE* outStream, FI
     celix_arrayList_sort(logServices);
     celix_arrayList_sort(sinks);
 
-    fprintf(outStream, "Log Admin provided log services:\n");
+    (void)fprintf(outStream, "Log Admin provided log services:\n");
     for (int i = 0 ; i < celix_arrayList_size(logServices); ++i) {
         const char *name = celix_arrayList_getString(logServices, i);
         celix_log_level_e level;
         bool detailed;
         bool found = celix_logAdmin_logServiceInfoEx(admin, name, &level, 
&detailed);
         if (found) {
-            fprintf(outStream, " |- %i) Log Service %20s, active log level %s, 
%s\n",
+            (void)fprintf(outStream, " |- %i) Log Service %20s, active log 
level %s, %s\n",
                     i+1, name, celix_logUtils_logLevelToString(level), 
detailed ? "detailed" : "brief");
         }
     }
     celix_arrayList_destroy(logServices);
 
     if (celix_arrayList_size(sinks) > 0) {
-        fprintf(outStream, "Log Admin found log sinks:\n");
+        (void)fprintf(outStream, "Log Admin found log sinks:\n");
         for (int i = 0 ; i < celix_arrayList_size(sinks); ++i) {
             const char *name = celix_arrayList_getString(sinks, i);
             bool enabled;
             bool found = celix_logAdmin_sinkInfo(admin, name, &enabled);
             if (found) {
-                fprintf(outStream, " |- %i) Log Sink %20s, %s\n", i+1, name, 
enabled ? "enabled" : "disabled");
+                (void)fprintf(outStream, " |- %i) Log Sink %20s, %s\n", i+1, 
name, enabled ? "enabled" : "disabled");
             }
         }
     } else {
-        fprintf(outStream, "Log Admin has found 0 log sinks\n");
+        (void)fprintf(outStream, "Log Admin has found 0 log sinks\n");
     }
     celix_arrayList_destroy(sinks);
 }
@@ -583,9 +664,9 @@ static void 
celix_logAdmin_setLogDetailedCmd(celix_log_admin_t* admin, const cha
     }
     if (valid) {
         size_t count = celix_logAdmin_setDetailed(admin, select, 
enableDetailed);
-        fprintf(outStream, "Updated %zu log services to %s.\n", count, 
enableDetailed ? "detailed" : "brief");
+        (void)fprintf(outStream, "Updated %zu log services to %s.\n", count, 
enableDetailed ? "detailed" : "brief");
     } else {
-        fprintf(errorStream, "Cannot convert '%s' to a boolean value.\n", 
detailed);
+        (void)fprintf(errorStream, "Cannot convert '%s' to a boolean 
value.\n", detailed);
     }
 }
 
@@ -608,7 +689,7 @@ static bool celix_logAdmin_executeCommand(void *handle, 
const char *commandLine,
             } else if (arg1 != NULL) {
                 celix_logAdmin_setLogLevelCmd(admin, NULL, arg1, outStream, 
errorStream);
             } else {
-                fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<log_level> or <log_service_selection> <log_level>");
+                (void)fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<log_level> or <log_service_selection> <log_level>");
             }
         } else if (strncmp("sink", subCmd, 64) == 0) {
             const char* arg1 = strtok_r(NULL, " ", &savePtr);
@@ -618,7 +699,7 @@ static bool celix_logAdmin_executeCommand(void *handle, 
const char *commandLine,
             } else if (arg1 != NULL) {
                 celix_logAdmin_setSinkEnabledCmd(admin, NULL, arg1, outStream, 
errorStream);
             } else {
-                fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<true|false> or <log_service_selection> <true|false>");
+                (void)fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<true|false> or <log_service_selection> <true|false>");
             }
         } else if (strncmp("detail", subCmd, 64) == 0) {
             const char* arg1 = strtok_r(NULL, " ", &savePtr);
@@ -628,10 +709,10 @@ static bool celix_logAdmin_executeCommand(void *handle, 
const char *commandLine,
             } else if (arg1 != NULL) {
                 celix_logAdmin_setLogDetailedCmd(admin, NULL, arg1, outStream, 
errorStream);
             } else {
-                fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<true|false> or <log_service_selection> <true|false>");
+                (void)fprintf(errorStream, "Invalid arguments. For log command 
expected 1 or 2 args. (<true|false> or <log_service_selection> <true|false>");
             }
         } else {
-            fprintf(errorStream, "Unexpected sub command '%s'. Expected empty, 
log or sink command.'n", subCmd);
+            (void)fprintf(errorStream, "Unexpected sub command '%s'. Expected 
empty, log or sink command.'n", subCmd);
         }
     } else {
         celix_logAdmin_InfoCmd(admin, outStream, errorStream);
@@ -644,14 +725,23 @@ static bool celix_logAdmin_executeCommand(void *handle, 
const char *commandLine,
 
 celix_log_admin_t* celix_logAdmin_create(celix_bundle_context_t *ctx) {
     celix_log_admin_t* admin = calloc(1, sizeof(*admin));
+    celix_autoptr(celix_string_hash_map_t) loggers = 
celix_stringHashMap_create();
+    celix_autoptr(celix_string_hash_map_t) sinks = 
celix_stringHashMap_create();
+
+    if (!admin || !loggers || !sinks) {
+        (void)fprintf(stderr, "Error creating log admin. Out of memory.\n");
+        free(admin);
+        return NULL;
+    }
+
     admin->ctx = ctx;
-    admin->loggers = hashMap_create((void*)celix_utils_stringHash, NULL, 
(void*)celix_utils_stringEquals, NULL);
-    admin->sinks = hashMap_create((void*)celix_utils_stringHash, NULL, 
(void*)celix_utils_stringEquals, NULL);
+    admin->loggers = celix_steal_ptr(loggers);
+    admin->sinks = celix_steal_ptr(sinks);
 
     admin->fallbackToStdOut = celix_bundleContext_getPropertyAsBool(ctx, 
CELIX_LOG_ADMIN_FALLBACK_TO_STDOUT_CONFIG_NAME, 
CELIX_LOG_ADMIN_FALLBACK_TO_STDOUT_DEFAULT_VALUE);
     admin->alwaysLogToStdOut = celix_bundleContext_getPropertyAsBool(ctx, 
CELIX_LOG_ADMIN_ALWAYS_USE_STDOUT_CONFIG_NAME, 
CELIX_LOG_ADMIN_ALWAYS_USE_STDOUT_DEFAULT_VALUE);
     const char* logLevelStr =  celix_bundleContext_getProperty(ctx, 
CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL_CONFIG_NAME, 
CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL_DEFAULT_VALUE);
-    admin->logServicesDefaultActiveLogLevel = 
celix_logUtils_logLevelFromString(logLevelStr, CELIX_LOG_LEVEL_INFO);
+    admin->logServicesDefaultActiveLogLevel = 
celix_logLevel_fromString(logLevelStr, CELIX_LOG_LEVEL_INFO);
     admin->sinksDefaultEnabled = celix_bundleContext_getPropertyAsBool(ctx, 
CELIX_LOG_ADMIN_LOG_SINKS_DEFAULT_ENABLED_CONFIG_NAME, 
CELIX_LOG_ADMIN_SINKS_DEFAULT_ENABLED_DEFAULT_VALUE);
 
     celixThreadRwlock_create(&admin->lock, NULL);
@@ -718,7 +808,7 @@ celix_log_admin_t* 
celix_logAdmin_create(celix_bundle_context_t *ctx) {
 }
 
 void celix_logAdmin_destroy(celix_log_admin_t *admin) {
-    if (admin != NULL) {
+    if (admin) {
         celix_logAdmin_remLogSvcForName(admin, 
CELIX_LOG_ADMIN_FRAMEWORK_LOG_NAME);
 
         celix_bundleContext_unregisterServiceAsync(admin->ctx, 
admin->cmdSvcId, NULL, NULL);
@@ -727,11 +817,11 @@ void celix_logAdmin_destroy(celix_log_admin_t *admin) {
         celix_bundleContext_stopTrackerAsync(admin->ctx, 
admin->logWriterTrackerId, NULL, NULL);
         celix_bundleContext_waitForEvents(admin->ctx);
 
-        assert(hashMap_size(admin->loggers) == 0); //note stopping service 
tracker tracker should triggered all needed remove events
-        hashMap_destroy(admin->loggers, false, false);
+        assert(celix_stringHashMap_size(admin->loggers) == 0); //note stopping 
service tracker tracker should triggered all needed remove events
+        celix_stringHashMap_destroy(admin->loggers);
 
-        assert(hashMap_size(admin->sinks) == 0); //note stopping service 
tracker should triggered all needed remove events
-        hashMap_destroy(admin->sinks, false, false);
+        assert(celix_stringHashMap_size(admin->sinks) == 0); //note stopping 
service tracker should triggered all needed remove events
+        celix_stringHashMap_destroy(admin->sinks);
 
         celixThreadRwlock_destroy(&admin->lock);
         free(admin);
diff --git a/bundles/logging/log_admin/src/celix_log_admin.h 
b/bundles/logging/log_admin/src/celix_log_admin.h
index b4ed5d41a..5a53e46c1 100644
--- a/bundles/logging/log_admin/src/celix_log_admin.h
+++ b/bundles/logging/log_admin/src/celix_log_admin.h
@@ -69,6 +69,18 @@ celix_log_admin_t* 
celix_logAdmin_create(celix_bundle_context_t* ctx);
  */
 void celix_logAdmin_destroy(celix_log_admin_t* admin);
 
+//Note exposed for testing purposes
+void celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const char* 
name);
+
+//Note exposed for testing purposes
+void celix_logAdmin_addSink(void* handle, void* svc, const celix_properties_t* 
props);
+
+//Note exposed for testing purposes
+size_t celix_logAdmin_nrOfLogServices(void* handle, const char* select);
+
+//Note exposed for testing purposes
+size_t celix_logAdmin_nrOfSinks(void *handle, const char* select);
+
 #ifdef __cplusplus
 };
 #endif
diff --git a/bundles/logging/log_service_api/include/celix_log_control.h 
b/bundles/logging/log_service_api/include/celix_log_control.h
index 98c9a31da..61f04e4aa 100644
--- a/bundles/logging/log_service_api/include/celix_log_control.h
+++ b/bundles/logging/log_service_api/include/celix_log_control.h
@@ -23,7 +23,6 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include "celix_log_level.h"
-#include "celix_array_list.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -36,13 +35,42 @@ extern "C" {
 typedef struct celix_log_control {
     void *handle;
 
+    /**
+    * @brief Get the number of provided log services.
+    */
     size_t (*nrOfLogServices)(void *handle, const char* select);
 
+    /**
+    * @brief Get the number of tracked log sinks.
+    */
     size_t (*nrOfSinks)(void *handle, const char* select);
 
-    size_t (*setActiveLogLevels)(void *handle, const char* select, 
celix_log_level_e activeLogLevel);
+    /**
+     * @brief Set the active log level for the selected loggers.
+     *
+     * Selection is done by using strcasestr on the logger name, so the select 
string can be a part of the logger name.
+     * For example, if the select string is "foo" all loggers with "foo" in 
the name will be selected, like "foo.bar"
+     * and "bar.foo".
+     *
+     * @param[in] handle The service handle.
+     * @param[in] select The select string that specifies the case-insensitive 
string part of target loggers.
+     * @param[in] activeLogLevel The active log level to set.
+     * @return The number of loggers selected.
+     */
+    size_t (*setActiveLogLevels)(void* handle, const char* select, 
celix_log_level_e activeLogLevel);
 
-    size_t (*setSinkEnabled)(void *handle, const char* select, bool enabled);
+    /**
+     * @brief Set the active log level for the selected loggers.
+     *
+     * Selection is done by using strcasestr on the sink name, so the select 
string can be a part of the sink name.
+     * For example, if the select string is "foo" all sinks with "foo" in the 
name will be selected, like "foo.bar"
+     * and "bar.foo".
+     *
+     * @param[in] handle The service handle.
+     * @param[in] select The select string that specifies the case-insensitive 
string part of target sinks.
+     * @param[in] activeLogLevel The active log level to set.
+     */
+    size_t (*setSinkEnabled)(void* handle, const char* select, bool enabled);
 
     /**
      * @brief Get a list of names for the log service provided by the log 
service.
@@ -60,8 +88,22 @@ typedef struct celix_log_control {
      */
     celix_array_list_t* (*currentSinks)(void *handle);
 
+    /**
+     * @brief Get the active log level for the selected logger.
+     * @param[in] handle The service handle.
+     * @param[in] loggerName The name of the target logger.
+     * @param[out[ outActiveLogLevel The active log level of the target 
logger. Cannot be NULL.
+     * @return True if the target logger is found, false otherwise.
+     */
     bool (*logServiceInfo)(void *handle, const char* loggerName, 
celix_log_level_e* outActiveLogLevel);
 
+    /**
+    * @brief Get the enabled state of the selected sink.
+    * @param[in] handle The service handle.
+    * @param[in] sinkName The name of the target sink.
+    * @param[out] outEnabled The enabled state of the target sink. Cannot be 
NULL.
+    * @return True if the target sink is found, false otherwise.
+    */
     bool (*sinkInfo)(void *handle, const char* sinkName, bool *outEnabled);
 
     /**

Reply via email to