External programs (im_exec)

This module will execute a program or script on startup and read its standard output. It can be used to easily integrate with exotic log sources which can be read only with the help of an external script or program.

To examine the supported platforms, see the list of installation packages.

If you are using a Perl script, consider using im_perl instead or turning on Autoflush with $| = 1;, otherwise im_exec might not receive data immediately due to Perl’s internal buffering. See the Perl language reference for more information about $|.

If you are using a Python script, we recommend disabling buffering of the stdout and stderr streams using the -u command-line option. Otherwise, NXLog Agent might not receive data immediately due to Python’s internal buffering.

Configuration

The im_exec module accepts the following directives in addition to the common module directives. The Command directive is required.

Required directives

The following directives are required for the module to start.

Command

This mandatory directive specifies the name of the program or script to be executed.

Programs, scripts, and commands are executed under the context of the user running NXLog Agent. When NXLog Agent is running as a service, the service user will be used. If the program, script, or command accesses environment variables, make sure that these are available for the NXLog Agent user.

Optional directives

Arg

This is an optional parameter. Arg can be specified multiple times, once for each argument that needs to be passed to the Command. Note that specifying multiple arguments with one Arg directive, with arguments separated by spaces, will not work (the Command would receive it as one argument).

ExitTimeout

This directive determines the time, in seconds, that the module waits for the child process to terminate. Valid values range from -1 to 54 seconds, with a default of 2 seconds. A value of -1 indicates that the module waits indefinitely for the child process to complete its processing, using the default value of 2 seconds as the waiting time. For further details see how NXLog Agent handles a child process exit.

InputType

See the InputType description in the global module configuration section.

Restart

Restart the process if it exits. There is a one-second delay before it is restarted to avoid a denial-of-service when a process is not behaving. Looping should be implemented in the script itself, this directive is only to provide some safety against malfunctioning scripts and programs. This boolean directive defaults to FALSE: the Command will not be restarted if it exits.

Creating and populating fields

im_exec populates the $raw_event core field with the log message read from the program or script’s standard output. Further processing of this field can be done to parse the message into structured data or convert it to a different output format, such as JSON or XML. See Parsing and converting log records below and Parsing standard log formats in the NXLog Platform User Guide for examples.

Child process exit handling

When the module receives a command to stop execution (exit) through a command via the xm_admin module or when the NXLog process exits entirely, the associated child process must terminate gracefully.

Windows operating systems
  1. The module closes the STDOUT and STDERR file handles of the child process.

  2. The module waits for the child process to terminate within the time specified by the ExitTimeout directive.

  3. If the child process continues running after ExitTimeout:

    • If ExitTimeout is not -1, the module terminates the child process using the Windows system function TerminateProcess.

    • If ExitTimeout is -1, the module does not forcefully terminate the child process.

Unix-like operating systems
  1. The module sends a SIGTERM signal to the child process, requesting it to exit gracefully.

  2. The module waits for the child process to terminate within the time specified by the ExitTimeout directive.

  3. If needed, SIGTERM signals are sent periodically until the child process exits.

  4. If the child process continues running after ExitTimeout:

    • The module closes the STDOUT and STDERR file handles of the child process.

    • If ExitTimeout is not -1, the module sends a SIGKILL signal to terminate the child process forcibly.

    • If ExitTimeout is -1, the module does not forcefully terminate the child process.

Ensure that the child process handles exceptions correctly when writing to file handlers, regardless of the operating system.

Examples

Example 1. Python 3 script with a graceful exit
import sys, os
import signal
import time


asked_to_exit = False


def put_exec_logger(st):
    with open("exec-logger.log", "a") as f:
        f.write(st)
        f.flush()


def to_handle(signum, frame):
    global asked_to_exit

    asked_to_exit = True
    auxst = 'im-exec Signal (' + str(signum) + ') received' + "\n"
    put_exec_logger(auxst)


def prepare_and_do_shutdown():
    c = 0
    while c < 10:
        auxst = 'im-exec Asked to finish - waiting 10 seconds before exiting... Counting ' + str(c) + "\n"
        put_exec_logger(auxst)
        time.sleep(1)
        c += 1
    put_exec_logger("im-exec Exiting grancefully" + "\n")
    exit(0)


