Skip to content

Commit ead1182

Browse files
authored
Merge pull request #1157 from randilt/raw-template-logging-implementation
Raw template logging implementation
2 parents 06c729e + 3ec3b58 commit ead1182

File tree

6 files changed

+173
-13
lines changed

6 files changed

+173
-13
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
# Change Log
22
This file contains all the notable changes done to the Ballerina TCP package through the releases.
33

4+
## [Unreleased]
5+
6+
### Added
7+
- [Support logging raw template value in log APIs](https://github.com/ballerina-platform/ballerina-library/issues/3331)
8+
49
## [2.5.1] - 2023-01-04
510

611
### Removed

ballerina/Dependencies.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[ballerina]
77
dependencies-toml-version = "2"
8-
distribution-version = "2201.11.0-20241121-075100-c4c87cbc"
8+
distribution-version = "2201.11.0-20241218-101200-109f6cc7"
99

1010
[[package]]
1111
org = "ballerina"

ballerina/natives.bal

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
// under the License.
1616

1717
import ballerina/io;
18-
import ballerina/observe;
1918
import ballerina/jballerina.java;
2019
import ballerina/lang.value;
20+
import ballerina/observe;
2121

2222
# Represents log level types.
2323
enum LogLevel {
@@ -27,8 +27,19 @@ enum LogLevel {
2727
WARN
2828
}
2929

30-
# A value of `anydata` type or a function pointer.
31-
public type Value anydata|Valuer;
30+
# A value of `anydata` type or a function pointer or raw template.
31+
public type Value anydata|Valuer|PrintableRawTemplate;
32+
33+
# Represents raw templates for logging.
34+
#
35+
# e.g: `The input value is ${val}`
36+
# + strings - String values of the template as an array
37+
# + insertions - Parameterized values/expressions after evaluations as an array
38+
public type PrintableRawTemplate object {
39+
*object:RawTemplate;
40+
public string[] & readonly strings;
41+
public Value[] insertions;
42+
};
3243

3344
# A function, which returns `anydata` type.
3445
public type Valuer isolated function () returns anydata;
@@ -82,6 +93,30 @@ public enum FileWriteOption {
8293
APPEND
8394
}
8495

96+
# Process the raw template and return the processed string.
97+
#
98+
# + template - The raw template to be processed
99+
# + return - The processed string
100+
isolated function processTemplate(PrintableRawTemplate template) returns string {
101+
string[] templateStrings = template.strings;
102+
Value[] insertions = template.insertions;
103+
string result = templateStrings[0];
104+
105+
foreach int i in 1 ..< templateStrings.length() {
106+
Value insertion = insertions[i - 1];
107+
string insertionStr = insertion is PrintableRawTemplate ?
108+
processTemplate(insertion) :
109+
insertion is Valuer ?
110+
insertion().toString() :
111+
insertion.toString();
112+
result += insertionStr + templateStrings[i];
113+
}
114+
return result;
115+
}
116+
117+
isolated function processMessage(string|PrintableRawTemplate msg) returns string =>
118+
msg !is string ? processTemplate(msg) : msg;
119+
85120
# Prints debug logs.
86121
# ```ballerina
87122
# log:printDebug("debug message", id = 845315)
@@ -91,7 +126,7 @@ public enum FileWriteOption {
91126
# + 'error - The error struct to be logged
92127
# + stackTrace - The error stack trace to be logged
93128
# + keyValues - The key-value pairs to be logged
94-
public isolated function printDebug(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
129+
public isolated function printDebug(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
95130
// Added `stackTrace` as an optional param due to https://github.com/ballerina-platform/ballerina-lang/issues/34572
96131
if isLogLevelEnabled(DEBUG, getModuleName(keyValues)) {
97132
print(DEBUG, msg, 'error, stackTrace, keyValues);
@@ -108,7 +143,7 @@ public isolated function printDebug(string msg, error? 'error = (), error:StackF
108143
# + 'error - The error struct to be logged
109144
# + stackTrace - The error stack trace to be logged
110145
# + keyValues - The key-value pairs to be logged
111-
public isolated function printError(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
146+
public isolated function printError(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
112147
if isLogLevelEnabled(ERROR, getModuleName(keyValues)) {
113148
print(ERROR, msg, 'error, stackTrace, keyValues);
114149
}
@@ -123,7 +158,7 @@ public isolated function printError(string msg, error? 'error = (), error:StackF
123158
# + 'error - The error struct to be logged
124159
# + stackTrace - The error stack trace to be logged
125160
# + keyValues - The key-value pairs to be logged
126-
public isolated function printInfo(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
161+
public isolated function printInfo(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
127162
if isLogLevelEnabled(INFO, getModuleName(keyValues)) {
128163
print(INFO, msg, 'error, stackTrace, keyValues);
129164
}
@@ -138,7 +173,7 @@ public isolated function printInfo(string msg, error? 'error = (), error:StackFr
138173
# + 'error - The error struct to be logged
139174
# + stackTrace - The error stack trace to be logged
140175
# + keyValues - The key-value pairs to be logged
141-
public isolated function printWarn(string msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
176+
public isolated function printWarn(string|PrintableRawTemplate msg, error? 'error = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
142177
if isLogLevelEnabled(WARN, getModuleName(keyValues)) {
143178
print(WARN, msg, 'error, stackTrace, keyValues);
144179
}
@@ -169,12 +204,12 @@ public isolated function setOutputFile(string path, FileWriteOption option = APP
169204
}
170205
}
171206

172-
isolated function print(string logLevel, string msg, error? err = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
207+
isolated function print(string logLevel, string|PrintableRawTemplate msg, error? err = (), error:StackFrame[]? stackTrace = (), *KeyValues keyValues) {
173208
LogRecord logRecord = {
174209
time: getCurrentTime(),
175210
level: logLevel,
176211
module: getModuleNameExtern() == "." ? "" : getModuleNameExtern(),
177-
message: msg
212+
message: processMessage(msg)
178213
};
179214
if err is error {
180215
logRecord.'error = getFullErrorDetails(err);
@@ -187,7 +222,7 @@ isolated function print(string logLevel, string msg, error? err = (), error:Stac
187222
logRecord["stackTrace"] = stackTraceArray;
188223
}
189224
foreach [string, Value] [k, v] in keyValues.entries() {
190-
anydata value = v is Valuer ? v() : v;
225+
anydata value = v is Valuer ? v() : v is PrintableRawTemplate ? processMessage(v) : v;
191226
logRecord[k] = value;
192227
}
193228
if observe:isTracingEnabled() {

integration-tests/Ballerina.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ path = "../test-utils/build/libs/log-test-utils-@project.version@.jar"
1717
[[platform.java21.dependency]]
1818
groupId = "io.ballerina.stdlib"
1919
artifactId = "io-native"
20-
path = "./lib/io-native-@io.native.version@.jar"
20+
path = "./lib/io-native-@io.native.version@.jar"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
2+
//
3+
// WSO2 Inc. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/log;
18+
19+
public function main() {
20+
string myname = "Alex92";
21+
int myage = 25;
22+
string action1 = "action1";
23+
string action2 = "action2";
24+
string action3 = "action3";
25+
log:printError(`error: My name is ${myname} and my age is ${myage}`);
26+
log:printWarn(`warning: My name is ${myname} and my age is ${myage}`);
27+
log:printInfo(`info: My name is ${myname} and my age is ${myage}`);
28+
log:printDebug(`debug: My name is ${myname} and my age is ${myage}`);
29+
log:printInfo("User details", details = `name: ${myname}, age: ${myage}`, actions = `actions: ${action1}, ${action2}, ${action3}`);
30+
}
31+

integration-tests/tests/tests_logfmt.bal

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
// specific language governing permissions and limitations
1515
// under the License.
1616

17-
import ballerina/jballerina.java;
1817
import ballerina/io;
18+
import ballerina/jballerina.java;
1919
import ballerina/test;
2020

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

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

56+
const string MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT = " level=ERROR module=\"\" message=\"error: My name is Alex92 and my age is 25\"";
57+
const string MESSAGE_WARN_RAW_TEMPLATE_LOGFMT = " level=WARN module=\"\" message=\"warning: My name is Alex92 and my age is 25\"";
58+
const string MESSAGE_INFO_RAW_TEMPLATE_LOGFMT = " level=INFO module=\"\" message=\"info: My name is Alex92 and my age is 25\"";
59+
const string MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT = " level=DEBUG module=\"\" message=\"debug: My name is Alex92 and my age is 25\"";
60+
const string MESSAGE_KEY_VALUE_PAIR_LOGFMT = " level=INFO module=\"\" message=\"User details\" details=\"name: Alex92, age: 25\" actions=\"actions: action1, action2, action3\"";
61+
5462
const string MESSAGE_ERROR_MAIN_LOGFMT = " level=ERROR module=myorg/myproject message=\"error log\\t\\n\\r\\\\\\\"\"";
5563
const string MESSAGE_WARN_MAIN_LOGFMT = " level=WARN module=myorg/myproject message=\"warn log\\t\\n\\r\\\\\\\"\"";
5664
const string MESSAGE_INFO_MAIN_LOGFMT = " level=INFO module=myorg/myproject message=\"info log\\t\\n\\r\\\\\\\"\"";
@@ -215,6 +223,86 @@ public function testDebugLevelLogfmt() returns error? {
215223
validateLog(logLines[8], MESSAGE_DEBUG_LOGFMT);
216224
}
217225

226+
@test:Config {}
227+
public function testErrorLevelRawTemplateLogfmt() returns error? {
228+
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_ERROR_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
229+
Process result = check execResult;
230+
int _ = check result.waitForExit();
231+
int _ = check result.exitCode();
232+
io:ReadableByteChannel readableResult = result.stderr();
233+
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
234+
string outText = check sc.read(100000);
235+
string[] logLines = re`\n`.split(outText.trim());
236+
test:assertEquals(logLines.length(), 6, INCORRECT_NUMBER_OF_LINES);
237+
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
238+
}
239+
240+
@test:Config {}
241+
public function testWarnLevelRawTemplateLogfmt() returns error? {
242+
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_WARN_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
243+
Process result = check execResult;
244+
int _ = check result.waitForExit();
245+
int _ = check result.exitCode();
246+
io:ReadableByteChannel readableResult = result.stderr();
247+
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
248+
string outText = check sc.read(100000);
249+
string[] logLines = re`\n`.split(outText.trim());
250+
test:assertEquals(logLines.length(), 7, INCORRECT_NUMBER_OF_LINES);
251+
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
252+
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
253+
}
254+
255+
@test:Config {}
256+
public function testInfoLevelRawTemplateLogfmt() returns error? {
257+
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_INFO_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
258+
Process result = check execResult;
259+
int _ = check result.waitForExit();
260+
int _ = check result.exitCode();
261+
io:ReadableByteChannel readableResult = result.stderr();
262+
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
263+
string outText = check sc.read(100000);
264+
string[] logLines = re`\n`.split(outText.trim());
265+
test:assertEquals(logLines.length(), 9, INCORRECT_NUMBER_OF_LINES);
266+
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
267+
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
268+
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
269+
}
270+
271+
@test:Config {}
272+
public function testDebugLevelRawTemplateLogfmt() returns error? {
273+
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_DEBUG_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
274+
Process result = check execResult;
275+
int _ = check result.waitForExit();
276+
int _ = check result.exitCode();
277+
io:ReadableByteChannel readableResult = result.stderr();
278+
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
279+
string outText = check sc.read(100000);
280+
string[] logLines = re`\n`.split(outText.trim());
281+
test:assertEquals(logLines.length(), 10, INCORRECT_NUMBER_OF_LINES);
282+
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
283+
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
284+
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
285+
validateLog(logLines[8], MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT);
286+
}
287+
@test:Config {}
288+
public function testRawTemplateKeyValuePair() returns error? {
289+
Process|error execResult = exec(bal_exec_path, {BAL_CONFIG_FILES: CONFIG_DEBUG_LOGFMT}, (), "run", LOG_LEVEL_RAW_TEMPLATE_FILE);
290+
Process result = check execResult;
291+
int _ = check result.waitForExit();
292+
int _ = check result.exitCode();
293+
io:ReadableByteChannel readableResult = result.stderr();
294+
io:ReadableCharacterChannel sc = new (readableResult, UTF_8);
295+
string outText = check sc.read(100000);
296+
string[] logLines = re`\n`.split(outText.trim());
297+
test:assertEquals(logLines.length(), 10, INCORRECT_NUMBER_OF_LINES);
298+
validateLog(logLines[5], MESSAGE_ERROR_RAW_TEMPLATE_LOGFMT);
299+
validateLog(logLines[6], MESSAGE_WARN_RAW_TEMPLATE_LOGFMT);
300+
validateLog(logLines[7], MESSAGE_INFO_RAW_TEMPLATE_LOGFMT);
301+
validateLog(logLines[8], MESSAGE_DEBUG_RAW_TEMPLATE_LOGFMT);
302+
validateLog(logLines[9], MESSAGE_KEY_VALUE_PAIR_LOGFMT);
303+
}
304+
305+
218306
@test:Config {}
219307
public function testProjectWithoutLogLevelLogfmt() returns error? {
220308
Process|error execResult = exec(bal_exec_path, {}, (), "run", temp_dir_path
@@ -475,3 +563,4 @@ function exec(@untainted string command, @untainted map<string> env = {},
475563
name: "exec",
476564
'class: "io.ballerina.stdlib.log.testutils.nativeimpl.Exec"
477565
} external;
566+

0 commit comments

Comments
 (0)