UNPKG

12.8 kBMarkdownView Raw
1# Amazon CloudWatch Logs Construct Library
2<!--BEGIN STABILITY BANNER-->
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<!--END STABILITY BANNER-->
18
19This library supplies constructs for working with CloudWatch Logs.
20
21## Log Groups/Streams
22
23The basic unit of CloudWatch is a *Log Group*. Every log group typically has the
24same kind of data logged to it, in the same format. If there are multiple
25applications or services logging into the Log Group, each of them creates a new
26*Log Stream*.
27
28Every log operation creates a "log event", which can consist of a simple string
29or a single-line JSON object. JSON objects have the advantage that they afford
30more filtering abilities (see below).
31
32The only configurable attribute for log streams is the retention period, which
33configures after how much time the events in the log stream expire and are
34deleted.
35
36The default retention period if not supplied is 2 years, but it can be set to
37one of the values in the `RetentionDays` enum to configure a different
38retention period (including infinite retention).
39
40[retention example](test/example.retention.lit.ts)
41
42## LogRetention
43
44The `LogRetention` construct is a way to control the retention period of log groups that are created outside of the CDK. The construct is usually
45used on log groups that are auto created by AWS services, such as [AWS
46lambda](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html).
47
48This is implemented using a [CloudFormation custom
49resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cfn-customresource.html)
50which pre-creates the log group if it doesn't exist, and sets the specified log retention period (never expire, by default).
51
52By default, the log group will be created in the same region as the stack. The `logGroupRegion` property can be used to configure
53log groups in other regions. This is typically useful when controlling retention for log groups auto-created by global services that
54publish their log group to a specific region, such as AWS Chatbot creating a log group in `us-east-1`.
55
56## Resource Policy
57
58CloudWatch Resource Policies allow other AWS services or IAM Principals to put log events into the log groups.
59A resource policy is automatically created when `addToResourcePolicy` is called on the LogGroup for the first time:
60
61```ts
62const logGroup = new logs.LogGroup(this, 'LogGroup');
63logGroup.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
70Or 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
73const logGroup = new logs.LogGroup(this, 'LogGroup');
74logGroup.grantWrite(new iam.ServicePrincipal('es.amazonaws.com'));
75```
76
77Be aware that any ARNs or tokenized values passed to the resource policy will be converted into AWS Account IDs.
78This is because CloudWatch Logs Resource Policies do not accept ARNs as principals, but they do accept
79Account ID strings. Non-ARN principals, like Service principals or Any princpals, are accepted by CloudWatch.
80
81## Encrypting Log Groups
82
83By default, log group data is always encrypted in CloudWatch Logs. You have the
84option to encrypt log group data using a AWS KMS customer master key (CMK) should
85you not wish to use the default AWS encryption. Keep in mind that if you decide to
86encrypt a log group, any service or IAM identity that needs to read the encrypted
87log streams in the future will require the same CMK to decrypt the data.
88
89Here's a simple example of creating an encrypted Log Group using a KMS CMK.
90
91```ts
92import * as kms from '@aws-cdk/aws-kms';
93
94new logs.LogGroup(this, 'LogGroup', {
95 encryptionKey: new kms.Key(this, 'Key'),
96});
97```
98
99See the AWS documentation for more detailed information about [encrypting CloudWatch
100Logs](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html).
101
102## Subscriptions and Destinations
103
104Log events matching a particular filter can be sent to either a Lambda function
105or a Kinesis stream.
106
107If the Kinesis stream lives in a different account, a `CrossAccountDestination`
108object needs to be added in the destination account which will act as a proxy
109for the remote Kinesis stream. This object is automatically created for you
110if you use the CDK Kinesis library.
111
112Create a `SubscriptionFilter`, initialize it with an appropriate `Pattern` (see
113below) and supply the intended destination:
114
115```ts
116import * as destinations from '@aws-cdk/aws-logs-destinations';
117declare const fn: lambda.Function;
118declare const logGroup: logs.LogGroup;
119
120new 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
129CloudWatch Logs can extract and emit metrics based on a textual log stream.
130Depending on your needs, this may be a more convenient way of generating metrics
131for you application than making calls to CloudWatch Metrics yourself.
132
133A `MetricFilter` either emits a fixed number every time it sees a log event
134matching a particular pattern (see below), or extracts a number from the log
135event and uses that as the metric value.
136
137Example:
138
139[metricfilter example](test/integ.metricfilter.lit.ts)
140
141Remember that if you want to use a value from the log event as the metric value,
142you must mention it in your pattern somewhere.
143
144A very simple MetricFilter can be created by using the `logGroup.extractMetric()`
145helper function:
146
147```ts
148declare const logGroup: logs.LogGroup;
149logGroup.extractMetric('$.jsonField', 'Namespace', 'MetricName');
150```
151
152Will extract the value of `jsonField` wherever it occurs in JSON-structed
153log records in the LogGroup, and emit them to CloudWatch Metrics under
154the name `Namespace/MetricName`.
155
156### Exposing Metric on a Metric Filter
157
158You can expose a metric on a metric filter by calling the `MetricFilter.metric()` API.
159This has a default of `statistic = 'avg'` if the statistic is not set in the `props`.
160
161```ts
162declare const logGroup: logs.LogGroup;
163const 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
172const metric = mf.metric();
173
174//you can use the metric to create a new alarm
175new cloudwatch.Alarm(this, 'alarm from metric filter', {
176 metric,
177 threshold: 100,
178 evaluationPeriods: 2,
179});
180```
181
182## Patterns
183
184Patterns describe which log events match a subscription or metric filter. There
185are three types of patterns:
186
187* Text patterns
188* JSON patterns
189* Space-delimited table patterns
190
191All patterns are constructed by using static functions on the `FilterPattern`
192class.
193
194In 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
204Text patterns match if the literal strings appear in the text form of the log
205line.
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
215Examples:
216
217```ts
218// Search for lines that contain both "ERROR" and "MainThread"
219const pattern1 = logs.FilterPattern.allTerms('ERROR', 'MainThread');
220
221// Search for lines that either contain both "ERROR" and "MainThread", or
222// both "WARN" and "Deadlock".
223const pattern2 = logs.FilterPattern.anyTermGroup(
224 ['ERROR', 'MainThread'],
225 ['WARN', 'Deadlock'],
226);
227```
228
229## JSON Patterns
230
231JSON 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
233or log level). JSON patterns can make comparisons on the values inside the
234fields.
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
241Fields in the JSON structure are identified by identifier the complete object as `$`
242and 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
263Example:
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.
269const 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
280If the log events are rows of a space-delimited table, this pattern can be used
281to identify the columns in that structure and add conditions on any of them. The
282canonical example where you would apply this type of pattern is Apache server
283logs.
284
285Text that is surrounded by `"..."` quotes or `[...]` square brackets will
286be 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
294After constructing a `SpaceDelimitedTextPattern`, you can use the following
295two 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
302Multiple restrictions can be added on the same column; they must all apply.
303
304Example:
305
306```ts
307// Search for all events where the component is "HttpServer" and the
308// result code is not equal to 200.
309const 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
316Creates a query definition for CloudWatch Logs Insights.
317
318Example:
319
320```ts
321new 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
333Be aware that Log Group ARNs will always have the string `:*` appended to
334them, to match the behavior of [the CloudFormation `AWS::Logs::LogGroup`
335resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#aws-resource-logs-loggroup-return-values).