Introduction to Rhai Scripting¶
Learn how to write simple Rhai scripts to filter and transform log events. This tutorial bridges the gap between basic CLI usage and advanced scripting.
What You'll Learn¶
- Understand the event object (
e) and field access - Write simple filter expressions with
--filter - Transform events with basic
--execscripts - Use string operations and conditionals
- Convert between types safely
- Understand why pipeline order matters
- Debug scripts with
-F inspectand--verbose - Avoid common mistakes
Prerequisites¶
- Getting Started: Input, Display & Filtering - Basic CLI usage
- Time: ~20 minutes
Sample Data¶
This tutorial uses examples/simple_json.jsonl - application logs with various services and events.
Preview the data:
timestamp='2024-01-15T10:00:00Z' level='INFO' message='Application started' service='api'
version='1.2.3'
timestamp='2024-01-15T10:00:05Z' level='DEBUG' message='Loading configuration' service='api'
config_file='/etc/app/config.yml'
timestamp='2024-01-15T10:00:10Z' level='INFO' message='Connection pool initialized'
service='database' max_connections=50
timestamp='2024-01-15T10:01:00Z' level='WARN' message='High memory usage detected' service='api'
memory_percent=85
timestamp='2024-01-15T10:01:30Z' level='ERROR' message='Query timeout' service='database'
query='SELECT * FROM users' duration_ms=5000
Step 1: Understanding the Event Object¶
Every event in Kelora is represented as a map (dictionary) accessible via the variable e in your Rhai scripts.
Accessing fields:
e.level # Direct field access
e["level"] # Bracket notation (useful for dynamic fields)
e.status # Access any field in the event
Let's see the structure with -F inspect:
Key insight: Field names become properties you can access in scripts.
Step 2: Simple Filter Expressions¶
Use --filter to keep only events where the expression returns true.
Filter by String Equality¶
Keep only ERROR level events:
timestamp='2024-01-15T10:01:30Z' level='ERROR' message='Query timeout' service='database'
query='SELECT * FROM users' duration_ms=5000
timestamp='2024-01-15T10:03:30Z' level='ERROR' message='Account locked' service='auth'
username='admin' attempts=5
timestamp='2024-01-15T10:16:00Z' level='ERROR' message='Service unavailable' service='api'
reason='disk space'
What happened: Only events where e.level equals "ERROR" are kept.
Filter by Numeric Comparison¶
Keep only slow queries (duration > 1000ms):
Important: This only keeps events that have a duration_ms field. Events without it are skipped.
Combine Conditions with Logical Operators¶
Find ERROR or WARN events from the database service:
Operators:
- == - Equals
- != - Not equals
- >, >=, <, <= - Comparison
- && - AND
- || - OR
- ! - NOT
Step 3: Basic Transformations with --exec¶
Use --exec to modify events or add new fields.
Add a Computed Field¶
Convert milliseconds to seconds:
Key insight: --exec runs before --filter, so the new field is available for filtering.
Modify Existing Fields¶
Normalize level to uppercase:
timestamp='2024-01-15T10:00:00Z' level='INFO' message='Application started' service='api'
version='1.2.3'
timestamp='2024-01-15T10:00:05Z' level='DEBUG' message='Loading configuration' service='api'
config_file='/etc/app/config.yml'
timestamp='2024-01-15T10:00:10Z' level='INFO' message='Connection pool initialized'
service='database' max_connections=50
Step 4: String Operations¶
Rhai provides powerful string methods for text processing.
Check if String Contains Text¶
Find events with "timeout" in the message:
Extract Parts of Strings¶
Extract just the error type from messages:
Common string methods:
- contains(substr) - Check if string contains text
- starts_with(prefix) - Check prefix
- ends_with(suffix) - Check suffix
- split(sep) - Split into array
- to_upper() / to_lower() - Change case
- trim() - Remove whitespace
- len() - String length
Step 5: Conditionals and Logic¶
Use if/else to make decisions in your transforms.
Add Severity Classification¶
kelora -j examples/simple_json.jsonl \
--exec 'e.severity = if e.level == "ERROR" || e.level == "CRITICAL" { "high" } else if e.level == "WARN" { "medium" } else { "low" }' \
-k level,severity,service,message \
--take 5
level='INFO' severity='low' service='api' message='Application started'
level='DEBUG' severity='low' service='api' message='Loading configuration'
level='INFO' severity='low' service='database' message='Connection pool initialized'
level='WARN' severity='medium' service='api' message='High memory usage detected'
level='ERROR' severity='high' service='database' message='Query timeout'
Syntax:
if condition {
// then branch
} else if another_condition {
// else-if branch
} else {
// else branch
}
Step 6: Type Conversions¶
Fields may be strings when you need numbers (or vice versa). Convert types safely.
Safe Conversion with Fallbacks¶
Use to_int_or() to handle conversion failures:
echo '{"id":"123","status":"200","invalid":"abc"}
{"id":"456","status":"404","invalid":"xyz"}' | \
kelora -j \
--exec 'e.id_num = e.id.to_int_or(-1)' \
--exec 'e.status_num = e.status.to_int_or(0)' \
--exec 'e.invalid_num = e.invalid.to_int_or(999)' \
-k id,id_num,status,status_num,invalid,invalid_num
echo '{"id":"123","status":"200","invalid":"abc"}
{"id":"456","status":"404","invalid":"xyz"}' | \
kelora -j \
--exec 'e.id_num = e.id.to_int_or(-1)' \
--exec 'e.status_num = e.status.to_int_or(0)' \
--exec 'e.invalid_num = e.invalid.to_int_or(999)' \
-k id,id_num,status,status_num,invalid,invalid_num
Safe conversion functions:
- to_int_or(fallback) - Convert to integer or use fallback
- to_float_or(fallback) - Convert to float or use fallback
- to_string() - Convert to string (always succeeds)
Step 7: Pipeline Order Matters¶
The order of --filter and --exec flags determines execution order.
❌ Wrong Order: Filter Before Creating Field¶
This won't work because duration_s doesn't exist yet:
# WRONG - will fail
kelora -j examples/simple_json.jsonl \
--filter 'e.duration_s > 1.0' \
--exec 'e.duration_s = e.duration_ms / 1000'
✅ Correct Order: Create Field Before Filtering¶
Rule: Fields must exist before you filter on them. Scripts run in CLI order.
Step 8: Checking if Fields Exist¶
Not all events have the same fields. Use has_field() to check before accessing.
Safe Field Access¶
kelora -j examples/simple_json.jsonl \
--exec 'e.has_duration = e.has_field("duration_ms")' \
--exec 'if e.has_field("duration_ms") { e.slow = e.duration_ms > 1000 } else { e.slow = false }' \
-k service,has_duration,slow,message \
--take 5
service='api' has_duration=false slow=false message='Application started'
service='api' has_duration=false slow=false message='Loading configuration'
service='database' has_duration=false slow=false message='Connection pool initialized'
service='api' has_duration=false slow=false message='High memory usage detected'
service='database' has_duration=true slow=true message='Query timeout'
Pattern:
if e.has_field("field_name") {
// Safe to access e.field_name
} else {
// Provide default behavior
}
Step 9: Debugging Your Scripts¶
When scripts don't work as expected, use these techniques.
Use -F inspect to See Types¶
Use --verbose to See Errors¶
When scripts fail in resilient mode, use --verbose to see what went wrong:
Debug workflow:
1. Use -F inspect to check field types
2. Use --verbose to see error messages
3. Use --strict to fail fast on first error
4. Add temporary fields to see intermediate values
Step 10: Multi-Stage Pipelines¶
Chain multiple --exec and --filter stages for complex logic.
Progressive Refinement¶
kelora -j examples/simple_json.jsonl \
--exec 'e.is_error = e.level == "ERROR" || e.level == "CRITICAL"' \
--exec 'e.is_slow = e.has_field("duration_ms") && e.duration_ms > 1000' \
--exec 'e.needs_attention = e.is_error || e.is_slow' \
--filter 'e.needs_attention' \
-k service,level,is_error,is_slow,message
kelora -j examples/simple_json.jsonl \
--exec 'e.is_error = e.level == "ERROR" || e.level == "CRITICAL"' \
--exec 'e.is_slow = e.has_field("duration_ms") && e.duration_ms > 1000' \
--exec 'e.needs_attention = e.is_error || e.is_slow' \
--filter 'e.needs_attention' \
-k service,level,is_error,is_slow,message
service='database' level='ERROR' is_error=true is_slow=true message='Query timeout'
service='auth' level='ERROR' is_error=true is_slow=false message='Account locked'
service='scheduler' level='INFO' is_error=false is_slow=true message='Backup completed'
service='disk' level='CRITICAL' is_error=true is_slow=false message='Disk space critical'
service='api' level='ERROR' is_error=true is_slow=false message='Service unavailable'
Pattern: Build up computed fields step-by-step, then filter on the final result.
Common Mistakes and Solutions¶
❌ Mistake 1: Accessing Missing Fields¶
Problem:
Solution:
❌ Mistake 2: String vs Number Comparison¶
Problem:
Solution:
❌ Mistake 3: Wrong Pipeline Order¶
Problem:
# Field doesn't exist yet!
kelora -j app.log --filter 'e.is_slow' --exec 'e.is_slow = e.duration > 1000'
Solution:
# Create field first, then filter
kelora -j app.log --exec 'e.is_slow = e.duration > 1000' --filter 'e.is_slow'
❌ Mistake 4: Forgetting Quotes¶
Problem:
# Shell interprets && as command separator
kelora -j app.log --filter e.level == ERROR && e.service == api
Solution:
Quick Reference¶
Accessing Fields¶
e.field_name # Direct access
e["field_name"] # Bracket notation
e.has_field("name") # Check existence
e.get("name", default) # Get with fallback
Filter Operators¶
String Methods¶
.contains("text") # Check substring
.starts_with("pre") # Check prefix
.ends_with("suf") # Check suffix
.to_upper() .to_lower() # Change case
.split(" ") # Split into array
.trim() # Remove whitespace
.len() # String length
Type Conversions¶
.to_int_or(fallback) # String → Int
.to_float_or(fallback) # String → Float
.to_string() # Any → String
Conditionals¶
Practice Exercises¶
Try these on your own:
Exercise 1: Find High-Memory Warnings¶
Filter for WARN events where memory_percent > 80:
Solution
Exercise 2: Classify Request Speeds¶
Add a speed field: "fast" if duration < 100ms, "normal" if < 1000ms, else "slow":
Solution
Exercise 3: Extract HTTP Method¶
For events with a method field, add is_safe_method (true for GET/HEAD):
Solution
Summary¶
You've learned:
- ✅ Access event fields with
e.field_name - ✅ Filter events with
--filterboolean expressions - ✅ Transform events with
--execscripts - ✅ Use string methods like
.contains(),.split(),.to_upper() - ✅ Convert types safely with
to_int_or(),to_float_or() - ✅ Write conditionals with
if/else - ✅ Check field existence with
has_field() - ✅ Debug with
-F inspectand--verbose - ✅ Understand pipeline order (exec before filter)
- ✅ Build multi-stage pipelines
Next Steps¶
Now that you understand basic Rhai scripting, continue to:
- Working with Time - Time filtering and timezone handling
- Metrics and Tracking - Aggregate data with
track_*()functions - Scripting Transforms - Advanced patterns and techniques
Related guides: - Function Reference - Complete function catalog - Rhai Cheatsheet - Quick syntax reference - How-To: Triage Production Errors - Practical examples