Skip to content

Commit

Permalink
Merge pull request #856 from mountaindude/master
Browse files Browse the repository at this point in the history
849
  • Loading branch information
mountaindude authored Aug 19, 2024
2 parents d86883b + 8ca9558 commit e87d2be
Show file tree
Hide file tree
Showing 10 changed files with 785 additions and 1,450 deletions.
1,881 changes: 461 additions & 1,420 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,11 @@
"homepage": "https://github.com/ptarmiganlabs/butler-sos#readme",
"dependencies": {
"@breejs/later": "^4.2.0",
"@influxdata/influxdb-client": "^1.34.0",
"@influxdata/influxdb-client-apis": "^1.34.0",
"@influxdata/influxdb-client": "^1.35.0",
"@influxdata/influxdb-client-apis": "^1.35.0",
"axios": "^1.7.4",
"commander": "^12.1.0",
"config": "^3.3.12",
"esbuild": "^0.23.0",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"fastify": "^4.28.1",
"fastify-healthcheck": "^4.4.0",
"fastify-metrics": "^11.0.0",
Expand All @@ -62,6 +56,9 @@
"yaml-validator": "^5.0.1"
},
"devDependencies": {
"esbuild": "^0.23.1",
"eslint": "^9.9.0",
"eslint-config-prettier": "^9.1.0",
"eslint-formatter-table": "^7.32.1",
"prettier": "^3.3.3",
"snyk": "^1.1292.4"
Expand Down
105 changes: 85 additions & 20 deletions src/config/production_template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,89 @@ Butler-SOS:
# Log events are used to capture Sense warnings, errors and fatals in real time
logEvents:
udpServerConfig:
serverHost: <IP or FQDN> # Host/IP where log event server will listen for events from Sense
portLogEvents: 9996 # Port on which log event server will listen for events from Sense
serverHost: <IP or FQDN> # Host/IP where log event server will listen for events from Sense
portLogEvents: 9996 # Port on which log event server will listen for events from Sense
tags:
# - tag: env
# value: DEV
# - tag: foo
# value: bar
source:
engine:
enable: false # Should log events from the engine service be handled?
enable: false # Should log events from the engine service be handled?
proxy:
enable: false # Should log events from the proxy service be handled?
enable: false # Should log events from the proxy service be handled?
repository:
enable: false # Should log events from the repository service be handled?
enable: false # Should log events from the repository service be handled?
scheduler:
enable: false # Should log events from the scheduler service be handled?
sendToMQTT:
enable: false # Should log events be sent as MQTT messages?
enable: false # Should log events from the scheduler service be handled?
categorise: # Take actions on log events based on their content
enable: true
rules: # Rules are used to match log events to filters
- description: Find access denied errors
logLevel: # Log events of this Log level will be matched. WARN, ERROR, FATAL. Case insensitive.
- WARN
- ERROR
action: categorise # Action to take on matched log events. Possible values are categorise, drop
category: # Category to assign to matched log events. Name/value pairs.
# Will be added to InfluxDB datapoints as tags.
- name: qs_log_category
value: access-denied
filter: # Filter used to match log events. Case sensitive.
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: "Access was denied for User:"
- type: so
value: was denied for User
- description: Find AD issues
logLevel: # Log events of this Log level will be matched. WARN, ERROR, FATAL. Case insensitive.
- ERROR
- WARN
action: categorise # Action to take on matched log events. Possible values are categorise, drop
category: # Category to assign to matched log events. Name/value pairs.
# Will be added to InfluxDB datapoints as tags.
- name: qs_log_category
value: user-directory
filter: # Filter used to match log events. Case sensitive.
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Duplicate entity with userId
- description: Qlik Sense service down
logLevel: # Log events of this Log level will be matched. WARN, ERROR, FATAL. Case insensitive.
- WARN
action: categorise # Action to take on matched log events. Possible values are categorise, drop
category: # Category to assign to matched log events. Name/value pairs.
# Will be added to InfluxDB datapoints as tags.
- name: qs_log_category
value: qs-service
filter: # Filter used to match log events. Case sensitive.
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Failed to request service alive response from
- type: so # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Unable to connect to the remote server
- description: Reload task failed
logLevel: # Log events of this Log level will be matched. WARN, ERROR, FATAL. Case insensitive.
- WARN
- ERROR
action: categorise # Action to take on matched log events. Possible values are categorise, drop
category: # Category to assign to matched log events. Name/value pairs.
# Will be added to InfluxDB datapoints as tags.
- name: qs_log_category
value: reload-failed
filter: # Filter used to match log events. Case sensitive.
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Task finished with state FinishedFail
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Task finished with state Error
- type: ew # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Reload failed in Engine. Check engine or script logs.
- type: sw # Type of filter. sw = starts with, ew = ends with, so = substring of
value: Reload sequence was not successful (Result=False, Finished=True, Aborted=False) for engine connection with handle
ruleDefault: # Default rule to use if no other rules match the log event
enable: true
category:
- name: qs_log_category
value: unknown
sendToMQTT:
enable: false # Should log events be sent as MQTT messages?
baseTopic: qliksense/logevent # What topic should log events be forwarded to?
postTo:
baseTopic: true
Expand All @@ -148,25 +213,25 @@ Butler-SOS:
# - Second NR account
source:
engine:
enable: true # Should log events from the engine service be handled?
enable: true # Should log events from the engine service be handled?
logLevel:
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
proxy:
enable: true # Should log events from the proxy service be handled?
enable: true # Should log events from the proxy service be handled?
logLevel:
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
repository:
enable: true # Should log events from the repository service be handled?
enable: true # Should log events from the repository service be handled?
logLevel:
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
scheduler:
enable: true # Should log events from the scheduler service be handled?
enable: true # Should log events from the scheduler service be handled?
logLevel:
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?
error: true # Should error level log events be handled by Butler SOS?
warn: true # Should warning level log events be handled by Butler SOS?

# Qlik Sense logging db config parameters
logdb:
Expand Down
25 changes: 25 additions & 0 deletions src/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,31 @@ if (config.has('Butler-SOS.logEvents.tags') && config.get('Butler-SOS.logEvents.
});
}

