-
Notifications
You must be signed in to change notification settings - Fork 412
/
Copy pathbase.py
237 lines (187 loc) · 6.72 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
from __future__ import annotations
import functools
import logging
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any
from aws_lambda_powertools.metrics.provider import cold_start
if TYPE_CHECKING:
from aws_lambda_powertools.shared.types import AnyCallableT
from aws_lambda_powertools.utilities.typing import LambdaContext
logger = logging.getLogger(__name__)
class BaseProvider(ABC):
"""
Interface to create a metrics provider.
BaseProvider implements `log_metrics` decorator for every provider as a value add feature.
Usage:
1. Inherit from this class.
2. Implement the required methods specific to your metric provider.
3. Customize the behavior and functionality of the metric provider in your subclass.
"""
@abstractmethod
def add_metric(self, *args: Any, **kwargs: Any) -> Any:
"""
Abstract method for adding a metric.
This method must be implemented in subclasses to add a metric and return a combined metrics dictionary.
Parameters
----------
*args:
Positional arguments.
*kwargs:
Keyword arguments.
Returns
----------
dict
A combined metrics dictionary.
Raises
----------
NotImplementedError
This method must be implemented in subclasses.
"""
raise NotImplementedError
@abstractmethod
def serialize_metric_set(self, *args: Any, **kwargs: Any) -> Any:
"""
Abstract method for serialize a metric.
This method must be implemented in subclasses to add a metric and return a combined metrics dictionary.
Parameters
----------
*args:
Positional arguments.
*kwargs:
Keyword arguments.
Returns
----------
dict
Serialized metrics
Raises
----------
NotImplementedError
This method must be implemented in subclasses.
"""
raise NotImplementedError
@abstractmethod
def flush_metrics(self, *args: Any, **kwargs) -> Any:
"""
Abstract method for flushing a metric.
This method must be implemented in subclasses to add a metric and return a combined metrics dictionary.
Parameters
----------
*args:
Positional arguments.
*kwargs:
Keyword arguments.
Raises
----------
NotImplementedError
This method must be implemented in subclasses.
"""
raise NotImplementedError
@abstractmethod
def clear_metrics(self, *args: Any, **kwargs) -> None:
"""
Abstract method for clear metric instance.
This method must be implemented in subclasses to clear the metric instance
Parameters
----------
*args:
Positional arguments.
*kwargs:
Keyword arguments.
Raises
----------
NotImplementedError
This method must be implemented in subclasses.
"""
raise NotImplementedError
@abstractmethod
def add_cold_start_metric(self, context: LambdaContext) -> Any:
"""
Abstract method for clear metric instance.
This method must be implemented in subclasses to add a metric and return a combined metrics dictionary.
Parameters
----------
*args:
Positional arguments.
*kwargs:
Keyword arguments.
Raises
----------
NotImplementedError
This method must be implemented in subclasses.
"""
raise NotImplementedError
def log_metrics(
self,
lambda_handler: AnyCallableT | None = None,
capture_cold_start_metric: bool = False,
raise_on_empty_metrics: bool = False,
**kwargs,
):
"""Decorator to serialize and publish metrics at the end of a function execution.
Be aware that the log_metrics **does call* the decorated function (e.g. lambda_handler).
Example
-------
**Lambda function using tracer and metrics decorators**
from aws_lambda_powertools import Metrics, Tracer
metrics = Metrics(service="payment")
tracer = Tracer(service="payment")
@tracer.capture_lambda_handler
@metrics.log_metrics
def handler(event, context):
...
Parameters
----------
lambda_handler : Callable[[Any, Any], Any], optional
lambda function handler, by default None
capture_cold_start_metric : bool, optional
captures cold start metric, by default False
raise_on_empty_metrics : bool, optional
raise exception if no metrics are emitted, by default False
default_dimensions: dict[str, str], optional
metric dimensions as key=value that will always be present
Raises
------
e
Propagate error received
"""
extra_args = {}
if kwargs.get("default_dimensions"):
extra_args.update({"default_dimensions": kwargs.get("default_dimensions")})
if kwargs.get("default_tags"):
extra_args.update({"default_tags": kwargs.get("default_tags")})
# If handler is None we've been called with parameters
# Return a partial function with args filled
if lambda_handler is None:
logger.debug("Decorator called with parameters")
return functools.partial(
self.log_metrics,
capture_cold_start_metric=capture_cold_start_metric,
raise_on_empty_metrics=raise_on_empty_metrics,
**extra_args,
)
@functools.wraps(lambda_handler)
def decorate(event, context, *args, **kwargs):
try:
response = lambda_handler(event, context, *args, **kwargs)
if capture_cold_start_metric:
self._add_cold_start_metric(context=context)
finally:
self.flush_metrics(raise_on_empty_metrics=raise_on_empty_metrics)
return response
return decorate
def _add_cold_start_metric(self, context: Any) -> None:
"""
Add cold start metric
Parameters
----------
context : Any
Lambda context
"""
if not cold_start.is_cold_start:
return
logger.debug("Adding cold start metric and function_name dimension")
self.add_cold_start_metric(context=context)
cold_start.is_cold_start = False
def reset_cold_start_flag_provider():
if not cold_start.is_cold_start:
cold_start.is_cold_start = True