Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c81fecc

Browse files
committedFeb 21, 2025·
Adding new logic to sampling request
1 parent 335ee51 commit c81fecc

File tree

2 files changed

+154
-5
lines changed

2 files changed

+154
-5
lines changed
 

‎aws_lambda_powertools/logging/logger.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ def __init__(
220220
serialize_stacktrace: bool = True,
221221
**kwargs,
222222
) -> None:
223+
224+
# Used in case of sampling
225+
self.initial_log_level = level
226+
223227
self.service = resolve_env_var_choice(
224228
choice=service,
225229
env=os.getenv(constants.SERVICE_NAME_ENV, "service_undefined"),
@@ -339,6 +343,17 @@ def _init_logger(
339343
self._logger.init = True # type: ignore[attr-defined]
340344
self._logger.powertools_handler = self.logger_handler # type: ignore[attr-defined]
341345

346+
def refresh_sample_rate_calculation(self) -> None:
347+
"""
348+
Refreshes the sample rate calculation by reconfiguring logging settings.
349+
350+
Returns
351+
-------
352+
None
353+
"""
354+
self._logger.setLevel(self.initial_log_level)
355+
self._configure_sampling()
356+
342357
def _configure_sampling(self) -> None:
343358
"""Dynamically set log level based on sampling rate
344359
@@ -347,15 +362,19 @@ def _configure_sampling(self) -> None:
347362
InvalidLoggerSamplingRateError
348363
When sampling rate provided is not a float
349364
"""
365+
if not self.sampling_rate:
366+
return
367+
350368
try:
351-
if self.sampling_rate and random.random() <= float(self.sampling_rate):
352-
logger.debug("Setting log level to Debug due to sampling rate")
369+
# This is not testing < 0 or > 1 conditions
370+
# Because I don't need other if condition here
371+
if random.random() <= float(self.sampling_rate):
353372
self._logger.setLevel(logging.DEBUG)
354373
except ValueError:
355374
raise InvalidLoggerSamplingRateError(
356375
(
357376
f"Expected a float value ranging 0 to 1, but received {self.sampling_rate} instead."
358-
"Please review POWERTOOLS_LOGGER_SAMPLE_RATE environment variable."
377+
"Please review POWERTOOLS_LOGGER_SAMPLE_RATE environment variable or `sampling_rate` parameter."
359378
),
360379
)
361380

@@ -465,7 +484,14 @@ def decorate(event, context, *args, **kwargs):
465484
logger.debug("Event received")
466485
self.info(extract_event_from_common_models(event))
467486

468-
return lambda_handler(event, context, *args, **kwargs)
487+
lambda_execution_return = lambda_handler(event, context, *args, **kwargs)
488+
489+
# Sampling rate is defined, so, we need to recalculate the sampling
490+
# See: https://github.com/aws-powertools/powertools-lambda-python/issues/6141
491+
if self.sampling_rate:
492+
self.refresh_sample_rate_calculation()
493+
494+
return lambda_execution_return
469495

470496
return decorate
471497

‎tests/functional/logger/required_dependencies/test_logger.py

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def test_setup_service_env_var(monkeypatch, stdout, service_name):
8686
assert service_name == log["service"]
8787

8888

89-
def test_setup_sampling_rate_env_var(monkeypatch, stdout, service_name):
89+
def test_setup_sampling_rate_env_var_with_100percent(monkeypatch, stdout, service_name):
9090
# GIVEN Logger is initialized
9191
# WHEN samping rate is explicitly set to 100% via POWERTOOLS_LOGGER_SAMPLE_RATE env
9292
sampling_rate = "1"
@@ -103,6 +103,129 @@ def test_setup_sampling_rate_env_var(monkeypatch, stdout, service_name):
103103
assert "I am being sampled" == log["message"]
104104

105105

106+
def test_setup_sampling_rate_constructor_with_100percent(stdout, service_name):
107+
# GIVEN Logger is initialized
108+
# WHEN samping rate is explicitly set to 100% via constructor
109+
sampling_rate = 1
110+
logger = Logger(service=service_name, sampling_rate=sampling_rate, stream=stdout)
111+
logger.debug("I am being sampled")
112+
113+
# THEN sampling rate should be equals sampling_rate value
114+
# log level should be DEBUG
115+
# and debug log statements should be in stdout
116+
log = capture_logging_output(stdout)
117+
assert sampling_rate == log["sampling_rate"]
118+
assert "DEBUG" == log["level"]
119+
assert "I am being sampled" == log["message"]
120+
121+
122+
def test_setup_sampling_rate_env_var_with_0percent(monkeypatch, stdout, service_name):
123+
# GIVEN Logger is initialized
124+
# WHEN samping rate is explicitly set to 0% via POWERTOOLS_LOGGER_SAMPLE_RATE env
125+
sampling_rate = "0"
126+
monkeypatch.setenv("POWERTOOLS_LOGGER_SAMPLE_RATE", sampling_rate)
127+
logger = Logger(service=service_name, stream=stdout)
128+
logger.debug("I am being sampled")
129+
130+
# THEN we should not log
131+
logs = list(stdout.getvalue().strip())
132+
assert not logs
133+
134+
135+
def test_setup_sampling_rate_constructor_with_0percent(stdout, service_name):
136+
# GIVEN Logger is initialized
137+
# WHEN samping rate is explicitly set to 100% via constructor
138+
sampling_rate = 0
139+
logger = Logger(service=service_name, sampling_rate=sampling_rate, stream=stdout)
140+
logger.debug("I am being sampled")
141+
142+
# THEN we should not log
143+
logs = list(stdout.getvalue().strip())
144+
assert not logs
145+
146+
147+
@pytest.mark.parametrize(
148+
"percent, minimum_logs, maximum_logs",
149+
[
150+
(0.5, 35, 65),
151+
(0.1, 0, 20),
152+
(0.9, 75, 115),
153+
],
154+
)
155+
def test_setup_sampling_rate_env_var_with_percent_and_decorator(
156+
lambda_context,
157+
stdout,
158+
service_name,
159+
percent,
160+
minimum_logs,
161+
maximum_logs,
162+
):
163+
# GIVEN the Logger is initialized with a specific sampling rate
164+
sampling_rate = percent
165+
total_runs = 100
166+
minimum_logs_excepted = minimum_logs
167+
maximum_logs_excepted = maximum_logs
168+
logger = Logger(service=service_name, level="INFO", sampling_rate=sampling_rate, stream=stdout)
169+
170+
@logger.inject_lambda_context
171+
def handler(event, context):
172+
logger.debug(random.random())
173+
174+
# WHEN A lambda handler is invoked multiple times with decorator
175+
for _i in range(total_runs):
176+
handler({}, lambda_context)
177+
178+
# THEN verify the number of logs falls within the expected range
179+
logs = list(stdout.getvalue().strip().split("\n"))
180+
assert (
181+
len(logs) >= minimum_logs_excepted
182+
), f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
183+
assert (
184+
len(logs) <= maximum_logs_excepted
185+
), f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
186+
187+
188+
@pytest.mark.parametrize(
189+
"percent, minimum_logs, maximum_logs",
190+
[
191+
(0.5, 35, 65),
192+
(0.1, 0, 20),
193+
(0.9, 75, 115),
194+
],
195+
)
196+
def test_setup_sampling_rate_env_var_with_percent_and_recalculate_manual_method(
197+
lambda_context,
198+
stdout,
199+
service_name,
200+
percent,
201+
minimum_logs,
202+
maximum_logs,
203+
):
204+
# GIVEN the Logger is initialized with a specific sampling rate
205+
sampling_rate = percent
206+
total_runs = 100
207+
minimum_logs_excepted = minimum_logs
208+
maximum_logs_excepted = maximum_logs
209+
logger = Logger(service=service_name, level="INFO", sampling_rate=sampling_rate, stream=stdout)
210+
211+
def handler(event, context):
212+
logger.debug(random.random())
213+
logger.refresh_sample_rate_calculation()
214+
215+
# WHEN A lambda handler is invoked multiple times with manual refresh_sample_rate_calculation()
216+
for _i in range(total_runs):
217+
handler({}, lambda_context)
218+
219+
# THEN verify the number of logs falls within the expected range
220+
logs = list(stdout.getvalue().strip().split("\n"))
221+
assert (
222+
len(logs) >= minimum_logs_excepted
223+
), f"Log count {len(logs)} should be at least {minimum_logs_excepted} for sampling rate {sampling_rate}"
224+
assert (
225+
len(logs) <= maximum_logs_excepted
226+
), f"Log count {len(logs)} should be at most {maximum_logs_excepted} for sampling rate {sampling_rate}"
227+
228+
106229
def test_inject_lambda_context(lambda_context, stdout, service_name):
107230
# GIVEN Logger is initialized
108231
logger = Logger(service=service_name, stream=stdout)

0 commit comments

Comments
 (0)
Please sign in to comment.