-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Introduce runtime configuration values for Ruby (these do not yet power any automated checks) - Add sam and terraform examples (these are pretty much straight ports of the Python examples)
- Loading branch information
James Bunch
committed
Mar 28, 2024
1 parent
8200a25
commit 29b9b28
Showing
15 changed files
with
475 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Instrumented Ruby Lambda | ||
|
||
This is a "Hello, World" style Lambda function in Ruby, instrumented | ||
with the New Relic agent. | ||
|
||
This example is both instructive and a diagnostic tool: if you can | ||
deploy this Lambda function and see its events in NR One, you'll | ||
know that all the telemetry plumbing is connected correctly. | ||
|
||
## Building and deploying | ||
|
||
### Prerequisites | ||
|
||
- The [AWS CLI v2](https://aws.amazon.com/cli/) | ||
- [Docker](https://docs.docker.com/get-docker/) | ||
- The [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) | ||
|
||
Make sure you've run the `newrelic-lambda integrations install` command in your | ||
AWS Region, and included the `--enable-license-key-secret` flag. | ||
|
||
### deploy script | ||
|
||
From a command prompt, in this directory, run | ||
|
||
./deploy.sh <accountId> <region> | ||
|
||
where `<accountId>` is your New Relic account ID, and `<region>` | ||
is your AWS Region, like "us-west-2". | ||
|
||
This will package and deploy the CloudFormation stack for this example | ||
function. | ||
|
||
At this point, you can invoke the function. As provided, the example | ||
function doesn't pay attention to its invocation event. If everything | ||
has gone well, each invocation gets reported to New Relic, and its | ||
telemetry appears in NR One. | ||
|
||
## Code Structure | ||
|
||
Now is also a good time to look at the structure of the example code. | ||
|
||
### template.yaml | ||
|
||
This function is deployed using a SAM template, which is a CloudFormation | ||
template with some extra syntactic sugar for Lambda functions. In it, we | ||
tell CloudFormation where to find lambda function code, what layers to use, and | ||
what IAM policies to add to the Lambda function's execution role. We also set | ||
environment variables that are available to the handler function. | ||
|
||
### app.rb | ||
|
||
Lambda functions written in Ruby are involve a Ruby method at a mininum and can | ||
optionally be found within a class and/or module based namespace. The runtime | ||
loads the Ruby code, and then invokes the handler function method for each | ||
invocation event. New Relic publishes a Lambda Layer that wraps your handler | ||
function and initializes the New Relic agent, allowing us to collect telemetry. | ||
|
||
There are a couple examples here of how you might add custom events and attributes | ||
to the default telemetry. | ||
|
||
Since Ruby is a dynamic, interpreted language, the Agent can inject instrumentation | ||
into the various client libraries you might be using in your function. This happens | ||
once, during cold start, and provides rich, detailed instrumentation out of the box, | ||
with minimal developer effort. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#!/bin/bash | ||
|
||
accountId=$1 | ||
region=$2 | ||
|
||
echo "region set to ${region}" | ||
|
||
sam build --use-container | ||
|
||
bucket="newrelic-example-${region}-${accountId}" | ||
|
||
aws s3 mb --region "${region}" "s3://${bucket}" | ||
|
||
sam package --region "${region}" --s3-bucket "${bucket}" --output-template-file packaged.yaml | ||
|
||
aws cloudformation deploy \ | ||
--region "${region}" \ | ||
--template-file packaged.yaml \ | ||
--stack-name NewrelicExamplePython \ | ||
--capabilities CAPABILITY_IAM \ | ||
--parameter-overrides "NRAccountId=${accountId}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
{ | ||
"body": "{\"message\": \"hello world\"}", | ||
"resource": "/{proxy+}", | ||
"path": "/path/to/resource", | ||
"httpMethod": "POST", | ||
"isBase64Encoded": false, | ||
"queryStringParameters": { | ||
"foo": "bar" | ||
}, | ||
"pathParameters": { | ||
"proxy": "/path/to/resource" | ||
}, | ||
"stageVariables": { | ||
"baz": "qux" | ||
}, | ||
"headers": { | ||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | ||
"Accept-Encoding": "gzip, deflate, sdch", | ||
"Accept-Language": "en-US,en;q=0.8", | ||
"Cache-Control": "max-age=0", | ||
"CloudFront-Forwarded-Proto": "https", | ||
"CloudFront-Is-Desktop-Viewer": "true", | ||
"CloudFront-Is-Mobile-Viewer": "false", | ||
"CloudFront-Is-SmartTV-Viewer": "false", | ||
"CloudFront-Is-Tablet-Viewer": "false", | ||
"CloudFront-Viewer-Country": "US", | ||
"Host": "1234567890.execute-api.us-east-1.amazonaws.com", | ||
"Upgrade-Insecure-Requests": "1", | ||
"User-Agent": "Custom User Agent String", | ||
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", | ||
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", | ||
"X-Forwarded-For": "127.0.0.1, 127.0.0.2", | ||
"X-Forwarded-Port": "443", | ||
"X-Forwarded-Proto": "https" | ||
}, | ||
"requestContext": { | ||
"accountId": "123456789012", | ||
"resourceId": "123456", | ||
"stage": "prod", | ||
"requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", | ||
"requestTime": "09/Apr/2015:12:34:56 +0000", | ||
"requestTimeEpoch": 1428582896000, | ||
"identity": { | ||
"cognitoIdentityPoolId": null, | ||
"accountId": null, | ||
"cognitoIdentityId": null, | ||
"caller": null, | ||
"accessKey": null, | ||
"sourceIp": "127.0.0.1", | ||
"cognitoAuthenticationType": null, | ||
"cognitoAuthenticationProvider": null, | ||
"userArn": null, | ||
"userAgent": "Custom User Agent String", | ||
"user": null | ||
}, | ||
"path": "/prod/path/to/resource", | ||
"resourcePath": "/{proxy+}", | ||
"httpMethod": "POST", | ||
"apiId": "1234567890", | ||
"protocol": "HTTP/1.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'net/http' | ||
require 'uri' | ||
|
||
# Example Ruby Lambda function - module and/or class | ||
# namespacing is optional | ||
class App | ||
def self.lambda_handler(event:, context:) | ||
# instrumentation | ||
uri = URI('https://newrelic.com') | ||
3.times { Net::HTTP.get(uri) } | ||
|
||
# custom attributes | ||
# ::NewRelic::Agent.add_custom_attributes(server: 'less', current_time: Time.now.to_s) | ||
|
||
# As normal, anything you write to stdout ends up in CloudWatch | ||
puts 'Hello, world' | ||
puts "Event size: #{event.size}" | ||
puts "Context size: #{context.size}" | ||
|
||
{ statusCode: 200, body: JSON.generate('Hello from Ruby Lambda!') } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Transform: AWS::Serverless-2016-10-31 | ||
Description: And example of a simple instrumented Ruby Lambda | ||
|
||
Parameters: | ||
NRAccountId: | ||
Type: String | ||
Description: Your New Relic account ID; necessary for distributed tracing. | ||
AllowedPattern: '[0-9]+' | ||
|
||
Resources: | ||
NewRelicExample: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
# In this example, we're using the SAM CLI to package and deploy our lambda. SAM will transform this value during the publish step. | ||
CodeUri: newrelic_example_ruby/ | ||
Description: A simple Lambda, with New Relic telemetry | ||
FunctionName: newrelic-example-ruby | ||
# The handler for your function needs to be the one provided by the instrumentation layer, below. | ||
Handler: newrelic_lambda_wrapper.handler | ||
Runtime: ruby3.2 | ||
# Currently, we don't support Image based PackageType | ||
PackageType: Zip | ||
Environment: | ||
Variables: | ||
# For the instrumentation handler to invoke your real handler, we need this value | ||
NEW_RELIC_LAMBDA_HANDLER: app.lambda_handler | ||
NEW_RELIC_ACCOUNT_ID: !Sub ${NRAccountId} | ||
# NEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS: true | ||
# NEW_RELIC_EXTENSION_LOG_LEVEL: DEBUG | ||
Layers: | ||
# This layer includes the New Relic Lambda Extension, a sidecar process that sends telemetry, | ||
# as well as the New Relic Agent for Ruby, and a handler wrapper that makes integration easy. | ||
- !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:451483290750:layer:NewRelicRuby32:1 | ||
Policies: | ||
# This policy allows the lambda to know the value of the New Relic licence key. We need this so | ||
# that we can send telemetry back to New Relic | ||
- AWSSecretsManagerGetSecretValuePolicy: | ||
SecretArn: !ImportValue NewRelicLicenseKeySecret-NewRelic-LicenseKeySecretARN | ||
Logs: | ||
Type: AWS::Logs::LogGroup | ||
Properties: | ||
LogGroupName: {"Fn::Join": ["", ["/aws/lambda/", {"Ref": "NewRelicExample"}]]} | ||
# Lambda functions will auto-create their log group on first execution, but it retains logs forever, which can get expensive. | ||
RetentionInDays: 7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#!/usr/bin/env ruby | ||
# frozen_string_literal: true | ||
|
||
# Unit tests | ||
# To run, simply have Ruby v3.2+ and execute this script | ||
# $ ./handler_test.rb | ||
|
||
require 'bundler/inline' | ||
gemfile do | ||
source 'https://rubygems.org' | ||
gem 'minitest' | ||
end | ||
|
||
require 'json' | ||
require 'minitest/autorun' | ||
|
||
# Test the Lambda handler | ||
class LambdaHandlerTest < Minitest::Test | ||
EVENT_FILE = '../events/event.json' | ||
|
||
def setup | ||
require_relative '../newrelic_example_ruby/app' | ||
end | ||
|
||
def test_lambda_handler | ||
event = JSON.parse(File.read(EVENT_FILE)) | ||
result = App.lambda_handler(event:, context: {}) | ||
|
||
assert_kind_of Hash, result, 'Expected a hash result from the Lambda function' | ||
assert_equal 200, result[:statusCode], 'Expected function result to have a 200 status code' | ||
assert_match 'Hello', result[:body], "Expected function result message to match 'Hello'" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Instrumented Ruby Lambda | ||
|
||
This is a "Hello, World" style Lambda function in Ruby, instrumented | ||
with the New Relic agent. | ||
|
||
This example is both instructive and a diagnostic tool: if you can | ||
deploy this Lambda function and see its events in NR One, you'll | ||
know that all the telemetry plumbing is connected correctly. | ||
|
||
## Building and deploying | ||
|
||
### Prerequisites | ||
|
||
- The [AWS CLI v2](https://aws.amazon.com/cli/) | ||
- [Docker](https://docs.docker.com/get-docker/) | ||
- The [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) | ||
|
||
Make sure you've run the `newrelic-lambda integrations install` command in your | ||
AWS Region, and included the `--enable-license-key-secret` flag. | ||
|
||
### deploy script | ||
|
||
From a command prompt, in this directory, run | ||
|
||
./deploy.sh <accountId> <region> | ||
|
||
where `<accountId>` is your New Relic account ID, and `<region>` | ||
is your AWS Region, like "us-west-2". | ||
|
||
This will package and deploy the CloudFormation stack for this example | ||
function. | ||
|
||
At this point, you can invoke the function. As provided, the example | ||
function doesn't pay attention to its invocation event. If everything | ||
has gone well, each invocation gets reported to New Relic, and its | ||
telemetry appears in NR One. | ||
|
||
## Code Structure | ||
|
||
Now is also a good time to look at the structure of the example code. | ||
|
||
### main.tf | ||
|
||
This function is deployed using this Terraform script. In it, we | ||
tell Terraform where to find lambda function code, what layers to use, and | ||
what IAM policies to add to the Lambda function's execution role. We also set | ||
environment variables that are available to the handler function. | ||
|
||
### app.rb | ||
|
||
Lambda functions written in Ruby are involve a Ruby method at a mininum and can | ||
optionally be found within a class and/or module based namespace. The runtime | ||
loads the Ruby code, and then invokes the handler function method for each | ||
invocation event. New Relic publishes a Lambda Layer that wraps your handler | ||
function and initializes the New Relic agent, allowing us to collect telemetry. | ||
|
||
There are a couple examples here of how you might add custom events and attributes | ||
to the default telemetry. | ||
|
||
Since Ruby is a dynamic, interpreted language, the Agent can inject instrumentation | ||
into the various client libraries you might be using in your function. This happens | ||
once, during cold start, and provides rich, detailed instrumentation out of the box, | ||
with minimal developer effort. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'net/http' | ||
require 'uri' | ||
|
||
# Example Ruby Lambda function - module and/or class | ||
# namespacing is optional | ||
class App | ||
def self.lambda_handler(event:, context:) | ||
# instrumentation | ||
uri = URI('https://newrelic.com') | ||
3.times { Net::HTTP.get(uri) } | ||
|
||
# custom attributes | ||
# ::NewRelic::Agent.add_custom_attributes(server: 'less', current_time: Time.now.to_s) | ||
|
||
# As normal, anything you write to stdout ends up in CloudWatch | ||
puts 'Hello, world' | ||
puts "Event size: #{event.size}" | ||
puts "Context size: #{context.size}" | ||
|
||
{ statusCode: 200, body: JSON.generate('Hello from Ruby Lambda!') } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/bin/bash | ||
|
||
accountId=$1 | ||
export TF_VAR_newrelic_account_id=$accountId | ||
|
||
region=$2 | ||
export TF_VAR_aws_region=$region | ||
echo "region set to ${region}" | ||
|
||
rm -f function.zip | ||
zip -rq function.zip app.rb | ||
|
||
terraform validate . | ||
terraform apply |
Oops, something went wrong.