Custom VRL
Creating a Custom Transformation
1
2
3
4
Writing VRL
How Transformations Work
# return exits early, but does NOT modify the log event
# "unmodified" is ignored and the log event (.) passes through unmodified
if bool(.skip_transform) ?? false {
return "unmodified"
}
# This modifies the log event - .new_field will be in the output
.new_field = "hello"Available Functions
Error Handling
Pattern 1: The ?? Operator (Fallback Values)
?? Operator (Fallback Values)# If .status is not a valid integer, default to 0
.status_code = to_int(.status) ?? 0
# If parsing fails, keep the original value
.parsed_message = parse_json(.message) ?? .messagePattern 2: Explicit Error Checking
parsed, err = parse_json(.message)
if err == null {
.message_parsed = parsed
.parse_status = "success"
} else {
.parse_status = "failed"
.parse_error = to_string(err)
}Handling Unwanted Events
Example Transformations
Normalizing Custom Logs to ECS
parsed, err = parse_regex(.message, r'^(?P<client_ip>\S+) - (?P<user>\S+) \[(?P<timestamp>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) (?P<protocol>[^"]+)" (?P<status>\d+) (?P<bytes>\d+)')
if err == null {
."@ecs".source.ip = parsed.client_ip
."@ecs".user.name = if parsed.user != "-" { parsed.user } else { null }
."@ecs".http.request.method = parsed.method
."@ecs".url.path = parsed.path
."@ecs".http.version = parsed.protocol
."@ecs".http.response.status_code = to_int(parsed.status) ?? 0
."@ecs".http.response.body.bytes = to_int(parsed.bytes) ?? 0
}Tagging Events by IP Range
Flattening Arrays (1:N Transform)
Dropping Events Conditionally
drop_event = false
if .url.path == "/health" || .url.path == "/ready" || .url.path == "/ping" {
drop_event = true
}
if drop_event {
. = []
return 0 # early exit; return value is ignored
}Enriching with Lookup Tables
username,full_name,department
alice,Alice Smith,Engineering
bob,Bob Jones,Salesusername = .user.name
if username == null { username = .userName }
if username != null {
employee, err = get_enrichment_table_record(
"employees",
{"username": username},
["full_name", "department"]
)
if err == null {
.user.full_name = employee.full_name
.user.department = employee.department
} else {
.user.department = "unknown"
}
}CIDR-Based Tagging with Lookup Tables
cidr,tag
172.31.124.0/24,scanner_production
10.0.0.0/8,rfc1918_private
172.16.0.0/12,rfc1918_private
192.168.0.0/16,rfc1918_private
54.0.0.0/8,aws_public
0.0.0.0/0,public_internetrecords = find_enrichment_table_records!("cidr_tags", {})
src_ip, err = to_string(.sourceIPAddress)
if err == null && is_ipv4(src_ip) {
matched = false
for_each(records) -> |_index, record| {
if !matched {
result, err = ip_cidr_contains(record.cidr, src_ip)
if err == null && result == true {
.network_tag = record.tag
matched = true
}
}
}
}Resources
Last updated
Was this helpful?