kelora 0.6.3

A command-line log analysis tool with embedded Rhai scripting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
/// Generate comprehensive function reference documentation
pub fn generate_help_text() -> &'static str {
    r#"
Available Rhai Functions for Kelora:

Sections: strings | arrays | maps | datetime | math | conversion | utility | tracking | file-output | events | examples
See the Rhai language guide at https://rhai.rs for syntax details.

STRING FUNCTIONS:
text.after(delimiter [,nth])         Text after occurrence of delimiter (nth: 1=first, -1=last)
text.before(delimiter [,nth])        Text before occurrence of delimiter (nth: 1=first, -1=last)
text.between(start, end)             Text between start and end delimiters
text.bucket()                        Fast hash for sampling/grouping (returns INT for modulo operations)
text.clip()                          Remove leading/trailing non-alphanumeric characters
text.col(spec [,separator])          Extract columns by index/range/list (e.g., '1', '1,3,5', '1:4')
text.contains(pattern)               Check if text contains pattern (builtin)
text.count(pattern)                  Count occurrences of pattern in text
text.decode_b64()                    Decode base64 string to text
text.decode_hex()                    Decode hexadecimal string to text
text.decode_url()                    Decode URL-encoded string
text.encode_b64()                    Encode text to base64 string
text.encode_hex()                    Encode text to hexadecimal string
text.encode_url()                    URL-encode text (percent encoding)
text.ending_with(suffix [,nth])      Return substring from start to end of suffix (nth: 1=first, -1=last)
text.escape_html()                   Escape HTML special characters (&, <, >, ", ')
text.escape_json()                   Escape JSON special characters
text.extract_all_re(pattern [,group]) Extract all regex matches as array
text.extract_domain()                Extract domain from URL or email address
text.extract_ip([nth])               Extract IP address from text (nth: 1=first, -1=last)
text.extract_ips()                   Extract all IP addresses as array
text.extract_re_maps(pattern, field) Extract regex matches as array of maps for fan-out
text.extract_re(pattern [,group])    Extract regex match or capture group
text.extract_url([nth])              Extract URL from text (nth: 1=first, -1=last)
text.has_matches(pattern)            Check if text matches regex pattern
text.hash([algo])                    Hash with algorithm (default: sha256, also: sha1, md5, xxh3, blake3)
text.index_of(pattern)               Find position of substring (-1 if not found) (builtin)
text.is_digit()                      Check if text contains only digits
text.is_in_cidr(cidr)                Check if IP address is in CIDR network (e.g., "10.0.0.0/8")
text.is_ipv4()                       Check if text is a valid IPv4 address
text.is_ipv6()                       Check if text is a valid IPv6 address
text.is_private_ip()                 Check if IP is in private ranges
text.lclip()                         Remove leading non-alphanumeric characters (left side only)
text.len                             Get string length (builtin)
text.lower()                         Convert text to lowercase
text.lstrip([chars])                 Remove leading whitespace or specified characters
text.mask_ip([octets])               Mask IP address (default: last octet)
text.parse_cef()                     Parse Common Event Format line into fields
text.parse_cols(spec [,sep])         Parse columns according to spec
text.parse_combined()                Parse Apache/Nginx combined log line
text.parse_content_disposition()     Parse Content-Disposition header parameters
text.parse_email()                   Parse email address into parts
text.parse_json()                    Parse JSON string into map/array
text.parse_jwt()                     Parse JWT header/payload without verification
text.parse_kv([sep [,kv_sep]])       Parse key-value pairs from text
text.parse_logfmt()                  Parse logfmt line into structured fields
text.parse_media_type()              Parse media type tokens and parameters
text.parse_path()                    Parse filesystem path into components
text.parse_query_params()            Parse URL query string into map
text.parse_syslog()                  Parse syslog line into structured fields
text.parse_url()                     Parse URL into structured components
text.parse_user_agent()              Parse common user-agent strings into components
text.rclip()                         Remove trailing non-alphanumeric characters (right side only)
text.replace(pattern, replacement)   Replace all occurrences of pattern (builtin)
text.rstrip([chars])                 Remove trailing whitespace or specified characters
text.slice(spec)                     Slice text using Python notation (e.g., "1:5", ":3", "-2:")
text.split_re(pattern)               Split text by regex pattern
text.split(separator)                Split string into array by delimiter (builtin)
text.starting_with(prefix [,nth])    Return substring from prefix to end (nth: 1=first, -1=last)
text.strip([chars])                  Remove whitespace or specified characters
text.sub_string(start [,length])     Extract substring from position (builtin)
text.to_float()                      Convert text to float (returns () on error)
text.to_int()                        Convert text to integer (returns () on error)
text.to_lower()                      Convert to lowercase (builtin)
text.to_upper()                      Convert to uppercase (builtin)
text.trim()                          Remove whitespace from start and end (builtin)
text.unescape_html()                 Unescape HTML entities to text
text.unescape_json()                 Unescape JSON escape sequences
text.upper()                         Convert text to uppercase
  
ARRAY FUNCTIONS:
array.all(|item| condition)          Check if all elements match condition (builtin)
array.contains_any(search_array)     Check if array contains any search values
array.contains(value)                Check if array contains value (builtin)
array.filter(|item| condition)       Keep elements matching condition (builtin)
array.flatten([style [,max_depth]])  Flatten nested arrays/objects
array.join(separator)                Join array elements with separator
array.len                            Get array length (builtin)
array.map(|item| expression)         Transform each element (builtin)
array.max()                          Find maximum value in array (no auto string-to-number coercion)
array.min()                          Find minimum value in array (no auto string-to-number coercion)
array.parse_cols(spec [,sep])        Apply column spec to pre-split values
array.percentile(pct)                Calculate percentile of numeric array
array.pop()                          Remove and return last item (builtin)
array.push(item)                     Add item to end of array (builtin)
array.reduce(|acc, item| expr, init) Aggregate array into single value (builtin)
array.reversed()                     Return new array in reverse order
array.some(|item| condition)         Check if any element matches condition (builtin)
array.sort()                         Sort array in place (builtin)
array.sorted_by(field)               Sort array of objects by field name
array.sorted()                       Return new sorted array (numeric/lexicographic)
array.starts_with_any(search_array)  Check if array starts with any search values
array.unique()                       Remove all duplicate elements (preserves first occurrence)
  
MAP/OBJECT FUNCTIONS:
map.contains("key")                  Check if map contains key (ignores value) (builtin)
map.flatten([separator [,style]])    Flatten nested object to dot notation
map.get_path("field.path" [,default]) Safe nested field access with fallback
map.has_field("key")                 Check if map contains key with non-unit value
map.has_path("field.path")           Check if nested field path exists
map.merge(other_map)                 Merge another map into this one
map.path_equals("path", value)       Safe nested field comparison
map.rename_field("old", "new")       Rename a field, returns true if successful
map.to_cef()                         Convert map to Common Event Format (CEF) string
map.to_combined()                    Convert map to Apache/Nginx combined log format
map.to_json([pretty])                Convert map to JSON string
map.to_kv([sep [,kv_sep]])           Convert map to key-value string with separators
map.to_logfmt()                      Convert map to logfmt format string
map.to_syslog()                      Convert map to syslog format string
map.unflatten([separator])           Reconstruct nested object from flat keys
  
DATETIME FUNCTIONS:
now_utc()                            Current UTC timestamp (DateTimeWrapper)
now_local()                          Current local timestamp (DateTimeWrapper)
to_datetime(text [,fmt [,tz]])       Convert string into DateTimeWrapper with optional hints
to_duration("1h30m")                 Convert duration string into DurationWrapper
duration_from_<unit>(n)              Create duration from seconds/minutes/hours/days/ms/ns
humanize_duration(ms)                Convert milliseconds to human-readable format (e.g., "1h 30m")
dt.to_iso()                          Convert datetime to ISO 8601 string
dt.format("format_string")           Format datetime using custom format string (see --help-time)
dt.year(), dt.month(), dt.day()      Extract date components
dt.hour(), dt.minute(), dt.second()  Extract time components
dt.to_utc(), dt.to_local()           Convert timezone
dt.to_timezone("tz_name")            Convert to named timezone
dt.timezone_name()                   Get timezone name as string
dt.ts_nanos()                        Get timestamp as nanoseconds
dt + duration, dt - duration         Add/subtract duration from datetime
dt1 - dt2                            Get duration between datetimes (returns DurationWrapper)
dt1 == dt2, dt1 != dt2               Compare datetimes for equality
dt1 > dt2, dt1 < dt2                 Compare datetimes (greater/less than)
dt1 >= dt2, dt1 <= dt2               Compare datetimes (greater/less or equal)
duration.as_seconds()                Convert duration to seconds
duration.as_milliseconds()           Convert duration to milliseconds
duration.as_nanoseconds()            Convert duration to nanoseconds
duration.as_minutes()                Convert duration to minutes
duration.as_hours()                  Convert duration to hours
duration.as_days()                   Convert duration to days
duration.to_string()                 Format duration as human-readable string (e.g., "1h 30m")
duration1 + duration2                Add durations
duration1 - duration2                Subtract durations (always returns positive result)
duration1 == duration2               Compare durations for equality
duration1 > duration2, duration1 < duration2  Compare durations (greater/less than)
duration1 >= duration2, duration1 <= duration2  Compare durations (greater/less or equal)

MATH FUNCTIONS:
abs(x)                               Absolute value of number
clamp(value, min, max)               Constrain value to be within min/max range
floor(x)                             Round down to nearest integer
mod(a, b) / a % b                    Modulo operation with division-by-zero protection
rand()                               Random float between 0 and 1
rand_int(min, max)                   Random integer between min and max (inclusive)
round(x)                             Round to nearest integer

TYPE CONVERSION FUNCTIONS:
to_int(value)                        Convert value to integer (returns () on error)
to_float(value)                      Convert value to float (returns () on error)
to_bool(value)                       Convert value to boolean (returns () on error)
to_int_or(value, default)            Convert value to integer with fallback
to_float_or(value, default)          Convert value to float with fallback
to_bool_or(value, default)           Convert value to boolean with fallback

UTILITY FUNCTIONS:
eprint(message)                      Print to stderr (suppressed with -qqq)
exit(code)                           Exit kelora with given exit code
get_env(var [,default])              Get environment variable with optional default
print(message)                       Print to stdout (suppressed with -qqq)
pseudonym(value, domain)             Generate domain-separated pseudonym (requires KELORA_SECRET)
read_file(path)                      Read file contents as string
read_lines(path)                     Read file as array of lines
type_of(value)                       Get type name as string (builtin)
window_numbers(field)                Get numeric field values from current window (requires --window)
window_values(field)                 Get field values from current window (requires --window)

TRACKING/METRICS FUNCTIONS (requires --metrics):
track_bucket(key, bucket)            Track values in buckets for histograms
track_count(key)                     Increment counter for key by 1
track_max(key, value)                Track maximum value for key
track_min(key, value)                Track minimum value for key
track_sum(key, value)                Accumulate numeric values for key
track_unique(key, value)             Track unique values for key

FILE OUTPUT (requires --allow-fs-writes):
append_file(path, text_or_array)     Append line(s) to file; arrays append one line per element
mkdir(path [,recursive])             Create directory (set recursive=true to create parents)
truncate_file(path)                  Create or zero-length a file for fresh output

EVENT MANIPULATION:
emit_each(array [,base_map])         Fan out array elements as separate events (returns emitted count)
e = ()                               Clear entire event (remove all fields)
e.field = ()                         Remove individual field from event
e.rename_field("old", "new")         Rename field, returns true if successful

Rhai lets you call functions as either `value.method(args)` or `function(value, args)`.
Use 'e' to access the current event. See --help-examples for common usage patterns.
"#
}

