Skip to content

Commit

Permalink
feat: error-in-log alarm defaults for json-based logging, pattern and…
Browse files Browse the repository at this point in the history
… alarm name/description can be customised

BREAKING CHANGE: default is now json based logging and no longer text based logging
  • Loading branch information
mmeylan committed Nov 15, 2023
1 parent 4c9dd5b commit d061a35
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 77 deletions.
15 changes: 14 additions & 1 deletion src/patterns/error-in-log-alarm/README.md
Expand Up @@ -2,4 +2,17 @@

Construct that contains a cloudwatch alarm that will trigger when an aws-logs log group will output errors.

What an "error" is can be configured with `props.errorFilterPattern`. It defaults to: `' error ', ' ERROR ', ' Error '`
What an "error" is can be configured with `props.errorFilterPattern`.

Its defaults expects JSON-based logging, and will trigger if one of these conditions is met:

- The json root field `level` equals `error`
- The json root field `error` exists and has a `name` field set to a string
- The json root field `message` contains the word "error" (case insensitive)

Examples (would all match):
```
{"timestamp":"2023-11-14T08:51:06.613Z","level":"info","message":"This would match because there the word error in the message"}
{"timestamp":"2023-11-14T08:51:06.613Z","level":"error","message":"This would match because of the level"}
{"timestamp":"2023-11-14T08:51:06.613Z","level":"debug","message":"This would match because of the next field","error":{"name":"Error","message":"OH NO!"}
```
34 changes: 0 additions & 34 deletions src/patterns/error-in-log-alarm/alarm-utils.test.ts

This file was deleted.

36 changes: 0 additions & 36 deletions src/patterns/error-in-log-alarm/alarm-utils.ts

This file was deleted.

31 changes: 25 additions & 6 deletions src/patterns/error-in-log-alarm/error-in-log-alarm.ts
@@ -1,22 +1,28 @@
import {Duration} from 'aws-cdk-lib'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'
import {createErrorLogAlarmDescription, createErrorLogAlarmName, errorLogFilterPattern} from './alarm-utils'
import {Construct} from 'constructs'

export interface ErrorInLogAlarmProps {
resourceName: string
logGroup: logs.ILogGroup
namespace?: string
/** filter pattern that will trigger the alarm when matching */
/** optional function to override the default alarm name. Defaults to `ErrorInLogAlarm{resourceName}` */
createAlarmName?: (resourceName: string) => string | undefined
/** optional function to override the default alarm description. Defaults to `Alarm for Errors in LogGroup: {logGroupName}` */
createAlarmDescription?: (resourceName: string, logGroupName: string) => string | undefined
/** filter pattern that will trigger the alarm when matching. See README.md for default */
errorFilterPattern?: logs.IFilterPattern
}

const ALARM_NAME_SUFFIX = 'ErrorInLogAlarm'
const ALARM_DESCRIPTION_PREFIX = 'Alarm for Errors in LogGroup: '

export class ErrorInLogAlarm extends Construct {
readonly alarm: cloudwatch.Alarm
private readonly props: Required<ErrorInLogAlarmProps>

constructor(scope: Construct, private readonly id: string, props: ErrorInLogAlarmProps) {
constructor(scope: Construct, id: string, props: ErrorInLogAlarmProps) {
super(scope, id)
this.props = this.applyDefaults(props)
this.alarm = this.createErrorLogAlarm(this.props.resourceName, this.props.logGroup)
Expand All @@ -26,7 +32,10 @@ export class ErrorInLogAlarm extends Construct {
return {
...props,
namespace: props.namespace ?? 'BGO/CustomMetrics',
errorFilterPattern: props.errorFilterPattern ?? errorLogFilterPattern(),
errorFilterPattern: props.errorFilterPattern ?? this.defaultErrorInLogFilterPattern(),
createAlarmName: props.createAlarmName ?? (resourceName => `${resourceName}${ALARM_NAME_SUFFIX}`),
createAlarmDescription:
props.createAlarmDescription ?? ((r, logGroupName) => `${ALARM_DESCRIPTION_PREFIX}${logGroupName}`),
}
}
private createErrorLogAlarm(resourceName: string, logGroup: logs.ILogGroup): cloudwatch.Alarm {
Expand All @@ -41,8 +50,8 @@ export class ErrorInLogAlarm extends Construct {

// create an alarm on top of the error in the logs metric
return new cloudwatch.Alarm(this, `ErrorInLogAlarm`, {
alarmName: createErrorLogAlarmName(resourceName),
alarmDescription: createErrorLogAlarmDescription(logGroup.logGroupName),
alarmName: this.props.createAlarmName(resourceName),
alarmDescription: this.props.createAlarmDescription(resourceName, logGroup.logGroupName),
metric: new cloudwatch.Metric({
namespace: errorMetricProps.metricNamespace,
metricName: errorMetricProps.metricName,
Expand All @@ -56,4 +65,14 @@ export class ErrorInLogAlarm extends Construct {
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
})
}

private defaultErrorInLogFilterPattern(): logs.IFilterPattern {
// pattern 1: any log with level error
// pattern 2: find an error json field ($.error doesn't to match anything when the error field is an object, so we check for the name subfield, which should exist in all errors)
// pattern 3: any log with message containing " error " (case insensitive)
// FYI: cloudwatch has an console to test filter patterns, in a log group > edit metric filter
return logs.FilterPattern.literal(
'{ ($.level = "error") || ($.error.name = "*") || ($.message = %\\s[eE][rR][rR][oO][rR]\\s%) }',
)
}
}

0 comments on commit d061a35

Please sign in to comment.