Events and Fields¶
Understanding how Kelora represents log data as structured events and how to access their fields.
What is an Event?¶
An event is Kelora's internal representation of a single log entry. After parsing, each log line becomes an event (a map/object) with fields that you can access and transform.
// In Rhai scripts, the current event is available as 'e'
e.timestamp // Access field directly
e.level // Access another field
e.message // And so on
Event Structure¶
Events are maps (key-value pairs) where:
- Keys are field names (strings)
- Values can be any JSON-compatible type
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "ERROR",
"service": "api",
"user": {
"id": 12345,
"name": "alice"
},
"tags": ["authentication", "security"],
"duration_ms": 1234
}
After parsing, this becomes an event with fields accessible as:
e.timestamp→"2024-01-15T10:30:00Z"e.level→"ERROR"e.service→"api"e.user→ Map withidandnamefieldse.tags→ Array["authentication", "security"]e.duration_ms→1234
Field Types¶
Kelora preserves JSON types after parsing:
| Type | Example Value | Rhai Access |
|---|---|---|
| String | "error" |
e.level |
| Integer | 404 |
e.status |
| Float | 1.234 |
e.duration |
| Boolean | true |
e.success |
| Null | null |
e.optional_field → () |
| Object/Map | {"key": "value"} |
e.metadata |
| Array | [1, 2, 3] |
e.scores |
Unit Type ():
In Rhai, null from JSON becomes the unit type (), representing "no value" or "empty".
Field Access Patterns¶
Direct Access¶
For simple field names (alphanumeric, no special characters):
Nested Access¶
Access nested fields using dot notation:
Important: Direct nested access requires the field to exist. If the field might be missing, check first or use get_path():
// Safe: Check before access
if "user" in e && "name" in e.user {
e.user.name
}
// Safer: Use get_path with default
e.get_path("user.name", "unknown") // Returns "unknown" if missing
e.get_path("metadata.region", "us-west") // Fallback value
Array Access¶
Access array elements by index:
e.tags[0] // First element
e.tags[1] // Second element
e.tags[-1] // Last element (negative indexing)
e.tags[-2] // Second-to-last element
Bracket Notation¶
For field names with special characters or dynamic access:
e["content-type"] // Hyphens in field name
e["@timestamp"] // @ symbol in name
e["user-agent"] // Multiple special chars
e.headers["authorization"] // Nested with special chars
Deep Nested Access¶
Combine patterns for complex structures:
e.user.addresses[0].city // Object → array → object
e.data.items[-1].metadata.tags[0] // Multiple levels
e.response.headers["content-type"] // Object → bracket notation
Safe Field Access¶
Check Field Existence¶
Before accessing fields, check if they exist:
// Top-level field
if "field" in e {
e.field
} else {
"default"
}
// Nested field
if e.has_path("user.role") {
e.user.role
} else {
"guest"
}
Array Bounds Checking¶
Check array length before accessing elements:
if e.scores.len() > 0 {
e.scores[0]
} else {
0
}
// Last element safely
if e.items.len() > 0 {
e.items[-1]
} else {
#{}
}
Safe Path Access¶
Use get_path() for safe nested access with defaults:
// Returns default if path doesn't exist
e.user_role = e.get_path("user.role", "guest")
e.first_tag = e.get_path("tags[0]", "untagged")
e.response_code = e.get_path("response.status", 0)
// Complex nested paths
e.city = e.get_path("user.address.city", "unknown")
Type Checking¶
Check if field has a value (not unit type):
Modifying Events¶
Add Fields¶
Assign values to new or existing fields:
Modify Existing Fields¶
Transform field values in place:
Remove Fields¶
Assign unit () to remove fields:
Removed fields won't appear in output.
Remove Entire Event¶
Clear all fields to filter out the event:
Empty events are counted as "filtered" in statistics.
Field Name Patterns¶
Common Field Names¶
Kelora recognizes these standard field names across formats:
Timestamps:
timestamp,ts,time,@timestamp
Log Levels:
level,severity,loglevel
Messages:
message,msg,text
Identifiers:
id,request_id,trace_id,span_id
Metadata:
service,host,hostname,source
Format-Specific Fields¶
Different parsers add format-specific fields:
JSON (-f json):
- Preserves all original fields
- Nested structures maintained
Syslog (-f syslog):
hostname,appname,procid,msgidfacility,severity
Combined/Apache (-f combined):
ip,timestamp,request,status,bytesmethod,path,protocolreferer,user_agentrequest_time(NGINX only)
CSV (-f csv):
- Column names from header row
- Or
col_0,col_1,col_2, etc. without header
Logfmt (-f logfmt):
- All key-value pairs as top-level fields
Working with Nested Structures¶
Objects/Maps¶
Access nested maps using dot notation:
Arrays¶
Process arrays with Rhai array functions:
kelora -f json input.log \
--exec 'e.tag_count = e.tags.len()' \
--exec 'e.first_tag = e.get_path("tags[0]", "none")' \
--exec 'e.unique_tags = unique(e.tags)' \
--keys timestamp,tag_count,first_tag,unique_tags
Fan-Out Arrays¶
Convert array elements to individual events:
Each element in e.items becomes a separate event.
Mixed Structures¶
Handle complex nested structures:
{
"user": {
"id": 12345,
"scores": [85, 92, 78],
"metadata": {
"tags": ["premium", "verified"]
}
}
}
Access patterns:
e.user_id = e.user.id // Nested object
e.first_score = e.user.scores[0] // Object → array
e.last_score = e.user.scores[-1] // Negative index
e.avg_score = e.user.scores.sum() / e.user.scores.len()
e.first_tag = e.user.metadata.tags[0] // Deep nesting
e.is_premium = "premium" in e.user.metadata.tags // Array membership
Field Naming Conventions¶
Output Format: Bracket Notation¶
The default formatter uses bracket notation for arrays:
scores[0]=85 scores[1]=92 scores[2]=78
user.name=alice user.scores[0]=85
items[0].id=1 items[0].status=active
This matches the path syntax used in get_path():
// Access and path syntax are consistent
e.get_path("scores[0]", 0) // Access first score
e.get_path("items[1].status", "") // Access nested array element
Field Selection: Top-Level Only¶
The --keys parameter operates on top-level fields only:
# ✅ Supported: Select top-level fields
kelora -f json input.log --keys user,timestamp,message
# ❌ Not supported: Nested paths in --keys
kelora -f json input.log --keys user.name,scores[0]
To extract nested fields, use --exec to promote them to top-level:
kelora -f json input.log \
--exec 'e.user_name = e.get_path("user.name", "")' \
--exec 'e.first_score = e.get_path("scores[0]", 0)' \
--keys user_name,first_score
Common Patterns¶
Extract Nested Fields¶
kelora -f json app.log \
--exec 'e.user_name = e.get_path("user.name", "unknown")' \
--exec 'e.user_role = e.get_path("user.role", "guest")' \
--keys timestamp,user_name,user_role
Flatten Structures¶
kelora -f json app.log \
--exec 'e.request_method = e.request.method' \
--exec 'e.request_path = e.request.path' \
--exec 'e.request = ()' \
--keys timestamp,request_method,request_path
Combine Fields¶
kelora -f json app.log \
--exec 'e.full_name = e.first_name + " " + e.last_name' \
--exec 'e.endpoint = e.method + " " + e.path' \
--keys timestamp,full_name,endpoint
Conditional Field Creation¶
kelora -f json app.log \
--exec 'if e.status >= 500 { e.severity = "critical" } else if e.status >= 400 { e.severity = "warning" } else { e.severity = "normal" }' \
--keys timestamp,status,severity
Array Transformations¶
kelora -f json app.log \
--exec 'e.tag_count = e.tags.len()' \
--exec 'e.sorted_tags = sorted(e.tags)' \
--exec 'e.unique_tags = unique(e.tags)' \
--keys timestamp,tag_count,sorted_tags
Safe Deep Access¶
kelora -f json app.log \
--exec 'e.city = e.get_path("user.address.city", "N/A")' \
--exec 'e.zip = e.get_path("user.address.zip", "00000")' \
--exec 'e.country = e.get_path("user.address.country", "Unknown")' \
--keys city,zip,country
Type Conversions¶
String to Number¶
e.status_code = e.status.to_int() // String to integer
e.duration = e.duration_str.to_float() // String to float
With defaults if conversion fails:
Number to String¶
Boolean Conversions¶
e.is_error = to_bool(e.error_flag) // "true"/"false" to boolean
e.success = to_bool_or(e.status_ok, false) // With default
Type Checking¶
e.field_type = type_of(e.field)
// Common checks
if type_of(e.field) == "i64" { /* integer */ }
if type_of(e.field) == "f64" { /* float */ }
if type_of(e.field) == "string" { /* string */ }
if type_of(e.field) == "array" { /* array */ }
if type_of(e.field) == "map" { /* object/map */ }
if type_of(e.field) == "()" { /* null/empty */ }
Field Access Performance¶
Direct vs Path Access¶
Direct access is fastest for known fields:
Use direct access when: - Field names are known at script time - Fields are guaranteed to exist (e.g., parser output) - Performance is critical
Path access provides safety and flexibility:
e.get_path("level", "INFO") // Safe with default
e.get_path("user.name", "unknown") // Handles missing fields
Use get_path() when:
- Fields might not exist (optional data)
- Working with inconsistent log formats
- You need default values for missing fields
- Path is dynamic or comes from configuration
Hybrid approach for best results:
// Check existence, then use direct access for speed
if "user" in e {
e.user_name = e.user.name // Fast once verified
} else {
e.user_name = "unknown"
}
// Or use get_path for one-liners
e.user_name = e.get_path("user.name", "unknown") // Simpler but slower
Field Existence Checks¶
Fastest - direct membership:
Flexible - path checking:
Array Operations¶
Array functions create new arrays (not in-place):
// Creates new sorted array
e.sorted_scores = sorted(e.scores)
// Original array unchanged
e.scores // Still unsorted
For large arrays, avoid unnecessary transformations.
Troubleshooting¶
Field Not Found¶
Problem: Accessing non-existent field causes error.
Solution: Use safe access patterns:
// Check before access
if "field" in e {
e.field
}
// Use get_path with default
e.get_path("field", "default")
Array Index Out of Bounds¶
Problem: Accessing array element beyond length.
Solution: Check array length first:
Type Mismatch¶
Problem: Field has unexpected type.
Solution: Use type conversion with defaults:
Nested Field Access Errors¶
Problem: Accessing non-existent nested fields causes errors.
Solution: Check field existence before access or use get_path():
// Error if user or name doesn't exist
e.user.name = "alice"
// Safe: Check first
if "user" in e {
e.user.name = "alice"
}
// Safest: Use get_path for reading
let current = e.get_path("user.name", "default")
Note: Direct nested assignment (e.user.name = "alice") works fine when the parent object exists.
Field Name with Special Characters¶
Problem: Field names contain hyphens, dots, or other special chars.
Solution: Use bracket notation:
See Also¶
- Pipeline Model - How events flow through processing stages
- Scripting Stages - Using --begin/--exec/--end with events
- Function Reference - All field manipulation functions
- Rhai Cheatsheet - Quick reference for Rhai syntax