// Add tags for log events categories, if enabled and configured
if (
config.has('Butler-SOS.logEvents.categorise.enable') &&
config.get('Butler-SOS.logEvents.categorise.enable') === true &&
config.has('Butler-SOS.logEvents.categorise.rules')
) {
// Add tags from Butler-SOS.logEvents.categorise.rules[].category[], where each object has properties 'name' and 'value'
config.get('Butler-SOS.logEvents.categorise.rules').forEach((rule) => {
rule.category.forEach((category) => {
tagValuesLogEvent.push(category.name);
});
});

// Add default rule categories, if enabled
if (
config.has('Butler-SOS.logEvents.categorise.ruleDefault.enable') &&
config.get('Butler-SOS.logEvents.categorise.ruleDefault.enable') === true &&
config.has('Butler-SOS.logEvents.categorise.ruleDefault.category')
) {
config.get('Butler-SOS.logEvents.categorise.ruleDefault.category').forEach((category) => {
tagValuesLogEvent.push(category.name);
});
}
}

// Create InfluxDB tags for data coming from log db
const tagValuesLogEventLogDb = tagValues.slice();
tagValuesLogEventLogDb.push('source_process');
Expand Down
31 changes: 31 additions & 0 deletions src/lib/config-file-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,37 @@ const confifgFileSchema = {
enable: 'boolean',
},
},
categorise: {
enable: 'boolean',
rules: [
{
description: 'string',
logLevel: ['string'],
action: 'string',
category: [
{
name: 'string',
value: 'string',
},
],
filter: [
{
type: 'string',
value: 'string',
},
],
},
],
ruleDefault: {
enable: 'boolean',
category: [
{
name: 'string',
value: 'string',
},
],
},
},
sendToMQTT: {
enable: 'boolean',
baseTopic: 'string',
Expand Down
1 change: 0 additions & 1 deletion src/lib/config-file-verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ async function verifyConfigFile() {
// 2. The tags specified for each server in `SOS.serversToMonitor.servers[].serverTags` must be present in `Butler-SOS.serversToMonitor.serverTagsDefinition`
// If either of the conditions above is false, an error should be logged and Butler SOS should not start.
try {

// Loop over all defined server tags
const serverTagsDefinition = config.get(
'Butler-SOS.serversToMonitor.serverTagsDefinition'
Expand Down
118 changes: 118 additions & 0 deletions src/lib/log-event-categorise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const { config, logger } = require('../globals');

// Function to categorise log events
//
//
// Parameters:
// - logLevel: The log level of the log event
// - logMessage: The message of the log event
//
// Output:
// - logEventCategory: The category of the log event. This is an object with the following properties:
// - category: The category of the log event. Array of objects with the following properties:
// - name: The name of the category
// - value: The value of the category
// - actionTaken: The action taken for the log event. Possible values are 'categorised', 'dropped'
function categoriseLogEvent(logLevel, logMessage) {
const logEventCategory = [];

try {
let match = false;

// Loop over all rules in the config file
// eslint-disable-next-line no-restricted-syntax
for (const rule of config.get('Butler-SOS.logEvents.categorise.rules')) {
// Check if the log event matches any of the rule's log levels (which are found in the array 'logLevel' property)
// Make the check case insensitive
if (rule.logLevel.map((x) => x.toLowerCase()).includes(logLevel.toLowerCase())) {
// Check if the log event message matches any of the rule's filters
// The rule filters are found in the 'filter' property, which is an array of objects with the following properties:
// - type: The type of the filter. Possible values are 'sw', 'ew', 'so'
// - value: The value of the filter
// Make the check case sensitive

// eslint-disable-next-line no-restricted-syntax
for (const filter of rule.filter) {
// More than one filter may match
if (filter.type === 'sw') {
if (logMessage.startsWith(filter.value)) {
// If action is 'drop', then drop the log event
if (rule.action === 'drop') {
return { category: [], actionTaken: 'dropped' };
}
match = true;
// Deep copy the categories from the rule to the log event
logEventCategory.push(...rule.category);
break;
}
}
if (filter.type === 'ew') {
if (logMessage.endsWith(filter.value)) {
// If action is 'drop', then drop the log event
if (rule.action === 'drop') {
return { category: [], actionTaken: 'dropped' };
}
match = true;
// Deep copy the categories from the rule to the log event
logEventCategory.push(...rule.category);
break;
}
}
if (filter.type === 'so') {
if (logMessage.includes(filter.value)) {
// If action is 'drop', then drop the log event
if (rule.action === 'drop') {
return { category: [], actionTaken: 'dropped' };
}
match = true;
// Deep copy the categories from the rule to the log event
logEventCategory.push(...rule.category);
break;
}
}

// Warn if the filter type is not recognised
if (!['sw', 'ew', 'so'].includes(filter.type)) {
logger.warn(
`LOG EVENT CATEGORISATION: Filter type '${filter.type}' is not recognised`
);
}
}
}
}

// Remove any duplicate categories
// Both name and value must match for the category to be considered a duplicate
const uniqueCategories = [];
logEventCategory.forEach((category) => {
if (
!uniqueCategories.some(
(x) => x.name === category.name && x.value === category.value
)
) {
uniqueCategories.push(category);
}
});

// If no rule matched, then use default rule (if enabled in the config file)
if (
match === false &&
config.get('Butler-SOS.logEvents.categorise.ruleDefault.enable') === true
) {
// Deep copy the categories from the default rule to the log event
uniqueCategories.push(
...config.get('Butler-SOS.logEvents.categorise.ruleDefault.category')
);
}

// Return the log event category and the action taken
return { category: uniqueCategories, actionTaken: 'categorised' };
} catch (err) {
logger.error(`LOG EVENT CATEGORISATION: Error processing log event: ${err}`);
return null;
}
}

module.exports = {
categoriseLogEvent,
};
17 changes: 17 additions & 0 deletions src/lib/post-to-influxdb.js
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,15 @@ async function postLogEventToInfluxdb(msg) {
};
}

// Add log event categories to tags if available
// The msg.category array contains objects with properties 'name' and 'value'
if (msg?.category?.length > 0) {
msg.category.forEach((category) => {
tags[category.name] = category.value;
});
}

// Add custom tags from config file to payload
if (
globals.config.has('Butler-SOS.logEvents.tags') &&
globals.config.get('Butler-SOS.logEvents.tags') !== null &&
Expand Down Expand Up @@ -1198,6 +1207,14 @@ async function postLogEventToInfluxdb(msg) {
if (msg?.result_code?.length > 0) point.tag('result_code', msg.result_code);
}

// Add log event categories to tags if available
// The msg.category array contains objects with properties 'name' and 'value'
if (msg?.category?.length > 0) {
msg.category.forEach((category) => {
point.tag(category.name, category.value);
});
}

// Add custom tags from config file to payload
if (
globals.config.has('Butler-SOS.logEvents.tags') &&
Expand Down
Loading

0 comments on commit e87d2be

Please sign in to comment.