-
Notifications
You must be signed in to change notification settings - Fork 250
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
Add Fixed-rate sampling logic for Azure Log Exporter #848
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
# limitations under the License. | ||
|
||
import logging | ||
import random | ||
import threading | ||
import time | ||
import traceback | ||
|
@@ -107,6 +108,16 @@ def stop(self, timeout=None): # pragma: NO COVER | |
return time.time() - start_time # time taken to stop | ||
|
||
|
||
class SamplingFilter(logging.Filter): | ||
|
||
def __init__(self, probability=1.0): | ||
super(SamplingFilter, self).__init__() | ||
self.probability = probability | ||
|
||
def filter(self, record): | ||
return random.random() < self.probability | ||
|
||
|
||
class AzureLogHandler(TransportMixin, BaseLogHandler): | ||
"""Handler for logging to Microsoft Azure Monitor. | ||
|
||
|
@@ -116,6 +127,9 @@ class AzureLogHandler(TransportMixin, BaseLogHandler): | |
def __init__(self, **options): | ||
self.options = Options(**options) | ||
utils.validate_instrumentation_key(self.options.instrumentation_key) | ||
if self.options.logging_sampling_rate < 0 or \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:You can move this at the beginning of the method to avoid doing anything else if sampling rate is incorrect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validating the instrumentation key is also a validation and fast fail in itself. |
||
self.options.logging_sampling_rate > 1.0: | ||
lzchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
raise ValueError('Sampling must be in the range: [0,1]') | ||
self.export_interval = self.options.export_interval | ||
self.max_batch_size = self.options.max_batch_size | ||
self.storage = LocalFileStorage( | ||
|
@@ -125,6 +139,7 @@ def __init__(self, **options): | |
retention_period=self.options.storage_retention_period, | ||
) | ||
super(AzureLogHandler, self).__init__() | ||
self.addFilter(SamplingFilter(self.options.logging_sampling_rate)) | ||
|
||
def close(self): | ||
self.storage.close() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,6 +78,12 @@ def test_ctor(self): | |
self.assertRaises(ValueError, lambda: log_exporter.AzureLogHandler()) | ||
Options._default.instrumentation_key = instrumentation_key | ||
|
||
def test_invalid_sampling_rate(self): | ||
self.assertRaises(ValueError, lambda: log_exporter.AzureLogHandler( | ||
lzchen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
instrumentation_key='12345678-1234-5678-abcd-12345678abcd', | ||
logging_sampling_rate=4.0, | ||
)) | ||
|
||
@mock.patch('requests.post', return_value=mock.Mock()) | ||
def test_exception(self, requests_mock): | ||
logger = logging.getLogger(self.id()) | ||
|
@@ -207,3 +213,37 @@ def test_log_with_invalid_custom_properties(self, requests_mock): | |
|
||
self.assertFalse('not_a_dict' in post_body) | ||
self.assertFalse('key_1' in post_body) | ||
|
||
@mock.patch('requests.post', return_value=mock.Mock()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surprising to see |
||
def test_log_record_sampled(self, requests_mock): | ||
logger = logging.getLogger(self.id()) | ||
handler = log_exporter.AzureLogHandler( | ||
instrumentation_key='12345678-1234-5678-abcd-12345678abcd', | ||
logging_sampling_rate=1.0, | ||
) | ||
logger.addHandler(handler) | ||
logger.warning('Hello_World') | ||
logger.warning('Hello_World2') | ||
logger.warning('Hello_World3') | ||
logger.warning('Hello_World4') | ||
handler.close() | ||
post_body = requests_mock.call_args_list[0][1]['data'] | ||
self.assertTrue('Hello_World' in post_body) | ||
self.assertTrue('Hello_World2' in post_body) | ||
self.assertTrue('Hello_World3' in post_body) | ||
self.assertTrue('Hello_World4' in post_body) | ||
|
||
@mock.patch('requests.post', return_value=mock.Mock()) | ||
def test_log_record_not_sampled(self, requests_mock): | ||
logger = logging.getLogger(self.id()) | ||
handler = log_exporter.AzureLogHandler( | ||
instrumentation_key='12345678-1234-5678-abcd-12345678abcd', | ||
logging_sampling_rate=0.0, | ||
) | ||
logger.addHandler(handler) | ||
logger.warning('Hello_World') | ||
logger.warning('Hello_World2') | ||
logger.warning('Hello_World3') | ||
logger.warning('Hello_World4') | ||
handler.close() | ||
self.assertFalse(requests_mock.called) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any desire to support adaptive sampling for Application Insights as well? If so, it may be worth it to name this
FixedRateSamplingFilter
to be more specific.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently in the roadmap there is no plan to add adaptive sampling. If this is ever added, it will be simple to iterate on this since the class is not exposed to customers.