1 | # Amazon CloudWatch Logs Construct Library
|
2 |
|
3 |
|
4 | ---
|
5 |
|
6 | ![End-of-Support](https://img.shields.io/badge/End--of--Support-critical.svg?style=for-the-badge)
|
7 |
|
8 | > AWS CDK v1 has reached End-of-Support on 2023-06-01.
|
9 | > This package is no longer being updated, and users should migrate to AWS CDK v2.
|
10 | >
|
11 | > For more information on how to migrate, see the [_Migrating to AWS CDK v2_ guide][doc].
|
12 | >
|
13 | > [doc]: https://docs.aws.amazon.com/cdk/v2/guide/migrating-v2.html
|
14 |
|
15 | ---
|
16 |
|
17 |
|
18 |
|
19 | This library supplies constructs for working with CloudWatch Logs.
|
20 |
|
21 | ## Log Groups/Streams
|
22 |
|
23 | The basic unit of CloudWatch is a *Log Group*. Every log group typically has the
|
24 | same kind of data logged to it, in the same format. If there are multiple
|
25 | applications or services logging into the Log Group, each of them creates a new
|
26 | *Log Stream*.
|
27 |
|
28 | Every log operation creates a "log event", which can consist of a simple string
|
29 | or a single-line JSON object. JSON objects have the advantage that they afford
|
30 | more filtering abilities (see below).
|
31 |
|
32 | The only configurable attribute for log streams is the retention period, which
|
33 | configures after how much time the events in the log stream expire and are
|
34 | deleted.
|
35 |
|
36 | The default retention period if not supplied is 2 years, but it can be set to
|
37 | one of the values in the `RetentionDays` enum to configure a different
|
38 | retention period (including infinite retention).
|
39 |
|
40 | [retention example](test/example.retention.lit.ts)
|
41 |
|
42 | ## LogRetention
|
43 |
|
44 | The `LogRetention` construct is a way to control the retention period of log groups that are created outside of the CDK. The construct is usually
|
45 | used on log groups that are auto created by AWS services, such as [AWS
|
46 | lambda](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html).
|
47 |
|
48 | This is implemented using a [CloudFormation custom
|
49 | resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html)
|
50 | which pre-creates the log group if it doesn't exist, and sets the specified log retention period (never expire, by default).
|
51 |
|
52 | By default, the log group will be created in the same region as the stack. The `logGroupRegion` property can be used to configure
|
53 | log groups in other regions. This is typically useful when controlling retention for log groups auto-created by global services that
|
54 | publish their log group to a specific region, such as AWS Chatbot creating a log group in `us-east-1`.
|
55 |
|
56 | ## Resource Policy
|
57 |
|
58 | CloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the log groups.
|
59 | A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time:
|
60 |
|
61 | ```ts
|
62 | const logGroup = new logs.LogGroup(this, 'LogGroup');
|
63 | logGroup.addToResourcePolicy(new iam.PolicyStatement({
|
64 | actions: ['logs:CreateLogStream', 'logs:PutLogEvents'],
|
65 | principals: [new iam.ServicePrincipal('es.amazonaws.com')],
|
66 | resources: [logGroup.logGroupArn],
|
67 | }));
|
68 | ```
|
69 |
|
70 | Or more conveniently, write permissions to the log group can be granted as follows which gives same result as in the above example.
|
71 |
|
72 | ```ts
|
73 | const logGroup = new logs.LogGroup(this, 'LogGroup');
|
74 | logGroup.grantWrite(new iam.ServicePrincipal('es.amazonaws.com'));
|
75 | ```
|
76 |
|
77 | Be aware that any ARNs or tokenized values passed to the resource policy will be converted into AWS Account IDs.
|
78 | This is because CloudWatch Logs Resource Policies do not accept ARNs as principals, but they do accept
|
79 | Account ID strings. Non-ARN principals, like Service principals or Any princpals, are accepted by CloudWatch.
|
80 |
|
81 | ## Encrypting Log Groups
|
82 |
|
83 | By default, log group data is always encrypted in CloudWatch Logs. You have the
|
84 | option to encrypt log group data using a AWS KMS customer master key (CMK) should
|
85 | you not wish to use the default AWS encryption. Keep in mind that if you decide to
|
86 | encrypt a log group, any service or IAM identity that needs to read the encrypted
|
87 | log streams in the future will require the same CMK to decrypt the data.
|
88 |
|
89 | Here's a simple example of creating an encrypted Log Group using a KMS CMK.
|
90 |
|
91 | ```ts
|
92 | import * as kms from '@aws-cdk/aws-kms';
|
93 |
|
94 | new logs.LogGroup(this, 'LogGroup', {
|
95 | encryptionKey: new kms.Key(this, 'Key'),
|
96 | });
|
97 | ```
|
98 |
|
99 | See the AWS documentation for more detailed information about [encrypting CloudWatch
|
100 | Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html).
|
101 |
|
102 | ## Subscriptions and Destinations
|
103 |
|
104 | Log events matching a particular filter can be sent to either a Lambda function
|
105 | or a Kinesis stream.
|
106 |
|
107 | If the Kinesis stream lives in a different account, a `CrossAccountDestination`
|
108 | object needs to be added in the destination account which will act as a proxy
|
109 | for the remote Kinesis stream. This object is automatically created for you
|
110 | if you use the CDK Kinesis library.
|
111 |
|
112 | Create a `SubscriptionFilter`, initialize it with an appropriate `Pattern` (see
|
113 | below) and supply the intended destination:
|
114 |
|
115 | ```ts
|
116 | import * as destinations from '@aws-cdk/aws-logs-destinations';
|
117 | declare const fn: lambda.Function;
|
118 | declare const logGroup: logs.LogGroup;
|
119 |
|
120 | new logs.SubscriptionFilter(this, 'Subscription', {
|
121 | logGroup,
|
122 | destination: new destinations.LambdaDestination(fn),
|
123 | filterPattern: logs.FilterPattern.allTerms("ERROR", "MainThread"),
|
124 | });
|
125 | ```
|
126 |
|
127 | ## Metric Filters
|
128 |
|
129 | CloudWatch Logs can extract and emit metrics based on a textual log stream.
|
130 | Depending on your needs, this may be a more convenient way of generating metrics
|
131 | for you application than making calls to CloudWatch Metrics yourself.
|
132 |
|
133 | A `MetricFilter` either emits a fixed number every time it sees a log event
|
134 | matching a particular pattern (see below), or extracts a number from the log
|
135 | event and uses that as the metric value.
|
136 |
|
137 | Example:
|
138 |
|
139 | [metricfilter example](test/integ.metricfilter.lit.ts)
|
140 |
|
141 | Remember that if you want to use a value from the log event as the metric value,
|
142 | you must mention it in your pattern somewhere.
|
143 |
|
144 | A very simple MetricFilter can be created by using the `logGroup.extractMetric()`
|
145 | helper function:
|
146 |
|
147 | ```ts
|
148 | declare const logGroup: logs.LogGroup;
|
149 | logGroup.extractMetric('$.jsonField', 'Namespace', 'MetricName');
|
150 | ```
|
151 |
|
152 | Will extract the value of `jsonField` wherever it occurs in JSON-structed
|
153 | log records in the LogGroup, and emit them to CloudWatch Metrics under
|
154 | the name `Namespace/MetricName`.
|
155 |
|
156 | ### Exposing Metric on a Metric Filter
|
157 |
|
158 | You can expose a metric on a metric filter by calling the `MetricFilter.metric()` API.
|
159 | This has a default of `statistic = 'avg'` if the statistic is not set in the `props`.
|
160 |
|
161 | ```ts
|
162 | declare const logGroup: logs.LogGroup;
|
163 | const mf = new logs.MetricFilter(this, 'MetricFilter', {
|
164 | logGroup,
|
165 | metricNamespace: 'MyApp',
|
166 | metricName: 'Latency',
|
167 | filterPattern: logs.FilterPattern.exists('$.latency'),
|
168 | metricValue: '$.latency',
|
169 | });
|
170 |
|
171 | //expose a metric from the metric filter
|
172 | const metric = mf.metric();
|
173 |
|
174 | //you can use the metric to create a new alarm
|
175 | new cloudwatch.Alarm(this, 'alarm from metric filter', {
|
176 | metric,
|
177 | threshold: 100,
|
178 | evaluationPeriods: 2,
|
179 | });
|
180 | ```
|
181 |
|
182 | ## Patterns
|
183 |
|
184 | Patterns describe which log events match a subscription or metric filter. There
|
185 | are three types of patterns:
|
186 |
|
187 | * Text patterns
|
188 | * JSON patterns
|
189 | * Space-delimited table patterns
|
190 |
|
191 | All patterns are constructed by using static functions on the `FilterPattern`
|
192 | class.
|
193 |
|
194 | In addition to the patterns above, the following special patterns exist:
|
195 |
|
196 | * `FilterPattern.allEvents()`: matches all log events.
|
197 | * `FilterPattern.literal(string)`: if you already know what pattern expression to
|
198 | use, this function takes a string and will use that as the log pattern. For
|
199 | more information, see the [Filter and Pattern
|
200 | Syntax](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html).
|
201 |
|
202 | ### Text Patterns
|
203 |
|
204 | Text patterns match if the literal strings appear in the text form of the log
|
205 | line.
|
206 |
|
207 | * `FilterPattern.allTerms(term, term, ...)`: matches if all of the given terms
|
208 | (substrings) appear in the log event.
|
209 | * `FilterPattern.anyTerm(term, term, ...)`: matches if all of the given terms
|
210 | (substrings) appear in the log event.
|
211 | * `FilterPattern.anyTermGroup([term, term, ...], [term, term, ...], ...)`: matches if
|
212 | all of the terms in any of the groups (specified as arrays) matches. This is
|
213 | an OR match.
|
214 |
|
215 | Examples:
|
216 |
|
217 | ```ts
|
218 | // Search for lines that contain both "ERROR" and "MainThread"
|
219 | const pattern1 = logs.FilterPattern.allTerms('ERROR', 'MainThread');
|
220 |
|
221 | // Search for lines that either contain both "ERROR" and "MainThread", or
|
222 | // both "WARN" and "Deadlock".
|
223 | const pattern2 = logs.FilterPattern.anyTermGroup(
|
224 | ['ERROR', 'MainThread'],
|
225 | ['WARN', 'Deadlock'],
|
226 | );
|
227 | ```
|
228 |
|
229 | ## JSON Patterns
|
230 |
|
231 | JSON patterns apply if the log event is the JSON representation of an object
|
232 | (without any other characters, so it cannot include a prefix such as timestamp
|
233 | or log level). JSON patterns can make comparisons on the values inside the
|
234 | fields.
|
235 |
|
236 | * **Strings**: the comparison operators allowed for strings are `=` and `!=`.
|
237 | String values can start or end with a `*` wildcard.
|
238 | * **Numbers**: the comparison operators allowed for numbers are `=`, `!=`,
|
239 | `<`, `<=`, `>`, `>=`.
|
240 |
|
241 | Fields in the JSON structure are identified by identifier the complete object as `$`
|
242 | and then descending into it, such as `$.field` or `$.list[0].field`.
|
243 |
|
244 | * `FilterPattern.stringValue(field, comparison, string)`: matches if the given
|
245 | field compares as indicated with the given string value.
|
246 | * `FilterPattern.numberValue(field, comparison, number)`: matches if the given
|
247 | field compares as indicated with the given numerical value.
|
248 | * `FilterPattern.isNull(field)`: matches if the given field exists and has the
|
249 | value `null`.
|
250 | * `FilterPattern.notExists(field)`: matches if the given field is not in the JSON
|
251 | structure.
|
252 | * `FilterPattern.exists(field)`: matches if the given field is in the JSON
|
253 | structure.
|
254 | * `FilterPattern.booleanValue(field, boolean)`: matches if the given field
|
255 | is exactly the given boolean value.
|
256 | * `FilterPattern.all(jsonPattern, jsonPattern, ...)`: matches if all of the
|
257 | given JSON patterns match. This makes an AND combination of the given
|
258 | patterns.
|
259 | * `FilterPattern.any(jsonPattern, jsonPattern, ...)`: matches if any of the
|
260 | given JSON patterns match. This makes an OR combination of the given
|
261 | patterns.
|
262 |
|
263 | Example:
|
264 |
|
265 | ```ts
|
266 | // Search for all events where the component field is equal to
|
267 | // "HttpServer" and either error is true or the latency is higher
|
268 | // than 1000.
|
269 | const pattern = logs.FilterPattern.all(
|
270 | logs.FilterPattern.stringValue('$.component', '=', 'HttpServer'),
|
271 | logs.FilterPattern.any(
|
272 | logs.FilterPattern.booleanValue('$.error', true),
|
273 | logs.FilterPattern.numberValue('$.latency', '>', 1000),
|
274 | ),
|
275 | );
|
276 | ```
|
277 |
|
278 | ## Space-delimited table patterns
|
279 |
|
280 | If the log events are rows of a space-delimited table, this pattern can be used
|
281 | to identify the columns in that structure and add conditions on any of them. The
|
282 | canonical example where you would apply this type of pattern is Apache server
|
283 | logs.
|
284 |
|
285 | Text that is surrounded by `"..."` quotes or `[...]` square brackets will
|
286 | be treated as one column.
|
287 |
|
288 | * `FilterPattern.spaceDelimited(column, column, ...)`: construct a
|
289 | `SpaceDelimitedTextPattern` object with the indicated columns. The columns
|
290 | map one-by-one the columns found in the log event. The string `"..."` may
|
291 | be used to specify an arbitrary number of unnamed columns anywhere in the
|
292 | name list (but may only be specified once).
|
293 |
|
294 | After constructing a `SpaceDelimitedTextPattern`, you can use the following
|
295 | two members to add restrictions:
|
296 |
|
297 | * `pattern.whereString(field, comparison, string)`: add a string condition.
|
298 | The rules are the same as for JSON patterns.
|
299 | * `pattern.whereNumber(field, comparison, number)`: add a numerical condition.
|
300 | The rules are the same as for JSON patterns.
|
301 |
|
302 | Multiple restrictions can be added on the same column; they must all apply.
|
303 |
|
304 | Example:
|
305 |
|
306 | ```ts
|
307 | // Search for all events where the component is "HttpServer" and the
|
308 | // result code is not equal to 200.
|
309 | const pattern = logs.FilterPattern.spaceDelimited('time', 'component', '...', 'result_code', 'latency')
|
310 | .whereString('component', '=', 'HttpServer')
|
311 | .whereNumber('result_code', '!=', 200);
|
312 | ```
|
313 |
|
314 | ## Logs Insights Query Definition
|
315 |
|
316 | Creates a query definition for CloudWatch Logs Insights.
|
317 |
|
318 | Example:
|
319 |
|
320 | ```ts
|
321 | new logs.QueryDefinition(this, 'QueryDefinition', {
|
322 | queryDefinitionName: 'MyQuery',
|
323 | queryString: new logs.QueryString({
|
324 | fields: ['@timestamp', '@message'],
|
325 | sort: '@timestamp desc',
|
326 | limit: 20,
|
327 | }),
|
328 | });
|
329 | ```
|
330 |
|
331 | ## Notes
|
332 |
|
333 | Be aware that Log Group ARNs will always have the string `:*` appended to
|
334 | them, to match the behavior of [the CloudFormation `AWS::Logs::LogGroup`
|
335 | resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#aws-resource-logs-loggroup-return-values).
|