This is an automated email from the ASF dual-hosted git repository. pengzheng pushed a commit to branch feature/490-add-verbose-log-ctrl in repository https://gitbox.apache.org/repos/asf/celix.git
commit a13c92b36373061be688e4b49a8d0db34f16c39a Author: PengZheng <[email protected]> AuthorDate: Thu May 11 19:02:28 2023 +0800 Add celix::log_admin detail subcommand to switch selected loggers between detailed and brief mode. --- bundles/logging/README.md | 5 +- .../log_admin/gtest/src/LogAdminTestSuite.cc | 127 +++++++++++++++++++++ bundles/logging/log_admin/src/celix_log_admin.c | 114 +++++++++++++++--- .../log_service_api/include/celix_log_control.h | 15 ++- 4 files changed, 236 insertions(+), 25 deletions(-) diff --git a/bundles/logging/README.md b/bundles/logging/README.md index 2dc02670..511c556e 100644 --- a/bundles/logging/README.md +++ b/bundles/logging/README.md @@ -16,11 +16,14 @@ The `Celix::log_admin` bundle facilitates the `celix_log_service_t` services and printed on stdout/stderr. The Celix shell command `celix::log_admin` can be used to view the existing log services and sinks, -changed the active log level per log services and enable/disable log sinks. +change the active log level per logger, switch loggers between detailed and brief mode, and enable/disable log sinks. + For example: - `celix::log_admin` list the available log services and log sinks. - `celix::log_admin log error` Set the active log level for all log services to `error`. - `celix::log_admin log celix_ trace` Set the active log level for all log services starting with 'celix_' to `trace`. +- `celix::log_admin detail false` Set all log services to brief mode. +- `celix::log_admin detail celix_ true` Set all log services starting with 'celix_' to detailed mode. - `celix::log_admin sink false` Disables all available log sinks. - `celix::log_admin sink celix_syslog true` Enables all log sinks starting with 'celix_syslog'. diff --git a/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc b/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc index c51d87b1..b0a7220a 100644 --- a/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc +++ b/bundles/logging/log_admin/gtest/src/LogAdminTestSuite.cc @@ -262,6 +262,18 @@ TEST_F(LogBundleTestSuite, LogServiceControl) { EXPECT_EQ(CELIX_LOG_LEVEL_DEBUG, activeLogLevel); + bool detailed; + EXPECT_EQ(2, control->setDetailed(control->handle, "test::group", false)); + EXPECT_TRUE(control->logServiceInfoEx(control->handle, "test::group::Log1", nullptr, &detailed)); + EXPECT_FALSE(detailed); + EXPECT_TRUE(control->logServiceInfoEx(control->handle, "test::group::Log2", nullptr, &detailed)); + EXPECT_FALSE(detailed); + EXPECT_EQ(4, control->setDetailed(control->handle, nullptr, true)); + EXPECT_TRUE(control->logServiceInfoEx(control->handle, "test::group::Log1", nullptr, &detailed)); + EXPECT_TRUE(detailed); + EXPECT_TRUE(control->logServiceInfoEx(control->handle, "test::group::Log2", nullptr, &detailed)); + EXPECT_TRUE(detailed); + auto *list = control->currentLogServices(control->handle); EXPECT_EQ(4, celix_arrayList_size(list)); for (int i = 0; i < celix_arrayList_size(list); ++i) { @@ -370,6 +382,59 @@ TEST_F(LogBundleTestSuite, LogServiceAndSink) { ls->fatal(ls->handle, "test %i %i %i", 1, 2, 3); //+0 (no log to sink, fallback to stdout) EXPECT_EQ(initial +11, count.load()); + celix_log_sink_t sink2; + count = 0; + sink2.handle = (void *)&count; + sink2.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*/) { + auto *count = static_cast<std::atomic<size_t>*>(handle); + count->fetch_add(1); + EXPECT_STREQ(__FILE__, file); + EXPECT_TRUE(function != nullptr); + EXPECT_LE(__LINE__, line); + EXPECT_STREQ("error", format); + }; + { + auto *svcProps = celix_properties_create(); + celix_properties_set(svcProps, "name", "test::Sink2"); + celix_service_registration_options_t opts{}; + opts.serviceName = CELIX_LOG_SINK_NAME; + opts.serviceVersion = CELIX_LOG_SINK_VERSION; + opts.properties = svcProps; + opts.svc = &sink2; + svcId = celix_bundleContext_registerServiceWithOptions(ctx.get(), &opts); + } + control->setDetailed(control->handle, "test::Log1", true); + ls->logDetails(ls->handle, CELIX_LOG_LEVEL_ERROR, __FILE__, __FUNCTION__, __LINE__, "error"); + EXPECT_EQ(1, count.load()); + celix_bundleContext_unregisterService(ctx.get(), svcId); //no log sink anymore + + + celix_log_sink_t sink3; + count = 0; + sink3.handle = (void *)&count; + sink3.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*/) { + auto *count = static_cast<std::atomic<size_t>*>(handle); + count->fetch_add(1); + EXPECT_TRUE(file == nullptr); + EXPECT_TRUE(function == nullptr); + EXPECT_EQ(0, line); + EXPECT_STREQ("error", format); + }; + { + auto *svcProps = celix_properties_create(); + celix_properties_set(svcProps, "name", "test::Sink3"); + celix_service_registration_options_t opts{}; + opts.serviceName = CELIX_LOG_SINK_NAME; + opts.serviceVersion = CELIX_LOG_SINK_VERSION; + opts.properties = svcProps; + opts.svc = &sink3; + svcId = celix_bundleContext_registerServiceWithOptions(ctx.get(), &opts); + } + control->setDetailed(control->handle, "test::Log1", false); + ls->logDetails(ls->handle, CELIX_LOG_LEVEL_ERROR, __FILE__, __FUNCTION__, __LINE__, "error"); + EXPECT_EQ(1, count.load()); + celix_bundleContext_unregisterService(ctx.get(), svcId); //no log sink anymore + celix_bundleContext_stopTracker(ctx.get(), trkId); } @@ -413,8 +478,10 @@ TEST_F(LogBundleTestSuite, LogAdminCmd) { fclose(ss); EXPECT_TRUE(strstr(cmdResult, "Log Admin provided log services:") != nullptr); EXPECT_TRUE(strstr(cmdResult, "Log Admin found log sinks:") != nullptr); + EXPECT_TRUE(strstr(cmdResult, "test::Log1, active log level info, brief") != nullptr); free(cmdResult); }; + control->setDetailed(control->handle, "test::Log1", false); bool called = celix_bundleContext_useServiceWithOptions(ctx.get(), &opts); EXPECT_TRUE(called); @@ -454,6 +521,66 @@ TEST_F(LogBundleTestSuite, LogAdminCmd) { called = celix_bundleContext_useServiceWithOptions(ctx.get(), &opts); EXPECT_TRUE(called); + opts.use = [](void*, void *svc) { + auto* cmd = static_cast<celix_shell_command_t*>(svc); + char *cmdResult = NULL; + size_t cmdResultLen; + char *errResult = NULL; + size_t errResultLen; + FILE *ss = open_memstream(&cmdResult, &cmdResultLen); + FILE *es = open_memstream(&errResult, &errResultLen); + cmd->executeCommand(cmd->handle, "celix::log_admin detail test::Log1 true", ss, es); //with selection + fclose(es); + fclose(ss); + EXPECT_STREQ(cmdResult, "Updated 1 log services to detailed.\n"); + EXPECT_STREQ(errResult, ""); + free(errResult); + free(cmdResult); + + ss = open_memstream(&cmdResult, &cmdResultLen); + es = open_memstream(&errResult, &errResultLen); + cmd->executeCommand(cmd->handle, "celix::log_admin detail test::Log1 false", ss, es); //with selection + fclose(es); + fclose(ss); + EXPECT_STREQ(cmdResult, "Updated 1 log services to brief.\n"); + EXPECT_STREQ(errResult, ""); + free(errResult); + free(cmdResult); + + + ss = open_memstream(&cmdResult, &cmdResultLen); + es = open_memstream(&errResult, &errResultLen); + cmd->executeCommand(cmd->handle, "celix::log_admin detail test::Log1 error", ss, es); //with selection + fclose(es); + fclose(ss); + EXPECT_STREQ(cmdResult, ""); + EXPECT_STREQ(errResult, "Cannot convert 'error' to a boolean value.\n"); + free(errResult); + free(cmdResult); + + ss = open_memstream(&cmdResult, &cmdResultLen); + es = open_memstream(&errResult, &errResultLen); + cmd->executeCommand(cmd->handle, "celix::log_admin detail true", ss, es); //with selection + fclose(es); + fclose(ss); + EXPECT_STREQ(cmdResult, "Updated 2 log services to detailed.\n"); + EXPECT_STREQ(errResult, ""); + free(errResult); + free(cmdResult); + + ss = open_memstream(&cmdResult, &cmdResultLen); + es = open_memstream(&errResult, &errResultLen); + cmd->executeCommand(cmd->handle, "celix::log_admin detail", ss, es); //with selection + fclose(es); + fclose(ss); + EXPECT_STREQ(cmdResult, ""); + EXPECT_STREQ(errResult, "Invalid arguments. For log command expected 1 or 2 args. (<true|false> or <log_service_selection> <true|false>"); + free(errResult); + free(cmdResult); + }; + called = celix_bundleContext_useServiceWithOptions(ctx.get(), &opts); + EXPECT_TRUE(called); + celix_bundleContext_unregisterService(ctx.get(), svcId); celix_bundleContext_stopTracker(ctx.get(), trkId); diff --git a/bundles/logging/log_admin/src/celix_log_admin.c b/bundles/logging/log_admin/src/celix_log_admin.c index 70df7d49..fcb5b53c 100644 --- a/bundles/logging/log_admin/src/celix_log_admin.c +++ b/bundles/logging/log_admin/src/celix_log_admin.c @@ -69,6 +69,7 @@ typedef struct celix_log_service_entry { //mutable and protected by admin->lock celix_log_level_e activeLogLevel; + bool detailed; } celix_log_service_entry_t; typedef struct celix_log_sink_entry { @@ -98,13 +99,19 @@ static void celix_logAdmin_vlogDetails(void *handle, celix_log_level_e level, co celix_log_sink_t *sink = sinkEntry->sink; va_list argCopy; va_copy(argCopy, formatArgs); - sink->sinkLog(sink->handle, level, entry->logSvcId, entry->name, file, function, line, format, argCopy); + 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)) { - celix_logUtils_vLogToStdoutDetails(entry->name, level, file, function, line, format, formatArgs); + celix_logUtils_vLogToStdoutDetails(entry->name, level, + entry->detailed ? file : NULL, + entry->detailed? function : NULL, + entry->detailed ? line : 0, + format, formatArgs); } } celixThreadRwlock_unlock(&entry->admin->lock); @@ -117,7 +124,7 @@ static void celix_logAdmin_vlog(void *handle, celix_log_level_e level, const cha static void celix_logAdmin_logDetails(void *handle, celix_log_level_e level, const char* file, const char* function, int line, const char *format, ...) { va_list args; va_start(args, format); - celix_logAdmin_vlogDetails(handle, level, file, function, 0, format, args); + celix_logAdmin_vlogDetails(handle, level, file, function, line, format, args); va_end(args); } @@ -188,6 +195,7 @@ static void celix_logAdmin_addLogSvcForName(celix_log_admin_t* admin, const char newEntry->name = celix_utils_strdup(name); newEntry->count = 1; newEntry->activeLogLevel = admin->logServicesDefaultActiveLogLevel; + newEntry->detailed = true; newEntry->logSvc.handle = newEntry; newEntry->logSvc.trace = celix_logAdmin_trace; newEntry->logSvc.debug = celix_logAdmin_debug; @@ -444,34 +452,66 @@ static celix_array_list_t* celix_logAdmin_currentSinks(void *handle) { return sinks; } -static bool celix_logAdmin_logServiceInfo(void *handle, const char* logServiceName, celix_log_level_e* outActiveLogLevel) { +static bool celix_logAdmin_sinkInfo(void *handle, const char* sinkName, bool* outEnabled) { celix_log_admin_t* admin = handle; celixThreadRwlock_readLock(&admin->lock); - celix_log_service_entry_t* found = hashMap_get(admin->loggers, logServiceName); - if (found != NULL && outActiveLogLevel != NULL) { - *outActiveLogLevel = found->activeLogLevel; + celix_log_sink_entry_t* found = hashMap_get(admin->sinks, sinkName); + if (found != NULL && outEnabled != NULL) { + *outEnabled = found->enabled; } celixThreadRwlock_unlock(&admin->lock); return found != NULL; } -static bool celix_logAdmin_sinkInfo(void *handle, const char* sinkName, bool* outEnabled) { +static size_t celix_logAdmin_setDetailed(void *handle, const char* select, bool detailed) { + 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); + if (select == NULL) { + visit->detailed = detailed; + count += 1; + } else { + 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 + visit->detailed = detailed; + count += 1; + } + } + } + celixThreadRwlock_unlock(&admin->lock); + return count; +} + +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_sink_entry_t* found = hashMap_get(admin->sinks, sinkName); - if (found != NULL && outEnabled != NULL) { - *outEnabled = found->enabled; + celix_log_service_entry_t* found = hashMap_get(admin->loggers, logServiceName); + if (found != NULL) { + if (outActiveLogLevel != NULL) { + *outActiveLogLevel = found->activeLogLevel; + } + if (outDetailed != NULL) { + *outDetailed = found->detailed; + } } celixThreadRwlock_unlock(&admin->lock); return found != NULL; } +static bool celix_logAdmin_logServiceInfo(void *handle, const char* logServiceName, celix_log_level_e* outActiveLogLevel) { + return celix_logAdmin_logServiceInfoEx(handle, logServiceName, outActiveLogLevel, NULL); +} + 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); if (converted) { size_t count = celix_logAdmin_setActiveLogLevels(admin, select, logLevel); - fprintf(outStream, "Updated %lu log services to log level %s\n", (long unsigned int) count, celix_logUtils_logLevelToString(logLevel)); + fprintf(outStream, "Updated %zu log services to log level %s\n", count, celix_logUtils_logLevelToString(logLevel)); } else { fprintf(errorStream, "Cannot convert '%s' to a valid celix log level.\n", level); } @@ -489,7 +529,7 @@ 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 %lu log sinks to %s.\n", (long unsigned int) count, enableSink ? "enabled" : "disabled"); + 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); } @@ -505,9 +545,11 @@ static void celix_logAdmin_InfoCmd(celix_log_admin_t* admin, FILE* outStream, FI for (int i = 0 ; i < celix_arrayList_size(logServices); ++i) { char *name = celix_arrayList_get(logServices, i); celix_log_level_e level; - bool found = celix_logAdmin_logServiceInfo(admin, name, &level); + bool detailed; + bool found = celix_logAdmin_logServiceInfoEx(admin, name, &level, &detailed); if (found) { - fprintf(outStream, " |- %i) Log Service %20s, active log level %s\n", i+1, name, celix_logUtils_logLevelToString(level)); + fprintf(outStream, " |- %i) Log Service %20s, active log level %s, %s\n", + i+1, name, celix_logUtils_logLevelToString(level), detailed ? "detailed" : "brief"); } free(name); } @@ -530,6 +572,24 @@ static void celix_logAdmin_InfoCmd(celix_log_admin_t* admin, FILE* outStream, FI celix_arrayList_destroy(sinks); } +static void celix_logAdmin_setLogDetailedCmd(celix_log_admin_t* admin, const char* select, const char* detailed, FILE* outStream, FILE* errorStream) { + bool enableDetailed; + bool valid = false; + if (strncasecmp("true", detailed, 16) == 0) { + enableDetailed = true; + valid = true; + } else if (strncasecmp("false", detailed, 16) == 0) { + enableDetailed = false; + valid = true; + } + if (valid) { + size_t count = celix_logAdmin_setDetailed(admin, select, enableDetailed); + 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); + } +} + static bool celix_logAdmin_executeCommand(void *handle, const char *commandLine, FILE *outStream, FILE *errorStream) { celix_log_admin_t* admin = handle; @@ -561,6 +621,16 @@ static bool celix_logAdmin_executeCommand(void *handle, const char *commandLine, } else { 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); + const char* arg2 = strtok_r(NULL, " ", &savePtr); + if (arg1 != NULL && arg2 != NULL) { + celix_logAdmin_setLogDetailedCmd(admin, arg1, arg2, outStream, errorStream); + } 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>"); + } } else { fprintf(errorStream, "Unexpected sub command '%s'. Expected empty, log or sink command.'n", subCmd); } @@ -609,7 +679,8 @@ celix_log_admin_t* celix_logAdmin_create(celix_bundle_context_t *ctx) { admin->controlSvc.sinkInfo = celix_logAdmin_sinkInfo; admin->controlSvc.setActiveLogLevels = celix_logAdmin_setActiveLogLevels; admin->controlSvc.setSinkEnabled = celix_logAdmin_setSinkEnabled; - + admin->controlSvc.setDetailed = celix_logAdmin_setDetailed; + admin->controlSvc.logServiceInfoEx = celix_logAdmin_logServiceInfoEx; celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; opts.serviceName = CELIX_LOG_CONTROL_NAME; @@ -624,8 +695,15 @@ celix_log_admin_t* celix_logAdmin_create(celix_bundle_context_t *ctx) { celix_properties_t* props = celix_properties_create(); celix_properties_set(props, CELIX_SHELL_COMMAND_NAME, "celix::log_admin"); - celix_properties_set(props, CELIX_SHELL_COMMAND_USAGE, "celix::log_admin [log <log_level> | log <log_service_selection> <log_level> | sink <log_sink_selection> (true|false)]"); - celix_properties_set(props, CELIX_SHELL_COMMAND_DESCRIPTION, "Show available log service and log sink, allows changing active log levels and enableling/disabling log sinks."); + celix_properties_set(props, CELIX_SHELL_COMMAND_USAGE, "celix::log_admin [" + "log <log_level> | log <log_service_selection> <log_level> | " + "sink <log_sink_selection> (true|false) | " + "detail (true|false) | detail <log_service_selection> (true|false)" + "]"); + celix_properties_set(props, CELIX_SHELL_COMMAND_DESCRIPTION, "Show available log service and log sink, " + "allows changing active log levels, " + "switching between detailed and brief mode, " + "and enabling/disabling log sinks."); celix_service_registration_options_t opts = CELIX_EMPTY_SERVICE_REGISTRATION_OPTIONS; opts.serviceName = CELIX_SHELL_COMMAND_SERVICE_NAME; 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 2123357d..70ee2a78 100644 --- a/bundles/logging/log_service_api/include/celix_log_control.h +++ b/bundles/logging/log_service_api/include/celix_log_control.h @@ -52,23 +52,26 @@ typedef struct celix_log_control { bool (*sinkInfo)(void *handle, const char* sinkName, bool *outEnabled); /** - * @brief Enable/disable verbose mode for selected loggers. + * @brief Switch between detailed and brief mode for selected loggers. + * @details In detailed mode, details(if available) are attached to log messages from the select loggers. + * For example, the file name, function name, and line number. * @param[in] handle The service handle. * @param[in] select The select string that specifies the case-insensitive name prefix of target loggers. - * @param[in] verbose True to enable verbose mode, false to disable verbose mode. + * @param[in] detailed True to enable detailed mode, false to disable detailed mode(i.e. enable brief mode). * @return Number of logger selected. */ - size_t (*setVerbose)(void *handle, const char* select, bool verbose); + size_t (*setDetailed)(void *handle, const char* select, bool detailed); /** - * @brief Get the active log level and the verbose mode for a selected logger. + * @brief Get the active log level and the detailed mode for a selected logger. + * @details If the logger is not found, the outActiveLogLevel and outDetailed are not changed. * @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. - * @param [out] outVerbose The verbose mode of the target logger. + * @param [out] outDetailed The detailed mode of the target logger. * @return True if the target logger is found, false otherwise. */ - bool (*logServiceInfoEx)(void *handle, const char* loggerName, celix_log_level_e* outActiveLogLevel, bool* outVerbose); + bool (*logServiceInfoEx)(void *handle, const char* loggerName, celix_log_level_e* outActiveLogLevel, bool* outDetailed); } celix_log_control_t;
