Skip to content

Lua Rules

In addition to the ability to use basic regular-expression style rules, there is the option to use the Lua language to create LogZilla rules. Lua is a simple computer language that is used across a broad variety of applications generally to provide sophisticated client customization of the application operation.

Overview

There are some core concepts involved in LogZilla's use of Lua rules. More detailed descriptions of these concepts will be provided below.

Though Lua does support regular expression use, that use is contraindicated and is not detailed here. Instead, the recommended approach is to use the faster regular-expression-like mechanism particular to Lua: LPEG (Lua Parsing Expression Grammar). LPEG is a more efficient means of implementing pattern matching which leads to higher event processing rates (EPS / events-per-second) rates for LogZilla.

There are examples of these files given below under Examples.

Tutorial

New Lua ("rewrite") rules are provided to LogZilla in the form of two files: a file containing the Lua logic to perform the rule processing; and a file containing test examples that LogZilla performs when loading the new rule. These files should be named similarly to 123-ruletitle.lua and 123-ruletitle.tests.yaml, corresponding to the Lua rule file and the tests file (these could be 123-mswindows.lua and 123-mswindows.tests.yaml as an example).

The Lua rule file is a plain text file that consists only of valid Lua code. The tests file is a YAML text file that describes a sample incoming log event and then what the event data should be following processing by the Lua rule.

The first example will be a simple demonstration of checking the program field of an incoming event for the value - and replacing it with the value Unknown.

It is recommended to write the tests file before the rule file. This helps clarify what the actual input will be that the rule needs to handle, and what processing the rule is to perform.

During loading of a new rule (or instructing testing thereof), LogZilla first reads the Lua rule file and verifies that it is valid Lua code, then executes the process function within that Lua code and provides as the data in the event function argument the data that is detailed in the tests file. After executing the function LogZilla compares the (modified) data in the event argument to the data that is detailed in the tests file, and if the data that is the result of the processing matches the data indicated in the tests file, success is the result.

The tests file starts with the line TEST_CASES:. This is followed by one or more individual tests -- it is recommended that there be at least one test for each different "type" of log message the rule is supposed to handle.

Each test begins with - event. This indicates the start of a single test and the start of the "incoming" event data for this test. This line is followed by sub-lines with key names corresponding to the event fields described above (such as program, host, timestamp, etc.), and the values indicated on those lines being that desired as the sample incoming data for the log event.

Then the second portion of this test is indicated by the line expect:, which indicates that the following data is the "expected" result data after processing by the rule. Similar to the event: portion of the test, the expect: portion of the test is divided into sub-line with key names again corresponding to the event fields described above, and the values being the "expected" event data after processing by the rule.

(On a more involved topic, as detailed in the reference below, the elements for extra_fields (for JSON data) and user_tags (for user tags set by the rule) are followed by indented lines, on each line being the sub-fields or elements of that dictionary.)

In the case of this simple example the tests file would be (using valid YAML):

TEST_CASES:
- event:
    program: "-"
  expect:
    program: Unknown

This indicates that for a log event with a program field of -, the expectation is that after rule processing the program field will instead be Unknown.

For the Lua rule, there must be a Lua function titled process expecting one argument (by convention usually named event). This function is executed one time for every log event encountered by LogZilla, with that log event as the function argument event.

Given the desired operation indicated in the tests file above, the corresponding rule file would be:

function process(event)
    event.program = 'Unknown'
end

This Lua rule checks the program field provided on the log event; if that program field is -, the rule changes that field to be Unknown.

This rule (and tests file) is now ready for use and can be verified and validated for correctness. This is accomplished using the logzilla rules test –path command-line utility, as in the following:

$ logzilla rules test --path tut1.lua
================================= test session starts ==================================
platform linux -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /tmp
collected 1 item

tut1.tests.yaml::test_case_1 PASSED                                              [100%]

================================== 1 passed in 0.02s ===================================

