How to Configure, Customize, and Use Ballerina Logs
Ballerina’s built-in logging module is designed to give you critical visibility into your applications with minimal setup. Whether you’re just getting started or already using Ballerina’s logging module, this guide will show you how to configure this module to log all the data you need, customize it to your desired destinations and formats, and utilize your logs to get deeper insights into your Ballerina applications. In this post, you will learn how to:
- Configure the priority level of logs
- Configure the output destination of logs
- Customize the log output in different formats
- Utilize Ballerina logging capabilities to optimize your application
Note that I am using Ballerina Swan Lake to run the examples in this blog.
Configuring the Log Level
The Ballerina log module has four log levels with their priority in descending order as follows.
- ERROR
- WARN
- INFO
- DEBUG
By default, all logs of the application that are INFO level and above are logged to the console. You can change the log level by a configuration through the Config.toml file.
For instance, consider a Ballerina project named “myproject”.
import ballerina/log;
public function main() {
log:printError("error log");
log:printWarn("warn log");
log:printInfo("info log");
log:printDebug("debug log");
}
If you run this, you will see that the ERROR, WARN, and INFO logs are printed to the console.
time = 2021-09-23T10:06:34.286+05:30 level = ERROR module = myorg/myproject message = "error log"
time = 2021-09-23T10:06:34.308+05:30 level = WARN module = myorg/myproject message = "warn log"
time = 2021-09-23T10:06:34.309+05:30 level = INFO module = myorg/myproject message = "info log"
To change the log level, create a file named Config.toml in the same directory that you are running the application and specify the log level. For instance, if you want to change the log level to DEBUG, add the below entry to the file.
[ballerina.log]
level = "DEBUG"
When you run the application, you will notice that all the logs of DEBUG level and above are now getting printed to the console.
time = 2021-09-23T10:52:04.359+05:30 level = DEBUG module = "myorg/myproject" message = "debug log"
The above configures the log level of the entire application. However, Ballerina also supports assigning different log levels to different modules. For example, consider a Ballerina project called “my-project” with two modules, “foo” and “bar”.
my-project
├── Ballerina.toml
├── Config.toml
├── main.bal
└── modules
├── bar
│ └── bar.bal
└── foo
└── foo.bal
Suppose both “foo” and “bar” modules have a function called “hello” as follows.
import ballerina/log;
public function hello() {
log:printError("error log");
log:printWarn("warn log");
log:printInfo("info log");
log:printDebug("debug log");
}
The following is the main function of the default package.
import ballerina/log;
import myorg/myproject.foo;
import myorg/myproject.bar;
public function main() {
log:printError("error log");
log:printWarn("warn log");
log:printInfo("info log");
log:printDebug("debug log");
foo:hello();
bar:hello();
}
When you run this project, you will notice that all the logs of INFO level and above in the entire application are getting printed to the console.
time = 2021-09-23T11:40:32.774+05:30 level = ERROR module = myorg/myproject message = "error log"
time = 2021-09-23T11:40:32.798+05:30 level = WARN module = myorg/myproject message = "warn log"
time = 2021-09-23T11:40:32.799+05:30 level = INFO module = myorg/myproject message = "info log"
time = 2021-09-23T11:40:32.801+05:30 level = ERROR module = myorg/myproject.foo message = "error log"
time = 2021-09-23T11:40:32.803+05:30 level = WARN module = myorg/myproject.foo message = "warn log"
time = 2021-09-23T11:40:32.810+05:30 level = INFO module = myorg/myproject.foo message = "info log"
time = 2021-09-23T11:40:32.812+05:30 level = ERROR module = myorg/myproject.bar message = "error log"
time = 2021-09-23T11:40:32.813+05:30 level = WARN module = myorg/myproject.bar message = "warn log"
time = 2021-09-23T11:40:32.814+05:30 level = INFO module = myorg/myproject.bar message = "info log"
However, you might want to assign different log levels to different modules. Suppose you want to set DEBUG level to the “foo” module, the ERROR level to the “bar” module, and WARN level to the rest of the application. You can achieve this by updating the Config.toml file as follows.
[ballerina.log]
level = "WARN"
[[ballerina.log.modules]]
name = "myorg/myproject.foo"
level = "DEBUG"
[[ballerina.log.modules]]
name = "myorg/myproject.bar"
level = "ERROR"
Now you will notice that in the “foo” module logs of DEBUG level and above are printed, while in the “bar” module, only ERROR level logs are printed, and in the rest of the application, all the logs of WARN level and above are printed to the console.
Customizing the Log Output in JSON Format
Ballerina logs are printed in LOGFMT format by default.
time = 2021-09-23T12:10:57.162+05:30 level = INFO module = myorg/myproject.foo message = "info log"
However, you can change the format to JSON by placing the below entry in the Config.toml file.
[ballerina.log]
format = "json"
Now all the logs of the application will be printed in JSON format.
{"time":"2021-09-23T14:05:14.639+05:30", "level":"INFO", "module":"myorg/myproject.foo", "message":"info log"}
Ballerina Logging Best Practices
Making Use of Key-Value Pairs
The Ballerina log module provides some features that can be used to improve your logging experience. One of them is the ability to log any number of key/value pairs. The keys are of string type, and values can be of anydata type, including string, int, float, boolean, etc. With this feature, you can capture all the important information related to an application event in one place without having the hassle of concatenating multiple parameters into a single message.
import ballerina/log;
public function main() {
log:printInfo("info log", id = 845315, name = "foo", successful = true);
}
This would print the following log.
time = 2021-09-23T14:38:25.178+05:30 level = INFO module = myorg/myproject message = "info log" id = 845315 name = "foo" successful = true
Note that the time printed in the log message is the current local time in RFC3339 format.
Print Errors as Convenient to You
Your application might have many occurrences where errors are generated, and you may want a quick and simple way to log them. All the print functions in the Ballerina log module accept an error as a separate optional parameter.
import ballerina/log;
public function main() {
error e = error("port already in use");
log:printError("failed to initialize server", 'error = e);
}
This way, you do not have to concatenate the error to the log message, and it is easier to identify the error when going through the logs.
time = 2021-09-23T19:04:04.289+05:30 level = ERROR module = myorg/myproject message = "failed to initialize server" error = "port already in use"
In some cases, you might want to elaborate on the error and print the complete error stack trace so that it is more convenient for debugging purposes. You can do this by passing the error stack trace as a key-value pair.
import ballerina/log;
public function main() {
error e = f1();
log:printError("error log", stackTrace = e.stackTrace().callStack);
}
function f1() returns error {
return f2();
}
function f2() returns error {
return f3();
}
function f3() returns error {
error e = error("error log");
return e;
}
time = 2021-10-06T14:33:08.391+05:30 level = ERROR module = myorg/myproject message = "error log" stackTrace = [{"callableName":"f3","fileName":"stacktrace.bal","lineNumber":17},{"callableName":"f2","fileName":"stacktrace.bal","lineNumber":13},{"callableName":"f1","fileName":"stacktrace.bal","lineNumber":9},{"callableName":"main","fileName":"stacktrace.bal","lineNumber":4}]
Passing function pointers to improve the performance
Suppose there is a DEBUG log that logs a value returned from a function with heavy computations, and the log level is INFO.
import ballerina/log;
public function main() {
decimal duration = getDutration(3);
log:printDebug("checking the duration", duration = duration);
}
function getDutration(int max) returns decimal {
// Some heavy computations
return 1.0;
}
Even when the DEBUG logs are not printed (since the log level is INFO) the application would still have to execute the function with heavy computations. However, you can avoid this by passing a pointer to the function as a value in a key/value pair like follows.
import ballerina/log;
public function main() {
log:printDebug("checking the duration", duration = isolated function() returns decimal {
// Some heavy computations
return 1.0;
});
}
Since the DEBUG level is not enabled, the log:printDebug
the function will not be called. Thus, the function with heavy computations will not be executed. This could improve the performance of the application significantly if there is a high number of such functions.
Configuring the Output Destination of Logs
Sometimes you may want to write the log output to a file instead of printing them to the console. For instance, for a command-line application, it would not be acceptable to spew all its logging information onto the console by default. In such situations, Ballerina supports writing the logs to a file and not printing the logs to the console. To achieve this, you can call the log:setOutputFile
function and provide the path of the file. eg:
var result = log:setOutputFile("./resources/myproject.log");
Note that the given file should have a “.log” extension.
Now all the subsequent logs will be written to this file instead of printing to the console. If the given file already exists, Ballerina will append the new logs to this existing file. Otherwise, a file will get created in the given path. However, you can also overwrite the content of the file if the given file already exists. To do that, give an additional parameter called log:OVERWRITE
to the above function. eg:
var result = log:setOutputFile("./resources/myproject.log", log:OVERWRITE);
Now every time you run the application, the existing content of the file will get cleared, and the subsequent logs of your application will get written to the file.
If you want to learn more about Ballerina logging and other functionalities, refer to Ballerina by examples. I hope you found this useful!