Skip to content

Commit

Permalink
feat(logs): MetricFilter exposes extracted Metric object (#8556)
Browse files Browse the repository at this point in the history
It is convention to expose metrics on AWS constructs, but metric filters currently do not.

This PR adds a `metric()` API that creates a metric from a metric filter with the same name and namespace. The user can specify what statistic to use, or it will default to average over 5 minutes.

fixes #1353 

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kaizencc committed Jun 18, 2020
1 parent 831092e commit a35a53b
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 0 deletions.
25 changes: 25 additions & 0 deletions packages/@aws-cdk/aws-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,31 @@ Will extract the value of `jsonField` wherever it occurs in JSON-structed
log records in the LogGroup, and emit them to CloudWatch Metrics under
the name `Namespace/MetricName`.

#### Exposing Metric on a Metric Filter

You can expose a metric on a metric filter by calling the `MetricFilter.metric()` API.
This has a default of `statistic = 'avg'` if the statistic is not set in the `props`.

```ts
const mf = new MetricFilter(this, 'MetricFilter', {
logGroup,
metricNamespace: 'MyApp',
metricName: 'Latency',
filterPattern: FilterPattern.exists('$.latency'),
metricValue: '$.latency',
});

//expose a metric from the metric filter
const metric = mf.metric();

//you can use the metric to create a new alarm
new Alarm(this, 'alarm from metric filter', {
metric,
threshold: 100,
evaluationPeriods: 2,
});
```

### Patterns

Patterns describe which log events match a subscription or metric filter. There
Expand Down
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-logs/lib/metric-filter.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Metric, MetricOptions } from '@aws-cdk/aws-cloudwatch';
import { Construct, Resource } from '@aws-cdk/core';
import { ILogGroup, MetricFilterOptions } from './log-group';
import { CfnMetricFilter } from './logs.generated';
Expand All @@ -16,9 +17,16 @@ export interface MetricFilterProps extends MetricFilterOptions {
* A filter that extracts information from CloudWatch Logs and emits to CloudWatch Metrics
*/
export class MetricFilter extends Resource {

private readonly metricName: string;
private readonly metricNamespace: string;

constructor(scope: Construct, id: string, props: MetricFilterProps) {
super(scope, id);

this.metricName = props.metricName;
this.metricNamespace = props.metricNamespace;

// It looks odd to map this object to a singleton list, but that's how
// we're supposed to do it according to the docs.
//
Expand All @@ -38,4 +46,18 @@ export class MetricFilter extends Resource {
}],
});
}

/**
* Return the given named metric for this Metric Filter
*
* @default avg over 5 minutes
*/
public metric(props?: MetricOptions): Metric {
return new Metric({
metricName: this.metricName,
namespace: this.metricNamespace,
statistic: 'avg',
...props,
}).attachTo(this);
}
}
40 changes: 40 additions & 0 deletions packages/@aws-cdk/aws-logs/test/integ.expose-metric.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"Resources": {
"LogGroupF5B46931": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"RetentionInDays": 731
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete"
},
"MetricFilter1B93B6E5": {
"Type": "AWS::Logs::MetricFilter",
"Properties": {
"FilterPattern": "{ $.latency = \"*\" }",
"LogGroupName": {
"Ref": "LogGroupF5B46931"
},
"MetricTransformations": [
{
"MetricName": "Latency",
"MetricNamespace": "MyApp",
"MetricValue": "$.latency"
}
]
}
},
"alarmfrommetricfilterF546D67D": {
"Type": "AWS::CloudWatch::Alarm",
"Properties": {
"ComparisonOperator": "GreaterThanOrEqualToThreshold",
"EvaluationPeriods": 2,
"MetricName": "Latency",
"Namespace": "MyApp",
"Period": 300,
"Statistic": "Average",
"Threshold": 100
}
}
}
}
41 changes: 41 additions & 0 deletions packages/@aws-cdk/aws-logs/test/integ.expose-metric.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Alarm } from '@aws-cdk/aws-cloudwatch';
import { App, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core';
import { FilterPattern, LogGroup, MetricFilter } from '../lib';

/*
* Stack verification steps:
*
* -- aws cloudwatch describe-alarms --alarm-name-prefix aws-cdk-expose-metric-integ
* has Namespace of `MyApp` and Statistic of `Average`
*/

class ExposeMetricIntegStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);

const logGroup = new LogGroup(this, 'LogGroup', {
removalPolicy: RemovalPolicy.DESTROY,
});

/// !show
const mf = new MetricFilter(this, 'MetricFilter', {
logGroup,
metricNamespace: 'MyApp',
metricName: 'Latency',
filterPattern: FilterPattern.exists('$.latency'),
metricValue: '$.latency',
});

new Alarm(this, 'alarm from metric filter', {
metric: mf.metric(),
threshold: 100,
evaluationPeriods: 2,
});

/// !hide
}
}

const app = new App();
new ExposeMetricIntegStack(app, 'aws-cdk-expose-metric-integ');
app.synth();
53 changes: 53 additions & 0 deletions packages/@aws-cdk/aws-logs/test/test.metricfilter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { Metric } from '@aws-cdk/aws-cloudwatch';
import { Stack } from '@aws-cdk/core';
import { Test } from 'nodeunit';
import { FilterPattern, LogGroup, MetricFilter } from '../lib';
Expand Down Expand Up @@ -31,4 +32,56 @@ export = {

test.done();
},

'metric filter exposes metric'(test: Test) {
// GIVEN
const stack = new Stack();
const logGroup = new LogGroup(stack, 'LogGroup');

// WHEN
const mf = new MetricFilter(stack, 'Subscription', {
logGroup,
metricNamespace: 'AWS/Test',
metricName: 'Latency',
metricValue: '$.latency',
filterPattern: FilterPattern.exists('$.latency'),
});

const metric = mf.metric();

// THEN
test.deepEqual(metric, new Metric({
metricName: 'Latency',
namespace: 'AWS/Test',
statistic: 'avg',
}));

test.done();
},

'metric filter exposes metric with custom statistic'(test: Test) {
// GIVEN
const stack = new Stack();
const logGroup = new LogGroup(stack, 'LogGroup');

// WHEN
const mf = new MetricFilter(stack, 'Subscription', {
logGroup,
metricNamespace: 'AWS/Test',
metricName: 'Latency',
metricValue: '$.latency',
filterPattern: FilterPattern.exists('$.latency'),
});

const metric = mf.metric({statistic: 'maximum'});

// THEN
test.deepEqual(metric, new Metric({
metricName: 'Latency',
namespace: 'AWS/Test',
statistic: 'maximum',
}));

test.done();
},
};

0 comments on commit a35a53b

Please sign in to comment.