/// Generate practical examples for common log analysis patterns
pub fn generate_examples_text() -> &'static str {
    r#"
Common Log Analysis Patterns:

WEB LOG ANALYSIS:
# Extract HTTP details from combined log
kelora -f combined --exec 'e.slow = e.request_time > 1.0' --filter 'e.slow'

# Parse request path parameters
kelora -f combined --exec '
  let params = e.path.after("?").parse_query_params();
  e.utm_source = params.get_path("utm_source", "");
  e.user_id = params.get_path("user_id", "").to_int()
'

# Mask IPs and extract domains from referers
kelora -f json --exec 'e.ip = e.client_ip.mask_ip(2)' \
  --exec 'e.referer_domain = e.get_path("referer", "").extract_domain()'

# Detect suspicious user agents
kelora -f combined --filter 'e.user_agent.has_matches("(?i)(bot|crawler|scanner)")'

ERROR TRACKING:
# Extract stack traces and error types
kelora -f json --filter 'e.level == "ERROR"' --exec '
  e.error_type = e.error.before(":");
  e.line_number = e.error.extract_re(r"line (\d+)", 1).to_int()
'

# Group errors by hash for deduplication
kelora -f json -l error --exec 'e.error_hash = e.message.hash("xxh3")' \
  --metrics --exec 'track_unique("errors", e.error_hash)'