def core():
    global asked_to_exit

    signal.signal(signal.SIGINT, to_handle)
    signal.signal(signal.SIGTERM, to_handle)
    # don't trap SIGPIPE - it will be handled as exception.

    c = 1
    while not asked_to_exit:
        auxst = 'im-exec Alive' + os.getcwd() + str(c)
        c += 1
        try:
            print(auxst)
            sys.stdout.flush()
            put_exec_logger(auxst + "\n")
            time.sleep(1)
        except Exception as e:
            #
            # Handle stdout closed by nxlog im_exec module
            #
            # On unix-like OSes it is expected the exception *BrokenPipeError* as it is on python manual page
            #        https://docs.python.org/3/library/signal.html#note-on-sigpipe
            #        It was tested on linux and it receives *BrokenPipeError* as expected
            # Testing on Windows the exception is OSError, with 22, 'Invalid argument'
            #
            asked_to_exit = True
            template = "im-exec  An exception of type {0} occurred. Arguments: {1!r}\n"
            message = template.format(type(e).__name__, e.args)
            put_exec_logger(message)
            #
            # Python flushes standard streams on exit; redirect remaining output
            # to devnull to avoid another BrokenPipeError at shutdown
            # https://docs.python.org/3/library/signal.html#note-on-sigpipe
            #
            devnull = os.open(os.devnull, os.O_WRONLY)
            os.dup2(devnull, sys.stdout.fileno())

    prepare_and_do_shutdown()


if __name__ == '__main__':
    core()
Example 2. Emulating im_file on Linux

This configuration uses the Linux tail command-line tool to read lines from a log file. The first Arg directive specifies the -f argument, which means that tail should monitor the file for new lines. The second Arg directive specifies the path of the log file. This is equivalent to executing the following command:

$ tail -f /var/log/messages
The im_file module should be used to read log messages from files. This example is only intended to demonstrate the use of the im_exec module.
nxlog.conf
<Input messages>
    Module     im_exec
    Command    /usr/bin/tail
    Arg        -f
    Arg        /var/log/messages
</Input>
Example 3. Executing an application

This configuration executes an application to read logs from a third-party source. The Command directive specifies the path to the application executable and the Arg directive specifies an application argument. This is equivalent to executing the following command:

$ /path/to/myapp --level=info
nxlog.conf
<Input myapp>
    Module      im_exec
    Command     /path/to/myapp

    # On Windows the path to the application executable
    # should include the file extension.
    #Command    C:\Program Files\MyApp\myapp.exe

    Arg         --level=info
</Input>
Example 4. Executing a script

This configuration executes a Python script to read logs from a third-party source. The Command directive specifies the path to the Python executable. The first Arg directive specifies the -u command-line option to disable buffering for the stdout and stderr streams. It is recommended to disable buffering because it may lead to a delay in receiving the logs. The second Arg directive specifies the path to the script. This is equivalent to executing the following command:

> python -u C:\Scripts\myscript.py
nxlog.conf
<Input python_script>
    Module     im_exec
    Command    C:\Python39\python.exe
    Arg        -u
    Arg        C:\Scripts\myscript.py
</Input>
Example 5. Executing commands under a specific shell

To execute commands under a specific shell, the Command directive should specify the path to the shell executable. The commands to execute can be passed as arguments according to the shell being used. The configuration below executes PowerShell commands from a file.

nxlog.conf
<Input powershell_script>
    Module     im_exec
    Command    C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
    Arg        C:\Scripts\myscript.ps1
</Input>
Example 6. Parsing and converting log records

This configuration executes a script and parses the $raw_event field with a regular expression. If the regular expression matches, fields are created according to the captured groups, otherwise the log record is dropped. Finally, the record is converted to JSON format using the to_json() procedure of the xm_json module.

nxlog.conf
<Extension json>
    Module    xm_json
</Extension>

<Input powershell_script>
    Module    im_exec
    Command   C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
    Arg       C:\Scripts\myscript.ps1
    <Exec>
        if $raw_event =~ /(?x)^(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\+\d\d:\d\d),
                          (.+),(.+)$/
        {
            $EventTime = parsedate($1);
            $Severity = $2;
            $Message = $3;
        }
        else
        {
            drop();
        }

        to_json();
    </Exec>
</Input>
Input sample
2021-11-05T14:03:40+01:00,INFO,The service started successfully
Output sample in JSON format
{
  "EventReceivedTime": "2021-11-05T14:04:24.244343+01:00",
  "SourceModuleName": "powershell_script",
  "SourceModuleType": "im_exec",
  "EventTime": "2021-11-05T14:03:40.000000+01:00",
  "Severity": "INFO",
  "Message": "The service started successfully"
}