NXLog language
The NXLog core has a built-in interpreted language. This language can be used to make complex decisions or build expressions in the NXLog configuration file. Code written in the NXLog language is similar to Perl, which is commonly used by developers and administrators for log processing tasks. When NXLog starts and reads its configuration file, directives containing NXLog language code are parsed and compiled into pseudo-code. If a syntax error is found, NXLog will print the error. This pseudo-code is then evaluated at run-time, as with other interpreted languages.
The features of the NXLog language are not limited to those in the NXLog core: modules can register functions and procedures to supplement built-in functions and procedures (see the xm_syslog functions, for example).
Due to the simplicity of the language there is no error handling available to the user, except for function return values. If an error occurs during the execution of the NXLog pseudo-code, usually the error is printed in the NXLog logs. If an error occurs during log message processing it is also possible that the message will be dropped. If sophisticated error handling or more complex processing is required, additional message processing can be implemented in an external script or program via the xm_exec module, in a dedicated NXLog module, or in Perl via the xm_perl module. |
The NXLog language is described in five sections.
- Types
-
All fields and other expressions in the NXLog language are typed.
- Expressions
-
An expression is evaluated to a value at run-time and the value is used in place of the expression. All expressions have types. Expressions can be used as arguments for some module directives.
- Statements
-
The evaluation of a statement will cause a change in the state of the NXLog engine, the state of a module instance, or the current event. Statements often contain expressions. Statements are used as arguments for the Exec module directive, where they are then executed for each event (unless scheduled).
- Variables
-
Variables store data persistently in a module instance, across multiple event records.
- Statistical counters
-
NXLog provides statistical counters with various algorithms that can be used for realtime analysis.
While this Guide provides many configuration examples, in some cases only statement examples are given. Statements must be used with the Exec directive (or Exec block). The following statement example shows one way to use the parsedate() function.
if $raw_event =~ /^(\w{3} \d{2} \d{2}:\d{2}:\d{2})/
$EventTime = parsedate($1);
The following configuration example uses the above statement in an Exec block.
<Input in>
Module im_file
File '/var/log/app.log'
<Exec>
if $raw_event =~ /^(\w{3} \d{2} \d{2}:\d{2}:\d{2})/
$EventTime = parsedate($1);
</Exec>
</Input>
Types
The NXLog language is a typed language. Fields, literals, and other expressions evaluate to values with specific types. This allows for stricter type-safety syntax checking when parsing the configuration. Note that fields and some functions can return values with types that can only be determined at run-time.
The language provides only simple types. Complex types such as arrays and hashes (associative arrays) are not supported. The language does support the undefined value similar to that in Perl. See the xm_perl module if you require more complex types. |
A log’s format must be parsed before its individual parts can be used for processing (see Fields). But even after the message has been parsed into its parts, additional processing may still be required, for example, to prepare a timestamp for comparison with another timestamp. This is a situation where typing is helpful: by converting all timestamps to the datetime type they can be easily compared—and converted back to strings later if required—using the functions and procedures provided. The same applies to other types.
The following illustrates the four steps NXLog performs with this configuration
as it manually processes a syslog event record using only
regular expressions on the core field $raw_event
and the core function parsedate().
<Input in>
# 1. New event record created
Module im_udp
Host 0.0.0.0
Port 514
<Exec>
# 2. Timestamp parsed from Syslog header
if $raw_event =~ /^(\w{3} \d{2} \d{2}:\d{2}:\d{2})/
{
# 3. parsedate() function converts from string to datetime
$EventTime = parsedate($1);
# 4. Datetime fields compared
if ( $EventReceivedTime - $EventTime ) > 60000000
log_warning('Message delayed more than 1 minute');
}
</Exec>
</Input>
-
NXLog creates a new event record for the incoming log message. The new event record contains the
$raw_event
string type field, with the contents of the entire syslog string. -
A regular expression is used to parse the timestamp from the event. The captured sub-string is a string type, not a datetime type.
-
The parsedate() function converts the captured string to a datetime type.
-
Two datetime fields are compared to determine if the message was delayed during delivery. The datetime type
$EventReceivedTime
field is added by NXLog to each event when it is received.
Normally the parse_syslog() procedure (provided by the xm_syslog extension module) would be used to parse a syslog event. It will create fields with the appropriate types during parsing, eliminating the need to directly call the parsedate() function. See Collecting and Parsing Syslog. |
Expressions
An expression is a language element that is dynamically evaluated to a value at run-time. The value is then used in place of the expression. Each expression evaluates to a type, but not always to the same type.
The following language elements are expressions: literals, regular expressions, fields, operations, and functions.
Expressions can be bracketed by parentheses ( )
to help improve code readability.
There are three statements below, one per line. Each statement contains multiple expressions, with parentheses added in various ways.
if 1 + 1 == (1 + 1) log_info("2");
if (1 + 1) == (1 + 1) log_info("2");
if ((1 + 1) == (1 + 1)) log_info("2");
Expressions are often used in statements.
This simple statement uses the log_info() procedure with an expression as its argument. In this case the expression is a literal.
In NXLog Enterprise Edition version 5, the log_info() procedure will truncate messages larger than 1024 bytes.
In subsequent versions, messages longer than specified at LogSizeLimit will be truncated.
|
log_info('This message will be logged.');
Here is a function (also an expression) that is used in the same procedure. It generates an internal event with the current time when each event is processed.
log_info(now());
Expressions can be used with module directives that support them.
The File directive of the om_file module supports expressions. This allows the output filename to be set dynamically for each individual event.
<Output out>
Module om_file
File "/var/log/nxlog/out_" + strftime($EventTime, "%Y%m%d")
</Output>
See Using dynamic filenames for more information.
Literals
A literal is a simple expression that represents a fixed value. Common literals include booleans, integers, and strings. The type of literal is detected by the syntax used to declare it.
This section demonstrates the use of literals by using examples with assignment statements. |
Boolean literals can be declared using the constants
TRUE
or FALSE
. Both are case-insensitive.
$Important = FALSE;
$Local = true;
Integer literals are declared with an unquoted integer. Negative integers, hexademical notation, and base-2 modifiers (Kilo, Mega, and Giga) are supported.
$Count = 42;
$NegativeCount = -42;
$BigCount = 42M;
$HexCount = 0x2A;
String literals are declared by quoting characters with single or double quotes. Escape sequences are available when using double quotes.
$Server = 'Alpha';
$Message = 'This is a test message.';
$NumberAsString = '12';
$StringWithNewline = "This is line 1.\nThis is line 2.";
For a list of all available literals, see the Reference Manual Literals section.
Regular expressions
NXLog supports regular expressions for matching, parsing, and modifying event records. In the context of the NXLog language, a regular expression is an expression that is evaluated to a boolean value at run-time. Regular expressions can be used to define complex search and replacement patterns for text matching and substitution.
Examples in this section use only simple patterns. See Extracting data and other topic-specific sections for more extensive examples. |
Matching can be used with an if statement to conditionally execute a statement.
The event record will be discarded if the $raw_event
field matches
the regular expression.
if $raw_event =~ /TEST: / drop();
Regular expression matching can also be used for extensive parsing, by capturing sub-strings for field assignment.
If the $raw_event
field contains the regular expression, the two
fields will be set to the corresponding captured sub-strings.
if $raw_event =~ /TEST(\d): (.+)/
{
$TestNumber = $1;
$TestName = $2;
}
Regular expression matching also supports named capturing groups. This can be useful when writing long regular expressions. Each captured group is automatically added to the event record as a field with the same name.
This regular expression uses the named groups TestNumber
and TestName
to
add corresponding $TestNumber
and $TestName
fields to the event record.
if $raw_event =~ /TEST(?<TestNumber>\d): (?<TestName>.+)/
{
$Message = $TestNumber + ' ' + $TestName;
}
Regular expression substitution can be used to modify a string. In
this case, the regular expression follows the form
s/pattern/replace/
. The result of the expression will be assigned to
the field to the left of the operator.
The first regular expression match will be removed from the
$raw_event
field.
$raw_event =~ s/TEST: //;
Global substitution is supported with the /g
modifier. Without the
/g
modifier, only the first match in the string will be replaced.
Every whitespace character in the $AlertType
field will be replaced
with an underscore (_
).
$AlertType =~ s/\s/_/g;
A statement can be conditionally executed according to the success of a regular expression substitution.
If the substitution succeeds, an internal log message will also be generated.
if $Hostname =~ s/myhost/yourhost/ log_info('Updated hostname');
For more information, see the following sections in the Reference Manual: Regular Expressions, =~, and !~.
Fields
When NXLog receives a log message, it creates an event record for it.
An event record is a set of fields (see Fields for more information).
A field is an expression which evaluates to a value with a specific type.
Each field has a name, and in the NXLog language it is represented with the dollar sign ($
) prefixed to the name of the field, like Perl’s scalar variables.
Fields are only available in an evaluation context which is triggered by a log message. For example, using a value of a field in the Exec directive of a Schedule block will result in a run-time error because the scheduled execution is not triggered by a log message.
Because it is through fields that the NXLog language accesses the contents of an event record, they are frequently referenced. The following examples show some common ways that fields are used in NXLog configurations.
This statement uses assignment to set the
$Department
field on log messages.
$Department = 'customer-service';
If the $Hostname
field does not match, the message will be discarded
with the drop() procedure.
if $Hostname != 'webserver' drop();
This statement will generate an internal event if $SeverityValue
integer field is greater than 2
(NXLog INFO
severity). The
generated event will include the contents of the $Message
field.
if $SeverityValue > 2 log_warning("ALERT: " + $Message);
Operations
Like other programming languages and especially Perl, the NXLog language has unary operations, binary operations, and the conditional ternary operation. These operations are expressions and evaluate to values.
- Unary operations
-
Unary operations work with a single operand and evaluate to a boolean value.
Example 15. Using a unary operationThis statement uses the defined operator to log a message only if the
$Hostname
field is defined in the event record.if defined $Hostname log_info('Event received');
- Binary operations
-
Binary operations work with two operands and evaluate to a value. The type of the evaluated value depends on the type of the operands. Execution might result in a run-time error if the type of the operands are unknown at compile time and then evaluate to types which are incompatible with the binary operation when executed.
- Ternary operation
-
The conditional or ternary operation requires three operands. The first is an expression that evaluates to a boolean. The second is an expression that is evaluated if the first expression is TRUE. The third is an expression that is evaluated if the first expression is FALSE.
Example 17. Using the ternary operationThis statement sets the
$Important
field to TRUE if$SeverityValue
is greater than2
, or FALSE otherwise. The parentheses are optional and have been added here for clarity.$Important = ( $SeverityValue > 2 ? TRUE : FALSE );
For a full list of supported operations, see the Reference Manual Operations section.
Functions
A function is an expression which returns a value. The returned value can be used to set fields, output log data, or make logic decisions. Functions can be polymorphic, meaning that the same function can take different argument types.
Many NXLog language features are provided through functions. As with other types of expressions, and unlike procedures, functions do not modify the state of the NXLog engine, the state of the module, or the current event.
See the list of available core functions. Modules can provide additional functions for use with the NXLog language.
These expressions call the now() function to return
the current time and the hostname() function to
return the hostname of the system where NXLog is installed. The returned
values are used to set the $EventTime
and $Relay
fields.
$EventTime = now();
$Relay = hostname();
In the example below, the size() function is called to
calculate the size of the $Message
field. If the field is over 4096 bytes,
an internal log is generated.
if size($Message) > 4096 log_info('Large message received.');
Functions for a specific module instance can be called using the ->
operator. This expression calls the file_name() and
file_size() functions of an om_file instance named
out
. The returned values are used to log the name and size of its current
output file.
x('Size of output file ' + out->file_name() + ' is ' + out->file_size());
Calling functions of a specific instance is especially useful when the
configuration contains more than one instance of the same module. These
expressions call the to_xml() function of two xm_xml
instances, one named xml_a
and the other named xml_b
.
xml_a->to_xml();
xml_b->to_xml();
Statements
The evaluation of a statement will usually result in a change in the state of the NXLog engine, the state of a module, or the log message.
Statements are used with the Exec module
directive. A statement is terminated by a semicolon (;
).
With this input configuration, an internal NXLog log message will be generated for each message received.
<Input in>
Module im_udp
Host 0.0.0.0
Port 514
Exec log_info("Message received on UDP port 514");
</Input>
Multiple statements can be specified, these will be evaluated and executed in order. Statements can also be given on multiple lines by using line continuation or by enclosing the statements in an Exec block.
This configuration generates an internal log message and sets the
$File
field.
<Input in1>
Module im_file
File '/var/log/app.log'
Exec log_info("App message read from log"); $File = file_name();
</Input>
This is the same, but the backslash (\
) is used to continue the
Exec directive to the next line.
<Input in2>
Module im_file
File '/var/log/app.log'
Exec log_info("App message read from log"); \
$File = file_name();
</Input>
The following configuration is functionally equivalent to the previous
configuration above. However, by creating an Exec
block, multiple statements can be specified without the need for a backslash
(\
) line continuation at the end of each line.
<Input in3>
Module im_file
File '/var/log/app.log'
<Exec>
log_info("App message read from log");
$File = file_name();
</Exec>
</Input>
Statements can also be executed based on a schedule by using the
Exec directive of a
Schedule block. The Exec directive is
slightly different in this example. Because its execution depends solely on a
schedule instead of any incoming log events, there is no event record that
can be associated with it. The $File
field assignment in the example above
would be impossible.
This input instance will generate an hourly internal log event.
<Input syslog_udp>
Module im_udp
Host 0.0.0.0
Port 514
<Schedule>
When @hourly
Exec log_info("The syslog_udp input module instance is active.");
</Schedule>
</Input>
Similar functionality is implemented by the im_mark module. |
Assignment
Each event record is made up of fields, and assignment is the
primary way that a value is written to a field in the NXLog
language. The assignment operation is declared with an equal sign
(=
). This operation loads the value from the expression evaluated on
the right into an event record field on the
left.
This input instance uses assignment operations to add two fields to each event record.
<Input in>
Module im_file
File '/var/log/messages'
<Exec>
$Department = 'processing';
$Tier = 1;
</Exec>
</Input>
Block
Statements can be declared inside a block by surrounding them with
curly braces ({}
). A statement block in the configuration is parsed
as if it were a single statement. Blocks are typically used with
conditional statements.
This statement uses a block to execute two statements if the
$Message
field matches.
<Input in>
Module im_file
File '/var/log/messages'
<Exec>
if $Message =~ /^br0:/
{
log_warning('br0 interface state changed');
$Tag = 'network';
}
</Exec>
</Input>
Procedures
A procedure is a statement that performs a set of actions. Procedures can accept arguments. Unlike a function, a procedure modifies its arguments, the state of the NXLog engine, the state of a module, or the current event. Procedures can be polymorphic, meaning that the same procedure can take different argument types.
Many NXLog language features are provided through procedures. See the list of available core procedures. Modules can provide additional procedures for use with the NXLog language.
This example uses the parse_syslog() procedure, provided by the xm_syslog module, to parse syslog records received via UDP.
<Input in>
Module im_udp
Host 0.0.0.0
Port 514
Exec parse_syslog();
</Input>
Procedures for a specific module instance can be called using the ->
operator. This statement calls the rotate_to()
procedure of an om_file instance named out
to rotate the current output
file.
out->rotate_to("output_logs");
Calling procedures of a specific instance is especially useful when the
configuration contains more than one instance of the same module. These
statements call the parse_xml() procedure of two
xm_xml instances, one named xml_a
and the other named xml_b
.
xml_a->parse_xml();
xml_b->parse_xml();
If-else
The if or conditional statement allows a
statement to be executed based on the boolean value of an
expression. When the boolean is TRUE
, the statement is executed. An
optional else keyword can be followed by another statement to be
executed if the boolean is FALSE
.
This example uses an if statement and the drop() procedure to discard any event that matches the regular expression.
<Input in1>
Module im_file
File '/var/log/messages'
Exec if $raw_event =~ /junk/ drop();
</Input>
Here, any event not matching the regular expression will be dropped.
<Input in2>
Module im_file
File '/var/log/messages'
Exec if not ($raw_event =~ /important/) drop();
</Input>
Finally, this statement shows more extensive use of the if
statement, with an else clause and blocks
defined by curly braces ({}
).
<Input in3>
Module im_file
File '/var/log/messages'
<Exec>
if $raw_event =~ /alert/
{
log_warning('Detected alert message');
}
else
{
log_info('Discarding non-alert message');
drop();
}
</Exec>
</Input>
Variables
While NXLog provides fields for storing data during the processing of an event, they are only available for the duration of that event record and can not be used to store a value across multiple events. For this purpose, module variables can be used. A variable stores a value for the module instance where it is set. It can only be accessed from the same module where it was created: a variable with the same name is a different variable when referenced from another module.
Each module variable can be created with an expiry value or an infinite lifetime. If an expiry is used, the variable will be destroyed automatically when the lifetime expires. This can be used as a garbage collection method or to reset variable values automatically.
A module variable is referenced by a string value and can store a value of any type. Module variables are supported by all modules. See the create_var(), delete_var(), set_var(), and get_var() procedures.
If the number of login failures exceeds 3 within 45 seconds, then an internal log message is generated.
<Input in>
Module im_file
File '/var/log/messages'
<Exec>
if $Message =~ /login failure/
{
if not defined get_var('login_failures')
{ # create the variable if it doesn't exist
create_var('login_failures', 45);
set_var('login_failures', 1);
}
else
{ # increase the variable and check if it is over the limit
set_var('login_failures', get_var('login_failures') + 1);
if get_var('login_failures') >= 3
log_warning(">= 3 login failures within 45 seconds");
}
}
</Exec>
</Input>
The pm_evcorr module is recommended instead for this case. This algorithm does not reliably detect failures because the lifetime of the variable is not affected by set_var(). For example, consider login failures at 0, 44, 46, and 47 seconds. The lifetime of the variable will be set when the first failure occurs, causing the variable to be cleared at 45 seconds. The variable is created with a new expiry at 46 seconds, but then only two failures are noticed. Also, this method can only work in real-time because the timing is not based on values available in the log message (although the event time could be stored in another variable). |
Statistical counters
Like variables, statistical counters provide data storage for a module instance. Counters only support integers, but a counter can use an algorithm to recalculate its value every time it is updated or read. With NXLog Enterprise Edition v4.x and earlier, a statistical counter will only return a value if the time specified in the interval argument has elapsed since it was created. Statistical counters can also be created with a lifetime. When a counter expires, it is destroyed, like module variables.
A statistical counter can be created with the create_stat() procedure call. After it is created, it can be updated with the add_stat() procedure call. The value of the counter can be read with the get_stat() function call. Note that the value of the statistical counter is only recalculated during these calls, rather than happening automatically. This can result in some slight distortion of the calculated value if the add and read operations are infrequent.
A time value can also be specified during creation, updating, and reading. This makes it possible for statistical counters to be used with offline log processing.
This input configuration uses a Schedule block and a statistical counter with the RATEMAX algorithm to calculate the maximum rate of events over a 1 hour period. An internal log message is generated if the rate exceeds 500 events/second at any point during the 1 hour period.
<Input in>
Module im_tcp
Host 0.0.0.0
Port 1514
<Exec>
parse_syslog();
if defined get_stat('eps') add_stat('eps', 1, $EventReceivedTime);
</Exec>
<Schedule>
Every 1 hour
<Exec>
create_stat('eps', 'RATEMAX', 1, now(), 3600);
if get_stat('eps') > 500
log_info('Inbound TCP rate peaked at ' + get_stat('eps')
+ ' events/second during the last hour');
</Exec>
</Schedule>
</Input>