Skip to content

Commit 9035cff

Browse files
adding docs + small refactor
1 parent 3e25974 commit 9035cff

File tree

6 files changed

+130
-52
lines changed

6 files changed

+130
-52
lines changed

aws_lambda_powertools/metrics/metrics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ def add_metric(
123123
def add_dimension(self, name: str, value: str) -> None:
124124
self.provider.add_dimension(name=name, value=value)
125125

126-
def add_dimensions(self, dimensions: dict[str, str]) -> None:
126+
def add_dimensions(self, **dimensions: str) -> None:
127127
"""Add a new set of dimensions creating an additional dimension array.
128128
129129
Creates a new dimension set in the CloudWatch EMF Dimensions array.
130130
"""
131-
self.provider.add_dimensions(dimensions=dimensions)
131+
self.provider.add_dimensions(**dimensions)
132132

133133
def serialize_metric_set(
134134
self,

aws_lambda_powertools/metrics/provider/cloudwatch_emf/cloudwatch.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,21 @@ def add_dimension(self, name: str, value: str) -> None:
326326

327327
self.dimension_set[name] = value
328328

329-
def add_dimensions(self, dimensions: dict[str, str]) -> None:
329+
def add_dimensions(self, **dimensions: str) -> None:
330330
"""Add a new set of dimensions creating an additional dimension array.
331331
332332
Creates a new dimension set in the CloudWatch EMF Dimensions array.
333+
334+
Example
335+
-------
336+
**Add multiple dimension sets**
337+
338+
metrics.add_dimensions(environment="prod", region="us-east-1")
339+
340+
Parameters
341+
----------
342+
dimensions : str
343+
Dimension key-value pairs as keyword arguments
333344
"""
334345
logger.debug(f"Adding dimension set: {dimensions}")
335346

@@ -341,11 +352,21 @@ def add_dimensions(self, dimensions: dict[str, str]) -> None:
341352
)
342353
return
343354

344-
# Convert values to strings and validate
355+
sanitized = self._sanitize_dimensions(dimensions)
356+
if not sanitized:
357+
return
358+
359+
self._validate_dimension_limit(sanitized)
360+
361+
self.dimension_sets.append({**self.default_dimensions, **sanitized})
362+
363+
def _sanitize_dimensions(self, dimensions: dict[str, str]) -> dict[str, str]:
364+
"""Convert dimension values to strings and filter out empty ones."""
345365
sanitized: dict[str, str] = {}
366+
346367
for name, value in dimensions.items():
347-
str_value = value if isinstance(value, str) else str(value)
348-
str_name = name if isinstance(name, str) else str(name)
368+
str_name = str(name)
369+
str_value = str(value)
349370

350371
if not str_name.strip() or not str_value.strip():
351372
warnings.warn(
@@ -357,24 +378,18 @@ def add_dimensions(self, dimensions: dict[str, str]) -> None:
357378

358379
sanitized[str_name] = str_value
359380

360-
if not sanitized:
361-
return
381+
return sanitized
362382

363-
# Count unique dimensions across all sets
383+
def _validate_dimension_limit(self, new_dimensions: dict[str, str]) -> None:
384+
"""Validate that adding new dimensions won't exceed CloudWatch limits."""
364385
all_keys = set(self.dimension_set.keys())
365386
for ds in self.dimension_sets:
366387
all_keys.update(ds.keys())
367-
all_keys.update(sanitized.keys())
388+
all_keys.update(new_dimensions.keys())
368389

369390
if len(all_keys) > MAX_DIMENSIONS:
370391
raise SchemaValidationError(f"Maximum dimensions ({MAX_DIMENSIONS}) exceeded")
371392

372-
# Add default dimensions to this set
373-
with_defaults = dict(self.default_dimensions)
374-
with_defaults.update(sanitized)
375-
376-
self.dimension_sets.append(with_defaults)
377-
378393
def add_metadata(self, key: str, value: Any) -> None:
379394
"""Adds high cardinal metadata for metrics object
380395

docs/core/metrics.md

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ If you're new to Amazon CloudWatch, there are five terminologies you must be awa
2222
* **Dimensions**. Metrics metadata in key-value format. They help you slice and dice metrics visualization, for example `ColdStart` metric by Payment `service`.
2323
* **Metric**. It's the name of the metric, for example: `SuccessfulBooking` or `UpdatedBooking`.
2424
* **Unit**. It's a value representing the unit of measure for the corresponding metric, for example: `Count` or `Seconds`.
25-
* **Resolution**. It's a value representing the storage resolution for the corresponding metric. Metrics can be either Standard or High resolution. Read more [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html#high-resolution-metrics){target="_blank"}.
25+
* **Resolution**. It's a value representing the storage resolution for the corresponding metric. Metrics can be either Standard or High resolution. Read more in the [high-resolution metrics documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/publishingMetrics.html#high-resolution-metrics){target="_blank"}.
2626

2727
<figure>
2828
<img src="../../media/metrics_terminology.png" alt="Terminology" />
@@ -136,6 +136,27 @@ If you'd like to remove them at some point, you can use `clear_default_dimension
136136

137137
**Note:** Dimensions with empty values will not be included.
138138

139+
### Adding multiple dimension sets
140+
141+
You can use `add_dimensions` method to create multiple dimension sets in a single EMF blob. This allows you to aggregate metrics across different dimension combinations without emitting separate metric blobs.
142+
143+
Each call to `add_dimensions` creates a new dimension array in the CloudWatch EMF output, enabling different views of the same metric data.
144+
145+
=== "add_dimensions.py"
146+
147+
```python hl_lines="12-13"
148+
--8<-- "examples/metrics/src/add_dimensions.py"
149+
```
150+
151+
=== "add_dimensions_output.json"
152+
153+
```json hl_lines="8-12"
154+
--8<-- "examples/metrics/src/add_dimensions_output.json"
155+
```
156+
157+
???+ tip "When to use multiple dimension sets"
158+
Use `add_dimensions` when you need to query the same metric with different dimension combinations. For example, you might want to see `SuccessfulBooking` aggregated by `environment` alone, or by both `environment` and `region`.
159+
139160
### Changing default timestamp
140161

141162
When creating metrics, we use the current timestamp. If you want to change the timestamp of all the metrics you create, utilize the `set_timestamp` function. You can specify a datetime object or an integer representing an epoch timestamp in milliseconds.
@@ -233,12 +254,12 @@ The priority of the `function_name` dimension value is defined as:
233254

234255
The following environment variable is available to configure Metrics at a global scope:
235256

236-
| Setting | Description | Environment variable | Default |
237-
| ------------------ | ------------------------------------------------------------ | ---------------------------------- | ------- |
238-
| **Namespace Name** | Sets **namespace** used for metrics. | `POWERTOOLS_METRICS_NAMESPACE` | `None` |
239-
| **Service** | Sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `None` |
240-
| **Function Name** | Function name used as dimension for the **ColdStart** metric. | `POWERTOOLS_METRICS_FUNCTION_NAME` | `None` |
241-
| **Disable Powertools Metrics** | **Disables** all metrics emitted by Powertools. | `POWERTOOLS_METRICS_DISABLED` | `None` |
257+
| Setting | Description | Environment variable | Default |
258+
| ------------------------------ | ------------------------------------------------------------------- | ---------------------------------- | ------- |
259+
| **Namespace Name** | Sets **namespace** used for metrics. | `POWERTOOLS_METRICS_NAMESPACE` | `None` |
260+
| **Service** | Sets **service** metric dimension across all metrics e.g. `payment` | `POWERTOOLS_SERVICE_NAME` | `None` |
261+
| **Function Name** | Function name used as dimension for the **ColdStart** metric. | `POWERTOOLS_METRICS_FUNCTION_NAME` | `None` |
262+
| **Disable Powertools Metrics** | **Disables** all metrics emitted by Powertools. | `POWERTOOLS_METRICS_DISABLED` | `None` |
242263

243264
`POWERTOOLS_METRICS_NAMESPACE` is also available on a per-instance basis with the `namespace` parameter, which will consequently override the environment variable value.
244265

@@ -393,8 +414,8 @@ We provide a thin-wrapper on top of the most requested observability providers.
393414

394415
Current providers:
395416

396-
| Provider | Notes |
397-
| ------------------------------------- | -------------------------------------------------------- |
417+
| Provider | Notes |
418+
| ---------------------------------------- | -------------------------------------------------------- |
398419
| [Datadog](./datadog.md){target="_blank"} | Uses Datadog SDK and Datadog Lambda Extension by default |
399420

400421
## Testing your code
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from aws_lambda_powertools import Metrics
2+
from aws_lambda_powertools.metrics import MetricUnit
3+
from aws_lambda_powertools.utilities.typing import LambdaContext
4+
5+
metrics = Metrics()
6+
7+
8+
@metrics.log_metrics
9+
def lambda_handler(event: dict, context: LambdaContext):
10+
# Add primary dimension
11+
metrics.add_dimension(name="service", value="booking")
12+
13+
# Add multiple dimension sets for different aggregation views
14+
metrics.add_dimensions(environment="prod", region="us-east-1")
15+
metrics.add_dimensions(environment="prod")
16+
17+
metrics.add_metric(name="SuccessfulBooking", unit=MetricUnit.Count, value=1)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"_aws": {
3+
"Timestamp": 1656620400000,
4+
"CloudWatchMetrics": [
5+
{
6+
"Namespace": "ServerlessAirline",
7+
"Dimensions": [
8+
["service"],
9+
["environment", "region"],
10+
["environment"]
11+
],
12+
"Metrics": [
13+
{
14+
"Name": "SuccessfulBooking",
15+
"Unit": "Count"
16+
}
17+
]
18+
}
19+
]
20+
},
21+
"service": "booking",
22+
"environment": "prod",
23+
"region": "us-east-1",
24+
"SuccessfulBooking": [1.0]
25+
}

tests/functional/metrics/test_dimension_sets.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ def test_add_dimensions_creates_multiple_dimension_sets(capsys):
1818

1919
# WHEN we add multiple dimension sets
2020
metrics.add_dimension(name="service", value="booking")
21-
metrics.add_dimensions({"environment": "prod", "region": "us-east-1"})
22-
metrics.add_dimensions({"environment": "prod"})
23-
metrics.add_dimensions({"region": "us-east-1"})
21+
metrics.add_dimensions(environment="prod", region="us-east-1")
22+
metrics.add_dimensions(environment="prod")
23+
metrics.add_dimensions(region="us-east-1")
2424
metrics.add_metric(name="SuccessfulRequests", unit=MetricUnit.Count, value=10)
2525

2626
# THEN the serialized output should contain multiple dimension arrays
@@ -50,8 +50,8 @@ def test_add_dimensions_with_metrics_wrapper(capsys):
5050
# WHEN we use add_dimensions through the Metrics wrapper
5151
@metrics.log_metrics
5252
def handler(event, context):
53-
metrics.add_dimensions({"environment": "staging", "region": "us-west-2"})
54-
metrics.add_dimensions({"environment": "staging"})
53+
metrics.add_dimensions(environment="staging", region="us-west-2")
54+
metrics.add_dimensions(environment="staging")
5555
metrics.add_metric(name="PaymentProcessed", unit=MetricUnit.Count, value=1)
5656

5757
handler({}, {})
@@ -76,8 +76,8 @@ def test_add_dimensions_with_default_dimensions():
7676
metrics.set_default_dimensions(tenant_id="123", application="api")
7777

7878
# WHEN we add dimension sets after setting defaults
79-
metrics.add_dimensions({"environment": "prod"})
80-
metrics.add_dimensions({"region": "eu-west-1"})
79+
metrics.add_dimensions(environment="prod")
80+
metrics.add_dimensions(region="eu-west-1")
8181
metrics.add_metric(name="ApiCalls", unit=MetricUnit.Count, value=5)
8282

8383
# THEN default dimensions should be included in all dimension sets
@@ -100,9 +100,9 @@ def test_add_dimensions_duplicate_keys_last_value_wins():
100100
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
101101

102102
# WHEN we add dimension sets with duplicate keys
103-
metrics.add_dimensions({"environment": "dev", "region": "us-east-1"})
104-
metrics.add_dimensions({"environment": "staging", "region": "us-west-2"})
105-
metrics.add_dimensions({"environment": "prod"}) # Last value for environment
103+
metrics.add_dimensions(environment="dev", region="us-east-1")
104+
metrics.add_dimensions(environment="staging", region="us-west-2")
105+
metrics.add_dimensions(environment="prod") # Last value for environment
106106
metrics.add_metric(name="TestMetric", unit=MetricUnit.Count, value=1)
107107

108108
# THEN the last value should be used in the root
@@ -113,13 +113,13 @@ def test_add_dimensions_duplicate_keys_last_value_wins():
113113
assert output["region"] == "us-west-2"
114114

115115

116-
def test_add_dimensions_empty_dict_warns():
116+
def test_add_dimensions_empty_kwargs_warns():
117117
# GIVEN metrics instance
118118
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
119119

120-
# WHEN we add an empty dimensions dict
120+
# WHEN we call add_dimensions without arguments
121121
with pytest.warns(UserWarning, match="Empty dimensions dictionary"):
122-
metrics.add_dimensions({})
122+
metrics.add_dimensions()
123123

124124
# THEN no dimension set should be added
125125
assert len(metrics.dimension_sets) == 0
@@ -129,9 +129,9 @@ def test_add_dimensions_invalid_dimensions_skipped():
129129
# GIVEN metrics instance
130130
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
131131

132-
# WHEN we add dimensions with empty names or values
132+
# WHEN we add dimensions with empty values
133133
with pytest.warns(UserWarning, match="empty name or value"):
134-
metrics.add_dimensions({"": "value", "key": ""})
134+
metrics.add_dimensions(key="")
135135

136136
# THEN no dimension set should be added
137137
assert len(metrics.dimension_sets) == 0
@@ -148,15 +148,15 @@ def test_add_dimensions_exceeds_max_dimensions():
148148
# WHEN we try to add dimension set that would exceed max
149149
# THEN it should raise SchemaValidationError
150150
with pytest.raises(SchemaValidationError, match="Maximum dimensions"):
151-
metrics.add_dimensions({"extra1": "val1", "extra2": "val2"})
151+
metrics.add_dimensions(extra1="val1", extra2="val2")
152152

153153

154154
def test_add_dimensions_converts_values_to_strings():
155155
# GIVEN metrics instance
156156
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
157157

158-
# WHEN we add dimensions with non-string values
159-
metrics.add_dimensions({"count": 123, "is_active": True, "ratio": 3.14})
158+
# WHEN we add dimensions with non-string values (using **dict for non-string values)
159+
metrics.add_dimensions(**{"count": 123, "is_active": True, "ratio": 3.14})
160160
metrics.add_metric(name="TestMetric", unit=MetricUnit.Count, value=1)
161161

162162
# THEN values should be converted to strings
@@ -172,8 +172,8 @@ def test_clear_metrics_clears_dimension_sets(capsys):
172172

173173
@metrics.log_metrics
174174
def handler(event, context):
175-
metrics.add_dimensions({"environment": "prod"})
176-
metrics.add_dimensions({"region": "us-east-1"})
175+
metrics.add_dimensions(environment="prod")
176+
metrics.add_dimensions(region="us-east-1")
177177
metrics.add_metric(name="Requests", unit=MetricUnit.Count, value=1)
178178

179179
handler({}, {})
@@ -189,9 +189,9 @@ def test_add_dimensions_order_preserved():
189189

190190
# WHEN we add dimension sets in specific order
191191
metrics.add_dimension(name="service", value="api")
192-
metrics.add_dimensions({"environment": "prod", "region": "us-east-1"})
193-
metrics.add_dimensions({"environment": "prod"})
194-
metrics.add_dimensions({"region": "us-east-1"})
192+
metrics.add_dimensions(environment="prod", region="us-east-1")
193+
metrics.add_dimensions(environment="prod")
194+
metrics.add_dimensions(region="us-east-1")
195195
metrics.add_metric(name="TestMetric", unit=MetricUnit.Count, value=1)
196196

197197
# THEN dimension sets should appear in order added
@@ -209,7 +209,7 @@ def test_add_dimensions_with_metadata():
209209
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
210210

211211
# WHEN we add dimension sets and metadata
212-
metrics.add_dimensions({"environment": "prod"})
212+
metrics.add_dimensions(environment="prod")
213213
metrics.add_metadata(key="request_id", value="abc-123")
214214
metrics.add_metric(name="ApiLatency", unit=MetricUnit.Milliseconds, value=150)
215215

@@ -227,8 +227,8 @@ def test_multiple_metrics_with_dimension_sets():
227227
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
228228

229229
# WHEN we add multiple metrics with dimension sets
230-
metrics.add_dimensions({"environment": "prod", "region": "us-east-1"})
231-
metrics.add_dimensions({"environment": "prod"})
230+
metrics.add_dimensions(environment="prod", region="us-east-1")
231+
metrics.add_dimensions(environment="prod")
232232
metrics.add_metric(name="SuccessCount", unit=MetricUnit.Count, value=100)
233233
metrics.add_metric(name="ErrorCount", unit=MetricUnit.Count, value=5)
234234
metrics.add_metric(name="Latency", unit=MetricUnit.Milliseconds, value=250)
@@ -249,7 +249,7 @@ def test_add_dimensions_with_high_resolution_metrics():
249249
metrics = AmazonCloudWatchEMFProvider(namespace="TestApp")
250250

251251
# WHEN we add dimension sets with high-resolution metrics
252-
metrics.add_dimensions({"function": "process_order"})
252+
metrics.add_dimensions(function="process_order")
253253
metrics.add_metric(
254254
name="ProcessingTime",
255255
unit=MetricUnit.Milliseconds,

0 commit comments

Comments
 (0)