@aws-blocks/bb-metrics 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +130 -0
- package/README.md +2 -0
- package/package.json +3 -2
package/DESIGN.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Metrics — Design
|
|
2
|
+
|
|
3
|
+
Design document for Metrics. For usage, see [README.md](./README.md).
|
|
4
|
+
|
|
5
|
+
**Package:** `@aws-blocks/bb-metrics`
|
|
6
|
+
**Type:** Primitive (no new infrastructure created)
|
|
7
|
+
**AWS Service:** Amazon CloudWatch Metrics (via Embedded Metric Format)
|
|
8
|
+
|
|
9
|
+
## Key Design Decision: EMF over PutMetricData
|
|
10
|
+
|
|
11
|
+
Metrics are emitted using **CloudWatch Embedded Metric Format (EMF)** rather than calling the `PutMetricData` API directly.
|
|
12
|
+
|
|
13
|
+
### Why EMF?
|
|
14
|
+
|
|
15
|
+
| Concern | PutMetricData | EMF |
|
|
16
|
+
|---------|---------------|-----|
|
|
17
|
+
| **Latency impact** | HTTP call to CloudWatch (~5-50ms) | Synchronous stdout write (~0ms) |
|
|
18
|
+
| **IAM permissions** | Requires `cloudwatch:PutMetricData` | Uses CloudWatch Logs (Lambda has this by default) |
|
|
19
|
+
| **Batching** | Developer must buffer and flush | CloudWatch extracts metrics from log lines automatically |
|
|
20
|
+
| **Error handling** | Network failures require retries | Stdout never fails (kernel-buffered) |
|
|
21
|
+
| **API surface** | Async (`Promise<void>`) | Sync (`void`) — simpler DX |
|
|
22
|
+
| **Cost** | ~$0.01 per 1000 PutMetricData API calls + metric storage | Log ingestion (already paid) + metric storage only |
|
|
23
|
+
| **Cold start** | AWS SDK init adds ~100ms | Zero additional cold start |
|
|
24
|
+
|
|
25
|
+
### Trade-offs
|
|
26
|
+
|
|
27
|
+
- **No aggregation control:** EMF extracts metrics at the data point level. If you emit `Latency: 42ms` 1000 times in one invocation, CloudWatch stores 1000 data points (or groups them into a single EMF document with multiple values). With PutMetricData you could pre-aggregate into a StatisticSet.
|
|
28
|
+
- **Log volume:** Each emit writes a JSON line to CloudWatch Logs. High-frequency metrics increase log volume (and log storage cost). Mitigated by CloudWatch Logs Infrequent Access class or retention policies.
|
|
29
|
+
- **Maximum metrics per document:** EMF supports up to 100 metrics per JSON document. The `emitBatch` limit of 100 matches this constraint (vs PutMetricData's 1000 per call).
|
|
30
|
+
|
|
31
|
+
### Why the trade-offs are acceptable
|
|
32
|
+
|
|
33
|
+
1. Most BB users emit <100 metrics per invocation — log volume is negligible.
|
|
34
|
+
2. The simplicity of a synchronous, zero-config API far outweighs the rare need for client-side aggregation.
|
|
35
|
+
3. Lambda log costs are typically dominated by application logs, not metric EMF lines.
|
|
36
|
+
|
|
37
|
+
## Architecture
|
|
38
|
+
|
|
39
|
+
### Runtime Flow (AWS + Local)
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
emit('Count', 1, { dimensions: { endpoint: '/api' } })
|
|
43
|
+
│
|
|
44
|
+
├─ validateMetricName('Count')
|
|
45
|
+
├─ mergeDimensions(defaults, { endpoint: '/api' })
|
|
46
|
+
├─ validateDimensions(merged)
|
|
47
|
+
│
|
|
48
|
+
└─ writeEmf(namespace, [{ name, value, unit, dimensions, timestamp, resolution }])
|
|
49
|
+
│
|
|
50
|
+
├─ groupByDimensions(metrics) // EMF requires same dims per entry
|
|
51
|
+
│
|
|
52
|
+
└─ for each group:
|
|
53
|
+
process.stdout.write(JSON.stringify(emfPayload) + '\n')
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### EMF Document Structure
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"_aws": {
|
|
61
|
+
"Timestamp": 1718450000000,
|
|
62
|
+
"CloudWatchMetrics": [{
|
|
63
|
+
"Namespace": "MyApp/Orders",
|
|
64
|
+
"Dimensions": [["service", "endpoint"]],
|
|
65
|
+
"Metrics": [
|
|
66
|
+
{ "Name": "RequestCount", "Unit": "Count", "StorageResolution": 60 },
|
|
67
|
+
{ "Name": "Latency", "Unit": "Milliseconds", "StorageResolution": 60 }
|
|
68
|
+
]
|
|
69
|
+
}]
|
|
70
|
+
},
|
|
71
|
+
"service": "orders",
|
|
72
|
+
"endpoint": "/api",
|
|
73
|
+
"RequestCount": 1,
|
|
74
|
+
"Latency": 42
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Dimension Grouping
|
|
79
|
+
|
|
80
|
+
EMF requires all metrics in a single `CloudWatchMetrics` entry to share the same dimension keys and values. When `emitBatch` receives metrics with different dimension sets, they are grouped and written as separate JSON lines.
|
|
81
|
+
|
|
82
|
+
### Child Emitters
|
|
83
|
+
|
|
84
|
+
`child(dimensions)` returns a lightweight `ChildMetrics` object (not a Scope node) that:
|
|
85
|
+
- Inherits the parent's namespace
|
|
86
|
+
- Merges the provided dimensions on top of the parent's `defaultDimensions`
|
|
87
|
+
- Supports further nesting via `child()` on the child itself
|
|
88
|
+
|
|
89
|
+
This enables per-request or per-endpoint metric scoping without creating new Scope nodes.
|
|
90
|
+
|
|
91
|
+
## Infrastructure (CDK)
|
|
92
|
+
|
|
93
|
+
Unlike most Building Blocks, Metrics does **not** create any AWS resources. CloudWatch namespaces are created implicitly on first metric data point arrival.
|
|
94
|
+
|
|
95
|
+
The CDK construct creates no AWS resources and adds no environment variables or IAM grants. EMF uses CloudWatch Logs (which Lambda already has), so no `cloudwatch:PutMetricData` grant is needed. The construct only:
|
|
96
|
+
1. **Resolves the namespace:** Computes the resolved `namespace` and exposes it as a readonly property.
|
|
97
|
+
2. **Exposes `defaultDimensions`:** Exposes `defaultDimensions` as a readonly property so that other CDK-time consumers (like the Dashboard BB) can read them and build matching CloudWatch widget queries.
|
|
98
|
+
|
|
99
|
+
### Namespace Resolution Order
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
ExternalMetricsRef.namespace (fromExisting)
|
|
103
|
+
→ options.namespace (constructor arg)
|
|
104
|
+
→ scope.fullId (default)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Mock Implementation
|
|
108
|
+
|
|
109
|
+
There is no separate mock — the AWS runtime (`index.aws.ts`) and the mock (`index.mock.ts`) are identical. Both write EMF JSON to stdout. In local dev, the output is visible in the terminal; in Lambda, it is captured by CloudWatch Logs.
|
|
110
|
+
|
|
111
|
+
This is unique among Building Blocks. Most BBs need a mock because their AWS runtime makes network calls (DynamoDB, S3, SQS, etc.). Metrics via EMF only writes to stdout, which works identically in all environments.
|
|
112
|
+
|
|
113
|
+
### Mock vs AWS Behavior Differences
|
|
114
|
+
|
|
115
|
+
| Behavior Difference | Impact | Mitigation |
|
|
116
|
+
|------------|--------|------------|
|
|
117
|
+
| No CloudWatch extraction | Metrics are written but not extracted into CloudWatch locally | Expected — local dev is for correctness testing, not dashboards |
|
|
118
|
+
| No alarms | Threshold breaches are not detected locally | Document the gap — alarms require CloudWatch infrastructure |
|
|
119
|
+
| No dashboards | Cannot preview dashboard visualizations locally | Document the gap — dashboards are a CloudWatch console feature |
|
|
120
|
+
| No namespace-scoped IAM | Permission errors only surface in AWS | IAM is handled by CDK grants automatically |
|
|
121
|
+
| No log retention limits | Stdout grows unbounded in local dev | Terminal scrollback is the natural limit |
|
|
122
|
+
|
|
123
|
+
## Validation
|
|
124
|
+
|
|
125
|
+
All validation matches CloudWatch constraints:
|
|
126
|
+
- **Metric name:** Non-empty, max 1024 characters
|
|
127
|
+
- **Dimensions:** Max 30 key-value pairs, non-empty keys and values, max 1024 chars each
|
|
128
|
+
- **Batch size:** Max 100 metrics per `emitBatch` call (EMF document limit)
|
|
129
|
+
|
|
130
|
+
Validation runs synchronously before the stdout write. Failures throw typed errors (`MetricsErrors.*`) that can be caught with `isBlocksError()`.
|
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ Custom application metrics backed by Amazon CloudWatch (via Embedded Metric Form
|
|
|
6
6
|
|
|
7
7
|
**When NOT to use:** If you need structured log output, use `Logging`. If you need distributed request tracing, use `Tracing`. If you need to store time-series data for querying, use `Database` or `DistributedTable`.
|
|
8
8
|
|
|
9
|
+
> Design & mock parity details: [DESIGN.md](./DESIGN.md)
|
|
10
|
+
|
|
9
11
|
## API
|
|
10
12
|
|
|
11
13
|
```typescript
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/bb-metrics",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"author": "Amazon Web Services",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
9
|
"README.md",
|
|
10
|
+
"DESIGN.md",
|
|
10
11
|
"src",
|
|
11
12
|
"LICENSE"
|
|
12
13
|
],
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@aws-blocks/core": "^0.1.1",
|
|
31
|
-
"@aws-blocks/bb-logger": "^0.1.
|
|
32
|
+
"@aws-blocks/bb-logger": "^0.1.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/node": "^20.0.0",
|