Skip to content

Commit 53a45a4

Browse files
authored
Merge pull request #1447 from daneshk/dynamic_log_config
Add dynamic log level change support
2 parents f8beeaa + 892dac1 commit 53a45a4

24 files changed

Lines changed: 1584 additions & 105 deletions

File tree

ballerina/Ballerina.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
org = "ballerina"
33
name = "log"
4-
version = "2.16.1"
4+
version = "2.17.0"
55
authors = ["Ballerina"]
66
keywords = ["level", "format"]
77
repository = "https://github.com/ballerina-platform/module-ballerina-log"
@@ -15,18 +15,18 @@ graalvmCompatible = true
1515
[[platform.java21.dependency]]
1616
groupId = "io.ballerina.stdlib"
1717
artifactId = "log-native"
18-
version = "2.16.1"
19-
path = "../native/build/libs/log-native-2.16.1.jar"
18+
version = "2.17.0"
19+
path = "../native/build/libs/log-native-2.17.0-SNAPSHOT.jar"
2020

2121
[[platform.java21.dependency]]
2222
groupId = "io.ballerina.stdlib"
2323
artifactId = "log-compiler-plugin"
24-
version = "2.16.1"
25-
path = "../compiler-plugin/build/libs/log-compiler-plugin-2.16.1.jar"
24+
version = "2.17.0"
25+
path = "../compiler-plugin/build/libs/log-compiler-plugin-2.17.0-SNAPSHOT.jar"
2626

2727
[[platform.java21.dependency]]
2828
groupId = "io.ballerina.stdlib"
2929
artifactId = "log-test-utils"
30-
version = "2.16.1"
31-
path = "../test-utils/build/libs/log-test-utils-2.16.1.jar"
30+
version = "2.17.0"
31+
path = "../test-utils/build/libs/log-test-utils-2.17.0-SNAPSHOT.jar"
3232
scope = "testOnly"

ballerina/CompilerPlugin.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ id = "log-compiler-plugin"
33
class = "io.ballerina.stdlib.log.compiler.LogCompilerPlugin"
44

55
[[dependency]]
6-
path = "../compiler-plugin/build/libs/log-compiler-plugin-2.16.1.jar"
6+
path = "../compiler-plugin/build/libs/log-compiler-plugin-2.17.0-SNAPSHOT.jar"

