Repository: serverless/serverless (JavaScript) — Feature

Issue:
Filter pattern improvement for cloudwatchLog events (aws)
# This is a Feature Proposal (Improvement)

## Description

For feature proposals:
* When adding a cloudwatchLog event, as mentioned by AWS document `Metric filter terms that include characters other than alphanumeric or underscore must be placed inside double quotes ("")`, if we want to add a filter like `"Total amount" - "level=DEBUG"`, we need to write it like this:

```yaml
events:
      - cloudwatchLog:
          logGroup: "/aws/lambda/my-lambda-log-group"
          filter: "\\\"Total amount\\\" -\\\"level=DEBUG\\\""
```
It is not straightforward and it is not obvious to users. I only found out that I need to write like above when I check the [source code](https://github.com/serverless/serverless/blob/v1.24.1/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js#L87).

* Do you think it necessary to modify it and let it accept format like below:
```yaml
events:
      - cloudwatchLog:
          logGroup: "/aws/lambda/my-lambda-log-group"
          filter: '"Total amount" -"level=DEBUG"'
```

* Need to consider backwards compatible
* I can try to implement the improvement if you agree to make this change

Similar or dependent issues:
* None

## Additional Data

* ***Serverless Framework Version you're using***: v1.24.1
* ***Operating System***: Mac OSX, Linux, AWS AIM
* ***Stack Trace***:
* ***Provider Error messages***:



Discussion:
Hi, @willwhy thanks for opening up this issue :+1: 
Fully agreed. Your proposal makes sense.

>I can try to implement the improvement if you agree to make this change

Awesome :100: 
it would be great if you will implement it and send PR! 
Let us know if you need our helps!
Cool. Thanks @horike37 

Resolving patch:
diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js
index 400a6fae0a4..697d9736d09 100644
--- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js
+++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js
@@ -78,13 +78,20 @@ class AwsCompileCloudWatchLogEvents {
               .getLambdaCloudWatchLogPermissionLogicalId(functionName,
                 cloudWatchLogNumberInFunction);
 
+            // unescape quotes once when the first quote is detected escaped
+            const idxFirstSlash = FilterPattern.indexOf('\\');
+            const idxFirstQuote = FilterPattern.indexOf('"');
+            if (idxFirstSlash >= 0 && idxFirstQuote >= 0 && idxFirstQuote > idxFirstSlash) {
+              FilterPattern = FilterPattern.replace(/\\("|\\|')/g, (match, g) => g);
+            }
+
             const cloudWatchLogRuleTemplate = `
               {
                 "Type": "AWS::Logs::SubscriptionFilter",
                 "DependsOn": "${lambdaPermissionLogicalId}",
                 "Properties": {
                   "LogGroupName": "${LogGroupName}",
-                  "FilterPattern": "${FilterPattern}",
+                  "FilterPattern": ${JSON.stringify(FilterPattern)},
                   "DestinationArn": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }
                 }
               }


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js
index 7777188d239..bfc3263cd35 100644
--- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js
+++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.test.js
@@ -138,6 +138,50 @@ describe('AwsCompileCloudWatchLogEvents', () => {
       ).to.equal('{$.userIdentity.type = Root}');
     });
 
+    it('should respect "filter" variable of plain text', () => {
+      awsCompileCloudWatchLogEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              cloudwatchLog: {
+                logGroup: '/aws/lambda/hello1',
+                filter: '"Total amount" -"level=Debug"',
+              },
+            },
+          ],
+        },
+      };
+
+      awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents();
+
+      expect(awsCompileCloudWatchLogEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1
+        .Properties.FilterPattern
+      ).to.equal('"Total amount" -"level=Debug"');
+    });
+
+    it('should respect escaped "filter" variable of plain text', () => {
+      awsCompileCloudWatchLogEvents.serverless.service.functions = {
+        first: {
+          events: [
+            {
+              cloudwatchLog: {
+                logGroup: '/aws/lambda/hello1',
+                filter: "\\\"Total amount\\\" -\\\"level=Debug\\\"", // eslint-disable-line quotes
+              },
+            },
+          ],
+        },
+      };
+
+      awsCompileCloudWatchLogEvents.compileCloudWatchLogEvents();
+
+      expect(awsCompileCloudWatchLogEvents.serverless.service
+        .provider.compiledCloudFormationTemplate.Resources.FirstLogsSubscriptionFilterCloudWatchLog1
+        .Properties.FilterPattern
+      ).to.equal('"Total amount" -"level=Debug"');
+    });
+
     it('should set an empty string for FilterPattern statement when "filter" variable is not given'
     , () => {
       awsCompileCloudWatchLogEvents.serverless.service.functions = {