# Time-based error clustering (5min windows)
kelora -f json -l error --window 100 --exec '
  let recent = window_values("timestamp").map(|ts| to_datetime(ts));
  let time_span = (recent[-1] - recent[0]).as_minutes();
  e.error_burst = time_span < 5 && recent.len() > 10
' --filter 'e.error_burst'

SECURITY & AUDIT:
# Check for private IPs in external traffic
kelora -f json --filter 'e.source_ip.is_ipv4() && !e.source_ip.is_private_ip()'

# JWT token analysis without verification
kelora -f json --exec 'let jwt = e.token.parse_jwt(); e.user_id = jwt.sub; e.role = jwt.role'

# Detect CIDR-based access patterns
kelora -f json --exec 'e.internal = e.ip.is_in_cidr("10.0.0.0/8")' --filter '!e.internal'

# Hash sensitive fields with domain separation
export KELORA_SECRET="your-secret-key"
kelora -f json --exec 'e.user_hash = pseudonym(e.email, "users")' --exec 'e.email = ()'

DATA TRANSFORMATION:
# Parse nested JSON strings
kelora -f json --exec 'e.metadata = e.json_payload.parse_json()' \
  --exec 'e.user_tier = e.get_path("metadata.subscription.tier", "free")'

# Column extraction from structured text
kelora -f line --exec 'e.cols = e.line.col("1,3,5", " ")' \
  --exec 'e.timestamp = e.cols[0]; e.level = e.cols[1]; e.msg = e.cols[2]'

