Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make percentage_format_string support str.format() syntax #403

Merged
merged 6 commits into from
Aug 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- Apply percentage_format_string to match_body percentage value; will appear in new percentage_formatted key - [#387](https://github.com/jertel/elastalert2/pull/387) - @iamxeph
- Add support for Kibana 7.14 for Kibana Discover - [#392](https://github.com/jertel/elastalert2/pull/392) - @nsano-rururu
- Add metric_format_string optional configuration for Metric Aggregation to format aggregated value - [#399](https://github.com/jertel/elastalert2/pull/399) - @iamxeph
- Make percentage_format_string support format() syntax in addition to old %-formatted syntax - [#403](https://github.com/jertel/elastalert2/pull/403) - @iamxeph

## Other changes
- [Tests] Improve test code coverage - [#331](https://github.com/jertel/elastalert2/pull/331) - @nsano-rururu
Expand Down
5 changes: 2 additions & 3 deletions docs/source/ruletypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ allign with the time elastalert runs, (This both avoid calculations on partial d
See: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#_offset for a
more comprehensive explaination.

``metric_format_string``: An optional format string applies to the aggregated metric value in the alert match text and match_body. This adds 'metric_{metric_agg_key}_formatted' value to the match_body in addition to raw, unformatted 'metric_{metric_agg_key}' value so that you can use the values for ``alert_subject_args`` and ``alert_text_args``. Must be a valid python format string. Both format() and %-formatted syntax works. For example, "{:.2%}" will format '0.966666667' to '96.67%', and "%.2f" will format '0.966666667' to '0.97'.
``metric_format_string``: An optional format string applies to the aggregated metric value in the alert match text and match_body. This adds 'metric_{metric_agg_key}_formatted' value to the match_body in addition to raw, unformatted 'metric_{metric_agg_key}' value so that you can use the values for ``alert_subject_args`` and ``alert_text_args``. Must be a valid python format string. Both str.format() and %-format syntax works. For example, "{:.2%}" will format '0.966666667' to '96.67%', and "%.2f" will format '0.966666667' to '0.97'.
See: https://docs.python.org/3.4/library/string.html#format-specification-mini-language


Expand Down Expand Up @@ -1347,8 +1347,7 @@ evaluated separately against the threshold(s).

``sync_bucket_interval``: See ``sync_bucket_interval`` in Metric Aggregation rule

``percentage_format_string``: An optional format string to apply to the percentage value in the alert match text. This also adds 'percentage_formatted' value to the match_body in addition to raw, unformatted 'percentage' value so that you can use the formatted value for ``alert_subject_args`` and ``alert_text_args``. Must be a valid python format string.
For example, "%.2f" will round it to 2 decimal places.
``percentage_format_string``: An optional format string applies to the percentage value in the alert match text and match_body. This adds 'percentage_formatted' value to the match_body in addition to raw, unformatted 'percentage' value so that you can use the values for ``alert_subject_args`` and ``alert_text_args``. Must be a valid python format string. Both str.format() and %-format syntax works. For example, both "{:.2f}" and "%.2f" will format '96.6666667' to '96.67'.
See: https://docs.python.org/3.4/library/string.html#format-specification-mini-language

``min_denominator``: Minimum number of documents on which percentage calculation will apply. Default is 0.
Expand Down
18 changes: 6 additions & 12 deletions elastalert/ruletypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from elastalert.util import (add_raw_postfix, dt_to_ts, EAException, elastalert_logger, elasticsearch_client,
format_index, hashable, lookup_es_key, new_get_event_ts, pretty_ts, total_seconds,
ts_now, ts_to_dt, expand_string_into_dict)
ts_now, ts_to_dt, expand_string_into_dict, format_string)


class RuleType(object):
Expand Down Expand Up @@ -1072,7 +1072,7 @@ def get_match_str(self, match):
message = 'Threshold violation, %s:%s %s (min: %s max : %s) \n\n' % (
self.rules['metric_agg_type'],
self.rules['metric_agg_key'],
self.format_string(metric_format_string, match[self.metric_key]) if metric_format_string else match[self.metric_key],
format_string(metric_format_string, match[self.metric_key]) if metric_format_string else match[self.metric_key],
self.rules.get('min_threshold'),
self.rules.get('max_threshold')
)
Expand All @@ -1098,7 +1098,7 @@ def check_matches(self, timestamp, query_key, aggregation_data):
self.metric_key: metric_val}
metric_format_string = self.rules.get('metric_format_string', None)
if metric_format_string is not None:
match[self.metric_key +'_formatted'] = self.format_string(metric_format_string, metric_val)
match[self.metric_key +'_formatted'] = format_string(metric_format_string, metric_val)
if query_key is not None:
match = expand_string_into_dict(match, self.rules['query_key'], query_key)
self.add_match(match)
Expand Down Expand Up @@ -1140,12 +1140,6 @@ def crossed_thresholds(self, metric_value):
return True
return False

def format_string(self, format_config, target_value):
if (format_config.startswith('{')):
return format_config.format(target_value)
else:
return format_config % (target_value)


class SpikeMetricAggregationRule(BaseAggregationRule, SpikeRule):
""" A rule that matches when there is a spike in an aggregated event compared to its reference point """
Expand Down Expand Up @@ -1258,7 +1252,7 @@ def __init__(self, *args):
def get_match_str(self, match):
percentage_format_string = self.rules.get('percentage_format_string', None)
message = 'Percentage violation, value: %s (min: %s max : %s) of %s items\n\n' % (
percentage_format_string % (match['percentage']) if percentage_format_string else match['percentage'],
format_string(percentage_format_string, match['percentage']) if percentage_format_string else match['percentage'],
self.rules.get('min_percentage'),
self.rules.get('max_percentage'),
match['denominator']
Expand Down Expand Up @@ -1297,7 +1291,7 @@ def check_matches(self, timestamp, query_key, aggregation_data):
match = {self.rules['timestamp_field']: timestamp, 'percentage': match_percentage, 'denominator': total_count}
percentage_format_string = self.rules.get('percentage_format_string', None)
if percentage_format_string is not None:
match['percentage_formatted'] = percentage_format_string % (match_percentage)
match['percentage_formatted'] = format_string(percentage_format_string, match_percentage)
if query_key is not None:
match = expand_string_into_dict(match, self.rules['query_key'], query_key)
self.add_match(match)
Expand All @@ -1307,4 +1301,4 @@ def percentage_violation(self, match_percentage):
return True
if 'min_percentage' in self.rules and match_percentage < self.rules['min_percentage']:
return True
return False
return False
15 changes: 15 additions & 0 deletions elastalert/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,3 +518,18 @@ def expand_string_into_dict(dictionary, string , value, sep='.'):
field1, new_string = string.split(sep, 1)
dictionary[field1] = _expand_string_into_dict(new_string, value)
return dictionary


def format_string(format_config, target_value):
"""
Formats number, supporting %-format and str.format() syntax.

:param format_config: string format syntax, for example '{:.2%}' or '%.2f'
:param target_value: number to format
:rtype: string
"""
if (format_config.startswith('{')):
return format_config.format(target_value)
else:
return format_config % (target_value)

6 changes: 6 additions & 0 deletions tests/rules_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,12 @@ def test_percentage_match():
assert '76.1589403974' in rule.get_match_str(rule.matches[0])
assert rule.matches[0]['percentage'] == 76.15894039742994
assert 'percentage_formatted' not in rule.matches[0]
rules['percentage_format_string'] = '{:.2f}'
rule = PercentageMatchRule(rules)
rule.check_matches(datetime.datetime.now(), None, create_percentage_match_agg(76.666666667, 24))
assert '76.16' in rule.get_match_str(rule.matches[0])
assert rule.matches[0]['percentage'] == 76.15894039742994
assert rule.matches[0]['percentage_formatted'] == '76.16'
rules['percentage_format_string'] = '%.2f'
rule = PercentageMatchRule(rules)
rule.check_matches(datetime.datetime.now(), None, create_percentage_match_agg(76.666666667, 24))
Expand Down
9 changes: 9 additions & 0 deletions tests/util_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from elastalert.util import ts_utc_to_tz
from elastalert.util import expand_string_into_dict
from elastalert.util import unixms_to_dt
from elastalert.util import format_string


@pytest.mark.parametrize('spec, expected_delta', [
Expand Down Expand Up @@ -502,3 +503,11 @@ def test_dt_to_int():
actual = dt_to_int(dt)
expected = 1625529600000
assert expected == actual


def test_format_string():
target = 0.966666667
expected_percent_formatting = '0.97'
assert format_string('%.2f', target) == expected_percent_formatting
expected_str_formatting = '96.67%'
assert format_string('{:.2%}', target) == expected_str_formatting