Transforming values is a fundamental part of data processing. This guide shows you how to convert between different data types, perform basic calculations, and manipulate simple values within your events.
Type conversions
Section titled “Type conversions”TQL provides functions to convert values between different types. This is essential when data arrives in the wrong format or when you need specific types for further processing.
Convert to numbers
Section titled “Convert to numbers”Use int and float to convert values to numeric types:
from {price: "42", quantity: "3.5"}, {price: "99", quantity: "1.0"}price = price.int()quantity = quantity.float(){price: 42, quantity: 3.5}{price: 99, quantity: 1.0}Convert to strings
Section titled “Convert to strings”Use string to convert any value to its string representation:
from {status: 200, ratio: 0.95}, {status: 404, ratio: 0.05}message = status.string() + " - " + (ratio * 100).string() + "%"{status: 200, ratio: 0.95, message: "200 - 95.0%"}{status: 404, ratio: 0.05, message: "404 - 5.0%"}Parse times and durations
Section titled “Parse times and durations”Convert strings to time values with time:
from {timestamp: "2024-01-15"}, {timestamp: "2024-02-20"}parsed_time = timestamp.time(){timestamp: "2024-01-15", parsed_time: 2024-01-15T00:00:00Z}{timestamp: "2024-02-20", parsed_time: 2024-02-20T00:00:00Z}Convert strings to durations with duration:
from {interval: "5s"}, {interval: "2.5min"}parsed_duration = interval.duration(){interval: "5s", parsed_duration: 5s}{interval: "2.5min", parsed_duration: 2.5min}Convert to unsigned integers
Section titled “Convert to unsigned integers”Use uint for non-negative integers:
from {count: "42", ratio: 3.7}, {count: "-5", ratio: 2.3}count_uint = count.uint()ratio_uint = ratio.uint(){count: "42", ratio: 3.7, count_uint: 42, ratio_uint: 3}{count: "-5", ratio: 2.3, count_uint: null, ratio_uint: 2}This pipeline elicits the following warning:
warning: `uint` failed to convert some string --> /tmp/pipeline.tql:3:14 |3 | count_uint = count.uint() | ~~~~~ |Work with IP addresses and subnets
Section titled “Work with IP addresses and subnets”TQL supports IP address and subnet literals directly. You can also parse them from strings using ip and subnet:
from {direct_ip: 192.168.1.1, direct_subnet: 10.0.0.0/24}, {direct_ip: ::1, direct_subnet: 2001:db8::/32}ipv6_check = direct_ip.is_v6(){ direct_ip: 192.168.1.1, direct_subnet: 10.0.0.0/24, ipv6_check: false,}{ direct_ip: ::1, direct_subnet: 2001:db8::/32, ipv6_check: true,}Parse from strings when needed:
from {client: "192.168.1.1", network: "10.0.0.0/24"}, {client: "10.0.0.5", network: "192.168.0.0/16"}client_ip = client.ip()network_subnet = network.subnet(){ client: "192.168.1.1", network: "10.0.0.0/24", client_ip: 192.168.1.1, network_subnet: 10.0.0.0/24,}{ client: "10.0.0.5", network: "192.168.0.0/16", client_ip: 10.0.0.5, network_subnet: 192.168.0.0/16,}IP address inspection
Section titled “IP address inspection”Analyze and categorize IP addresses with inspection functions:
Check IP address types
Section titled “Check IP address types”Use IP inspection functions like is_v4, is_v6, is_private, is_global, is_loopback, and is_multicast to analyze addresses:
from {ip1: 192.168.1.1, ip2: 8.8.8.8, ip3: ::1}, {ip1: 10.0.0.1, ip2: 224.0.0.1, ip3: 2001:db8::1}is_v4 = ip1.is_v4()is_v6 = ip3.is_v6()is_private = ip1.is_private()is_global = ip2.is_global()is_loopback = ip3.is_loopback()is_multicast = ip2.is_multicast(){ ip1: 192.168.1.1, ip2: 8.8.8.8, ip3: ::1, is_v4: true, is_v6: true, is_private: true, is_global: true, is_loopback: true, is_multicast: false,}{ ip1: 10.0.0.1, ip2: 224.0.0.1, ip3: 2001:db8::1, is_v4: true, is_v6: true, is_private: true, is_global: false, is_loopback: false, is_multicast: true,}Categorize IP addresses
Section titled “Categorize IP addresses”Get detailed IP address classification with ip_category:
from {client: "192.168.1.100", server: "8.8.8.8", local: "127.0.0.1"}, {client: "10.0.0.5", server: "224.0.0.251", local: "::1"}client_category = client.ip().ip_category()server_category = server.ip().ip_category()local_category = local.ip().ip_category(){ client: "192.168.1.100", server: "8.8.8.8", local: "127.0.0.1", client_category: "private", server_category: "global", local_category: "loopback",}{ client: "10.0.0.5", server: "224.0.0.251", local: "::1", client_category: "private", server_category: "multicast", local_category: "loopback",}Check link-local addresses
Section titled “Check link-local addresses”Identify link-local addresses with is_link_local:
from {addr1: 169.254.1.1, addr2: fe80::1, addr3: 192.168.1.1}, {addr1: 169.254.0.1, addr2: 2001:db8::1, addr3: 10.0.0.1}link_local1 = addr1.is_link_local()link_local2 = addr2.is_link_local()link_local3 = addr3.is_link_local(){ addr1: 169.254.1.1, addr2: fe80::1, addr3: 192.168.1.1, link_local1: true, link_local2: true, link_local3: false,}{ addr1: 169.254.0.1, addr2: 2001:db8::1, addr3: 10.0.0.1, link_local1: true, link_local2: false, link_local3: false,}Classify values with match
Section titled “Classify values with match”Use match when a transformation depends on a
small set of cases. A match statement checks patterns from top to bottom and
runs the first matching branch. Patterns can be constants, alternatives, ranges,
or the wildcard _. Guards add boolean conditions to matching branches.
Normalize firewall actions
Section titled “Normalize firewall actions”Firewall and network devices often use vendor-specific action names. Use | to
put equivalent values in the same branch and normalize them into a shared
verdict before routing or aggregating the events:
from {vendor: "fortinet", action: "accept", src_ip: 10.0.0.5}, {vendor: "paloalto", action: "deny", src_ip: 203.0.113.10}, {vendor: "checkpoint", action: "drop", src_ip: 192.168.1.20}, {vendor: "custom", action: "reset", src_ip: 198.51.100.7}match action { "accept" | "allow" | "pass" => { verdict = "allowed" } "deny" | "drop" | "block" => { verdict = "blocked" } _ => { verdict = "unknown" }}{ vendor: "fortinet", action: "accept", src_ip: 10.0.0.5, verdict: "allowed",}{ vendor: "paloalto", action: "deny", src_ip: 203.0.113.10, verdict: "blocked",}{ vendor: "checkpoint", action: "drop", src_ip: 192.168.1.20, verdict: "blocked",}{ vendor: "custom", action: "reset", src_ip: 198.51.100.7, verdict: "unknown",}Every match statement must include an unguarded final wildcard branch. Use it
to handle all values that don’t match earlier branches.
Classify numeric ranges
Section titled “Classify numeric ranges”Range patterns use lower..upper and exclude both bounds. They’re useful for
bucketizing status codes, ports, scores, or severities:
from {status: 200}, {status: 204}, {status: 404}, {status: 503}match status { 199..300 => { status_class = "success" } 399..500 => { status_class = "client_error" } 499..600 => { status_class = "server_error" } _ => { status_class = "other" }}{ status: 200, status_class: "success",}{ status: 204, status_class: "success",}{ status: 404, status_class: "client_error",}{ status: 503, status_class: "server_error",}Add context with guards
Section titled “Add context with guards”Use a guard when the same pattern needs different handling depending on another field. This is common for retry decisions: the status code tells you that a request failed, but the retry counter decides whether you should retry, page, or throttle.
from {service: "api", status: 503, attempts: 1, max_attempts: 3}, {service: "api", status: 503, attempts: 3, max_attempts: 3}, {service: "api", status: 429, attempts: 2, max_attempts: 2}, {service: "api", status: 404, attempts: 1, max_attempts: 3}match status { 499..600 | 429 if attempts < max_attempts => { action = "retry" } 499..600 => { action = "page" } 429 => { action = "throttle" } _ => { action = "ignore" }}{ service: "api", status: 503, attempts: 1, max_attempts: 3, action: "retry",}{ service: "api", status: 503, attempts: 3, max_attempts: 3, action: "page",}{ service: "api", status: 429, attempts: 2, max_attempts: 2, action: "throttle",}{ service: "api", status: 404, attempts: 1, max_attempts: 3, action: "ignore",}The guarded branch catches retryable server errors and rate limits only while
attempts remain. When the guard is false, the event continues to the later arms,
so exhausted 503 errors become pages and exhausted 429 errors become
throttling signals.
Constants can also be lists or records. These values are compared by equality, so the whole value must match:
from {id: 1, value: ["prod", "checkout"]}, {id: 2, value: ["prod"]}, {id: 3, value: {action: "allow", port: 443}}, {id: 4, value: {action: "allow"}}match value { ["prod", "checkout"] => { class = "checkout" } {action: "allow", port: 443} => { class = "allow_web" } _ => { class = "other" }}sort id{ id: 1, value: [ "prod", "checkout", ], class: "checkout",}{ id: 2, value: [ "prod", ], class: "other",}{ id: 3, value: { action: "allow", port: 443, }, class: "allow_web",}{ id: 4, value: { action: "allow", }, class: "other",}Basic string operations
Section titled “Basic string operations”Transform strings with simple operations to clean and standardize your data.
Change case
Section titled “Change case”Convert strings to different cases:
from {name: "alice smith", code: "xyz"}, {name: "BOB JONES", code: "ABC"}name = name.to_title()code = code.to_upper(){name: "Alice Smith", code: "XYZ"}{name: "Bob Jones", code: "ABC"}Trim whitespace
Section titled “Trim whitespace”Remove unwanted whitespace from strings:
from {input: " hello ", data: "world "}, {input: " test", data: " value "}input = input.trim()data = data.trim(){input: "hello", data: "world"}{input: "test", data: "value"}Capitalize strings
Section titled “Capitalize strings”Capitalize the first letter of a string:
from {word: "hello", phrase: "good morning"}, {word: "world", phrase: "how are you"}word = word.capitalize(){word: "Hello", phrase: "good morning"}{word: "World", phrase: "how are you"}Mathematical operations
Section titled “Mathematical operations”Perform calculations on numeric values within your events.
Basic arithmetic
Section titled “Basic arithmetic”Use standard arithmetic operators:
from {a: 10, b: 3}, {a: 20, b: 4}sum = a + bdiff = a - bproduct = a * bquotient = (a / b).int(){a: 10, b: 3, sum: 13, diff: 7, product: 30, quotient: 3}{a: 20, b: 4, sum: 24, diff: 16, product: 80, quotient: 5}Rounding numbers
Section titled “Rounding numbers”Round numbers to specific precision:
from {value: 3.14159}, {value: 2.71828}rounded = value.round()ceil_val = value.ceil()floor_val = value.floor(){value: 3.14159, rounded: 3, ceil_val: 4, floor_val: 3}{value: 2.71828, rounded: 3, ceil_val: 3, floor_val: 2}Mathematical functions
Section titled “Mathematical functions”Use abs for absolute values and
sqrt for square roots:
from {x: -5, y: 16}, {x: -10, y: 25}abs_x = x.abs()sqrt_y = y.sqrt(){ x: -5, y: 16, abs_x: 5, sqrt_y: 4.0,}{ x: -10, y: 25, abs_x: 10, sqrt_y: 5.0,}Working with null values
Section titled “Working with null values”Handle missing or null values gracefully in your data.
Provide default values
Section titled “Provide default values”Use the else keyword to replace null values:
from {name: "alice", age: 30}, {name: "bob"}, {name: "charlie", age: 25}age = age? else 0status = status? else "unknown"{name: "alice", age: 30, status: "unknown"}{name: "bob", age: 0, status: "unknown"}{name: "charlie", age: 25, status: "unknown"}Normalize sentinel values
Section titled “Normalize sentinel values”Many data sources use string placeholders like "None", "N/A", "-", or
empty strings to represent missing values. Use
replace to normalize these to actual null:
from {status: "active", error: "None"}, {status: "N/A", error: "timeout"}, {status: "-", error: ""}replace what="None", with=nullreplace what="N/A", with=nullreplace what="-", with=nullreplace what="", with=null{status: "active", error: null}{status: null, error: "timeout"}{status: null, error: null}To normalize a specific field rather than all matching strings, specify the field name:
from {enabled: "YES", disabled: "NO"}replace enabled, what="YES", with=truereplace enabled, what="NO", with=falsereplace disabled, what="YES", with=truereplace disabled, what="NO", with=false{enabled: true, disabled: false}Create new values
Section titled “Create new values”Generate new values using built-in functions:
Generate unique identifiers
Section titled “Generate unique identifiers”Use uuid to create unique identifiers:
from {user: "alice", action: "login"}, {user: "bob", action: "create"}event_id = uuid(version="v7")session_id = uuid(){ user: "alice", action: "login", event_id: "0198147a-d167-7292-80fa-2665c1263279", session_id: "a09a7f44-b665-4f95-bc44-c52fbdb8f428",}{ user: "bob", action: "create", event_id: "0198147a-d167-72ad-80b4-e052c2287add", session_id: "030349dc-2585-49ad-af58-d448ff718c05",}Generate random numbers
Section titled “Generate random numbers”Use random to generate random values:
from { random_float: random(), random_int: (random() * 100).int(), random_choice: "heads" if random() < 0.5 else "tails",}{ random_float: 0.3215780368890365, random_int: 88, random_choice: "tails",}Access external values
Section titled “Access external values”Retrieve values from external sources like the environment, configuration, or files:
Read environment variables
Section titled “Read environment variables”Use env to access environment variables:
from { home_dir: env("HOME"), shell: env("SHELL"), custom_var: env("MY_APP_CONFIG") else "/default/config",}{ home_dir: "/Users/alice", shell: "/opt/homebrew/bin/fish", custom_var: "/default/config",}Access configuration
Section titled “Access configuration”Use config to read Tenzir’s configuration:
from { tenzir_config: config(),}Read file contents
Section titled “Read file contents”Use file_contents to read files:
from { api_key: file_contents("/etc/secrets/api_key"),}Access secrets
Section titled “Access secrets”Use secret to retrieve
secrets:
from { auth_token: secret("AUTH_TOKEN"),}Type inspection
Section titled “Type inspection”Examine data types at runtime:
Get type information
Section titled “Get type information”Use type_of to inspect value types. Note that this function returns detailed type information as objects:
from { str: "hello", num: 42, float: 3.14, bool: true, arr: [1, 2, 3], obj: {key: "value"}}str_type = str.type_of()num_type = num.type_of()float_type = float.type_of()bool_type = bool.type_of()arr_type = arr.type_of()obj_type = obj.type_of(){ str: "hello", num: 42, float: 3.14, bool: true, arr: [1, 2, 3], obj: {key: "value"}, str_type: {name: null, kind: "string", attributes: [], state: null}, num_type: {name: null, kind: "int64", attributes: [], state: null}, float_type: {name: null, kind: "double", attributes: [], state: null}, bool_type: {name: null, kind: "bool", attributes: [], state: null}, arr_type: {name: null, kind: "list", attributes: [], state: {type: {name: null, kind: "int64", attributes: [], state: null}}}, obj_type: {name: null, kind: "record", attributes: [], state: {fields: [{name: "key", type: {name: null, kind: "string", attributes: [], state: null}}]}}}Get type identifiers
Section titled “Get type identifiers”Use type_id for type comparison:
from {value1: "text", value2: 123, value3: "456"}type1 = value1.type_id()type2 = value2.type_id()type3 = value3.type_id()same_type = value1.type_id() == value3.type_id(){ value1: "text", value2: 123, value3: "456", type1: "2476398993549b5", type2: "5b0d4f0b0b167404", type3: "2476398993549b5", same_type: true,}Combining transformations
Section titled “Combining transformations”Real-world data often requires multiple transformations:
from {temp_f: "72.5", location: " new york "}, {temp_f: "89.1", location: "los angeles"}temp_c = ((temp_f.float() - 32) * 5 / 9).round()location = location.trim().to_title()reading = f"{temp_c}°C in {location}"{ temp_f: "72.5", location: "New York", temp_c: 23, reading: "23°C in New York",}{ temp_f: "89.1", location: "Los Angeles", temp_c: 32, reading: "32°C in Los Angeles",}Best practices
Section titled “Best practices”-
Validate before converting: Check that values can be converted to avoid errors.
-
Use appropriate types: Convert to the most specific type needed (e.g.,
intinstead offloatfor whole numbers). -
Handle edge cases: Always consider what happens with null values or invalid input.
-
Chain operations efficiently: Combine multiple transformations in a single
setstatement when possible.