# Parse key-value logs (multiple formats)
kelora -f line --exec 'e = e.line.parse_logfmt()'  # logfmt: key=value
kelora -f line --exec 'e = e.line.parse_kv(" ", "=")'  # custom separators

# Email header parsing
kelora -f line --exec 'let email = e.from.parse_email(); e.domain = email.domain; e.user = email.local'

METRICS & AGGREGATION:
# Response time percentiles (requires --window)
kelora -f json --window 1000 --metrics --end '
  let times = window_numbers("response_time");
  print("p50: " + times.percentile(50));
  print("p95: " + times.percentile(95));
  print("p99: " + times.percentile(99))
'

# Status code distribution
kelora -f combined --metrics --exec 'track_bucket("status", e.status / 100 * 100)' \
  --end 'print(metrics.status)'

# Unique users per endpoint
kelora -f json --metrics --exec 'track_unique(e.path, e.user_id)' \
  --end 'for (path, users) in metrics { print(path + ": " + users.len() + " users") }'

ARRAY & FAN-OUT PROCESSING:
# Process nested arrays - fan out items
kelora -f json --exec 'emit_each(e.items)' --filter 'e.status == "active"'

# Multi-level fan-out with context preservation
kelora -f json --exec 'emit_each(e.batches)' \
  --exec 'let ctx = #{batch: e.id}; emit_each(e.items, ctx)' \
  --filter 'e.priority == "high"'

# Array transformations and sorting
kelora -f json --exec 'e.top_scores = sorted(e.scores)[-3:]' \
  --exec 'e.winners = sorted_by(e.players, "score")[-5:].map(|p| p.name)'

