Skip to content

Commit

Permalink
Merge pull request #1157 from randilt/raw-template-logging-implementa…
Browse files Browse the repository at this point in the history
…tion

Raw template logging implementation
  • Loading branch information
daneshk authored Jan 17, 2025
2 parents 06c729e + 3ec3b58 commit ead1182
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 13 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Change Log
This file contains all the notable changes done to the Ballerina TCP package through the releases.

## [Unreleased]

### Added
- [Support logging raw template value in log APIs](https://github.com/ballerina-platform/ballerina-library/issues/3331)

## [2.5.1] - 2023-01-04

### Removed
Expand Down
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.11.0-20241121-075100-c4c87cbc"
distribution-version = "2201.11.0-20241218-101200-109f6cc7"

[[package]]
org = "ballerina"
Expand Down
55 changes: 45 additions & 10 deletions ballerina/natives.bal
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
// under the License.

import ballerina/io;
import ballerina/observe;
import ballerina/jballerina.java;
import ballerina/lang.value;
import ballerina/observe;

# Represents log level types.
enum LogLevel {
Expand All @@ -27,8 +27,19 @@ enum LogLevel {
WARN
}

# A value of `anydata` type or a function pointer.
public type Value anydata|Valuer;
# A value of `anydata` type or a function pointer or raw template.
public type Value anydata|Valuer|PrintableRawTemplate;

# Represents raw templates for logging.
#
# e.g: `The input value is ${val}`
# + strings - String values of the template as an array
# + insertions - Parameterized values/expressions after evaluations as an array
public type PrintableRawTemplate object {
*object:RawTemplate;
public string[] & readonly strings;
public Value[] insertions;
};

# A function, which returns `anydata` type.
public type Valuer isolated function () returns anydata;
Expand Down Expand Up @@ -82,6 +93,30 @@ public enum FileWriteOption {
APPEND
}

# Process the raw template and return the processed string.
#
# + template - The raw template to be processed
# + return - The processed string
isolated function processTemplate(PrintableRawTemplate template) returns string {
string[] templateStrings = template.strings;
Value[] insertions = template.insertions;
string result = templateStrings[0];

foreach int i in 1 ..< templateStrings.length() {
Value insertion = insertions[i - 1];
string insertionStr = insertion is PrintableRawTemplate ?
processTemplate(insertion) :
insertion is Valuer ?
insertion().toString() :
insertion.toString();
result += insertionStr + templateStrings[i];
}
return result;
}

isolated function processMessage(string|PrintableRawTemplate msg) returns string =>
msg !is string ? processTemplate(msg) : msg;

# Prints debug logs.
# ```ballerina
# log:printDebug("debug message", id = 845315)
Expand All @@ -91,7 +126,7 @@ public enum FileWriteOption {
# + 'error - The error struct to be logged
# + stackTrace - The error stack trace to be logged
# + keyValues - The key-value pairs to be logged
public isolated function printDebug(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
public isolated function printDebug(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
// Added `stackTrace` as an optional param due to https://github.com/ballerina-platform/ballerina-lang/issues/34572
if isLogLevelEnabled(DEBUG, getModuleName(keyValues)) {
print(DEBUG, msg, 'error, stackTrace, keyValues);
Expand All @@ -108,7 +143,7 @@ public isolated function printDebug(string msg, error? 'error = (), error:StackF
# + 'error - The error struct to be logged
# + stackTrace - The error stack trace to be logged
# + keyValues - The key-value pairs to be logged
public isolated function printError(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
public isolated function printError(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
if isLogLevelEnabled(ERROR, getModuleName(keyValues)) {
print(ERROR, msg, 'error, stackTrace, keyValues);
}
Expand All @@ -123,7 +158,7 @@ public isolated function printError(string msg, error? 'error = (), error:StackF
# + 'error - The error struct to be logged
# + stackTrace - The error stack trace to be logged
# + keyValues - The key-value pairs to be logged
public isolated function printInfo(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
public isolated function printInfo(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
if isLogLevelEnabled(INFO, getModuleName(keyValues)) {
print(INFO, msg, 'error, stackTrace, keyValues);
}
Expand All @@ -138,7 +173,7 @@ public isolated function printInfo(string msg, error? 'error = (), error:StackFr
# + 'error - The error struct to be logged
# + stackTrace - The error stack trace to be logged
# + keyValues - The key-value pairs to be logged
public isolated function printWarn(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
public isolated function printWarn(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
if isLogLevelEnabled(WARN, getModuleName(keyValues)) {
print(WARN, msg, 'error, stackTrace, keyValues);
}
Expand Down Expand Up @@ -169,12 +204,12 @@ public isolated function setOutputFile(string path, FileWriteOption option = APP
}
}

isolated function print(string logLevel, string msg, error? err = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
isolated function print(string logLevel, string|PrintableRawTemplate msg, error? err = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
LogRecord logRecord = {
time: getCurrentTime(),
level: logLevel,
module: getModuleNameExtern() == "." ? "" : getModuleNameExtern(),
message: msg
message: processMessage(msg)
};
if err is error {
logRecord.'error = getFullErrorDetails(err);
Expand All @@ -187,7 +222,7 @@ isolated function print(string logLevel, string msg, error? err = (), error:Stac
logRecord["stackTrace"] = stackTraceArray;
}
foreach [string, Value] [k, v] in keyValues.entries() {
anydata value = v is Valuer ? v() : v;
anydata value = v is Valuer ? v() : v is PrintableRawTemplate ? processMessage(v) : v;
logRecord[k] = value;
}
if observe:isTracingEnabled() {
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/Ballerina.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ path = "../test-utils/build/libs/log-test-utils-@project.version@.jar"
[[platform.java21.dependency]]
groupId = "io.ballerina.stdlib"
artifactId = "io-native"
path = "./lib/io-native-@io.native.version@.jar"
path = "./lib/io-native-@io.native.version@.jar"
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 Inc. 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.

import ballerina/log;

public function main() {
string myname = "Alex92";
int myage = 25;
string action1 = "action1";
string action2 = "action2";
string action3 = "action3";
log:printError(`error: My name is ${myname} and my age is ${myage}`);
log:printWarn(`warning: My name is ${myname} and my age is ${myage}`);
log:printInfo(`info: My name is ${myname} and my age is ${myage}`);
log:printDebug(`debug: My name is ${myname} and my age is ${myage}`);
log:printInfo("User details", details = `name: ${myname}, age: ${myage}`, actions = `actions: ${action1}, ${action2}, ${action3}`);
}

91 changes: 90 additions & 1 deletion integration-tests/tests/tests_logfmt.bal
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
// specific language governing permissions and limitations
// under the License.

import ballerina/jballerina.java;
import ballerina/io;
import ballerina/jballerina.java;
import ballerina/test;

const string UTF_8 = "UTF-8";
Expand All @@ -25,7 +25,9 @@ const string PRINT_INFO_FILE = "tests/resources/samples/print-functions/info.bal
const string PRINT_WARN_FILE = "tests/resources/samples/print-functions/warn.bal";
const string PRINT_DEBUG_FILE = "tests/resources/samples/print-functions/debug.bal";
const string PRINT_ERROR_FILE = "tests/resources/samples/print-functions/error.bal";
const string PRINT_RAW_TEMPLATE_FILE = "tests/resources/samples/print-functions/raw_template.bal";
const string LOG_LEVEL_FILE = "tests/resources/samples/log-levels/main.bal";
const string LOG_LEVEL_RAW_TEMPLATE_FILE = "tests/resources/samples/log-levels-raw-template/main.bal";

const string FILE_WRITE_OUTPUT_OVERWRITE_INPUT_FILE_LOGFMT = "tests/resources/samples/file-write-output/single-file/overwrite-logfmt.bal";
const string FILE_WRITE_OUTPUT_APPEND_INPUT_FILE_LOGFMT = "tests/resources/samples/file-write-output/single-file/append-logfmt.bal";
Expand All @@ -51,6 +53,12 @@ const string MESSAGE_WARN_LOGFMT = " level=WARN module=\"\" message=\"warn log\"
const string MESSAGE_INFO_LOGFMT = " level=INFO module=\"\" message=\"info log\"";
const string MESSAGE_DEBUG_LOGFMT = " level=DEBUG module=\"\" message=\"debug log\"";

const string MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT = " level=ERROR module=\"\" message=\"error: My name is Alex92 and my age is 25\"";
const string MESSAGE_WARN_RAW_TEMPLATE_LOGFMT = " level=WARN module=\"\" message=\"warning: My name is Alex92 and my age is 25\"";
const string MESSAGE_INFO_RAW_TEMPLATE_LOGFMT = " level=INFO module=\"\" message=\"info: My name is Alex92 and my age is 25\"";
const string MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT = " level=DEBUG module=\"\" message=\"debug: My name is Alex92 and my age is 25\"";
const string MESSAGE_KEY_VALUE_PAIR_LOGFMT = " level=INFO module=\"\" message=\"User details\" details=\"name: Alex92, age: 25\" actions=\"actions: action1, action2, action3\"";

const string MESSAGE_ERROR_MAIN_LOGFMT = " level=ERROR module=myorg/myproject message=\"error log\\t\\n\\r\\\\\\\"\"";
const string MESSAGE_WARN_MAIN_LOGFMT = " level=WARN module=myorg/myproject message=\"warn log\\t\\n\\r\\\\\\\"\"";
const string MESSAGE_INFO_MAIN_LOGFMT = " level=INFO module=myorg/myproject message=\"info log\\t\\n\\r\\\\\\\"\"";
Expand Down Expand Up @@ -215,6 +223,86 @@ public function testDebugLevelLogfmt() returns error? {
validateLog(logLines[8], MESSAGE_DEBUG_LOGFMT);
}

@test:Config {}
public function testErrorLevelRawTemplateLogfmt() returns error? {
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_ERROR_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
Process result = check execResult;
int _ = check result.waitForExit();
int _ = check result.exitCode();
io:ReadableByteChannel readableResult = result.stderr();
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
string outText = check sc.read(100000);
string[] logLines = re`\n`.split(outText.trim());
test:assertEquals(logLines.length(), 6, INCORRECT_NUMBER_OF_LINES);
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
}

@test:Config {}
public function testWarnLevelRawTemplateLogfmt() returns error? {
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_WARN_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
Process result = check execResult;
int _ = check result.waitForExit();
int _ = check result.exitCode();
io:ReadableByteChannel readableResult = result.stderr();
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
string outText = check sc.read(100000);
string[] logLines = re`\n`.split(outText.trim());
test:assertEquals(logLines.length(), 7, INCORRECT_NUMBER_OF_LINES);
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
}

@test:Config {}
public function testInfoLevelRawTemplateLogfmt() returns error? {
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_INFO_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
Process result = check execResult;
int _ = check result.waitForExit();
int _ = check result.exitCode();
io:ReadableByteChannel readableResult = result.stderr();
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
string outText = check sc.read(100000);
string[] logLines = re`\n`.split(outText.trim());
test:assertEquals(logLines.length(), 9, INCORRECT_NUMBER_OF_LINES);
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
}

@test:Config {}
public function testDebugLevelRawTemplateLogfmt() returns error? {
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_DEBUG_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
Process result = check execResult;
int _ = check result.waitForExit();
int _ = check result.exitCode();
io:ReadableByteChannel readableResult = result.stderr();
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
string outText = check sc.read(100000);
string[] logLines = re`\n`.split(outText.trim());
test:assertEquals(logLines.length(), 10, INCORRECT_NUMBER_OF_LINES);
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[8], MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT);
}
@test:Config {}
public function testRawTemplateKeyValuePair() returns error? {
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_DEBUG_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
Process result = check execResult;
int _ = check result.waitForExit();
int _ = check result.exitCode();
io:ReadableByteChannel readableResult = result.stderr();
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
string outText = check sc.read(100000);
string[] logLines = re`\n`.split(outText.trim());
test:assertEquals(logLines.length(), 10, INCORRECT_NUMBER_OF_LINES);
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[8], MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT);
validateLog(logLines[9], MESSAGE_KEY_VALUE_PAIR_LOGFMT);
}


@test:Config {}
public function testProjectWithoutLogLevelLogfmt() returns error? {
Process|error execResult = exec(bal_exec_path, {}, (), "run", temp_dir_path
Expand Down Expand Up @@ -475,3 +563,4 @@ function exec(@untainted string command, @untainted map<string> env = {},
name: "exec",
'class: "io.ballerina.stdlib.log.testutils.nativeimpl.Exec"
} external;

0 comments on commit ead1182

Please sign in to comment.