FilterX language basics
This page describes the building blocks of the FilterX language: statements, variables, types, and how FilterX data reaches your destinations. For a hands-on introduction, see Get started with FilterX.
Truthy and falsy values
FilterX blocks consist of a list of FilterX statements, each statement evaluates either to truthy or falsy. If a message matches all FilterX statements, it passes through the FilterX block to the next element of the log path, for example, the destination.
- Truthy values are:
- Complex values (for example, a datetime object),
- non-empty lists and objects,
- non-empty strings,
- non-zero numbers,
- the
trueboolean object.
- Falsy values are:
- empty strings,
- the
falsevalue, - the
0value, null,
Statements that result in an error (for example, if a comparison cannot be evaluated because of type error, or a field or a dictionary referenced in the statement doesn’t exist or is unset) are also treated as falsy. To find and fix such errors, see Troubleshoot FilterX.
FilterX statements
A FilterX block contains one or more FilterX statements. The order of the statements is important, as they are processed sequentially. If any of the statements is falsy (or results in an error), AxoSyslog drops the message from that log path.
FilterX statements can be one of the following:
-
A comparison, for example,
${HOST} == "my-host";. This statement is true only for messages where the value of the${HOST}field ismy-host. Such simple comparison statements can be the equivalents of traditional filter functions . -
A value assignment for a name-value pair or a local variable , for example,
${my-field} = "bar";. The left-side variable automatically gets the type of the right-hand expression. Assigning the false value to a variable (${my-field} = false;) is a valid statement that doesn’t automatically cause the FilterX block to return as false. -
Existence of a variable of field. For example, the
${HOST};expression is true only if the${HOST}macro exists and isn’t empty. -
A conditional statement (
if (expr) { ... } elif (expr) {} else { ... };) which allows you to evaluate complex decision trees. Starting with version 4.10, you can also use switch-case expressions. -
A declaration of a pipeline variable , for example,
declare my_pipeline_variable = "something";. -
A FilterX action. This can be one of the following:
drop;: Intentionally drop the message. This means that the message was successfully processed, but discarded. Processing the dropped message stops at thedropstatement, subsequent sections or other branches of the FilterX block won’t process the message. For example, you can use this to discard unneeded messages, like debug logs. Available in AxoSyslog 4.9 and later.done;: Return truthy and don’t execute the rest of the FilterX block, returns with true. This is an early return that you can use to avoid unnecessary processing, for example, when the message matches an early classification in the block. Available in AxoSyslog 4.9 and later.
- The
true;andfalse;literals are also valid as statements. They can be useful in complex conditional (if/elif/else) statements. - A name-value pair or a variable in itself is also a statement. For example,
${HOST};. If the name-value pair or variable is empty or doesn’t exist, the statement is considered falsy.
When you assign the value of a variable using another variable (for example, ${MESSAGE} = ${HOST};), AxoSyslog copies the current value of the ${HOST} variable. If a statement later changes the value of the ${HOST} field, the ${MESSAGE} field won’t change. For example:
filterx {
${HOST} = "first-hostname";
${MESSAGE} = ${HOST}; # The value of ${MESSAGE} is first-hostname
${HOST} = "second-hostname"; # The value of ${MESSAGE} is still first-hostname
};The same is true for complex objects, like JSON, for example:
js = json({
"key": "value",
"second-key": "another-value"
});
${MESSAGE} = js;
js.third_key = "third-value-not-available-in-MESSAGE";You can use FilterX operators and functions .
Data model and scope
Each FilterX block can access data from the following elements.
-
Macros and name-value pairs of the message being processed (for example,
$PROGRAM). The names of macros and name-value pairs begin with the$character. If you define a new variable in a FilterX block and its name begins with the$character, it’s automatically added to the name-value pairs of the message.NoteUsing curly braces around macro names is not mandatory, and the
"$MESSAGE"and"${MESSAGE}"formats are equivalent. If the name contains only alphanumeric characters and the underscore character, you don’t need the curly braces. If it contains any other characters (like a hyphen (-) or a dot (.)), you need to add the curly braces, therefore it’s best to always use curly braces.Names are case-sensitive, so
"$message"and"$MESSAGE"are not the same. -
Local variables. These have a name that doesn’t start with a
$character, for example,my_local_variable. Local variables are available only in the FilterX block they’re defined. -
Pipeline variables. These are similar to local variables, but must be declared before first use, for example,
declare my_pipeline_variable=5;Pipeline variables are available in the current and all subsequent FilterX block. They’re global in the sense that you can access them from multiple FilterX blocks, but note that they’re still attached to the particular message that is processed, so the values of pipeline variables aren’t preserved between messages.
If you don’t need to pass the variable to another FilterX block, use local variables, as pipeline variables have a slight performance overhead.
- If you want to pass data between two FilterX blocks of a log statement, use pipeline variables. That has better performance than name-value pairs.
- Local and pipeline variables aren’t available in destination templates. For details, see FilterX variables in destinations .
Variable names
FilterX variable names have more restrictions than generic name-value pair names. They:
- can contain alphanumeric characters and the underscore character (
_), but cannot contain hyphens, - cannot begin with numbers,
- can begin with underscore.
Variable types
Variables can have the following types. All of these types have a matching function that can be used to type cast something into the specific type.
booleanbytes(to represent binary data)datetimedictdoubleintegeripfor individual IPv4 or IPv6 addresses.jsonandjson_arrayfor JSON or JSON-like objects.listmetrics_labelsnullotel_arrayotel_kvlistotel_logrecordotel_resourceotel_scopeprotobufstringsubnetfor IPv4 or IPv6 subnets in CIDR notation.tupleis a read-only, list-like data type similar to a Python tuple. Available in AxoSyslog 4.27 and later.
Assign values
To assign value to a name-value pair or a variable, use the following syntax:
<variable-name> = <value-of-the-variable>;In most cases you can omit the type, and AxoSyslog automatically assigns the type based on the syntax of the value, for example:
mystring = "string-value";myint = 3;mydouble = 2.5;myboolean = true;
When needed, you can explicitly specify the type of the variable, and AxoSyslog attempts to convert the value to the specified type:
<variable-name> = <variable-type>(<value-of-the-variable>);For example:
filterx {
${MESSAGE} = string("Example string message");
};You can also assign the value of other name-value pairs, for example:
filterx {
${MESSAGE} = ${HOST};
};When processing RFC5424-formatted (IETF-syslog) messages, you can modify the SDATA part of the message as well. The following example sets the sequenceId:
filterx {
${.SDATA.meta.sequenceId} = 55555;
};Template functions
You can use the traditional template functions of AxoSyslog to access and format name-value pairs. For that you must enclose the template function expression between double-quotes, for example:
${MESSAGE} = "$(format-json --subkeys values.)";However, note that template functions cannot access the local and pipeline variables created in FilterX blocks.
Delete values
To delete a value without deleting the object itself (for example, name-value pair), use the null value, for example:
${MY-NV-PAIR-KEY} = null;To delete the name-value pair (or a key from an object), use the unset function:
unset(${MY-NV-PAIR-KEY});
unset(${MY-JSON}["key-to-delete"]);To unset every empty field of an object, use the unset-empties
function:
Add two values
The plus operator (+) adds two arguments, if possible. (For example, you can’t add two datetime values.)
-
You can use it to add two numbers (two integers, two double values). If you add a double to an integer, the result is a double.
-
Adding two strings concatenates the strings. Note that if you want to have spaces between the added elements, you have to add them manually, like in Python, for example:
Terminal window ${MESSAGE} = ${HOST} + " first part of the message," + " second part of the message" + "\n"; -
Adding two lists merges the lists. Available in AxoSyslog 4.9 and later.
-
Adding two dicts updates the dict with the values of the second operand. For example:
Terminal window x = {"key1": "value1", "key2": "value1"}; y = {"key3": "value1", "key2": "value2"}; ${MESSAGE} = x + y; # ${MESSAGE} value is {"key1": "value1", "key3": "value1", "key2": "value2"};Available in AxoSyslog 4.9 and later.
For other arithmetic operators, see Arithmetic operators.
Complex types: lists, dicts, and JSON
The list and dict types are similar to their Python counterparts
. FilterX uses JSON to represent generic dictionary and list types, but you can create other, specific dictionary and list types as well (currently for OTEL, for example, otel_kvlist, or otel_array). All supported dictionary and list types are compatible with each other, and you can convert them to and from each other, copy values between them (retaining the type), and so on.
For example:
my_list = []; # Creates an empty list (which defaults to a JSON list)
my_array = {}; # Creates an empty dictionary (which defaults to a JSON object)
my_list2 = json_array(); # Creates an empty JSON list
my_array2 = json(); # Creates an empty JSON object.You can add elements to lists and dictionaries like this:
list = json_array(); # Create an empty JSON list
#list = otel_array(); # Create an OTEL list
list += ["first_element"]; # Append entries to the list
list += ["second_element"];
list += ["third_element"];
${MESSAGE} = list;You can also create the list and assign values in a single step:
list = json_array(["first_element", "second_element", "third_element"]);
${MESSAGE} = list;You can refer to the elements using an index (starting with 0):
list = json_array(); # Create an empty JSON list
list[0] = "first_element"; # Append entries to the list
list[1] = "second_element";
list[2] = "third_element";
${MESSAGE} = list;In all three cases, the value of ${MESSAGE} is the same JSON array: ["first_element", "second_element", "third_element"].
You can define JSON objects using the json() type, for example:
js1 = json();
js1 += {
"body": "mystring",
"time_unix_nano": 123456789,
"attributes": {
"int": 42,
"flag": true
}
};
js2 = json({"key": "value"})Naturally, you can assign values from other variables to an object, for example:
js = json_array(["foo", "bar", "baz"]);
${MESSAGE} = json({
"key": "value",
"list": list
});or
js = json({
"key": ${MY-NAME-VALUE-PAIR},
"key-from-expression": isset(${HOST}) ? ${HOST} : "default-hostname",
"list": list
});When working with dicts, note the following points:
-
To create a field only if the assigned value is non-null, see Create dict element if non-null (
:??) . -
To assign a value to a non-existing key where only this key doesn’t exist, you can use a simple value assignment, for example:
Terminal window js = json({ "key1": "one", "key2": "two" }); js.key3 = "three"However, if you want to assign a value where multiple elements of the path don’t exist, use the
dpathFilterX function, for example:Terminal window dpath(js.key4.key41.key412) = "nested value"The value of the dictionary will be:
Terminal window js = json({ "key1": "one", "key2": "two", "key3": "three", "key4": { "key41": { "key412": "nested value" } } });
Within a FilterX block, you can access the fields of complex data types by using indexes and the dot notation, for example:
- dot notation:
js.key - indexing:
js["key"] - or mixed mode if needed:
js.list[1]
When referring to the field of a name-value pair (which begins with the $ character), place the dot or the square bracket outside the curly bracket surrounding the name of the name-value pair, for example: ${MY-LIST}[2] or ${MY-OBJECT}.mykey. If the name of the key contains characters that are not permitted in FilterX variable names, for example, a hyphen (-), use the bracketed syntax and enclose the key in double quotes: ${MY-LIST}["my-key-name"].
You can add two lists or two dicts using the Plus operator.
Tuples
Available in AxoSyslog 4.27 and later.
A tuple is a read-only, list-like data type similar to a Python tuple. You can initialize a tuple only once, then it remains read-only until the end of its lifecycle.
Tuple examples:
t = (); # empty tuple
t = ("foo",); # singleton
t = (1,2,3); # a tuple of 3 elements
d = {'foo':'foovalue','bar':'barvalue'};
t = tuple([1,2,3,d]); # a tuple with the value (1,2,3,{"foo":"foovalue","bar":"barvalue"})List membership
Available in AxoSyslog 4.12 and later.
You can check whether a value is in a list using the in and not in membership operators. If you’re checking the value of a variable that can have another type than the elements of a list, explicitly convert the value to the correct type (for example, string) before the comparison.
my_array = json_array(['hostname_one', 'hostname_two', 'hostname_three']);
if (${HOST} in my_array) {
# ...
}Starting with AxoSyslog 4.22, you can also use the operators to check membership in dict keys. For example:
my_dict = {"key-1": "value", "key-2": "another value"};
my_search_key = "key-1";
if (my_search_key in my_dict) {
${MSG} = "Found: " + my_dict[my_search_key];
} else {
${MSG} = "Not Found";
};The in operator can be used to replicate the functionality of the in-list() filter function in FilterX. If you want to populate the list from a file, use the cache_json_file()
FilterX function.
Type casting
To convert a value into another type, call the function named after the target type, for example, int(${.apache.status}). The following type casting functions are available:
If a value can’t be converted, the cast returns an error, and the statement is falsy. To use a default value instead, use the null coalescing operator
, for example: ${.status} = int(${.apache.status}) ?? 0;
FilterX variables in destinations
If you’re modifying messages using FilterX (for example, you extract a value from the message and add it to another field of the message), note the following points:
- Macros and name-value pairs (variables with names beginning with the
$character) are included in the outgoing message in case the template of the destination includes them. For example, if you change the value of the${MESSAGE}macro, it’s automatically sent to the destination if the destination template includes this macro. - Local and pipeline variables are not included in the message, you must assign their value to a macro or name-value pair that’s included in the destination template to send them to the destination.
- When sending data to
opentelemetry()destinations, if you’re modifying messages received via theopentelemetry()source, then you must explicitly update the original (raw) data structures in your FilterX block, otherwise the changes won’t be included in the outgoing message. For details, see Modify incoming OTEL.