Skip to main content

Logging in AWS lambda

Problem :

Logs are very important aspect of any microservice or any AWS service and in AWS if your rate of generating Cloudwatch logs is high ,then  it can increase your AWS costs significantly. There should be some limit on the growth of the logs to keep costs under control.

Solution :

There are multiple ways to manage the logging. Let's begin with some of the best practices and then some framework level changes which can help in reducing the overall logging.


Best Practices :


1. Most of the times we are only interested to find error scenarios from the logs so it is important to use log.error while logging error cases. Never use other logging levels for logging errors

2. Never use print statements instead use a proper logging framework to log the statements

2. Be diligent about the log statements and categorize them correctly at different log levels : DEBUG, INFO, ERROR

4. Don't log data unless there is a critical need for same. Even if required, the data should be printed only at DEBUG level 



Framework level changes :


Apart from the best practices listed above, it is also required that we put some controls in place to keep a check on unwanted logging. Here are few services on which I have implemented logging framework .

  •  Lambda functions - Python/Java
  •  ECS services- in Java

It is important that we handle logging from both the services.
Idea is to keep the logging levels at minimum for all the development environments. So for most of the dev/test environments, logging will be kept at ERROR while UAT and Prod may run on INFO but can be increased to DEBUG temporarily for troubleshooting.





1. ECS services

Following steps should be taken to retrofit logging in all ECS components:

1. Add following properties in application.properties:
1
2
3
4
5
logging.level.root=${LOG_LEVEL}
LOG_LEVEL=INFO
#endpoints.loggers.sensitive=false
management.endpoints.web.exposure.include=health,info,loggers
org.springframework.boot:type=Endpoint,name=Loggers


2. Enable Spring Actuator framework by adding following dependencies in pom.xml:

pom.xml:
1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>


3. Make following changes in CF template of the project :
1. Add security group - Below is a sample config. Rename the variables as per project name and usage :

Preview:
2. HelloComponentSecurityGroup:
3.   Type: AWS::EC2::SecurityGroup
4.   Properties:
5.     GroupName: !Sub "hello-component-${EnvironmentName}-sg"
6.     GroupDescription: ECS security group
7.     SecurityGroupIngress:
8.     - IpProtocol: tcp
9.       FromPort: 8080
10.       ToPort: 8080
11.       CidrIp: 10.0.0.0/8
12.     SecurityGroupEgress:
13.     - IpProtocol: tcp
14.       FromPort: 0
15.       ToPort: 1111
16.       CidrIp: 0.0.0.0/0
17.     VpcId:
18.       Fn::ImportValue: "vpc-id"
19.     Tags:
20.     - Key: Company
21.       Value: !Ref CompanyTag    
22.     - Key: StageName
23.       Value: !Ref StageName
24.     - Key: EnvironmentName
25.       Value: !Ref EnvironmentName
26.     - Key: Name
27.       Value: !Sub "hello-component-${EnvironmentName}-sg"
28. Make sure to include the security group in ECSService block:

29. EcsService:
30.   Type: 'AWS::ECS::Service'
31.   Properties:
32.     Cluster: !Sub ${ClusterName}-${EnvironmentName}
33.     ServiceName: !Sub "${ServiceName}-service"
34.     LaunchType: FARGATE
35.     DesiredCount: !Ref ServiceDesiredCount
36.     TaskDefinition: !Ref TaskDefinition
37.     DeploymentConfiguration:
38.       MaximumPercent: 200
39.       MinimumHealthyPercent: 100
40.     NetworkConfiguration:
41.       AwsvpcConfiguration:
42.         AssignPublicIp: DISABLED
43.         ############## Include the statements below #############
44.         SecurityGroups:
45.           - !Ref HelloComponentSecurityGroup

46. Add another Environment variable in TaskDefinition → Environment :
47. Environment:
48.   -
49.     Name: LOG_LEVEL

    Value: !Ref LogLevel ### (This LogLevel field should be defined in parameters and should be derived through config project)



