diff --git a/src/autometrics/decorator.py b/src/autometrics/decorator.py index ef6d977..6832e7c 100644 --- a/src/autometrics/decorator.py +++ b/src/autometrics/decorator.py @@ -1,5 +1,5 @@ """Autometrics module.""" -from contextvars import ContextVar +from contextvars import ContextVar, Token import time import inspect @@ -20,7 +20,8 @@ T = TypeVar("T") -caller_var: ContextVar[str] = ContextVar("caller", default="") +caller_module_var: ContextVar[str] = ContextVar("caller.module", default="") +caller_function_var: ContextVar[str] = ContextVar("caller.function", default="") # Bare decorator usage @@ -63,12 +64,19 @@ def track_start(function: str, module: str): function=function, module=module, track_concurrency=track_concurrency ) - def track_result_ok(start_time: float, function: str, module: str, caller: str): + def track_result_ok( + start_time: float, + function: str, + module: str, + caller_module: str, + caller_function: str, + ): get_tracker().finish( start_time, function=function, module=module, - caller=caller, + caller_module=caller_module, + caller_function=caller_function, objective=objective, track_concurrency=track_concurrency, result=Result.OK, @@ -78,13 +86,15 @@ def track_result_error( start_time: float, function: str, module: str, - caller: str, + caller_module: str, + caller_function: str, ): get_tracker().finish( start_time, function=function, module=module, - caller=caller, + caller_module=caller_module, + caller_function=caller_function, objective=objective, track_concurrency=track_concurrency, result=Result.ERROR, @@ -100,16 +110,23 @@ def sync_decorator(func: Callable[P, T]) -> Callable[P, T]: @wraps(func) def sync_wrapper(*args: P.args, **kwds: P.kwargs) -> T: start_time = time.time() - caller = caller_var.get() - context_token = None + caller_module = caller_module_var.get() + caller_function = caller_function_var.get() + context_token_module: Optional[Token] = None + context_token_function: Optional[Token] = None try: - context_token = caller_var.set(func_name) + context_token_module = caller_module_var.set(module_name) + context_token_function = caller_function_var.set(func_name) if track_concurrency: track_start(module=module_name, function=func_name) result = func(*args, **kwds) track_result_ok( - start_time, function=func_name, module=module_name, caller=caller + start_time, + function=func_name, + module=module_name, + caller_module=caller_module, + caller_function=caller_function, ) except Exception as exception: @@ -118,14 +135,17 @@ def sync_wrapper(*args: P.args, **kwds: P.kwargs) -> T: start_time, function=func_name, module=module_name, - caller=caller, + caller_module=caller_module, + caller_function=caller_function, ) # Reraise exception raise exception finally: - if context_token is not None: - caller_var.reset(context_token) + if context_token_module is not None: + caller_module_var.reset(context_token_module) + if context_token_function is not None: + caller_function_var.reset(context_token_function) return result @@ -142,16 +162,23 @@ def async_decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T] @wraps(func) async def async_wrapper(*args: P.args, **kwds: P.kwargs) -> T: start_time = time.time() - caller = caller_var.get() - context_token = None + caller_module = caller_module_var.get() + caller_function = caller_function_var.get() + context_token_module: Optional[Token] = None + context_token_function: Optional[Token] = None try: - context_token = caller_var.set(func_name) + context_token_module = caller_module_var.set(module_name) + context_token_function = caller_function_var.set(func_name) if track_concurrency: track_start(module=module_name, function=func_name) result = await func(*args, **kwds) track_result_ok( - start_time, function=func_name, module=module_name, caller=caller + start_time, + function=func_name, + module=module_name, + caller_module=caller_module, + caller_function=caller_function, ) except Exception as exception: @@ -160,14 +187,17 @@ async def async_wrapper(*args: P.args, **kwds: P.kwargs) -> T: start_time, function=func_name, module=module_name, - caller=caller, + caller_module=caller_module, + caller_function=caller_function, ) # Reraise exception raise exception finally: - if context_token is not None: - caller_var.reset(context_token) + if context_token_module is not None: + caller_module_var.reset(context_token_module) + if context_token_function is not None: + caller_function_var.reset(context_token_function) return result diff --git a/src/autometrics/test_caller.py b/src/autometrics/test_caller.py index 58e143f..1442286 100644 --- a/src/autometrics/test_caller.py +++ b/src/autometrics/test_caller.py @@ -38,6 +38,6 @@ def bar(): assert blob is not None data = blob.decode("utf-8") - expected = """function_calls_total{caller="test_caller_detection..bar",function="test_caller_detection..foo",module="autometrics.test_caller",objective_name="",objective_percentile="",result="ok"} 1.0""" + expected = """function_calls_total{caller_function="test_caller_detection..bar",caller_module="autometrics.test_caller",function="test_caller_detection..foo",module="autometrics.test_caller",objective_name="",objective_percentile="",result="ok"} 1.0""" assert "wrapper" not in data assert expected in data diff --git a/src/autometrics/test_decorator.py b/src/autometrics/test_decorator.py index c9f53de..cf35e37 100644 --- a/src/autometrics/test_decorator.py +++ b/src/autometrics/test_decorator.py @@ -66,7 +66,7 @@ def test_basic(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="basic_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="basic_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 1.0""" assert total_count in data for latency in ObjectiveLatency: @@ -94,7 +94,7 @@ async def test_basic_async(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="basic_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="basic_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 1.0""" assert total_count in data for latency in ObjectiveLatency: @@ -130,7 +130,7 @@ def test_objectives(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="{function_name}",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="{function_name}",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 1.0""" assert total_count in data # Check the latency buckets @@ -170,7 +170,7 @@ async def test_objectives_async(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="basic_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="basic_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 1.0""" assert total_count in data # Check the latency buckets @@ -199,7 +199,7 @@ def test_exception(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="error_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="error_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 1.0""" assert total_count in data for latency in ObjectiveLatency: @@ -230,7 +230,7 @@ async def test_async_exception(self): assert blob is not None data = blob.decode("utf-8") - total_count = f"""function_calls_total{{caller="",function="error_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 1.0""" + total_count = f"""function_calls_total{{caller_function="",caller_module="",function="error_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 1.0""" assert total_count in data for latency in ObjectiveLatency: @@ -253,10 +253,10 @@ def test_initialize_counters_sync(self): assert blob is not None data = blob.decode("utf-8") - total_count_ok = f"""function_calls_total{{caller="",function="never_called_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 0.0""" + total_count_ok = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 0.0""" assert total_count_ok in data - total_count_error = f"""function_calls_total{{caller="",function="never_called_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 0.0""" + total_count_error = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 0.0""" assert total_count_error in data def test_initialize_counters_sync_with_objective(self): @@ -273,10 +273,10 @@ def test_initialize_counters_sync_with_objective(self): assert blob is not None data = blob.decode("utf-8") - total_count_ok = f"""function_calls_total{{caller="",function="never_called_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 0.0""" + total_count_ok = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 0.0""" assert total_count_ok in data - total_count_error = f"""function_calls_total{{caller="",function="never_called_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="error"}} 0.0""" + total_count_error = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="error"}} 0.0""" assert total_count_error in data @pytest.mark.asyncio @@ -290,10 +290,10 @@ async def test_initialize_counters_async(self): assert blob is not None data = blob.decode("utf-8") - total_count_ok = f"""function_calls_total{{caller="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 0.0""" + total_count_ok = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="ok"}} 0.0""" assert total_count_ok in data - total_count_error = f"""function_calls_total{{caller="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 0.0""" + total_count_error = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="",objective_percentile="",result="error"}} 0.0""" assert total_count_error in data @pytest.mark.asyncio @@ -311,8 +311,8 @@ async def test_initialize_counters_async_with_objective(self): assert blob is not None data = blob.decode("utf-8") - total_count_ok = f"""function_calls_total{{caller="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 0.0""" + total_count_ok = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="ok"}} 0.0""" assert total_count_ok in data - total_count_error = f"""function_calls_total{{caller="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="error"}} 0.0""" + total_count_error = f"""function_calls_total{{caller_function="",caller_module="",function="never_called_async_function",module="autometrics.test_decorator",objective_name="{objective_name}",objective_percentile="{success_rate.value}",result="error"}} 0.0""" assert total_count_error in data diff --git a/src/autometrics/tracker/opentelemetry.py b/src/autometrics/tracker/opentelemetry.py index 96e4031..585a5d5 100644 --- a/src/autometrics/tracker/opentelemetry.py +++ b/src/autometrics/tracker/opentelemetry.py @@ -80,7 +80,8 @@ def __count( self, function: str, module: str, - caller: str, + caller_module: str, + caller_function: str, objective: Optional[Objective], exemplar: Optional[dict], result: Result, @@ -98,7 +99,8 @@ def __count( "function": function, "module": module, "result": result.value, - "caller": caller, + "caller.module": caller_module, + "caller.function": caller_function, OBJECTIVE_NAME: objective_name, OBJECTIVE_PERCENTILE: percentile, }, @@ -164,7 +166,8 @@ def finish( start_time: float, function: str, module: str, - caller: str, + caller_module: str, + caller_function: str, result: Result = Result.OK, objective: Optional[Objective] = None, track_concurrency: Optional[bool] = False, @@ -176,7 +179,15 @@ def finish( # https://github.com/autometrics-dev/autometrics-py/issues/41 # if os.getenv("AUTOMETRICS_EXEMPLARS") == "true": # exemplar = get_exemplar() - self.__count(function, module, caller, objective, exemplar, result) + self.__count( + function, + module, + caller_module, + caller_function, + objective, + exemplar, + result, + ) self.__histogram(function, module, start_time, objective, exemplar) if track_concurrency: self.__up_down_counter_concurrency_instance.add( @@ -194,6 +205,25 @@ def initialize_counters( objective: Optional[Objective] = None, ): """Initialize tracking metrics for a function call at zero.""" - caller = "" - self.__count(function, module, caller, objective, None, Result.OK, 0) - self.__count(function, module, caller, objective, None, Result.ERROR, 0) + caller_module = "" + caller_function = "" + self.__count( + function, + module, + caller_module, + caller_function, + objective, + None, + Result.OK, + 0, + ) + self.__count( + function, + module, + caller_module, + caller_function, + objective, + None, + Result.ERROR, + 0, + ) diff --git a/src/autometrics/tracker/prometheus.py b/src/autometrics/tracker/prometheus.py index 5499fef..534a689 100644 --- a/src/autometrics/tracker/prometheus.py +++ b/src/autometrics/tracker/prometheus.py @@ -35,7 +35,8 @@ class PrometheusTracker: "function", "module", "result", - "caller", + "caller_module", + "caller_function", OBJECTIVE_NAME_PROMETHEUS, OBJECTIVE_PERCENTILE_PROMETHEUS, ], @@ -66,7 +67,8 @@ def _count( self, func_name: str, module_name: str, - caller: str, + caller_module: str, + caller_function: str, objective: Optional[Objective] = None, exemplar: Optional[dict] = None, result: Result = Result.OK, @@ -84,7 +86,8 @@ def _count( func_name, module_name, result.value, - caller, + caller_module, + caller_function, objective_name, percentile, ).inc(inc_by, exemplar) @@ -133,7 +136,8 @@ def finish( start_time: float, function: str, module: str, - caller: str, + caller_module: str, + caller_function: str, result: Result = Result.OK, objective: Optional[Objective] = None, track_concurrency: Optional[bool] = False, @@ -143,7 +147,15 @@ def finish( if os.getenv("AUTOMETRICS_EXEMPLARS") == "true": exemplar = get_exemplar() - self._count(function, module, caller, objective, exemplar, result) + self._count( + function, + module, + caller_module, + caller_function, + objective, + exemplar, + result, + ) self._histogram(function, module, start_time, objective, exemplar) if track_concurrency: @@ -156,6 +168,25 @@ def initialize_counters( objective: Optional[Objective] = None, ): """Initialize tracking metrics for a function call at zero.""" - caller = "" - self._count(function, module, caller, objective, None, Result.OK, 0) - self._count(function, module, caller, objective, None, Result.ERROR, 0) + caller_module = "" + caller_function = "" + self._count( + function, + module, + caller_module, + caller_function, + objective, + None, + Result.OK, + 0, + ) + self._count( + function, + module, + caller_module, + caller_function, + objective, + None, + Result.ERROR, + 0, + ) diff --git a/src/autometrics/tracker/tracker.py b/src/autometrics/tracker/tracker.py index 2181cde..0922e8c 100644 --- a/src/autometrics/tracker/tracker.py +++ b/src/autometrics/tracker/tracker.py @@ -29,7 +29,8 @@ def finish( start_time: float, function: str, module: str, - caller: str, + caller_module: str, + caller_function: str, result: Result = Result.OK, objective: Optional[Objective] = None, track_concurrency: Optional[bool] = False,