Log Parser - Event Trigger Script - MikroTik Script RouterOS
This script will parse a log buffer, and take specified action if a log entry has been added.
There is an official "detect new log entry" script Manual:Scripting-examples#Detect_new_log_entry, however, this will only detect the last log entry. If multiple log entries have occurred since last check, you would only receive the last one.
This script will solve that problem by first reading the log buffer to internal memory, clearing the log buffer, then parsing the log entries (now loaded in memory). This allows for a very accurate log reading, and insures you will not miss multiple log entries before the next script execution. Also, it will produce a standard time format when reading the logs (mmm/dd/yyyy hh:mm:ss), meaning no extra conversion is necessary when reading log entries from the current day, or current year. For more details on this visit: [[1]] This script is also very fast, as it uses print as-value instead of find to read the log buffer.
First, you must create a memory log buffer to hold the information you want to parse.
/system logging action add memory-lines=100 memory-stop-on-full=no name=logParse target=memory
Next, create the topics to store in the newly created log buffer.
/system logging add action=logParse disabled=no prefix="" topics=system,info
/system logging add action=logParse disabled=no prefix="" topics=system,error,critical
/system logging add action=logParse disabled=no prefix="" topics=dhcp
Log Parser Script
# Script Name: Log-Parser
# This script reads a specified log buffer. At each log entry read,
# the global variable 'logParseVar' is set to ",,"
# then a parser action script is run. The parser action script reads the global variable, and performs specified actions.
# The log buffer is then cleared, so only new entries are read each time this script gets executed.
# Set this to a "memory" action log buffer
:local logBuffer "logParse"
# Set to name of parser script to run against each log entry in buffer
:local logParserScript "Log-Parser-Script"
# Internal processing below....
# -----------------------------------
:global logParseVar ""
:local loglastparsetime
:local loglastparsemessage
:local findindex
:local property
:local value
:local logEntryTopics
:local logEntryTime
:local logEntryMessage
:local curDate
:local curMonth
:local curDay
:local curYear
:local clearedbuf
:local lines
# Get current date settings
:set curDate [/system clock get date]
:set curMonth [:pick [:tostr $curDate] 0 3]
:set curDay [:pick [:tostr $curDate] 4 6]
:set curYear [:pick [:tostr $curDate] 7 11]
:set clearedbuf 0
:foreach rule in=[/log print as-value where buffer=($logBuffer)] do={
# Now all data is collected in memory..
# Clear log buffer right away so new entries come in
:if ($clearedbuf = 0) do={
/system logging action {
:set lines [get ($logBuffer) memory-lines]
set ($logBuffer) memory-lines 1
set ($logBuffer) memory-lines $lines
}
:set clearedbuf 1
}
# End clear log buffer
:set logEntryTime ""
:set logEntryTopics ""
:set logEntryMessage ""
# Get each log entry's properties
:foreach item in=[:toarray $rule] do={
:set findindex [:find [:tostr $item] "="]
:set property [:tostr [:pick [:tostr $item] 0 $findindex]]
:set value [:tostr [:pick [:tostr $item] ($findindex + 1) [:len [:tostr $item]]]]
:if ([:tostr $property] = "time") do={ :set logEntryTime $value }
:if ([:tostr $property] = "topics") do={ :set logEntryTopics $value }
:if ([:tostr $property] = "message") do={ :set logEntryMessage $value }
# end foreach item
}
# Set logEntryTime to full time format (mmm/dd/yyyy HH:MM:SS)
:set findindex [:find [:tostr $logEntryTime] " "]
# If no spaces are found, only time is given (HH:MM:SS), insert mmm/dd/yyyy
:if ([:len $findindex] = 0) do={
:set logEntryTime ($curMonth . "/" . $curDay . "/" . $curYear . " " . \
[:tostr $logEntryTime])
}
# Only (mmm/dd HH:MM:SS) is given, insert year
:if ($findindex = 6) do={
:set logEntryTime ([:pick [:tostr $logEntryTime] 0 $findindex] . "/" . $curYear . \
[:pick [:tostr $logEntryTime] $findindex [:len [:tostr $logEntryTime]]])
}
# Only (mmm HH:MM:SS) is given, insert day and year
:if ($findindex = 3) do={
:set logEntryTime ([:pick [:tostr $logEntryTime] 0 $findindex] . "/" . $curDay . "/" . $curYear . \
[:pick [:tostr $logEntryTime] $findindex [:len [:tostr $logEntryTime]]])
}
# End set logEntryTime to full time format
# Skip if logEntryTime and logEntryMessage are the same as previous parsed log entry
:if ($logEntryTime = $loglastparsetime && $logEntryMessage = $loglastparsemessage) do={
} else={
# Set logParseVar, then run parser script
:set logParseVar ($logEntryTime . "," . $logEntryTopics . "," . $logEntryMessage)
/system script run ($logParserScript)
# Update last parsed time, and last parsed message
:set loglastparsetime $logEntryTime
:set loglastparsemessage $logEntryMessage
}
# end foreach rule
}
Parser Action Script
Next, create the parser action script that will run after each log entry is read.
# Script Name: Log-Parser-Script
#
# This is an EXAMPLE script. Modify it to your requirements.
#
# This script will work with all v3.x and v4.x
# If your version >= v3.23, you can use the ~ operator to match against
# regular expressions.
# Get log entry data from global variable and store it locally
:global logParseVar
:local logTime [:pick [:toarray $logParseVar] 0]
:local logTopics [:pick [:toarray $logParseVar] 1]
:local logMessage [:pick [:toarray $logParseVar] 2]
:set logParseVar ""
:local ruleop
:local loguser
:local logsettings
:local findindex
:local tmpstring
# Uncomment to view the log entry's details
#:put ("Log Time: " . $logTime
#:put ("Log Topics: " . $logTopics)
#:put ("Log Message: " . $logMessage)
# Check for login failure
:if ([:find [:tostr $logMessage] "login failure"] != "") do={
:beep frequency=90 length=500ms
:beep frequency=130 length=500ms
:put ("A login failure has occured. Take some action")
}
# End check for login failure
# Check for logged in users
:if ([:find [:tostr $logMessage] "logged in"] != "") do={
:beep frequency=900 length=300ms
:beep frequency=1300 length=200ms
:put ("A user has logged in.")
}
# End check for logged in users
# Check for configuration changes: added, changed, or removed
:if ([:tostr $logTopics] = "system;info") do={
:set ruleop ""
:if ([:len [:find [:tostr $logMessage] "changed "]] > 0) do={ :set ruleop "changed" }
:if ([:len [:find [:tostr $logMessage] "added "]] > 0) do={ :set ruleop "added" }
:if ([:len [:find [:tostr $logMessage] "removed "]] > 0) do={ :set ruleop "removed" }
:if ([:len $ruleop] > 0) do={
:set tmpstring $logMessage
:set findindex [:find [:tostr $tmpstring] [:tostr $ruleop]]
:set tmpstring ([:pick [:tostr $tmpstring] 0 $findindex] . \
[:pick [:tostr $tmpstring] ($findindex + [:len [:tostr $ruleop]]) [:len [:tostr $tmpstring]]])
:set findindex [:find [:tostr $tmpstring] " by "]
:set loguser ([:pick [:tostr $tmpstring] ($findindex + 4) [:len [:tostr $tmpstring]]])
:set logsettings [:pick [:tostr $tmpstring] 0 $findindex]
:put ($loguser . " " . $ruleop . " " . $logsettings . " configuration. We should take a backup now.")
}
}
# End check for configuration changes
# Check for DHCP lease assigned/deassigned
:if ([:tostr $logTopics] = "dhcp;info") do={
:set ruleop ""
:if ([:len [:find [:tostr $logMessage] "assigned "]] > 0) do={ :set ruleop "assigned" }
:if ([:len [:find [:tostr $logMessage] "deassigned "]] > 0) do={ :set ruleop "deassigned" }
:if ([:len $ruleop] > 0) do={
:if ($ruleop = "assigned") do={
:put ("A new dhcp lease has been assigned. Check the DHCP IP Pool addresses")
}
:if ($ruleop = "deassigned") do={
:put ("A dhcp lease has been removed. Remove the host-name from static DNS")
}
}
}
# End check for new DHCP lease assigned
Now, you have a way to trigger events based on log entries (and any event in RouterOS that is logged).