2. Lambda Services

Let's try to define logging strategy for both Python and Java based Lambda functions:


2.1. Python based lambda

Python has inbuilt logging framework which can be used to log statements at different logging levels (much like in Java). Following steps can be used to enable the same:

1. import logging in main lambda function python file (file which has lambda_handler) and before beginning of the function write following statements:

import logging
logger = logging.getLogger()
logger.setLevel(os.environ['LOGGING_LEVEL'])


2. Use log.info, log.error or log.debug instead of context.log to log statements appropriately.



2.2. Java based Lambda

Unfortunately, for the lambda(s) built in Java, the standard context logging framework does not support different logging levels. So we will have to use aws-lambda-java-log4j framework to make logging inline with other compute resources.

1. Add following dependencies in pom.xml :

pom.xml:
<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-log4j2</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
</dependency>


2.  Paste following file in src/main/resources/log4j.xml:

log4j.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration debug="true"
 xmlns:log4j='http://jakarta.apache.org/log4j/'>

 <appender name="file" class="org.apache.log4j.RollingFileAppender">
    <param name="append" value="false" />
    <param name="maxFileSize" value="10KB" />
    <param name="maxBackupIndex" value="5" />
    <!-- For Tomcat -->
    <param name="file" value="${catalina.home}/logs/myStruts1App.log" />
    <layout class="org.apache.log4j.PatternLayout">
  <param name="ConversionPattern" 
   value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
    </layout>
 </appender>

 <root>
  <level value="ERROR" />
  <appender-ref ref="file" />
 </root>

</log4j:configuration>

We need to replace context.log statements to log.debug /log.info / log.error statements as applicable. But before we use log statements, we need to define the logger for the given class:

private  static  final  Logger log = LogManager.getLogger(Hello.class);
.... 
log.error("Error:  Exception processing input"  + e.getMessage(), e);







Comments

Popular posts from this blog

Extent report plugin for cucumber framework

Extent Reports  are the most popular  reporting  used with Selenium. ExtentReport API makes our life easy to generate interactive  report  with simple configuartions. It supports almost all Java and .NET test frameworks such as TestNG , JUnit , NUnit etc Here we are discussing about  a plugin which is build on  Extent Report specially for Cucumber. This plugin is used to simple out the implementation of  Extent Report  in  Cucumber Framework .  We are creating a maven project to implement the integration of our plugin with cucumber 1. Create new maven project in any tool eclipse/sts/intellij 2. Open pom.xml and update below entries. Step 1 : Add Cucumber Extent Reporter library to Maven Project Add  cucumber-extentsreport <dependency>      <groupId> com.vimalselvam </groupId>      <artifactId> cucumber-extentsreport </artif...

java: You aren't using a compiler supported by lombok, so lombok will not work and has been disabled.

  In order to make projects compile with the existing builds of Lombok processor, as a workaround you can use the flag -Djps.track.ap.dependencies=false which should be added to File | Settings | Build, Execution, Deployment | Compiler | Build process VM options field. This will disable collection of dependencies specified by an annotation processor when Filer methods are called

Execution default of goal org.springframework.boot:spring-boot-maven-plugin:1.2.3.RELEASE:repackage failed: Unable to find main class

Solutions:  Solution 1 : You needed to change the packaging parameter to jar from pom. Also, the repositories , pluginRepositories , the maven-compiler-plugin and the spring-boot-maven-plugin's version and executions weren't needed. Solution 2:  Try mvn install and see if it works Solution 3: Preview: <properties> <!-- The main class to start by executing java -jar --> <start-class> com.mycorp.starter.HelloWorldApplication </start-class> </properties> Solution 4: Enable the main() method in your Application.java. Configure spring-boot-maven-plugin to specify the class with the main class (Spring should find it anyway if you have one, but good to be explicit): Preview: <plugin> <groupId> org.springframework.boot </groupId> <artifactId> spring-boot-maven-plugin </artifactId> <version> ${spring-boot-version} </version>...