This shows that upon execution of the rules test command, LogZilla successfully validated the Lua code and verified that the rule performed as expected.

For demonstrating a failure to verify the results of the rule processing, the tests could be modified as follows (so that the execution of the rule will not result in the indicated test result):

TEST_CASES:
- event:
    program: "-"
  expect:
    program: Unknown
- event:
    program: syslog
  expect:
    program: syslog

Now upon execution of logzilla rules test --path tut1.lua the following result would be received:

$ logzilla rules test --path tut1.lua
================================= test session starts ==================================
platform linux -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /tmp
collected 2 items

tut1.tests.yaml::test_case_1 PASSED                                              [ 50%]
tut1.tests.yaml::test_case_2 FAILED                                              [100%]

======================================= FAILURES =======================================
_____________________________ tut1.tests.yaml::test_case_2 _____________________________
Failed test at /tmp/tut1.tests.yaml:6:
- event:
    program: syslog
  expect:
    program: syslog

Event before:
cisco_mnemonic: ''
counter: 1
facility: 0
first_occurrence: 1617280957288796
host: ''
id: 0
last_occurrence: 1617280957288796
message: Some random message
program: syslog
severity: 0
status: 0
user_tags: {}

Event after:
cisco_mnemonic: ''
counter: 1
facility: 0
first_occurrence: 1617280957288796
host: ''
id: 0
last_occurrence: 1617280957288796
message: Some random message
program: Unknown
severity: 0
status: 0
user_tags: {}

Error: Wrong value of program, got: "Unknown", expected: "syslog"
=============================== short test summary info ================================
FAILED ../../../tmp/tut1.tests.yaml::test_case_2 - Wrong value of program, got: "Unkn...
============================= 1 failed, 1 passed in 0.02s ==============================

This shows that the first test succeeded but the second failed. The results show the log event details before and after rule execution, and in detail show the discrepancy between the expected results and the received results.

For this example, to modify the rule so that the given test succeeds the rule could be changed as follows:

function process(event)
    if event.program == '-' then
        event.program = 'Unknown'
    end
end

This causes the rule to only modify the program field of the event when that program field is -; this will cause the execution of the first test to meet that condition and execute the conditional behavior but will cause the second test to not meet that condition which will then leave the program field unmodified.

When tested now the rule will pass:

$ logzilla rules test --path tut1.lua
================================= test session starts ==================================
platform linux -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /tmp
collected 2 items

tut1.tests.yaml::test_case_1 PASSED                                              [ 50%]
tut1.tests.yaml::test_case_2 PASSED                                              [100%]

================================== 2 passed in 0.02s ===================================

At this point the rule can be added to LogZilla:

$ logzilla rules add tut1.lua
Rule tut1 added and enabled
Reloading rules ...
Rules reloaded

The addition can be verified:

$ logzilla rules list
Name            Source    Type    Status
--------------  --------  ------  --------
600-lz-program  system    lua     enabled
tut1            user      lua     enabled

This is the process that should be followed when implementing new rules: the creation of the tests file, creation of the rule, testing the rule, addition of the rule. At this point, the rule would be active and would run upon receipt of every log message. If desired further verification can be performed using the logzilla sender command to perform actual receipt of (predetermined) log messages and viewing of the results in the LogZilla user interface.

Handling Errors

There are three types of errors that can be encountered when adding new rules to LogZilla: the rule can be invalid Lua and be unable to be interpreted; the rule can result in a Lua execution failure while running (a runtime error), or the results of rule execution do not match the expected results as detailed in the tests file.

Invalid Lua Errors

Invalid Lua errors are recognized when adding the rule. An example of such an error would be:

junction process(event)
    if event.program == '-' then
        event.program = 'Unknown'
    end
end

This example states junction rather than function causing the Lua interpreter to not understand the intent.

Now when testing or loading the rule the following error would be received:

