class Instana::Serverless

@since 1.198.0

Public Class Methods

new(agent: ::Instana.agent, tracer: ::Instana.tracer, logger: ::Instana.logger) click to toggle source
# File lib/instana/serverless.rb, line 18
def initialize(agent: ::Instana.agent, tracer: ::Instana.tracer, logger: ::Instana.logger)
  @agent = agent
  @tracer = tracer
  @logger = logger
end

Public Instance Methods

wrap_aws(event, context, &block) click to toggle source
# File lib/instana/serverless.rb, line 24
def wrap_aws(event, context, &block)
  Thread.current[:instana_function_arn] = [context.invoked_function_arn, context.function_version].join(':')
  trigger, event_tags, span_context = trigger_from_event(event, context)

  tags = {
    lambda: {
      arn: context.invoked_function_arn,
      functionName: context.function_name,
      functionVersion: context.function_version,
      runtime: 'ruby',
      trigger: trigger
    }
  }

  if event_tags.key?(:http)
    tags = tags.merge(event_tags)
  else
    tags[:lambda] = tags[:lambda].merge(event_tags)
  end

  @tracer.start_or_continue_trace(:'aws.lambda.entry', tags, span_context, &block)
ensure
  begin
    @agent.send_bundle
  rescue StandardError => e
    @logger.error(e.message)
  end
  Thread.current[:instana_function_arn] = nil
end

Private Instance Methods

context_from_lambda_context(context) click to toggle source
# File lib/instana/serverless.rb, line 86
def context_from_lambda_context(context)
  return {} unless context.client_context

  begin
    context = JSON.parse(Base64.decode64(context.client_context))

    {
      trace_id: context['X-INSTANA-T'],
      span_id: context['X-INSTANA-S'],
      level: Integer(context['X-INSTANA-L'])
    }
  rescue TypeError, JSON::ParserError, NoMethodError => _e
    {}
  end
end
decode_cloudwatch_events(event) click to toggle source
# File lib/instana/serverless.rb, line 112
def decode_cloudwatch_events(event)
  {
    events: {
      id: event['id'],
      resources: event['resources']
    }
  }
end
decode_cloudwatch_logs(event) click to toggle source
# File lib/instana/serverless.rb, line 121
def decode_cloudwatch_logs(event)
  logs = begin
    payload = JSON.parse(Zlib::Inflate.inflate(Base64.decode64(event['awslogs']['data'])))

    {
      group: payload['logGroup'],
      stream: payload['logStream']
    }
  rescue StandardError => e
    {
      decodingError: e.message
    }
  end

  {logs: logs}
end
decode_s3(event) click to toggle source
# File lib/instana/serverless.rb, line 138
def decode_s3(event)
  span_events = event['Records'].map do |record|
    {
      name: record['eventName'],
      bucket: record['s3'] && record['s3']['bucket'] ? record['s3']['bucket']['name'] : nil,
      object: record['s3'] && record['s3']['object'] ? record['s3']['object']['key'] : nil
    }
  end

  {events: span_events}
end
decode_sqs(event) click to toggle source
# File lib/instana/serverless.rb, line 150
def decode_sqs(event)
  span_events = event['Records'].map do |record|
    {
      queue: record['eventSourceARN']
    }
  end

  {messages: span_events}
end
event_to_rack(event) click to toggle source
# File lib/instana/serverless.rb, line 102
def event_to_rack(event)
  event['headers']
    .transform_keys { |k| "HTTP_#{k.gsub('-', '_').upcase}" }
    .merge(
      'QUERY_STRING' => URI.encode_www_form(event['queryStringParameters'] || {}),
      'PATH_INFO' => event['path'],
      'REQUEST_METHOD' => event['httpMethod']
    )
end
trigger_from_event(event, context) click to toggle source
# File lib/instana/serverless.rb, line 56
def trigger_from_event(event, context) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  case event
  when ->(e) { defined?(::Instana::InstrumentedRequest) && e.is_a?(Hash) && e.key?('requestContext') && e['requestContext'].key?('elb') }
    request = InstrumentedRequest.new(event_to_rack(event))
    ['aws:application.load.balancer', {http: request.request_tags}, request.incoming_context]
  when ->(e) { defined?(::Instana::InstrumentedRequest) && e.is_a?(Hash) && e.key?('httpMethod') && e.key?('path') && e.key?('headers') }
    request = InstrumentedRequest.new(event_to_rack(event))
    ['aws:api.gateway', {http: request.request_tags}, request.incoming_context]
  when ->(e) { e.is_a?(Hash) && e['source'] == 'aws.events' && e['detail-type'] == 'Scheduled Event' }
    tags = decode_cloudwatch_events(event)
    ['aws:cloudwatch.events', {cw: tags}, {}]
  when ->(e) { e.is_a?(Hash) && e.key?('awslogs') }
    tags = decode_cloudwatch_logs(event)
    ['aws:cloudwatch.logs', {cw: tags}, {}]
  when ->(e) { e.is_a?(Hash) && e.key?('Records') && e['Records'].is_a?(Array) && e['Records'].first && e['Records'].first['source'] == 'aws:s3' }
    tags = decode_s3(event)
    ['aws:s3', {s3: tags}, {}]
  when ->(e) { e.is_a?(Hash) && e.key?('Records') && e['Records'].is_a?(Array) && e['Records'].first && e['Records'].first['source'] == 'aws:sqs' }
    tags = decode_sqs(event)
    ['aws:sqs', {sqs: tags}, {}]
  else
    ctx = context_from_lambda_context(context)
    if ctx.empty?
      ['aws:api.gateway.noproxy', {}, {}]
    else
      ['aws.lambda.invoke', {}, ctx]
    end
  end
end