keboola/serverless-papertrail-logging

View on GitHub
src/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
'use strict';

const _ = require('lodash');
const fs = require('fs');
const path = require('path');

class PapertrailLogging {
  constructor(serverless) {
    this.serverless = serverless;
    this.service = serverless.service;

    this.provider = this.serverless.getProvider('aws');

    this.hooks = {
      'before:package:createDeploymentArtifacts': this.beforePackageCreateDeploymentArtifacts.bind(this),
      'before:package:compileEvents': this.beforePackageCompileEvents.bind(this),
      'after:deploy:deploy': this.afterDeployDeploy.bind(this),
    };

    if (!_.has(this.service, 'custom.papertrail.port')) {
      throw new this.serverless.classes.Error('Configure Papertrail port in custom.papertrail.port of the serverless.yml');
    }
  }

  static getFunctionName() {
    return 'papertrailLogger';
  }

  getEnvFilePath() {
    return path.join(this.serverless.config.servicePath, PapertrailLogging.getFunctionName());
  }

  beforePackageCreateDeploymentArtifacts() {
    this.serverless.cli.log('Creating temporary logger function...');
    let functionPath = this.getEnvFilePath();

    if (!fs.existsSync(functionPath)) {
      fs.mkdirSync(functionPath);
    }

    this.papertrailHost = _.get(this.service, 'custom.papertrail.host', 'logs.papertrailapp.com');

    const loggerFunctionFullName = `${this.service.service}-${this.service.provider.stage}-${PapertrailLogging.getFunctionName()}`;
    _.merge(
      this.service.provider.compiledCloudFormationTemplate.Resources,
      {
        PapertrailLoggerLogGroup: {
          Type: "AWS::Logs::LogGroup",
          Properties: {
            LogGroupName: `/aws/lambda/${loggerFunctionFullName}`,
          },
        },
      }
    );

    let templatePath = path.resolve(__dirname, './logger.handler.js');
    let templateFile = fs.readFileSync(templatePath, 'utf-8');

    const papertrailHostname = _.get(this.service, 'custom.papertrail.hostname', this.service.service);
    const papertrailProgram = _.get(this.service, 'custom.papertrail.program', this.service.provider.stage);

    let handlerFunction = templateFile
      .replace('%papertrailHost%', this.papertrailHost)
      .replace('%papertrailPort%', this.service.custom.papertrail.port)
      .replace('%papertrailHostname%', papertrailHostname)
      .replace('%papertrailProgram%', papertrailProgram);
    fs.writeFileSync(path.join(functionPath, 'handler.js'), handlerFunction);
    this.service.functions[PapertrailLogging.getFunctionName()] = {
      handler: `${PapertrailLogging.getFunctionName()}/handler.handler`,
      name: loggerFunctionFullName,
      tags: _.has(this.service.provider, 'stackTags') ? this.service.provider.stackTags : {},
      events: [],
    };
  }

  beforePackageCompileEvents() {
    this.serverless.cli.log('Creating log subscriptions...');

    const loggerLogicalId = this.provider.naming.getLambdaLogicalId(PapertrailLogging.getFunctionName());

    _.each(this.service.provider.compiledCloudFormationTemplate.Resources, (item, key) => {
      if (_.has(item, 'Type') && item.Type === 'AWS::Logs::LogGroup') {
        this.service.provider.compiledCloudFormationTemplate.Resources[key].Properties.RetentionInDays = 30;
      }
    });

    _.merge(
      this.service.provider.compiledCloudFormationTemplate.Resources,
      {
        LambdaPermissionForSubscription: {
          Type: 'AWS::Lambda::Permission',
          Properties: {
            FunctionName: { 'Fn::GetAtt': [loggerLogicalId, 'Arn'] },
            Action: 'lambda:InvokeFunction',
            Principal: { 'Fn::Sub': 'logs.${AWS::Region}.amazonaws.com' },
            SourceArn: { 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*' },
          },
          DependsOn: [loggerLogicalId],
        },
      }
    );

    const functions = this.service.getAllFunctions();
    functions.forEach((functionName) => {
      if (functionName !== PapertrailLogging.getFunctionName()) {
        const functionData = this.service.getFunction(functionName);
        const normalizedFunctionName = this.provider.naming.getNormalizedFunctionName(functionName);
        _.merge(
          this.service.provider.compiledCloudFormationTemplate.Resources,
          {
            [`${normalizedFunctionName}SubscriptionFilter`]: {
              Type: 'AWS::Logs::SubscriptionFilter',
              Properties: {
                DestinationArn: { 'Fn::GetAtt': [loggerLogicalId, "Arn"] },
                FilterPattern: '',
                LogGroupName: `/aws/lambda/${functionData.name}`,
              },
              DependsOn: ['LambdaPermissionForSubscription'],
            },
          }
        );
      }
    });
  }

  afterDeployDeploy() {
    this.serverless.cli.log('Removing temporary logger function');
    let functionPath = this.getEnvFilePath();

    try {
      if (fs.existsSync(functionPath)) {
        if (fs.existsSync(path.join(functionPath, 'handler.js'))) {
          fs.unlinkSync(path.join(functionPath, 'handler.js'));
        }
        fs.rmdirSync(functionPath);
      }
    } catch (err) {
      throw new Error(err);
    }
  }
}

module.exports = PapertrailLogging;