$ logzilla rules test --path err.lua
================================= test session starts ==================================
platform linux -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /tmp
collected 1 item

err.tests.yaml::test_case_1 ERROR                                                [100%]

======================================== ERRORS ========================================
____________________ ERROR at setup of err.tests.yaml::test_case_1 _____________________
Error in rule /tmp/err.lua
-> junction process(event)
       if event.program == '-' then
           event.program = 'Unknown'
       end
   end

Error loading rule err.lua
sol: syntax error: /tmp/err.lua:1: '=' expected near 'process'
-------------------------------- Captured stderr setup ---------------------------------
[sol3] An error occurred and has been passed to an error handler: sol: syntax error: /tmp/err.lua:1: '=' expected near 'process'
 lz.Rule    WARNING  Rule err.lua validation errors:
 lz.Rule    WARNING  ... sol: syntax error: /tmp/err.lua:1: '=' expected near 'process'
---------------------------------- Captured log setup ----------------------------------
WARNING  lz.Rule:rules.py:151 Rule err.lua validation errors:
WARNING  lz.Rule:rules.py:153 ... sol: syntax error: /tmp/err.lua:1: '=' expected near 'process'
=============================== short test summary info ================================
ERROR ../../../tmp/err.tests.yaml::test_case_1 - Error loading rule err.lua
=================================== 1 error in 0.34s ===================================

This output details the location and nature of the problem:

Error in rule /tmp/err.lua
-> junction process(event)

shows the actual source code line with the problem, and sol: syntax error: /tmp/err.lua:1: '=' expected near 'process' details the nature of the error (in this case this is indicating that Lua is interpreting junction as a variable declaration and is expecting it to be followed by = and the variable value).

Since the rule is not valid Lua the tests file cannot be run (to determine if the expected results match those returned).

Lua Execution Errors

Lua execution errors are errors in which, although the Lua is syntactically and grammatically correct and is "understood" by Lua when running the Lua rule results in a Lua error or failure condition (before completion).

An example of a Lua rule exhibiting this scenario:

function process(event)
    call_some_unexistent_function()
    if event.program == '-' then
        event.program = 'Unknown'
    end
end

As shown, call_some_unexistent_function() is understood by Lua to be a request for execution of that function, and thus is valid Lua; however upon execution, since no such function was defined in the rule Lua is unable to find and execute that function and is unable to complete execution.

The following error would be received:

$ logzilla rules test --path err.lua
================================= test session starts ==================================
platform linux -- Python 3.8.5, pytest-6.2.2, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /tmp
collected 1 item

err.tests.yaml::test_case_1 FAILED                                               [100%]

======================================= FAILURES =======================================
_____________________________ err.tests.yaml::test_case_1 ______________________________
Error in rule /tmp/err.lua
   function process(event)
->     call_some_unexistent_function()
       if event.program == '-' then
           event.program = 'Unknown'
       end
   end

sol: runtime error: /tmp/err.lua:2: attempt to call global 'call_some_unexistent_function' (a nil value)
stack traceback:
    /tmp/err.lua:2: in function </tmp/err.lua:1>
--------------------------------- Captured stderr call ---------------------------------
2021-04-06 14:48:11.785641 lz.parser WARNING Error in LUA rule: /tmp/err.lua:2: attempt to call global 'call_some_unexistent_function' (a nil value)
stack traceback:
    /tmp/err.lua:2: in function </tmp/err.lua:1>
2021-04-06 14:48:11.785685 lz.parser WARNING Failure of rule err.lua
=============================== short test summary info ================================
FAILED ../../../tmp/err.tests.yaml::test_case_1 - sol: runtime error: /tmp/err.lua:2:...
================================== 1 failed in 0.02s ===================================

Like the previous example, the error text indicates the line, location, and reason for the error, but also (for more advanced users) includes the "stack trace" showing the (nested) function execution resulting in the error.

Runtime Errors That Pass Tests

