Im making a custom alert action with a python script where I want to send an html email.
I looked into the default sendemail.py file of splunk but I do not understand it. It uses: splunk.Intersplunk to load the email settings needed for sending mail with the smtplib library. But i can not find any documentation about intersplunk and the code from sendemail.py itself is not clear enough to understand without comments.
Is there any example about how to send an html email in a custom python script that uses the splunk settings for mailing.
Even any documentation about how to send emails from a custom python scripts would help me.
I ended up with the following script.
# This script gets the results from a saved search and sends an email based on the content.
# The searchquery that triggers this alert should contain the following fields
#
# Severity The severity of the kpi, the colors in the table are based on the severity value
# Service The name of the service
# KPI The name of the kpi
# Description A description that describes the error
# Mail_to A list of users that should receive the mail separated with ,
# New error Values 'Yes' if the error is new
# Machine ID The name of the machine that triggers this script
import sys
import gzip
import logging
import csv
import json
import re
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import splunk.entity as entity
import splunk.secure_smtplib as secure_smtplib
from splunk.rest import simpleRequest
logging.basicConfig(filename='development.log', level=logging.DEBUG)
# CSS stylings
style_table = "font-family: verdana,arial,sans-serif; " \
"font-size:11px; " \
"color:#333333; " \
"border-width: 1px; " \
"border-color: #3A3A3A; " \
"border-collapse: collapse; "
style_thead = "border-width: 1px; " \
"padding: 8px; " \
"text-align:left; " \
"border-style: solid; " \
"border-color: #3A3A3A; " \
"background-color: #B3B3B3; "
style_thead_tr = "padding: 8px; "
style_tr_even = "border-width: 1px; " \
"padding: 8px; " \
"border-style: solid; " \
"border-color: #3A3A3A; " \
"background-color: #cccccc; "
style_td = "padding:8px; "
class Alert(object):
def __init__(self, severity, service, kpi, description, is_new):
self.severity = severity
self.service = service
self.kpi = kpi
self.description = description
self.is_new = is_new
def create_table_data(data, styling=""):
if styling != "":
styling = 'style="%s"' % styling
return "<td %s>\n%s\n</td>\n" % (styling, data)
def status_to_color(status):
status = status.lower()
status_colors = {}
status_colors["critical"] = "#efbebe"
status_colors["high"] = "#facdae"
if status in status_colors:
return status_colors[status]
return "#dadada"
def create_table_row(data, styling=""):
styling = 'style="%s"' % styling
return "<tr %s>\n%s\n</tr>\n" % (styling, data)
def create_table_head(headings, styling=""):
result = ""
for heading in headings:
result += ('<th style="%s">\n%s\n</th>\n' % (styling, heading))
return create_table_row(result, style_thead)
def create_alerts_table(alerts):
table = '<table cellspacing="0" cellpadding="0" style="%s">' % style_table
table += create_table_head(["Severity", "Service", "Kpi", "Description", "New error"], style_thead_tr)
x = 0
for alert in alerts:
event_row_styling = "font-family: Arial; "
x += 1
row_data = create_table_data(alert.severity, style_td)
row_data += create_table_data(alert.service, style_td)
row_data += create_table_data(alert.kpi, style_td)
row_data += create_table_data(alert.description, style_td)
row_data += create_table_data(alert.is_new, style_td)
if alert.is_new:
event_row_styling += "background-color: #f48989;"
else:
event_row_styling += "background-color: %s" % (status_to_color(alert.severity))
table += create_table_row(row_data, event_row_styling)
table += "</table>"
return table
def create_html_template(content):
mail_template_filled = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' \
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html ' \
'xmlns="http://www.w3.org/1999/xhtml">\n <head>\n <meta http-equiv="Content-Type" ' \
'content="text/html; charset=UTF-8" />\n <title>\n HTML email\n</title>\n <meta ' \
'name="viewport" content="width=device-width, ' \
'initial-scale=1.0"/>\n</head>\n<body>\n%s\n</body>\n</html>\n ' % content
return mail_template_filled
def send_mail(mail_to, email_message, mail_from="default@somedomain.com", mailserver="localhost", subject="Message from python script"):
msg = MIMEMultipart('alternative')
part1 = MIMEText(email_message, 'html')
msg.attach(part1)
msg['To'] = mail_to
msg['Subject'] = subject
mail_to_list = mail_to.split(",")
try:
smtp = secure_smtplib.SecureSMTP(host=mailserver)
logging.debug(mail_to_list)
logging.debug(mail_to)
smtp.sendmail(mail_from, mail_to_list, msg.as_string())
smtp.quit()
except Exception as e:
logging.error(str(e))
if __name__ == "__main__":
try:
alerts = []
data = sys.stdin.read()
parsed_result = json.loads(data)
# Get the address of the gzip file.
csv_gzip_location = parsed_result["results_file"]
owner = parsed_result["owner"]
app = parsed_result["app"]
session_key = parsed_result["session_key"]
# Read the results of the query that triggered the alert
csv_reader = csv.DictReader(gzip.open(csv_gzip_location, 'rb'))
to_mail_list = ""
line_counter = 0
new_events = 0
total_events = 0
machine_id = ""
for line in csv_reader:
total_events += 1
if line["New error"]:
new_events += 1
alerts.append(Alert(severity=line["Severity"], service=line["Service"], kpi=line["KPI"],
description=line["Description"], is_new=line["New error"]))
if not machine_id:
if "Machine ID" in line:
machine_id = line["Machine ID"]
if not to_mail_list:
if 'Mail_to' in line:
to_mail_list = line["Mail_to"]
# Write the HTML formatted email content
message = "<h1>Notable events</h1>\n"
message += "<p>New events: <b>%s</b></p>\n" % new_events
message += "<p>Total events: <b>%s</b></p>\n" % total_events
message += "<p>Machine ID: <b>%s</b></p>\n" % machine_id
events_table = create_alerts_table(alerts)
message += events_table
message = create_html_template(message)
# Get the mailserver
uri = entity.buildEndpoint(
[
'saved',
'searches',
'_new'
],
namespace=app,
owner=owner
)
responseHeaders, responseBody = simpleRequest(uri, method='GET', getargs={'output_mode': 'json'},
sessionKey=session_key)
savedSearch = json.loads(responseBody)
ssContent = savedSearch['entry'][0]['content']
mail_server = ssContent.get('action.email.mailserver', 'localhost')
mail_from = ssContent.get('action.email.from', 'localhost')
mail_subject = (" %s | New notable events: %s, Total events: %s."% (machine_id, new_events, total_events))
try:
f = open('email_content.html','w')
f.write(message)
send_mail(mail_to=to_mail_list, mail_from=mail_from, email_message=message, mailserver=mail_server, subject=mail_subject)
except:
logging.exception('Failed sending email')
except Exception as e:
logging.exception("Failed while getting the information for the email")
Note that this only uses unsecured smtplib email, if you want to use ssl/password you should update the following part to get the mail information:
ssContent = savedSearch['entry'][0]['content']
mail_server = ssContent.get('action.email.mailserver', 'localhost')
mail_from = ssContent.get('action.email.from', 'localhost')
sendresults could possibly be used here and this can send HTML-style email content, you just need to change the body of the email. I believe there will be other solutions here as well such as the one you have posted, thanks for sharing!
I ended up with the following script.
# This script gets the results from a saved search and sends an email based on the content.
# The searchquery that triggers this alert should contain the following fields
#
# Severity The severity of the kpi, the colors in the table are based on the severity value
# Service The name of the service
# KPI The name of the kpi
# Description A description that describes the error
# Mail_to A list of users that should receive the mail separated with ,
# New error Values 'Yes' if the error is new
# Machine ID The name of the machine that triggers this script
import sys
import gzip
import logging
import csv
import json
import re
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import splunk.entity as entity
import splunk.secure_smtplib as secure_smtplib
from splunk.rest import simpleRequest
logging.basicConfig(filename='development.log', level=logging.DEBUG)
# CSS stylings
style_table = "font-family: verdana,arial,sans-serif; " \
"font-size:11px; " \
"color:#333333; " \
"border-width: 1px; " \
"border-color: #3A3A3A; " \
"border-collapse: collapse; "
style_thead = "border-width: 1px; " \
"padding: 8px; " \
"text-align:left; " \
"border-style: solid; " \
"border-color: #3A3A3A; " \
"background-color: #B3B3B3; "
style_thead_tr = "padding: 8px; "
style_tr_even = "border-width: 1px; " \
"padding: 8px; " \
"border-style: solid; " \
"border-color: #3A3A3A; " \
"background-color: #cccccc; "
style_td = "padding:8px; "
class Alert(object):
def __init__(self, severity, service, kpi, description, is_new):
self.severity = severity
self.service = service
self.kpi = kpi
self.description = description
self.is_new = is_new
def create_table_data(data, styling=""):
if styling != "":
styling = 'style="%s"' % styling
return "<td %s>\n%s\n</td>\n" % (styling, data)
def status_to_color(status):
status = status.lower()
status_colors = {}
status_colors["critical"] = "#efbebe"
status_colors["high"] = "#facdae"
if status in status_colors:
return status_colors[status]
return "#dadada"
def create_table_row(data, styling=""):
styling = 'style="%s"' % styling
return "<tr %s>\n%s\n</tr>\n" % (styling, data)
def create_table_head(headings, styling=""):
result = ""
for heading in headings:
result += ('<th style="%s">\n%s\n</th>\n' % (styling, heading))
return create_table_row(result, style_thead)
def create_alerts_table(alerts):
table = '<table cellspacing="0" cellpadding="0" style="%s">' % style_table
table += create_table_head(["Severity", "Service", "Kpi", "Description", "New error"], style_thead_tr)
x = 0
for alert in alerts:
event_row_styling = "font-family: Arial; "
x += 1
row_data = create_table_data(alert.severity, style_td)
row_data += create_table_data(alert.service, style_td)
row_data += create_table_data(alert.kpi, style_td)
row_data += create_table_data(alert.description, style_td)
row_data += create_table_data(alert.is_new, style_td)
if alert.is_new:
event_row_styling += "background-color: #f48989;"
else:
event_row_styling += "background-color: %s" % (status_to_color(alert.severity))
table += create_table_row(row_data, event_row_styling)
table += "</table>"
return table
def create_html_template(content):
mail_template_filled = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' \
'"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html ' \
'xmlns="http://www.w3.org/1999/xhtml">\n <head>\n <meta http-equiv="Content-Type" ' \
'content="text/html; charset=UTF-8" />\n <title>\n HTML email\n</title>\n <meta ' \
'name="viewport" content="width=device-width, ' \
'initial-scale=1.0"/>\n</head>\n<body>\n%s\n</body>\n</html>\n ' % content
return mail_template_filled
def send_mail(mail_to, email_message, mail_from="default@somedomain.com", mailserver="localhost", subject="Message from python script"):
msg = MIMEMultipart('alternative')
part1 = MIMEText(email_message, 'html')
msg.attach(part1)
msg['To'] = mail_to
msg['Subject'] = subject
mail_to_list = mail_to.split(",")
try:
smtp = secure_smtplib.SecureSMTP(host=mailserver)
logging.debug(mail_to_list)
logging.debug(mail_to)
smtp.sendmail(mail_from, mail_to_list, msg.as_string())
smtp.quit()
except Exception as e:
logging.error(str(e))
if __name__ == "__main__":
try:
alerts = []
data = sys.stdin.read()
parsed_result = json.loads(data)
# Get the address of the gzip file.
csv_gzip_location = parsed_result["results_file"]
owner = parsed_result["owner"]
app = parsed_result["app"]
session_key = parsed_result["session_key"]
# Read the results of the query that triggered the alert
csv_reader = csv.DictReader(gzip.open(csv_gzip_location, 'rb'))
to_mail_list = ""
line_counter = 0
new_events = 0
total_events = 0
machine_id = ""
for line in csv_reader:
total_events += 1
if line["New error"]:
new_events += 1
alerts.append(Alert(severity=line["Severity"], service=line["Service"], kpi=line["KPI"],
description=line["Description"], is_new=line["New error"]))
if not machine_id:
if "Machine ID" in line:
machine_id = line["Machine ID"]
if not to_mail_list:
if 'Mail_to' in line:
to_mail_list = line["Mail_to"]
# Write the HTML formatted email content
message = "<h1>Notable events</h1>\n"
message += "<p>New events: <b>%s</b></p>\n" % new_events
message += "<p>Total events: <b>%s</b></p>\n" % total_events
message += "<p>Machine ID: <b>%s</b></p>\n" % machine_id
events_table = create_alerts_table(alerts)
message += events_table
message = create_html_template(message)
# Get the mailserver
uri = entity.buildEndpoint(
[
'saved',
'searches',
'_new'
],
namespace=app,
owner=owner
)
responseHeaders, responseBody = simpleRequest(uri, method='GET', getargs={'output_mode': 'json'},
sessionKey=session_key)
savedSearch = json.loads(responseBody)
ssContent = savedSearch['entry'][0]['content']
mail_server = ssContent.get('action.email.mailserver', 'localhost')
mail_from = ssContent.get('action.email.from', 'localhost')
mail_subject = (" %s | New notable events: %s, Total events: %s."% (machine_id, new_events, total_events))
try:
f = open('email_content.html','w')
f.write(message)
send_mail(mail_to=to_mail_list, mail_from=mail_from, email_message=message, mailserver=mail_server, subject=mail_subject)
except:
logging.exception('Failed sending email')
except Exception as e:
logging.exception("Failed while getting the information for the email")
Note that this only uses unsecured smtplib email, if you want to use ssl/password you should update the following part to get the mail information:
ssContent = savedSearch['entry'][0]['content']
mail_server = ssContent.get('action.email.mailserver', 'localhost')
mail_from = ssContent.get('action.email.from', 'localhost')
I had problems with html and sendemail too. The best solution I found was modifying sendemail.py.
Need to modify {msg|h} to {msg} inside the script.
Credits to:
https://answers.splunk.com/answers/389294/how-to-use-html-code-in-email-alerts-to-format-the.html
@elpred0 I added my solution it might help you.
@elpred0 I want to use my custom python script not the sendmail,py i just use it as a reference,