import copy import json import requests import warnings from elastalert.alerts import Alerter, DateTimeEncoder from elastalert.util import elastalert_logger, EAException, lookup_es_key from requests.exceptions import RequestException class SlackAlerter(Alerter): """ Creates a Slack room message for each alert """ required_options = frozenset(['slack_webhook_url']) def __init__(self, rule): super(SlackAlerter, self).__init__(rule) self.slack_webhook_url = self.rule.get('slack_webhook_url', None) if isinstance(self.slack_webhook_url, str): self.slack_webhook_url = [self.slack_webhook_url] self.slack_proxy = self.rule.get('slack_proxy', None) self.slack_username_override = self.rule.get('slack_username_override', 'elastalert') self.slack_channel_override = self.rule.get('slack_channel_override', '') if isinstance(self.slack_channel_override, str): self.slack_channel_override = [self.slack_channel_override] self.slack_title_link = self.rule.get('slack_title_link', '') self.slack_title = self.rule.get('slack_title', '') self.slack_emoji_override = self.rule.get('slack_emoji_override', ':ghost:') self.slack_icon_url_override = self.rule.get('slack_icon_url_override', '') self.slack_msg_color = self.rule.get('slack_msg_color', 'danger') self.slack_parse_override = self.rule.get('slack_parse_override', 'none') self.slack_text_string = self.rule.get('slack_text_string', '') self.slack_alert_fields = self.rule.get('slack_alert_fields', '') self.slack_ignore_ssl_errors = self.rule.get('slack_ignore_ssl_errors', False) self.slack_timeout = self.rule.get('slack_timeout', 10) self.slack_ca_certs = self.rule.get('slack_ca_certs') self.slack_attach_kibana_discover_url = self.rule.get('slack_attach_kibana_discover_url', False) self.slack_kibana_discover_color = self.rule.get('slack_kibana_discover_color', '#ec4b98') self.slack_kibana_discover_title = self.rule.get('slack_kibana_discover_title', 'Discover in Kibana') self.slack_attach_opensearch_discover_url = self.rule.get('slack_attach_opensearch_discover_url', False) self.slack_opensearch_discover_color = self.rule.get('slack_opensearch_discover_color', '#ec4b98') self.slack_opensearch_discover_title = self.rule.get('slack_opensearch_discover_title', 'Discover in Opensearch') self.slack_footer = self.rule.get('slack_footer', '') self.slack_footer_icon = self.rule.get('slack_footer_icon', '') self.slack_image_url = self.rule.get('slack_image_url', '') self.slack_thumb_url = self.rule.get('slack_thumb_url', '') self.slack_author_name = self.rule.get('slack_author_name', '') self.slack_author_link = self.rule.get('slack_author_link', '') self.slack_author_icon = self.rule.get('slack_author_icon', '') self.slack_msg_pretext = self.rule.get('slack_msg_pretext', '') self.slack_attach_jira_ticket_url = self.rule.get('slack_attach_jira_ticket_url', False) self.slack_jira_ticket_color = self.rule.get('slack_jira_ticket_color', '#ec4b98') self.slack_jira_ticket_title = self.rule.get('slack_jira_ticket_title', 'Jira Ticket') def format_body(self, body): # https://api.slack.com/docs/formatting return body def get_aggregation_summary_text__maximum_width(self): width = super(SlackAlerter, self).get_aggregation_summary_text__maximum_width() # Reduced maximum width for prettier Slack display. return min(width, 75) def get_aggregation_summary_text(self, matches): text = super(SlackAlerter, self).get_aggregation_summary_text(matches) if text: text = '```\n{0}```\n'.format(text) return text def populate_fields(self, matches): alert_fields = [] for arg in self.slack_alert_fields: arg = copy.copy(arg) arg['value'] = lookup_es_key(matches[0], arg['value']) alert_fields.append(arg) return alert_fields def alert(self, matches): body = self.create_alert_body(matches) body = self.format_body(body) # post to slack headers = {'content-type': 'application/json'} # set https proxy, if it was provided proxies = {'https': self.slack_proxy} if self.slack_proxy else None payload = { 'username': self.slack_username_override, 'parse': self.slack_parse_override, 'text': self.slack_text_string, 'attachments': [ { 'color': self.slack_msg_color, 'title': self.create_title(matches), 'text': body, 'mrkdwn_in': ['text', 'pretext'], 'fields': [] } ] } # if we have defined fields, populate noteable fields for the alert if self.slack_alert_fields != '': payload['attachments'][0]['fields'] = self.populate_fields(matches) if self.slack_icon_url_override != '': payload['icon_url'] = self.slack_icon_url_override else: payload['icon_emoji'] = self.slack_emoji_override if self.slack_title != '': payload['attachments'][0]['title'] = self.slack_title if self.slack_title_link != '': payload['attachments'][0]['title_link'] = self.slack_title_link if self.slack_footer != '': payload['attachments'][0]['footer'] = self.slack_footer if self.slack_footer_icon != '': payload['attachments'][0]['footer_icon'] = self.slack_footer_icon if self.slack_image_url != '': payload['attachments'][0]['image_url'] = self.slack_image_url if self.slack_thumb_url != '': payload['attachments'][0]['thumb_url'] = self.slack_thumb_url if self.slack_author_name != '': payload['attachments'][0]['author_name'] = self.slack_author_name if self.slack_author_link != '': payload['attachments'][0]['author_link'] = self.slack_author_link if self.slack_author_icon != '': payload['attachments'][0]['author_icon'] = self.slack_author_icon if self.slack_msg_pretext != '': payload['attachments'][0]['pretext'] = self.slack_msg_pretext if self.slack_attach_kibana_discover_url: kibana_discover_url = lookup_es_key(matches[0], 'kibana_discover_url') if kibana_discover_url: payload['attachments'].append({ 'color': self.slack_kibana_discover_color, 'title': self.slack_kibana_discover_title, 'title_link': kibana_discover_url }) if self.slack_attach_opensearch_discover_url: opensearch_discover_url = lookup_es_key(matches[0], 'opensearch_discover_url') if opensearch_discover_url: payload['attachments'].append({ 'color': self.slack_opensearch_discover_color, 'title': self.slack_opensearch_discover_title, 'title_link': opensearch_discover_url }) if self.slack_attach_jira_ticket_url and self.pipeline is not None and 'jira_ticket' in self.pipeline: jira_url = '%s/browse/%s' % (self.pipeline['jira_server'], self.pipeline['jira_ticket']) payload['attachments'].append({ 'color': self.slack_jira_ticket_color, 'title': self.slack_jira_ticket_title, 'title_link': jira_url }) for url in self.slack_webhook_url: for channel_override in self.slack_channel_override: try: if self.slack_ca_certs: verify = self.slack_ca_certs else: verify = not self.slack_ignore_ssl_errors if self.slack_ignore_ssl_errors: requests.packages.urllib3.disable_warnings() payload['channel'] = channel_override response = requests.post( url, data=json.dumps(payload, cls=DateTimeEncoder), headers=headers, verify=verify, proxies=proxies, timeout=self.slack_timeout) warnings.resetwarnings() response.raise_for_status() except RequestException as e: raise EAException("Error posting to slack: %s" % e) elastalert_logger.info("Alert '%s' sent to Slack" % self.rule['name']) def get_info(self): return {'type': 'slack', 'slack_username_override': self.slack_username_override}