ballerina/Dependencies.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ modules = [
8888
[[package]]
8989
org = "ballerina"
9090
name = "log"
91-
version = "2.16.1"
91+
version = "2.17.0"
9292
dependencies = [
9393
{org = "ballerina", name = "io"},
9494
{org = "ballerina", name = "jballerina.java"},

ballerina/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,41 @@ The log module supports contextual logging, allowing you to create loggers with
151151
152152
For more details and advanced usage, see the module specification and API documentation.
153153
154+
### Runtime Log Level Modification
155+
156+
The log module supports modifying log levels at runtime without restarting the application. All loggers created via `fromConfig` are registered in a logger registry with a unique ID and can be discovered and updated at runtime.
157+
158+
```ballerina
159+
// Create a logger with an explicit ID
160+
log:Logger paymentLogger = check log:fromConfig(id = "payment-service", level = log:INFO);
161+
162+
// Change the level at runtime
163+
check paymentLogger.setLevel(log:DEBUG);
164+
log:Level current = paymentLogger.getLevel(); // DEBUG
165+
```
166+
167+
The logger registry provides a way to discover and manage all registered loggers:
168+
169+
```ballerina
170+
log:LoggerRegistry registry = log:getLoggerRegistry();
171+
172+
// List all registered logger IDs
173+
string[] ids = registry.getIds();
174+
// e.g., ["root", "myorg/payment:payment-service", "myorg/payment:init"]
175+
176+
// Look up a logger by ID and update its level
177+
log:Logger? logger = registry.getById("myorg/payment:payment-service");
178+
if logger is log:Logger {
179+
check logger.setLevel(log:DEBUG);
180+
}
181+
```
182+
183+
The registry contains:
184+
- `"root"` — the global root logger
185+
- All loggers created via `fromConfig` (module-prefixed user IDs or auto-generated IDs)
186+
187+
> **Note:** Per-module log levels configured via `[[ballerina.log.modules]]` in `Config.toml` are static — they apply at startup and cannot be changed at runtime through the registry. Child loggers (created via `withContext`) are also not registered and always inherit their level from the parent.
188+
154189
### Sensitive Data Masking
155190

156191
The log module provides capabilities to mask sensitive data in log messages to maintain data privacy and security when dealing with personally identifiable information (PII) or other sensitive data.

ballerina/init.bal

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ function init() returns error? {
2121
rootLogger = new RootLogger();
2222
check validateDestinations(destinations);
2323
setModule();
24+
25+
// Register the global root logger in the registry
26+
lock {
27+
loggerRegistry["root"] = rootLogger;
28+
}
29+
30+
// Populate the Java-side ConcurrentHashMap with per-module level overrides so that
31+
// RootLogger.print() can do a lock-free level lookup on the hot logging path.
32+
// Module loggers are intentionally NOT registered in the Ballerina-side LoggerRegistry.
33+
foreach Module mod in modules {
34+
setModuleLevelNative(mod.name, mod.level);
35+
}
2436
}
2537

2638
isolated function validateDestinations(OutputDestination[] destinations) returns Error? {

ballerina/logger.bal

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,20 @@ public type Logger isolated object {
5353
# + keyValues - The key-value pairs to be added to the logger context
5454
# + return - A new Logger instance with the given key-values added to its context
5555
public isolated function withContext(*KeyValues keyValues) returns Logger|error;
56+
57+
# Returns the effective log level of this logger.
58+
# For root and custom loggers, returns the explicitly set level.
59+
# For child loggers (created via `withContext`), returns the inherited level from the parent logger.
60+
#
61+
# + return - The effective log level
62+
public isolated function getLevel() returns Level;
63+
64+
# Sets the log level of this logger at runtime.
65+
# This is supported on root loggers, module loggers, and loggers created via `fromConfig`.
66+
# Child loggers (created via `withContext`) do not support this operation and will return an error.
67+
# To change a child logger's effective level, set the level on its parent logger instead.
68+
#
69+
# + level - The new log level to set
70+
# + return - An error if the operation is not supported, nil on success
71+
public isolated function setLevel(Level level) returns error?;
5672
};

ballerina/natives.bal

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,14 +468,17 @@ isolated function replaceString(handle receiver, handle target, handle replaceme
468468
paramTypes: ["java.lang.CharSequence", "java.lang.CharSequence"]
469469
} external;
470470

471-
isolated function isLogLevelEnabled(string loggerLogLevel, string logLevel, string moduleName) returns boolean {
472-
string moduleLogLevel = loggerLogLevel;
473-
if modules.length() > 0 {
474-
if modules.hasKey(moduleName) {
475-
moduleLogLevel = modules.get(moduleName).level;
476-
}
477-
}
478-
return logLevelWeight.get(logLevel) >= logLevelWeight.get(moduleLogLevel);
471+
final readonly & map<int> LOG_LEVEL_WEIGHT = {
472+
"ERROR": 1000,
473+
"WARN": 900,
474+
"INFO": 800,
475+
"DEBUG": 700
476+
};
477+
478+
isolated function isLevelEnabled(string effectiveLevel, string logLevel) returns boolean {
479+
int requestedWeight = LOG_LEVEL_WEIGHT[logLevel] ?: 0;
480+
int effectiveWeight = LOG_LEVEL_WEIGHT[effectiveLevel] ?: 800;
481+
return requestedWeight >= effectiveWeight;
479482
}
480483

481484
isolated function getModuleName(KeyValues keyValues, int offset = 2) returns string {
@@ -492,3 +495,20 @@ isolated function getCurrentFileSize(string filePath) returns int = @java:Method
492495
isolated function getTimeSinceLastRotation(string filePath, string policy, int maxFileSize, int maxAgeInMillis, int maxBackupFiles) returns int = @java:Method {'class: "io.ballerina.stdlib.log.Utils"} external;
493496

494497
isolated function rotateLog(string filePath, string policy, int maxFileSize, int maxAgeInMillis, int maxBackupFiles) returns error? = @java:Method {'class: "io.ballerina.stdlib.log.Utils"} external;
498+
499+
// ========== Internal native function declarations for runtime log configuration ==========
500+
501+
isolated function generateLoggerIdNative(int stackOffset) returns string = @java:Method {
502+
'class: "io.ballerina.stdlib.log.LogConfigManager",
503+
name: "generateLoggerId"
504+
} external;
505+
506+
isolated function getModuleLevelNative(string moduleName) returns Level? = @java:Method {
507+
'class: "io.ballerina.stdlib.log.LogConfigManager",
508+
name: "getModuleLevel"
509+
} external;
510+
511+
isolated function setModuleLevelNative(string moduleName, Level level) = @java:Method {
512+
'class: "io.ballerina.stdlib.log.LogConfigManager",
513+
name: "setModuleLevel"
514+
} external;

0 commit comments

Comments
 (0)