uCalc API Version: 5.7.0-preview.1 Released: 7/30/2026
Warning
uCalc API Preview Release Notice:This preview documentation describes the intended behavior of the API. It is not fully accurate or complete.The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.Use of the preview version in your production code is not recommended.
A step-by-step guide to building a server log parser that extracts and aggregates key performance metrics using the uCalc Transformer.
This project will guide you through building a powerful and efficient server log parser. It's a classic real-world problem that perfectly showcases the advantages of uCalc's token-aware Transformer over traditional tools like Regular Expressions or manual string splitting.
Our objective is to take a raw, unstructured log file and transform it into a structured summary of key performance indicators (KPIs). We want to answer questions like:
Here is a sample of the access.log content we will be working with. It contains a mix of successful requests and errors, with inconsistent spacing.
2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms2024-10-26 10:00:06 INFO 192.168.1.15 "GET /api/products HTTP/1.1" 200 22msThis is a malformed line and should be ignored.2024-10-26 10:00:07 ERROR 192.168.1.22 "POST /api/login HTTP/1.1" 500 120ms2024-10-26 10:00:08 INFO 192.168.1.10 "GET /api/users/1 HTTP/1.1" 200 8msWe will use a single Transformer rule to process the entire log file. This rule will be designed to:
{@Exec}: The replacement part of the rule will not produce any text. Instead, it will use a series of {@Exec} directives to update a set of counter variables for each line it processes. This is a highly efficient way to perform calculations as a side effect of a transformation.First, we need to define the variables in our uCalc instance that will store the aggregated metrics. We initialize them all to zero.
csharp
uc.DefineVariable("request_count = 0");
uc.DefineVariable("error_count = 0");
uc.DefineVariable("total_response_time = 0.0");
uc.DefineVariable("max_response_time = 0.0");
Next, we create our Transformer and define the main rule. The pattern is designed to be robust against variable whitespace. The key is using {@String:request} to capture the entire HTTP request, including the spaces inside it, as a single token.
csharp
var t = new uCalc.Transformer();
// The pattern captures all fields from a valid log line.
var pattern = "{@date} {@time} {@level} {@ip} {@String:request} {@Number:status} {@Number:time}ms";
The replacement string is where the magic happens. It contains no visible text, only a sequence of {@Exec} calls that update our metric variables for each match. Note that we must use Double(time) to convert the captured time string into a number for calculations.
csharp
var replacement = ""
+ "{@Exec: request_count++}"
+ "{@Exec: total_response_time = total_response_time + Double(time)}"
+ "{@Exec: max_response_time = Max(max_response_time, Double(time))}"
+ "{@Exec: if(status >= 400, error_count++)}";
t.FromTo(pattern, replacement);
Finally, we pass the entire multi-line log string to the Transform method. After it completes, our metric variables will hold the final aggregated values, which we can retrieve with EvalStr.
csharp
// After running t.Transform(logText)...
Console.WriteLine("--- Log Analysis Summary ---");
Console.WriteLine($"Total Requests: {uc.EvalStr("request_count")}");
Console.WriteLine($"Total Errors: {uc.EvalStr("error_count")}");
Console.WriteLine($"Average Response Time: {uc.EvalStr("total_response_time / request_count")}ms");
Console.WriteLine($"Maximum Response Time: {uc.EvalStr("max_response_time")}ms");
Split(): A simple Split(' ') would fail because of the spaces inside the quoted request string (e.g., "GET /api/users HTTP/1.1"). uCalc's tokenizer correctly identifies this as a single string token, making the pattern robust and simple.ID: 1388
using uCalcSoftware;
var uc = new uCalc();
using (var t = new uCalc.Transformer()) {
string logLine = """
2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms
""";
// Define a pattern to capture the status and time
string pattern = "{@String:request} {@Number:status} {@Number:time}ms";
// The replacement string formats the captured data for display
// {request} or {request(1)} would return the string without surrounding quotes
// {request(0)} returns the string with surrounding quotes
string replacement = "Request: {request(0)}, Status: {status}, Time: {time}ms";
t.FromTo(pattern, replacement);
// Use Filter() to extract only the transformed match
Console.WriteLine(t.Transform(logLine).Matches);
}
Request: "GET /api/users HTTP/1.1", Status: 200, Time: 15ms using uCalcSoftware; var uc = new uCalc(); using (var t = new uCalc.Transformer()) { string logLine = """ 2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms """; // Define a pattern to capture the status and time string pattern = "{@String:request} {@Number:status} {@Number:time}ms"; // The replacement string formats the captured data for display // {request} or {request(1)} would return the string without surrounding quotes // {request(0)} returns the string with surrounding quotes string replacement = "Request: {request(0)}, Status: {status}, Time: {time}ms"; t.FromTo(pattern, replacement); // Use Filter() to extract only the transformed match Console.WriteLine(t.Transform(logLine).Matches); }
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
{
uCalc::Transformer t;
t.Owned(); // Causes t to be released when it goes out of scope
string logLine = R"(2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms)";
// Define a pattern to capture the status and time
string pattern = "{@String:request} {@Number:status} {@Number:time}ms";
// The replacement string formats the captured data for display
// {request} or {request(1)} would return the string without surrounding quotes
// {request(0)} returns the string with surrounding quotes
string replacement = "Request: {request(0)}, Status: {status}, Time: {time}ms";
t.FromTo(pattern, replacement);
// Use Filter() to extract only the transformed match
cout << t.Transform(logLine).Matches() << endl;
}
}
Request: "GET /api/users HTTP/1.1", Status: 200, Time: 15ms #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; { uCalc::Transformer t; t.Owned(); // Causes t to be released when it goes out of scope string logLine = R"(2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms)"; // Define a pattern to capture the status and time string pattern = "{@String:request} {@Number:status} {@Number:time}ms"; // The replacement string formats the captured data for display // {request} or {request(1)} would return the string without surrounding quotes // {request(0)} returns the string with surrounding quotes string replacement = "Request: {request(0)}, Status: {status}, Time: {time}ms"; t.FromTo(pattern, replacement); // Use Filter() to extract only the transformed match cout << t.Transform(logLine).Matches() << endl; } }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
Using t As New uCalc.Transformer()
Dim logLine As String = "2024-10-26 10:00:05 INFO 192.168.1.10 ""GET /api/users HTTP/1.1"" 200 15ms"
'// Define a pattern to capture the status and time
Dim pattern As String = "{@String:request} {@Number:status} {@Number:time}ms"
'// The replacement string formats the captured data for display
'// {request} or {request(1)} would return the string without surrounding quotes
'// {request(0)} returns the string with surrounding quotes
Dim replacement As String = "Request: {request(0)}, Status: {status}, Time: {time}ms"
t.FromTo(pattern, replacement)
'// Use Filter() to extract only the transformed match
Console.WriteLine(t.Transform(logLine).Matches)
End Using
End Sub
End Module
Request: "GET /api/users HTTP/1.1", Status: 200, Time: 15ms Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() Using t As New uCalc.Transformer() Dim logLine As String = "2024-10-26 10:00:05 INFO 192.168.1.10 ""GET /api/users HTTP/1.1"" 200 15ms" '// Define a pattern to capture the status and time Dim pattern As String = "{@String:request} {@Number:status} {@Number:time}ms" '// The replacement string formats the captured data for display '// {request} or {request(1)} would return the string without surrounding quotes '// {request(0)} returns the string with surrounding quotes Dim replacement As String = "Request: {request(0)}, Status: {status}, Time: {time}ms" t.FromTo(pattern, replacement) '// Use Filter() to extract only the transformed match Console.WriteLine(t.Transform(logLine).Matches) End Using End Sub End Module
ID: 1389
using uCalcSoftware;
var uc = new uCalc();
// 1. Define variables to hold the metrics
uc.DefineVariable("request_count = 0");
uc.DefineVariable("error_count = 0");
uc.DefineVariable("total_response_time = 0.0");
uc.DefineVariable("max_response_time = 0.0");
// 2. Create the transformer and define the rule
using (var t = new uCalc.Transformer(uc)) {
var pattern = "{@String:request} {@Number:status} {@Number:time}ms";
// 3. The replacement string uses @Exec for side-effects (updating variables)
var replacement = """
{@Exec: request_count++}
{@Exec: total_response_time = total_response_time + Double(time)}
{@Exec: max_response_time = Max(max_response_time, Double(time))}
{@Exec: iif(Double(status) >= 400, error_count++, 0)}
""";
t.FromTo(pattern, replacement);
// 4. Define the multi-line log data
var logText = """
2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms
2024-10-26 10:00:06 INFO 192.168.1.15 "GET /api/products HTTP/1.1" 200 22ms
2024-10-26 10:00:07 ERROR 192.168.1.22 "POST /api/login HTTP/1.1" 500 120ms
2024-10-26 10:00:08 INFO 192.168.1.10 "GET /api/users/1 HTTP/1.1" 200 8ms
""";
// 5. Run the transformation (the output will be empty as we only use @Exec)
t.Transform(logText);
}
// 6. Display the final aggregated metrics
Console.WriteLine("--- Log Analysis Summary ---");
Console.WriteLine($"Total Requests: {uc.EvalStr("request_count")}");
Console.WriteLine($"Total Errors: {uc.EvalStr("error_count")}");
Console.WriteLine($"Average Response Time: {uc.EvalStr("total_response_time / request_count")}ms");
Console.WriteLine($"Maximum Response Time: {uc.EvalStr("max_response_time")}ms");
--- Log Analysis Summary ---
Total Requests: 4
Total Errors: 1
Average Response Time: 41.25ms
Maximum Response Time: 120ms using uCalcSoftware; var uc = new uCalc(); // 1. Define variables to hold the metrics uc.DefineVariable("request_count = 0"); uc.DefineVariable("error_count = 0"); uc.DefineVariable("total_response_time = 0.0"); uc.DefineVariable("max_response_time = 0.0"); // 2. Create the transformer and define the rule using (var t = new uCalc.Transformer(uc)) { var pattern = "{@String:request} {@Number:status} {@Number:time}ms"; // 3. The replacement string uses @Exec for side-effects (updating variables) var replacement = """ {@Exec: request_count++} {@Exec: total_response_time = total_response_time + Double(time)} {@Exec: max_response_time = Max(max_response_time, Double(time))} {@Exec: iif(Double(status) >= 400, error_count++, 0)} """; t.FromTo(pattern, replacement); // 4. Define the multi-line log data var logText = """ 2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms 2024-10-26 10:00:06 INFO 192.168.1.15 "GET /api/products HTTP/1.1" 200 22ms 2024-10-26 10:00:07 ERROR 192.168.1.22 "POST /api/login HTTP/1.1" 500 120ms 2024-10-26 10:00:08 INFO 192.168.1.10 "GET /api/users/1 HTTP/1.1" 200 8ms """; // 5. Run the transformation (the output will be empty as we only use @Exec) t.Transform(logText); } // 6. Display the final aggregated metrics Console.WriteLine("--- Log Analysis Summary ---"); Console.WriteLine($"Total Requests: {uc.EvalStr("request_count")}"); Console.WriteLine($"Total Errors: {uc.EvalStr("error_count")}"); Console.WriteLine($"Average Response Time: {uc.EvalStr("total_response_time / request_count")}ms"); Console.WriteLine($"Maximum Response Time: {uc.EvalStr("max_response_time")}ms");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
// 1. Define variables to hold the metrics
uc.DefineVariable("request_count = 0");
uc.DefineVariable("error_count = 0");
uc.DefineVariable("total_response_time = 0.0");
uc.DefineVariable("max_response_time = 0.0");
// 2. Create the transformer and define the rule
{
uCalc::Transformer t(uc);
t.Owned(); // Causes t to be released when it goes out of scope
auto pattern = "{@String:request} {@Number:status} {@Number:time}ms";
// 3. The replacement string uses @Exec for side-effects (updating variables)
auto replacement = R"(
{@Exec: request_count++}
{@Exec: total_response_time = total_response_time + Double(time)}
{@Exec: max_response_time = Max(max_response_time, Double(time))}
{@Exec: iif(Double(status) >= 400, error_count++, 0)}
)";
t.FromTo(pattern, replacement);
// 4. Define the multi-line log data
auto logText = R"(
2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms
2024-10-26 10:00:06 INFO 192.168.1.15 "GET /api/products HTTP/1.1" 200 22ms
2024-10-26 10:00:07 ERROR 192.168.1.22 "POST /api/login HTTP/1.1" 500 120ms
2024-10-26 10:00:08 INFO 192.168.1.10 "GET /api/users/1 HTTP/1.1" 200 8ms
)";
// 5. Run the transformation (the output will be empty as we only use @Exec)
t.Transform(logText);
}
// 6. Display the final aggregated metrics
cout << "--- Log Analysis Summary ---" << endl;
cout << "Total Requests: " << uc.EvalStr("request_count") << endl;
cout << "Total Errors: " << uc.EvalStr("error_count") << endl;
cout << "Average Response Time: " << uc.EvalStr("total_response_time / request_count") << "ms" << endl;
cout << "Maximum Response Time: " << uc.EvalStr("max_response_time") << "ms" << endl;
}
--- Log Analysis Summary ---
Total Requests: 4
Total Errors: 1
Average Response Time: 41.25ms
Maximum Response Time: 120ms #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; // 1. Define variables to hold the metrics uc.DefineVariable("request_count = 0"); uc.DefineVariable("error_count = 0"); uc.DefineVariable("total_response_time = 0.0"); uc.DefineVariable("max_response_time = 0.0"); // 2. Create the transformer and define the rule { uCalc::Transformer t(uc); t.Owned(); // Causes t to be released when it goes out of scope auto pattern = "{@String:request} {@Number:status} {@Number:time}ms"; // 3. The replacement string uses @Exec for side-effects (updating variables) auto replacement = R"( {@Exec: request_count++} {@Exec: total_response_time = total_response_time + Double(time)} {@Exec: max_response_time = Max(max_response_time, Double(time))} {@Exec: iif(Double(status) >= 400, error_count++, 0)} )"; t.FromTo(pattern, replacement); // 4. Define the multi-line log data auto logText = R"( 2024-10-26 10:00:05 INFO 192.168.1.10 "GET /api/users HTTP/1.1" 200 15ms 2024-10-26 10:00:06 INFO 192.168.1.15 "GET /api/products HTTP/1.1" 200 22ms 2024-10-26 10:00:07 ERROR 192.168.1.22 "POST /api/login HTTP/1.1" 500 120ms 2024-10-26 10:00:08 INFO 192.168.1.10 "GET /api/users/1 HTTP/1.1" 200 8ms )"; // 5. Run the transformation (the output will be empty as we only use @Exec) t.Transform(logText); } // 6. Display the final aggregated metrics cout << "--- Log Analysis Summary ---" << endl; cout << "Total Requests: " << uc.EvalStr("request_count") << endl; cout << "Total Errors: " << uc.EvalStr("error_count") << endl; cout << "Average Response Time: " << uc.EvalStr("total_response_time / request_count") << "ms" << endl; cout << "Maximum Response Time: " << uc.EvalStr("max_response_time") << "ms" << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
'// 1. Define variables to hold the metrics
uc.DefineVariable("request_count = 0")
uc.DefineVariable("error_count = 0")
uc.DefineVariable("total_response_time = 0.0")
uc.DefineVariable("max_response_time = 0.0")
'// 2. Create the transformer and define the rule
Using t As New uCalc.Transformer(uc)
Dim pattern = "{@String:request} {@Number:status} {@Number:time}ms"
'// 3. The replacement string uses @Exec for side-effects (updating variables)
Dim replacement = "
{@Exec: request_count++}
{@Exec: total_response_time = total_response_time + Double(time)}
{@Exec: max_response_time = Max(max_response_time, Double(time))}
{@Exec: iif(Double(status) >= 400, error_count++, 0)}
"
t.FromTo(pattern, replacement)
'// 4. Define the multi-line log data
Dim logText = "
2024-10-26 10:00:05 INFO 192.168.1.10 ""GET /api/users HTTP/1.1"" 200 15ms
2024-10-26 10:00:06 INFO 192.168.1.15 ""GET /api/products HTTP/1.1"" 200 22ms
2024-10-26 10:00:07 ERROR 192.168.1.22 ""POST /api/login HTTP/1.1"" 500 120ms
2024-10-26 10:00:08 INFO 192.168.1.10 ""GET /api/users/1 HTTP/1.1"" 200 8ms
"
'// 5. Run the transformation (the output will be empty as we only use @Exec)
t.Transform(logText)
End Using
'// 6. Display the final aggregated metrics
Console.WriteLine("--- Log Analysis Summary ---")
Console.WriteLine($"Total Requests: {uc.EvalStr("request_count")}")
Console.WriteLine($"Total Errors: {uc.EvalStr("error_count")}")
Console.WriteLine($"Average Response Time: {uc.EvalStr("total_response_time / request_count")}ms")
Console.WriteLine($"Maximum Response Time: {uc.EvalStr("max_response_time")}ms")
End Sub
End Module
--- Log Analysis Summary ---
Total Requests: 4
Total Errors: 1
Average Response Time: 41.25ms
Maximum Response Time: 120ms Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() '// 1. Define variables to hold the metrics uc.DefineVariable("request_count = 0") uc.DefineVariable("error_count = 0") uc.DefineVariable("total_response_time = 0.0") uc.DefineVariable("max_response_time = 0.0") '// 2. Create the transformer and define the rule Using t As New uCalc.Transformer(uc) Dim pattern = "{@String:request} {@Number:status} {@Number:time}ms" '// 3. The replacement string uses @Exec for side-effects (updating variables) Dim replacement = " {@Exec: request_count++} {@Exec: total_response_time = total_response_time + Double(time)} {@Exec: max_response_time = Max(max_response_time, Double(time))} {@Exec: iif(Double(status) >= 400, error_count++, 0)} " t.FromTo(pattern, replacement) '// 4. Define the multi-line log data Dim logText = " 2024-10-26 10:00:05 INFO 192.168.1.10 ""GET /api/users HTTP/1.1"" 200 15ms 2024-10-26 10:00:06 INFO 192.168.1.15 ""GET /api/products HTTP/1.1"" 200 22ms 2024-10-26 10:00:07 ERROR 192.168.1.22 ""POST /api/login HTTP/1.1"" 500 120ms 2024-10-26 10:00:08 INFO 192.168.1.10 ""GET /api/users/1 HTTP/1.1"" 200 8ms " '// 5. Run the transformation (the output will be empty as we only use @Exec) t.Transform(logText) End Using '// 6. Display the final aggregated metrics Console.WriteLine("--- Log Analysis Summary ---") Console.WriteLine($"Total Requests: {uc.EvalStr("request_count")}") Console.WriteLine($"Total Errors: {uc.EvalStr("error_count")}") Console.WriteLine($"Average Response Time: {uc.EvalStr("total_response_time / request_count")}ms") Console.WriteLine($"Maximum Response Time: {uc.EvalStr("max_response_time")}ms") End Sub End Module