# Remove duplicate elements
kelora -f json --exec 'e.unique_tags = unique(e.tags)'

DATETIME & FILTERING:
# Recent events (last 2 hours)
kelora -f json --since -2h --until now

# Business hours filter (9-5 local time)
kelora -f json --exec 'let dt = to_datetime(e.timestamp).to_local(); e.hour = dt.hour()' \
  --filter 'e.hour >= 9 && e.hour < 17'

# Calculate durations and SLA violations
kelora -f json --exec '
  let start = to_datetime(e.start_time);
  let end = to_datetime(e.end_time);
  let duration = end - start;
  e.duration_ms = duration.as_milliseconds();
  e.sla_breach = duration.as_seconds() > 5
' --filter 'e.sla_breach'

ADVANCED PATTERNS:
# Sampling - process 10% of events
kelora -f json --filter 'e.request_id.bucket() % 10 == 0'

# Conditional field removal based on sensitivity
kelora -f json --exec 'if e.level != "DEBUG" { e.stack_trace = (); e.locals = () }'

# Dynamic field creation from arrays
kelora -f json --exec 'for (idx, tag) in e.tags { e["tag_" + idx] = tag }'

# Format conversion pipeline
kelora -f json --exec 'e.syslog_compat = e.to_syslog()' -F json > output.jsonl

# CI/CD integration with exit codes
kelora -f json -qq -l error logs/*.json && echo "✓ No errors" || echo "✗ Errors found"

# Export results to file from script
kelora -f json --allow-fs-writes --exec '
  if e.severity == "critical" {
    append_file("alerts.log", e.to_json())
  }
'

MULTI-FILE & METADATA:
# Track errors by source file (meta.filename)
kelora -f json logs/*.log --metrics --exec '
  if e.level == "ERROR" {
    track_count(meta.filename)
  }
'

# Add source context to each event
kelora -f json server1.log server2.log --exec 'e.source = meta.filename'

# Debugging with line numbers (meta.line_num)
kelora -f json --filter 'e.status >= 500' --exec '
  eprint("⚠️  Server error at " + meta.filename + ":" + meta.line_num)
'

# Conditional processing based on filename
kelora -f json prod-*.log staging-*.log --exec '
  e.environment = if meta.filename.contains("prod") { "production" } else { "staging" }
' --filter 'e.environment == "production" && e.level == "ERROR"'

# Access raw line for re-parsing (meta.line)
kelora -f json --exec '
  if e.message.contains("CUSTOM:") {
    let custom = meta.line.after("CUSTOM:").parse_json();
    e.custom_data = custom
  }
'

# Track unique files with errors
kelora -f json logs/**/*.log --metrics --exec '
  if e.level == "ERROR" {
    track_unique("error_files", meta.filename)
  }
' --end 'print("Files with errors: " + metrics.error_files.len())'

# Create audit trail with source location
kelora -f json --allow-fs-writes --exec '
  if e.action == "admin_action" {
    let audit = "File: " + meta.filename + " Line: " + meta.line_num + " Event: " + e.to_json();
    append_file("audit.log", audit)
  }
'

COMMON IDIOMS:
# Method chaining              → e.domain = e.url.extract_domain().to_lower().strip()
# Safe nested access           → e.get_path("user.role", "guest")
# Safe type conversion         → to_int_or(e.port, 8080)
# Check field exists           → e.has_path("user.id")
# Remove sensitive fields      → e.password = (); e.ssn = ()
# Hash for grouping/sampling   → e.session_id.bucket() % 100
# Parse then extract           → e.url.parse_url().path
# Regex capture groups         → e.log.extract_re(r"duration: (\d+)", 1)
# Array bounds safety          → if e.items.len() > 0 { e.first = e.items[0] }
# Negative indexing            → e.last_score = e.scores[-1]
# Clamp values to range        → e.normalized = clamp(e.value, 0, 100)
# Remove duplicate elements    → unique([1,2,2,3,2,1]) = [1,2,3]

See --help-functions for complete function reference. Visit https://rhai.rs for Rhai language details.
"#
}