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:

    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.

  1. ERROR
  2. WARN
  3. INFO
  4. 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”.

Java
 
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.

Java
 
[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”.

Java
 
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.

Java
 
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.

Java
 

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.

Java
 
[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.

Java
 
[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.

Java
 
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.

Java
 
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.

Java
 
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.

Java
 
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.

Java
 
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:

Java
 
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:

Java
 
 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!

 

 

 

 

Top