In some scenarios, the rule will pass tests (including syntax/grammar, execution, and results validation) but when used "live" will result in errors.

An example scenario would be similar to the above rule with the invalid function call_some_unexistent_function() but attempting to execute it only in certain conditions (in this case a condition not exercised by the tests file, which reinforces the need for the tests file to check all "types" of log messages received by the rule):

function process(event)
    if event.program == "somespecialprogram" then
        unknown_function()
    end
    if event.program == '-' then
        event.program = 'Unknown'
    end
end

Because the error code was not executed during the test, this rule would be added and would go "live". Then in "real" operation, it could result in errors. The fact that in-use errors are being encountered would be revealed by listing the rules, logzilla rules list:

$ logzilla rules list
Name           Source    Type    Status    Errors
-------------  --------  ------  --------  --------
err            user      lua     enabled   3

This indicates that for all the events received by LogZilla (and processed by the rule) 3 of those events resulted in the rule failing.

When rules failures are encountered "live" the details of the errors encountered can be displayed using

$ logzilla rules errors err
======================================================================
Rule err, 3 errors in last hour:
----------------------------------------------------------------------
Time: 2021-04-20 07:50:11

Event:
    cisco_mnemonic: ''
    counter: 1
    facility: 0
    first_occurrence: 1618905011.405836
    host: Host1
    id: 0
    last_occurrence: 1618905011.405836
    message: Message nr 1
    program: fail
    severity: 0
    status: 0
    user_tags: {}

Error:
    /etc/logzilla/rules/user/err.lua:5: attempt to call global 'unknown_function' (a nil value)
    stack traceback:
            /etc/logzilla/rules/user/err.lua:5: in function </etc/logzilla/rules/user/err.lua:1>
----------------------------------------------------------------------

This provides the method for understanding the error so that it can be corrected.

Note: For any given rule LogZilla has a limit on the number of errors per hour that can be encountered before the rule is automatically disabled -- by default 5 errors per hour. Any rule that reaches this limit becomes disabled and will no longer be run for each incoming log event.

The fact that rule execution has been disabled might be noticed in that any LogZilla display or trigger elements depending on that rule execution cease to work. In addition, the error condition can be manually revealed:

$ logzilla rules list
Name           Source    Type    Status    Errors
-------------  --------  ------  --------  --------
err            user      lua     disabled  5

The rule failure would also be exhibited in LogZilla logs (logzilla logs):

2021-04-20 08:01:33.186795 [parsermodule] lz.parser WARNING Failure of rule err.lua on event Event({"id":0,"severity":1,"facility":0,"message":"Message nr 2","host":"Host2","program":"fail","cisco_mnemonic":"","first_occurrence":1618905692885420,"last_occurrence":1618905692885420,"counter":1,"status":0,"user_tags":{}}):
/etc/logzilla/rules/user/err.lua:5: attempt to call global 'unknown_function' (a nil value)
stack traceback:
    /etc/logzilla/rules/user/err.lua:5: in function </etc/logzilla/rules/user/err.lua:1>
2021-04-20 08:01:34.108472 [parsermodule/1] lz.ParserModule WARNING Reached limit of errors in rule err (limit: 5, errors: 5),  disabling rule.

When the rule is corrected (in this example possibly by providing the missing unknown_function()) the rule can be re-added to LogZilla, to update that rule and re-enable it: logzilla rules add myrule.lua -f to add the rule, resulting in:

$ logzilla rules list
Name           Source    Type    Status    Errors
-------------  --------  ------  --------  --------
err            user      lua     enabled   -

In unusual circumstances the rule can be re-enabled without changing it, using logzilla rules enable - this will also reset the error counter and clean the error log for the given rule (the old error messages would still be available via logzilla logs).

Finally, the error limit can be configured by the logzilla config RULE_ERROR_LIMIT command, which sets the rate (per hour) of failures that result in disabling of the rule (as mentioned this value is 5 by default).