From 364b620986a48113a0ba22436df248981024a6b7 Mon Sep 17 00:00:00 2001 From: Emilio Garcia Date: Wed, 25 Jan 2023 14:18:19 -0500 Subject: [PATCH] telemetry api extension beta 1 milestone --- .gitignore | 3 +- AwsLambdaExtension/Makefile | 17 - AwsLambdaExtension/README.md | 16 - AwsLambdaExtension/agentTelemetry/config.go | 100 --- AwsLambdaExtension/build-deploy.sh | 14 - AwsLambdaExtension/go.mod | 21 - AwsLambdaExtension/go.sum | 156 ---- AwsLambdaExtension/main.go | 110 --- Makefile | 31 +- README.md | 136 +--- .../batch.go | 0 .../batch_test.go | 14 +- .../client.go | 17 +- .../client_test.go | 10 +- agentTelemetry/close.go | 13 + .../dispatcher.go | 9 +- .../agentTelemetry => agentTelemetry}/ipc.go | 3 +- .../ipc_test.go | 0 .../payload.go | 0 .../payload_test.go | 2 +- .../request.go | 2 +- .../request_test.go | 0 agentTelemetry/util_test.go | 17 + checks/agent_version_check.go | 56 -- checks/agent_version_check_test.go | 45 -- checks/handler_check.go | 59 -- checks/handler_check_test.go | 86 -- checks/runtime_check.go | 62 -- checks/runtime_check_test.go | 49 -- checks/runtime_config.go | 58 -- checks/sanity_check.go | 35 - checks/sanity_check_test.go | 62 -- checks/startup_check.go | 57 -- checks/startup_check_test.go | 80 -- checks/vendor_check.go | 21 - checks/vendor_check_test.go | 34 - config/config.go | 198 ++--- config/config_test.go | 191 +++-- credentials/credentials.go | 99 --- credentials/credentials_test.go | 98 --- examples/sam/go/go.mod | 19 + examples/sam/go/main.go | 1 + .../extensionApi => extensionApi}/client.go | 0 go.mod | 30 +- go.sum | 153 +--- lambda/extension/api/api.go | 116 --- lambda/extension/api/api_test.go | 26 - lambda/extension/client/client.go | 274 ------- lambda/extension/client/client_test.go | 448 ----------- lambda/logserver/logserver.go | 221 ----- lambda/logserver/logserver_test.go | 205 ----- local-build.sh | 52 -- main.go | 391 ++------- main_test.go | 753 ------------------ telemetry/batch.go | 178 ----- telemetry/batch_test.go | 174 ---- telemetry/client.go | 307 ------- telemetry/client_test.go | 176 ---- telemetry/ipc.go | 49 -- telemetry/ipc_test.go | 14 - telemetry/payload.go | 123 --- telemetry/payload_test.go | 89 --- telemetry/request.go | 164 ---- telemetry/request_test.go | 22 - .../telemetryApi => telemetryApi}/client.go | 0 .../dispatcher.go | 5 +- .../telemetryApi => telemetryApi}/listener.go | 0 .../send_to_new_relic.go | 0 68 files changed, 448 insertions(+), 5523 deletions(-) delete mode 100644 AwsLambdaExtension/Makefile delete mode 100644 AwsLambdaExtension/README.md delete mode 100644 AwsLambdaExtension/agentTelemetry/config.go delete mode 100755 AwsLambdaExtension/build-deploy.sh delete mode 100644 AwsLambdaExtension/go.mod delete mode 100644 AwsLambdaExtension/go.sum delete mode 100644 AwsLambdaExtension/main.go rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/batch.go (100%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/batch_test.go (93%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/client.go (91%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/client_test.go (97%) create mode 100644 agentTelemetry/close.go rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/dispatcher.go (88%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/ipc.go (92%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/ipc_test.go (100%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/payload.go (100%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/payload_test.go (99%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/request.go (98%) rename {AwsLambdaExtension/agentTelemetry => agentTelemetry}/request_test.go (100%) create mode 100644 agentTelemetry/util_test.go delete mode 100644 checks/agent_version_check.go delete mode 100644 checks/agent_version_check_test.go delete mode 100644 checks/handler_check.go delete mode 100644 checks/handler_check_test.go delete mode 100644 checks/runtime_check.go delete mode 100644 checks/runtime_check_test.go delete mode 100644 checks/runtime_config.go delete mode 100644 checks/sanity_check.go delete mode 100644 checks/sanity_check_test.go delete mode 100644 checks/startup_check.go delete mode 100644 checks/startup_check_test.go delete mode 100644 checks/vendor_check.go delete mode 100644 checks/vendor_check_test.go delete mode 100644 credentials/credentials.go delete mode 100644 credentials/credentials_test.go create mode 100644 examples/sam/go/go.mod rename {AwsLambdaExtension/extensionApi => extensionApi}/client.go (100%) delete mode 100644 lambda/extension/api/api.go delete mode 100644 lambda/extension/api/api_test.go delete mode 100644 lambda/extension/client/client.go delete mode 100644 lambda/extension/client/client_test.go delete mode 100644 lambda/logserver/logserver.go delete mode 100644 lambda/logserver/logserver_test.go delete mode 100755 local-build.sh delete mode 100644 main_test.go delete mode 100644 telemetry/batch.go delete mode 100644 telemetry/batch_test.go delete mode 100644 telemetry/client.go delete mode 100644 telemetry/client_test.go delete mode 100644 telemetry/ipc.go delete mode 100644 telemetry/ipc_test.go delete mode 100644 telemetry/payload.go delete mode 100644 telemetry/payload_test.go delete mode 100644 telemetry/request.go delete mode 100644 telemetry/request_test.go rename {AwsLambdaExtension/telemetryApi => telemetryApi}/client.go (100%) rename {AwsLambdaExtension/telemetryApi => telemetryApi}/dispatcher.go (87%) rename {AwsLambdaExtension/telemetryApi => telemetryApi}/listener.go (100%) rename {AwsLambdaExtension/telemetryApi => telemetryApi}/send_to_new_relic.go (100%) diff --git a/.gitignore b/.gitignore index 744e13e..a59b9b0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ extensions/ response.out .aws-sam/ coverage.txt -preview-extensions-ggqizro707 +extenstion.zip +.DS_Store diff --git a/AwsLambdaExtension/Makefile b/AwsLambdaExtension/Makefile deleted file mode 100644 index 2435198..0000000 --- a/AwsLambdaExtension/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# ----- Target used with SAM (Serverless Application Model) ----- -build-AwsLambdaExtension: - echo :build-AwsLambdaExtension - GOOS=linux GOARCH=amd64 go build -o $(ARTIFACTS_DIR)/extensions/AwsLambdaExtension main.go - chmod +x $(ARTIFACTS_DIR)/extensions/AwsLambdaExtension - -# ----- Target illustrating manual steps required to create the extension layer ----- -buildAndDeployExtensionLayer: - echo :buildAndDeployExtensionLayer - rm -rf bin - GOOS=linux GOARCH=amd64 go build -o bin/extensions/AwsLambdaExtension main.go - chmod +x bin/extensions/AwsLambdaExtension - cd bin && zip -r extension.zip extensions - - aws lambda publish-layer-version \ - --layer-name "AwsLambdaExtension" \ - --zip-file "fileb://bin/extension.zip" diff --git a/AwsLambdaExtension/README.md b/AwsLambdaExtension/README.md deleted file mode 100644 index e97b3f4..0000000 --- a/AwsLambdaExtension/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# New Relic Telemetry API Extension - -The New Relic Telemetry API Extension collects telemetry data from both Lambda Telemetry API and New Relic Agents and sends it to New Relic. -The following environment variables can be used to configre it: - -| Environment Variable | Required | Description | -| --- | --- | --- | -| NEW_RELIC_ACCOUNT_ID | **always** | The account ID of the New Relic account you want to send data to | -| NEW_RELIC_LICENSE_KEY | *conditional* | A plaintext New Relic license key. Either this or the value retrieved from NEW_RELIC_LICENSE_KEY_SECRET must contain a valid New Relic license key or the application will exit. If a plaintext license key is provided, it will override the license key retrieved from an AWS Secret. | -| NEW_RELIC_LICENSE_KEY_SECRET | *conditional* | The name of an AWS Secrets Manager Secret containing a New Relic license key. If no plaintext license key is provided, the value of this variable must be set to the name of an AWS Secret containing a valid New Relic license key. | -| NEW_RELIC_EXTENSION_AGENT_DATA_COLLECTION_ENABLED | optional | Setting this to "false" will prevent the extension from collecting data from New Relic Agents. This will not prevent the agent from running. | -| NEW_RELIC_EXTENSION_AGENT_DATA_BATCH_SIZE | optional | The number of invocations to store before sending Agent Data to New Relic. If your lamba function gets invoked at a high frequency, increasing this number will improve the performance of the extension and avoid dropped data and improve performance. Default: 1 | -| NEW_RELIC_EXTENSION_TELEMETRY_API_BATCH_SIZE | optional | The number of Telemetry API events and logs to batch before sending them to New Relic. If your application invokes frequently, increase this number to avoid data getting dropped and to improve performance. Default: 1 | -| NEW_RELIC_EXTENSION_DATA_COLLECTION_TIMEOUT | optional | A valid time.Duration string for how long the extension should wait to attempt to send agent data to New Relic in the event of a timeout/retry loop scenario. Example: 1s, 1500ms; Default: 10s | -| NEW_RELIC_EXTENSION_COLLECTOR_OVERRIDE | optional | An override for the New Relic collection endpoint you want to send data to. By default, this will be detected based on the region of your New Relic license key. | -| NEW_RELIC_EXTENSION_LOG_LEVEL | optional | The log level of the New Relic Telemetry API Extension. For more verbose logs, set to "debug". For error logs only, set to "error". | diff --git a/AwsLambdaExtension/agentTelemetry/config.go b/AwsLambdaExtension/agentTelemetry/config.go deleted file mode 100644 index bd9ddb1..0000000 --- a/AwsLambdaExtension/agentTelemetry/config.go +++ /dev/null @@ -1,100 +0,0 @@ -package agentTelemetry - -import ( - "os" - "path" - "strconv" - "strings" - "time" - - log "github.com/sirupsen/logrus" -) - -type Config struct { - DataCollectionTimeout time.Duration - LogLevel log.Level - AgentTelemetryBatchSize int - TelemetryAPIBatchSize int64 - AgentTelemetryRegion string - LicenseKey string - AccountID string - ExtensionName string - CollectAgentData bool -} - -const ( - defaultCollectionTimeout = 10 * time.Second - defaultAgentTelemtryBatchSize = 1 - defaultTelemtryAPIBatchSize = 1 - - // Optional Environment variables that can be used to talior the user experience to your needs - agentDataEnabledVariable = "NEW_RELIC_EXTENSION_AGENT_DATA_COLLECTION_ENABLED" - agentDataBatchSizeVariable = "NEW_RELIC_EXTENSION_AGENT_DATA_BATCH_SIZE" - clientRetryTimeoutVariable = "NEW_RELIC_EXTENSION_DATA_COLLECTION_TIMEOUT" - agentTelemetryRegionVariable = "NEW_RELIC_EXTENSION_COLLECTOR_OVERRIDE" - extensionLogLevelVariable = "NEW_RELIC_EXTENSION_LOG_LEVEL" - telAPIBatchSizeVariable = "NEW_RELIC_EXTENSION_TELEMETRY_API_BATCH_SIZE" - NrAccountIDVariable = "NEW_RELIC_ACCOUNT_ID" -) - -func GetConfig() Config { - // Set Defaults - conf := Config{ - CollectAgentData: true, - DataCollectionTimeout: defaultCollectionTimeout, - AgentTelemetryBatchSize: defaultAgentTelemtryBatchSize, - TelemetryAPIBatchSize: defaultTelemtryAPIBatchSize, - LogLevel: log.InfoLevel, - ExtensionName: path.Base(os.Args[0]), - } - - conf.AccountID = os.Getenv("NEW_RELIC_ACCOUNT_ID") - conf.AgentTelemetryRegion = os.Getenv(agentTelemetryRegionVariable) - - // Enable or disable collection of agent telemetry data - enableAgent := os.Getenv(agentDataEnabledVariable) - if strings.ToLower(enableAgent) == "false" { - conf.CollectAgentData = false - } - // How long agent will try to resend - clientTimeout := os.Getenv(clientRetryTimeoutVariable) - if clientTimeout != "" { - dur, err := time.ParseDuration(clientTimeout) - if err != nil { - environmentVariableError(clientRetryTimeoutVariable, err) - } - if dur > time.Millisecond*400 { - conf.DataCollectionTimeout = dur - } - } - - telApiBatchSize, err := strconv.ParseInt(os.Getenv(telAPIBatchSizeVariable), 0, 16) - if err != nil { - environmentVariableError(telAPIBatchSizeVariable, err) - } else { - conf.TelemetryAPIBatchSize = telApiBatchSize - } - - buffer := os.Getenv(agentDataBatchSizeVariable) - if buffer != "" { - val, err := strconv.Atoi(buffer) - if err != nil { - environmentVariableError(agentDataBatchSizeVariable, err) - } else { - conf.AgentTelemetryBatchSize = val - } - } - - logLevel := strings.ToLower(os.Getenv(extensionLogLevelVariable)) - if logLevel == "debug" { - conf.LogLevel = log.DebugLevel - } else if logLevel == "error" { - conf.LogLevel = log.ErrorLevel - } - - return conf -} - -func environmentVariableError(variable string, err error) { - l.Warnf("[config] error parsing environment variable \"%s\": %v", variable, err) -} diff --git a/AwsLambdaExtension/build-deploy.sh b/AwsLambdaExtension/build-deploy.sh deleted file mode 100755 index 33464d4..0000000 --- a/AwsLambdaExtension/build-deploy.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -if [ ! -d "./extensions" ]; then - mkdir ./extensions -fi - -GOOS=linux GOARCH=arm64 go build -o ./extensions/AwsLambdaExtension main.go -chmod +x ./extensions/AwsLambdaExtension - -#aws lambda publish-layer-version \ -# --layer-name "AwsLambdaExtension" \ -# --description "New Relic Telemetry API Extension" \ -# --compatible-architectures "arm64" \ -# --zip-file "fileb://extension.zip" diff --git a/AwsLambdaExtension/go.mod b/AwsLambdaExtension/go.mod deleted file mode 100644 index 17984a4..0000000 --- a/AwsLambdaExtension/go.mod +++ /dev/null @@ -1,21 +0,0 @@ -module newrelic-lambda-extension/AwsLambdaExtension - -go 1.18 - -require ( - github.com/aws/aws-sdk-go v1.44.176 - github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 - github.com/google/uuid v1.3.0 - github.com/newrelic/newrelic-lambda-extension v1.2.4 - github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.0 - github.com/stretchr/testify v1.7.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.1.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect -) diff --git a/AwsLambdaExtension/go.sum b/AwsLambdaExtension/go.sum deleted file mode 100644 index f730ba4..0000000 --- a/AwsLambdaExtension/go.sum +++ /dev/null @@ -1,156 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.11.0/go.mod h1:Rr2SMTLeSMKgD45uep9V/NP8tnbCcySgu04cx0k/6cw= -github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/aws/aws-sdk-go v1.34.21/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.44.176 h1:mxcfI3IWHemX+5fEKt5uqIS/hdbaR7qzGfJYo5UyjJE= -github.com/aws/aws-sdk-go v1.44.176/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= -github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/newrelic/go-agent/v3 v3.4.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= -github.com/newrelic/go-agent/v3 v3.9.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= -github.com/newrelic/go-agent/v3/integrations/nrlambda v1.2.0/go.mod h1:IZemD4LiJXNBAV652z2x3Awa1Z9Rlx7hEO4OUyqnr+U= -github.com/newrelic/newrelic-lambda-extension v1.2.4 h1:dbaz6pF1FZLP9KVvJ1o+u8vlFkWwt4L9wQFnF4K42Ns= -github.com/newrelic/newrelic-lambda-extension v1.2.4/go.mod h1:EElyNzAOfF8Xj57Plw3v4m8l0kHHEHTUM9todurgQes= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200910191746-8ad3c7ee2cd1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/AwsLambdaExtension/main.go b/AwsLambdaExtension/main.go deleted file mode 100644 index 06580d5..0000000 --- a/AwsLambdaExtension/main.go +++ /dev/null @@ -1,110 +0,0 @@ -/** - -Notes: -- Because of the asynchronous nature of the system, it is possible that telemetry for one invoke will be - processed during the next invoke slice. Likewise, it is possible that telemetry for the last invoke will - be processed during the SHUTDOWN event. - -*/ - -package main - -import ( - "context" - "newrelic-lambda-extension/AwsLambdaExtension/agentTelemetry" - "newrelic-lambda-extension/AwsLambdaExtension/extensionApi" - "newrelic-lambda-extension/AwsLambdaExtension/telemetryApi" - "os" - "os/signal" - "syscall" - - log "github.com/sirupsen/logrus" -) - -var ( - l = log.WithFields(log.Fields{"pkg": "main"}) -) - -func main() { - // Handle User Configured Settings - conf := agentTelemetry.GetConfig() - log.SetLevel(conf.LogLevel) - - l.Info("[main] Starting the New Relic Telemetry API extension") - ctx, cancel := context.WithCancel(context.Background()) - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) - go func() { - s := <-sigs - cancel() - l.Info("[main] Received", s) - l.Info("[main] Exiting") - }() - - // Step 1 - Register the extension with Extensions API - l.Debug("[main] Registering extension") - extensionApiClient := extensionApi.NewClient(conf.LogLevel) - extensionId, err := extensionApiClient.Register(ctx, conf.ExtensionName) - if err != nil { - l.Fatal(err) - } - l.Debug("[main] Registation success with extensionId", extensionId) - - // Step 2 - Start the local http listener which will receive data from Telemetry API - l.Debug("[main] Starting the Telemetry listener") - telemetryListener := telemetryApi.NewTelemetryApiListener() - telemetryListenerUri, err := telemetryListener.Start() - if err != nil { - l.Fatal(err) - } - - // Step 3 - Subscribe the listener to Telemetry API - l.Debug("[main] Subscribing to the Telemetry API") - telemetryApiClient := telemetryApi.NewClient(conf.LogLevel) - _, err = telemetryApiClient.Subscribe(ctx, extensionId, telemetryListenerUri) - if err != nil { - l.Fatal(err) - } - l.Debug("[main] Subscription success") - dispatcher := telemetryApi.NewDispatcher(extensionApiClient.GetFunctionName(), &conf, ctx, conf.TelemetryAPIBatchSize) - - // Set up new relic agent telemetry client - agentDispatcher := agentTelemetry.NewDispatcher(conf) - - l.Info("[main] New Relic Telemetry API Extension succesfully registered and subscribed") - - // Will block until invoke or shutdown event is received or cancelled via the context. - for { - select { - case <-ctx.Done(): - return - default: - l.Debug("[main] Waiting for next event...") - - // This is a blocking action - res, err := extensionApiClient.NextEvent(ctx) - if err != nil { - l.Errorf("[main] Exiting. Error: %v", err) - return - } - l.Debugf("[main] Received event %+v", res) - - // Dispatching log events from previous invocations - agentDispatcher.AddRequest(res) - dispatcher.Dispatch(ctx, telemetryListener.LogEventsQueue, false) - agentDispatcher.Dispatch(ctx, res, false) - - if res.EventType == extensionApi.Invoke { - l.Debug("[handleInvoke]") - // we no longer care about this but keep it here just in case - } else if res.EventType == extensionApi.Shutdown { - // force dispatch all remaining telemetry, handle shutdown - l.Debug("[handleShutdown]") - dispatcher.Dispatch(ctx, telemetryListener.LogEventsQueue, true) - agentDispatcher.Dispatch(ctx, res, true) - l.Info("[main] New Relic Telemetry API Extension successfully shut down") - return - } - } - } -} diff --git a/Makefile b/Makefile index 5650f3d..25cf8d8 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,29 @@ -build: clean - go build -o ./extensions/newrelic-lambda-extension +build-arm: clean + mkdir extensions + env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o ./extensions/newrelic-lambda-extension + chmod +x ./extensions/newrelic-lambda-extension + zip -r ./extensions/extension.zip ./extensions/ + +build-amd64: clean + mkdir extensions + env GOARCH=arm64 GOOS=linux go build -ldflags="-s -w" -o ./extensions/newrelic-lambda-extension + chmod +x ./extensions/newrelic-lambda-extension + zip -r ./extensions/extension.zip ./extensions/ clean: rm -rf extensions - rm -f preview-extensions-ggqizro707 - rm -f /tmp/newrelic-lambda-extension.x86_64.zip - rm -f /tmp/newrelic-lambda-extension.arm64.zip -dist-x86_64: clean +ci-build-x86_64: clean env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -o ./extensions/newrelic-lambda-extension - touch preview-extensions-ggqizro707 -dist-arm64: clean +ci-build-arm64: clean env GOARCH=arm64 GOOS=linux go build -ldflags="-s -w" -o ./extensions/newrelic-lambda-extension - touch preview-extensions-ggqizro707 -zip-x86_64: dist-x86_64 - zip -r /tmp/newrelic-lambda-extension.x86_64.zip preview-extensions-ggqizro707 extensions +zip-x86_64: ci-build-x86_64 + zip -r /tmp/newrelic-lambda-extension.x86_64.zip extensions zip-arm64: dist-arm64 - zip -r /tmp/newrelic-lambda-extension.arm64.zip preview-extensions-ggqizro707 extensions + zip -r /tmp/newrelic-lambda-extension.arm64.zip extensions test: @echo "Normal tests" @@ -29,6 +33,3 @@ test: coverage: ./coverage.sh - -publish: zip-x86_64 - aws lambda publish-layer-version --no-cli-pager --layer-name newrelic-lambda-extension-x86_64 --zip-file fileb:///tmp/newrelic-lambda-extension.x86_64.zip diff --git a/README.md b/README.md index cf3b7ff..697980b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,18 @@ -[![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project) - -# newrelic-lambda-extension [![Build Status](https://circleci.com/gh/newrelic/newrelic-lambda-extension.svg?style=svg)](https://circleci.com/gh/newrelic/newrelic-lambda-extension) [![Coverage](https://codecov.io/gh/newrelic/newrelic-lambda-extension/branch/main/graph/badge.svg?token=T73UEDVA5K)](https://codecov.io/gh/newrelic/newrelic-lambda-extension) - -An AWS Lambda extension to collect, enhance, and transport telemetry data from your AWS Lambda functions to New Relic without requiring an external transport such as CloudWatch Logs or Kinesis. - -This lightweight AWS Lambda Extension runs alongside your AWS Lambda functions and automatically handles the collection and transport of telemetry data from -supported New Relic serverless agents. +# New Relic Telemetry API Extension + +The New Relic Telemetry API Extension collects telemetry data from both AWS Lambda Telemetry API and New Relic Agents and sends it to New Relic. Please note that Telemetry API collects all logs for an invocation, so when using an agent do not forward logs or they will be duplicated. If you are using a New Relic Agent, be sure to follow its [lambda/serverless installation documentation](https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/enable-lambda-monitoring/instrument-example/). The use of this layer does not require a New Relic agent to be present, and can be configured to send telemetry without one. The following environment variables are available to configure this extension: + +| Environment Variable | Required | Description | +| --- | --- | --- | +| NEW_RELIC_ACCOUNT_ID | **always** | The account ID of the New Relic account you want to send data to | +| NEW_RELIC_LICENSE_KEY | *conditional* | A plaintext New Relic license key. Either this or the value retrieved from NEW_RELIC_LICENSE_KEY_SECRET must contain a valid New Relic license key or the application will exit. If a plaintext license key is provided, it will override the license key retrieved from an AWS Secret. | +| NEW_RELIC_LICENSE_KEY_SECRET | *conditional* | The name of an AWS Secrets Manager Secret containing a New Relic license key. If no plaintext license key is provided, the value of this variable must be set to the name of an AWS Secret containing a valid New Relic license key. | +| NEW_RELIC_EXTENSION_AGENT_DATA_COLLECTION_ENABLED | optional | Setting this to "false" will prevent the extension from collecting data from New Relic Agents. This will not prevent the agent from running. | +| NEW_RELIC_EXTENSION_AGENT_DATA_BATCH_SIZE | optional | The number of invocations to store before sending Agent Data to New Relic. If your lamba function gets invoked at a high frequency, increasing this number will improve the performance of the extension and avoid dropped data and improve performance. Default: 1 | +| NEW_RELIC_EXTENSION_TELEMETRY_API_BATCH_SIZE | optional | The number of Telemetry API events and logs to batch before sending them to New Relic. If your application invokes frequently, increase this number to avoid data getting dropped and to improve performance. Default: 1 | +| NEW_RELIC_EXTENSION_DATA_COLLECTION_TIMEOUT | optional | A valid time.Duration string for how long the extension should wait to attempt to send agent data to New Relic in the event of a timeout/retry loop scenario. Example: 1s, 1500ms; Default: 10s | +| NEW_RELIC_EXTENSION_COLLECTOR_OVERRIDE | optional | An override for the New Relic collection endpoint you want to send data to. By default, this will be detected based on the region of your New Relic license key. | +| NEW_RELIC_EXTENSION_LOG_LEVEL | optional | The log level of the New Relic Telemetry API Extension. For more verbose logs, set to "debug". For error logs only, set to "error". | ## Installation @@ -16,118 +23,9 @@ Lambda function. The current layer ARN can be found [here][3]. **Note:** This extension is included with all New Relic AWS Lambda layers going forward. -You'll also need to make the New Relic license key available to the extension. Use the [New Relic Lambda CLI][4] -to install the managed secret, and then add the permission for the secret to your Lambda execution role. - -[4]: https://github.com/newrelic/newrelic-lambda-cli - - newrelic-lambda integrations install \ - --nr-account-id \ - --nr-api-key \ - --linked-account-name \ - --enable-license-key-secret - -Each of the example functions in the `examples` directory has the appropriate license key secret permission. - -After deploying your AWS Lambda function with one of the layer ARNs from the -link above you should begin seeing telemetry data in New Relic. - -See below for details on supported New Relic agents. - -## Supported Configurations - -AWS's [Extension API supports](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html) only a subset -of all their runtimes. Notably absent as of this writing are Node JS before 10, Python before 3.7, Go (all versions), -Dotnet before 3.1, and the older "java8" runtime, though "java8.al2" is supported. - -For Go lambdas, we suggest using "provided" or "provided.al2". The Go example's deploy script contains compiler flags -that produce a suitable self-hosting Go executable. See the [Custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) -docs for more details on this feature. - -All of our layers include the extension, and the latest Agent version for the Layer's runtime. The latest -layer version ARNs for your runtime and region are available [here](https://layers.newrelic-external.com/). The -`NewRelicLambdaExtension` layer is suitable for Go, Java and Dotnet. - -## Building - -Use the included `Makefile` to compile the extension. - -```sh -make dist -``` - -This creates the extension binary in `./extensions/newrelic-lambda-extension`. The binary is compiled for Amazon Linux, which is likely different from the platform you're working on. - -## Deploying - -To publish the extension to your AWS account, run the following command: - -```sh - make publish -``` - -This packages the extension, and publishes a new layer version in your AWS account. Be sure that the AWS CLI is configured correctly. You can use the usual [AWS CLI environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) to control the account and region for the CLI. - -## Startup Checks - -This Lambda Extension will perform a series of checks on initialization. Should any of -these checks fail, the extension wil attempt to output troubleshooting recommendations to both -CloudWatch Logs and New Relic Logs. If you have any issues using this extension, be sure -to check your logs for messages starting with `Startup check failed:` for -troubleshooting recommendations. - -Startup checks include: - -* New Relic agent version checks -* Lambda handler configuration checks -* Lambda environment variable checks -* Vendored New Relic agent checks - -## Disabling Extension - -The New Relic Lambda Extension is enabled by default. To disable it, after adding or -updating the Lambda layer, set the `NEW_RELIC_LAMBDA_EXTENSION_ENABLED` environment -variable to `false`. - -## Testing - -To test locally, acquire the AWS extension test harness first. Then: - ->TODO: Link to the AWS SDK that has the test harness, assuming it gets published. - -1. (Optional) Use the `newrelic-lambda` CLI to create the license key managed secret in your AWS account and region. -2. Build the docker container for sample function code. Give it the tag `lambda_ext`. - - Be sure to include your lambda function in the container. -3. Start up your container. - - - Using AWS Secret Manager - - export AWS_ACCESS_KEY_ID=$(aws configure get aws_access_key_id --profile default) - export AWS_SECRET_ACCESS_KEY=$(aws configure get aws_secret_access_key --profile default) - export AWS_SESSION_TOKEN=$(aws configure get aws_session_token --profile default) - - docker run --rm -v $(pwd)/extensions:/opt/extensions -p 9001:8080 \ - -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN \ - lambda_ext:latest \ - -h function.handler -c '{}' -t 60000 - - - Or, setting the license key directly - - docker run --rm \ - -v $(pwd)/extensions:/opt/extensions \ - -p 9001:8080 \ - lambda_ext:latest \ - -h function.handler -c '{"NEW_RELIC_LICENSE_KEY": "your-license-key-here"}' -t 60000 - -4. To invoke the sample lambda run: - - curl -XPOST 'http://localhost:9001/2015-03-31/functions/function.handler/invocations' \ - -d 'invoke-payload' - -5. Finally, you can exercise the container shutdown lifecycle event with: +## Building Locally - curl -XPOST 'http://localhost:9001/test/shutdown' \ - -d '{"timeoutMs": 5000 }' +Use the `make build-arm64` or `make build-amd64` commands to build local version of this binary. Make sure that you have docker enabled and conifgured properly. This runs the build command in a linux docker container to prevent errors caused by MacOS filesystem artifacts from occuring. ## Support diff --git a/AwsLambdaExtension/agentTelemetry/batch.go b/agentTelemetry/batch.go similarity index 100% rename from AwsLambdaExtension/agentTelemetry/batch.go rename to agentTelemetry/batch.go diff --git a/AwsLambdaExtension/agentTelemetry/batch_test.go b/agentTelemetry/batch_test.go similarity index 93% rename from AwsLambdaExtension/agentTelemetry/batch_test.go rename to agentTelemetry/batch_test.go index 5acfdd0..a7b9639 100644 --- a/AwsLambdaExtension/agentTelemetry/batch_test.go +++ b/agentTelemetry/batch_test.go @@ -18,7 +18,7 @@ const ( testNoSuchRequestId = "test_z" ) -var DefaultBatchSize = defaultAgentTelemtryBatchSize +var DefaultBatchSize = 1 func TestMissingInvocation(t *testing.T) { batch := NewBatch(DefaultBatchSize, false, log.InfoLevel) @@ -58,9 +58,16 @@ func TestFullHarvest(t *testing.T) { batch.AddTelemetry(testRequestId2, bytes.NewBufferString(testTelemetry).Bytes()) harvested := batch.Harvest(false) + + // 2/3 of invocations have data assert.Equal(t, 2, len(harvested)) - assert.Equal(t, testRequestId, harvested[0].RequestId) - assert.Equal(t, 2, len(harvested[0].Telemetry)) + + // all harvested invocations should have at least 1 payload + // and harvests without any payloads should not be harvested + for _, harvest := range harvested { + assert.NotEqual(t, testRequestId3, harvest.RequestId) + assert.GreaterOrEqual(t, len(harvest.Telemetry), 1) + } } func TestHarvestWithTraceID(t *testing.T) { @@ -85,7 +92,6 @@ func TestHarvestWithTraceID(t *testing.T) { for _, harvest := range harvested { assert.GreaterOrEqual(t, len(harvest.Telemetry), 1) } - } func TestNotFullHarvest(t *testing.T) { diff --git a/AwsLambdaExtension/agentTelemetry/client.go b/agentTelemetry/client.go similarity index 91% rename from AwsLambdaExtension/agentTelemetry/client.go rename to agentTelemetry/client.go index d8367fa..e9a5d6a 100644 --- a/AwsLambdaExtension/agentTelemetry/client.go +++ b/agentTelemetry/client.go @@ -15,12 +15,18 @@ import ( crypto_rand "crypto/rand" math_rand "math/rand" - "github.com/newrelic/newrelic-lambda-extension/util" + "newrelic-lambda-extension/config" + "newrelic-lambda-extension/util" ) const ( InfraEndpointEU string = "https://cloud-collector.eu01.nr-data.net/aws/lambda/v1" InfraEndpointUS string = "https://cloud-collector.newrelic.com/aws/lambda/v1" + + // WIP (configuration options?) + SendTimeoutRetryBase time.Duration = 200 * time.Millisecond + SendTimeoutMaxRetries int = 20 + SendTimeoutMaxBackOff time.Duration = 5 * time.Second ) type Client struct { @@ -34,7 +40,7 @@ type Client struct { } // New creates a telemetry client with sensible defaults -func New(conf Config, batch *Batch, collectTraceID bool) *Client { +func New(conf config.Config, batch *Batch, collectTraceID bool) *Client { httpClient := &http.Client{ Timeout: 2400 * time.Millisecond, //TODO: make this much lower once collector repaired } @@ -159,9 +165,9 @@ type AttemptData struct { } func (c *Client) attemptSend(currentPayloadBytes []byte, builder requestBuilder, dataChan chan AttemptData, quit chan bool) { - baseSleepTime := 200 * time.Millisecond + baseSleepTime := SendTimeoutRetryBase - for attempts := 0; ; attempts++ { + for attempts := 0; attempts < SendTimeoutMaxRetries; attempts++ { select { case <-quit: return @@ -208,6 +214,9 @@ func (c *Client) attemptSend(currentPayloadBytes []byte, builder requestBuilder, if attempts%3 == 0 { baseSleepTime *= 2 } + if baseSleepTime > SendTimeoutMaxBackOff { + baseSleepTime = SendTimeoutMaxBackOff + } } else { // All other error types are fatal dataChan <- AttemptData{ diff --git a/AwsLambdaExtension/agentTelemetry/client_test.go b/agentTelemetry/client_test.go similarity index 97% rename from AwsLambdaExtension/agentTelemetry/client_test.go rename to agentTelemetry/client_test.go index be84150..366591e 100644 --- a/AwsLambdaExtension/agentTelemetry/client_test.go +++ b/agentTelemetry/client_test.go @@ -10,7 +10,9 @@ import ( "testing" "time" - "github.com/newrelic/newrelic-lambda-extension/util" + "newrelic-lambda-extension/config" + "newrelic-lambda-extension/util" + "github.com/stretchr/testify/assert" ) @@ -29,7 +31,7 @@ func TestClientSend(t *testing.T) { reqBytes, err := io.ReadAll(r.Body) assert.NoError(t, err) - defer util.Close(r.Body) + defer Close(r.Body) assert.NotEmpty(t, reqBytes) reqBody, err := util.Uncompress(reqBytes) @@ -55,7 +57,7 @@ func TestClientSend(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 1, successCount) - conf := Config{ + conf := config.Config{ DataCollectionTimeout: clientTestingTimeout, ExtensionName: "", LicenseKey: "mock license key", @@ -83,7 +85,7 @@ func TestClientSendRetry(t *testing.T) { reqBytes, err := io.ReadAll(r.Body) assert.NoError(t, err) - defer util.Close(r.Body) + defer Close(r.Body) assert.NotEmpty(t, reqBytes) reqBody, err := util.Uncompress(reqBytes) diff --git a/agentTelemetry/close.go b/agentTelemetry/close.go new file mode 100644 index 0000000..b4e09a2 --- /dev/null +++ b/agentTelemetry/close.go @@ -0,0 +1,13 @@ +package agentTelemetry + +import ( + "io" +) + +// Close closes things and logs errors if it fails +func Close(thing io.Closer) { + err := thing.Close() + if err != nil { + l.Errorf("[agentTelemetry]: %v", err) + } +} diff --git a/AwsLambdaExtension/agentTelemetry/dispatcher.go b/agentTelemetry/dispatcher.go similarity index 88% rename from AwsLambdaExtension/agentTelemetry/dispatcher.go rename to agentTelemetry/dispatcher.go index 1f89b94..515f454 100644 --- a/AwsLambdaExtension/agentTelemetry/dispatcher.go +++ b/agentTelemetry/dispatcher.go @@ -2,7 +2,8 @@ package agentTelemetry import ( "context" - "newrelic-lambda-extension/AwsLambdaExtension/extensionApi" + "newrelic-lambda-extension/extensionApi" + "newrelic-lambda-extension/config" "time" ) @@ -14,7 +15,7 @@ type AgentTelemetryDispatcher struct { } // NewDispatcher creates a new dispatcher object to manage the collection and sending of New Relic Agent data -func NewDispatcher(conf Config) *AgentTelemetryDispatcher { +func NewDispatcher(conf config.Config) *AgentTelemetryDispatcher { batch := NewBatch(conf.AgentTelemetryBatchSize, false, conf.LogLevel) telemetryClient := New(conf, batch, true) telemetryChan, err := InitTelemetryChannel() @@ -30,8 +31,8 @@ func NewDispatcher(conf Config) *AgentTelemetryDispatcher { } } -// AddInvocation creats a place to store agent telemetry data for the current invocation -func (disp *AgentTelemetryDispatcher) AddRequest(res *extensionApi.NextEventResponse) { +// AddEvent creats a place to store agent telemetry data for the current AWS Event +func (disp *AgentTelemetryDispatcher) AddEvent(res *extensionApi.NextEventResponse) { disp.batch.AddInvocation(res.RequestID, time.Now()) } diff --git a/AwsLambdaExtension/agentTelemetry/ipc.go b/agentTelemetry/ipc.go similarity index 92% rename from AwsLambdaExtension/agentTelemetry/ipc.go rename to agentTelemetry/ipc.go index f2cbf34..08391e0 100644 --- a/AwsLambdaExtension/agentTelemetry/ipc.go +++ b/agentTelemetry/ipc.go @@ -16,7 +16,8 @@ func InitTelemetryChannel() (chan []byte, error) { return nil, err } - telemetryChan := make(chan []byte) + // buffer channel to avoid deadlocks + telemetryChan := make(chan []byte, 5) go func() { for { diff --git a/AwsLambdaExtension/agentTelemetry/ipc_test.go b/agentTelemetry/ipc_test.go similarity index 100% rename from AwsLambdaExtension/agentTelemetry/ipc_test.go rename to agentTelemetry/ipc_test.go diff --git a/AwsLambdaExtension/agentTelemetry/payload.go b/agentTelemetry/payload.go similarity index 100% rename from AwsLambdaExtension/agentTelemetry/payload.go rename to agentTelemetry/payload.go diff --git a/AwsLambdaExtension/agentTelemetry/payload_test.go b/agentTelemetry/payload_test.go similarity index 99% rename from AwsLambdaExtension/agentTelemetry/payload_test.go rename to agentTelemetry/payload_test.go index 58099fe..20c515c 100644 --- a/AwsLambdaExtension/agentTelemetry/payload_test.go +++ b/agentTelemetry/payload_test.go @@ -73,7 +73,7 @@ func TestExtractTraceIDInvalid(t *testing.T) { assert.Empty(t, traceId) payload2 := []byte("[foobar]") - payload2 = []byte(base64.StdEncoding.EncodeToString(payload)) + payload2 = []byte(base64.StdEncoding.EncodeToString(payload2)) traceId, err = ExtractTraceID(payload2) diff --git a/AwsLambdaExtension/agentTelemetry/request.go b/agentTelemetry/request.go similarity index 98% rename from AwsLambdaExtension/agentTelemetry/request.go rename to agentTelemetry/request.go index dfed5bb..061e1d1 100644 --- a/AwsLambdaExtension/agentTelemetry/request.go +++ b/agentTelemetry/request.go @@ -7,7 +7,7 @@ import ( "fmt" "net/http" - "github.com/newrelic/newrelic-lambda-extension/util" + "newrelic-lambda-extension/util" ) const ( diff --git a/AwsLambdaExtension/agentTelemetry/request_test.go b/agentTelemetry/request_test.go similarity index 100% rename from AwsLambdaExtension/agentTelemetry/request_test.go rename to agentTelemetry/request_test.go diff --git a/agentTelemetry/util_test.go b/agentTelemetry/util_test.go new file mode 100644 index 0000000..401690a --- /dev/null +++ b/agentTelemetry/util_test.go @@ -0,0 +1,17 @@ +package agentTelemetry + +import ( + "fmt" + "testing" +) + +type mockCloseable struct{} + +func (mockCloseable) Close() error { + return fmt.Errorf("Something went wrong") +} + +func TestClose(t *testing.T) { + c := &mockCloseable{} + Close(c) +} diff --git a/checks/agent_version_check.go b/checks/agent_version_check.go deleted file mode 100644 index 5dea9b1..0000000 --- a/checks/agent_version_check.go +++ /dev/null @@ -1,56 +0,0 @@ -package checks - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "path/filepath" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" - "golang.org/x/mod/semver" -) - -type LayerAgentVersion struct { - Version string `json:"version"` -} - -// We are only returning an error message when an out of date agent version is detected. -// All other errors will result in a nil return value. -func agentVersionCheck(ctx context.Context, conf *config.Configuration, reg *api.RegistrationResponse, r runtimeConfig) error { - if r.AgentVersion == "" { - return nil - } - - v := LayerAgentVersion{} - - for i := range r.layerAgentPaths { - f := filepath.Join(r.layerAgentPaths[i], r.agentVersionFile) - if !util.PathExists(f) { - continue - } - - b, err := ioutil.ReadFile(f) - if err != nil { - return nil - } - - if r.language == Python { - v.Version = string(b) - } else { - err = json.Unmarshal([]byte(b), &v) - if err != nil { - return nil - } - } - } - - // semver requires a prepended v on version string - if v.Version != "" && semver.Compare("v"+v.Version, r.AgentVersion) < 0 { - return fmt.Errorf("Agent version out of date: v%s, in order access up to date features please upgrade to the latest New Relic %s layer that includes agent version %s", v.Version, r.language, r.AgentVersion) - } - - return nil -} diff --git a/checks/agent_version_check_test.go b/checks/agent_version_check_test.go deleted file mode 100644 index 7ba2379..0000000 --- a/checks/agent_version_check_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package checks - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/stretchr/testify/assert" -) - -func TestAgentVersion(t *testing.T) { - conf := config.Configuration{} - reg := api.RegistrationResponse{} - r := runtimeConfig{} - ctx := context.Background() - - // No version set - err := agentVersionCheck(ctx, &conf, ®, r) - assert.Nil(t, err) - - // Error - dirname, err := os.MkdirTemp("", "") - assert.Nil(t, err) - defer os.RemoveAll(dirname) - - testFile := filepath.Join(dirname, "opt", "python", "lib", "python3.8", "site-packages", "newrelic") - r = runtimeConfigs[Python] - r.AgentVersion = "v10.1.2" - r.layerAgentPaths = []string{testFile} - - os.MkdirAll(testFile, os.ModePerm) - f, _ := os.Create(filepath.Join(testFile, r.agentVersionFile)) - f.WriteString("10.1.0") - - err = agentVersionCheck(ctx, &conf, ®, r) - assert.EqualError(t, err, "Agent version out of date: v10.1.0, in order access up to date features please upgrade to the latest New Relic python layer that includes agent version v10.1.2") - - // Success - r.AgentVersion = "10.1.0" - err = agentVersionCheck(ctx, &conf, ®, r) - assert.Nil(t, err) -} diff --git a/checks/handler_check.go b/checks/handler_check.go deleted file mode 100644 index 73d120a..0000000 --- a/checks/handler_check.go +++ /dev/null @@ -1,59 +0,0 @@ -package checks - -import ( - "context" - "fmt" - "strings" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -type handlerConfigs struct { - handlerName string - conf *config.Configuration -} - -var handlerPath = "/var/task" - -func handlerCheck(ctx context.Context, conf *config.Configuration, reg *api.RegistrationResponse, r runtimeConfig) error { - if r.language != "" { - h := handlerConfigs{ - handlerName: reg.Handler, - conf: conf, - } - - if !r.check(h) { - return fmt.Errorf("Missing handler file %s (NEW_RELIC_LAMBDA_HANDLER=%s)", h.handlerName, conf.NRHandler) - } - } - - return nil -} - -func (r runtimeConfig) check(h handlerConfigs) bool { - functionHandler := r.getTrueHandler(h) - p := removePathMethodName(functionHandler) - p = pathFormatter(p, r.fileType) - return util.PathExists(p) -} - -func (r runtimeConfig) getTrueHandler(h handlerConfigs) string { - if h.handlerName != r.wrapperName { - util.Logln("Warning: handler not set to New Relic layer wrapper", r.wrapperName) - return h.handlerName - } - - return h.conf.NRHandler -} - -func removePathMethodName(p string) string { - s := strings.Split(p, ".") - return strings.Join(s[:len(s)-1], "/") -} - -func pathFormatter(functionHandler string, fileType string) string { - p := fmt.Sprintf("%s/%s.%s", handlerPath, functionHandler, fileType) - return p -} diff --git a/checks/handler_check_test.go b/checks/handler_check_test.go deleted file mode 100644 index deed06a..0000000 --- a/checks/handler_check_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package checks - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/stretchr/testify/assert" -) - -var testHandler = "path/to/app.handler" - -func TestRuntimeMethods(t *testing.T) { - conf := config.Configuration{} - r := runtimeConfigs[Node] - h := handlerConfigs{ - handlerName: r.wrapperName, - conf: &conf, - } - conf.NRHandler = testHandler - - t1 := r.getTrueHandler(h) - t2 := removePathMethodName(t1) - t3 := pathFormatter(t2, r.fileType) - - e1 := testHandler - e2 := "path/to/app" - e3 := "/var/task/path/to/app.js" - - assert.Equal(t, e1, t1) - assert.Equal(t, e2, t2) - assert.Equal(t, e3, t3) - - r = runtimeConfigs[Python] - - h = handlerConfigs{ - handlerName: r.wrapperName, - conf: &conf, - } - - t1 = r.getTrueHandler(h) - t2 = removePathMethodName(t1) - t3 = pathFormatter(t2, r.fileType) - - e1 = testHandler - e2 = "path/to/app" - e3 = "/var/task/path/to/app.py" - - assert.Equal(t, e1, t1) - assert.Equal(t, e2, t2) - assert.Equal(t, e3, t3) -} - -func TestHandlerCheck(t *testing.T) { - conf := config.Configuration{} - reg := api.RegistrationResponse{} - r := runtimeConfigs[Node] - ctx := context.Background() - - // No Runtime - err := handlerCheck(ctx, &conf, ®, runtimeConfig{}) - assert.Nil(t, err) - - // Error - reg.Handler = testHandler - conf.NRHandler = config.EmptyNRWrapper - err = handlerCheck(ctx, &conf, ®, r) - assert.EqualError(t, err, "Missing handler file path/to/app.handler (NEW_RELIC_LAMBDA_HANDLER=Undefined)") - - // Success - dirname, err := os.MkdirTemp("", "") - assert.Nil(t, err) - defer os.RemoveAll(dirname) - - handlerPath = filepath.Join(dirname, "var", "task") - os.MkdirAll(filepath.Join(handlerPath, "path", "to"), os.ModePerm) - os.Create(filepath.Join(handlerPath, "path", "to", "app.js")) - - reg.Handler = testHandler - conf.NRHandler = config.EmptyNRWrapper - err = handlerCheck(ctx, &conf, ®, r) - assert.Nil(t, err) -} diff --git a/checks/runtime_check.go b/checks/runtime_check.go deleted file mode 100644 index 8bb3917..0000000 --- a/checks/runtime_check.go +++ /dev/null @@ -1,62 +0,0 @@ -package checks - -import ( - "context" - "net/http" - "path/filepath" - "regexp" - "time" - - "github.com/google/go-github/v44/github" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -type httpClient interface { - Get(string) (*http.Response, error) -} - -var ( - client httpClient - githubClient *github.Client - re = regexp.MustCompile(`\/releases\/tag\/(v[0-9.]+)`) -) - -func init() { - client = &http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Timeout: time.Second * 10, - } - githubClient = github.NewClient(&http.Client{Timeout: time.Second * 10}) -} - -func checkAndReturnRuntime() (runtimeConfig, error) { - for k, v := range runtimeConfigs { - p := filepath.Join(runtimeLookupPath, string(k)) - if util.PathExists(p) { - err := latestAgentTag(&v) - return v, err - } - } - - // If we make it here that means the runtime is not one we - // currently validate so we don't want to warn against anything - return runtimeConfig{}, nil -} - -func latestAgentTag(r *runtimeConfig) error { - ctx := context.Background() - release, _, err := githubClient.Repositories.GetLatestRelease(ctx, r.agentVersionGitOrg, r.agentVersionGitRepo) - - if err != nil { - util.Debugf("Could not retrieve latest GitHub release: %v", err) - return nil - } - - if release.TagName != nil { - r.AgentVersion = *release.TagName - } - - return nil -} diff --git a/checks/runtime_check_test.go b/checks/runtime_check_test.go deleted file mode 100644 index f20f267..0000000 --- a/checks/runtime_check_test.go +++ /dev/null @@ -1,49 +0,0 @@ -//go:build !race -// +build !race - -package checks - -import ( - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestRuntimeCheck(t *testing.T) { - dirname, err := os.MkdirTemp("", "") - assert.Nil(t, err) - defer os.RemoveAll(dirname) - - oldPath := runtimeLookupPath - defer func() { - runtimeLookupPath = oldPath - }() - runtimeLookupPath = filepath.Join(dirname, runtimeLookupPath) - - os.MkdirAll(filepath.Join(runtimeLookupPath, "node"), os.ModePerm) - r, err := checkAndReturnRuntime() - assert.Equal(t, runtimeConfigs[Node].language, r.language) - assert.Nil(t, err) -} - -func TestRuntimeCheckNil(t *testing.T) { - r, err := checkAndReturnRuntime() - assert.Equal(t, runtimeConfig{}, r) - assert.Nil(t, err) -} - -func TestLatestAgentTag(t *testing.T) { - r := &runtimeConfig{agentVersionGitOrg: runtimeConfigs[Python].agentVersionGitOrg, agentVersionGitRepo: runtimeConfigs[Python].agentVersionGitRepo} - err := latestAgentTag(r) - assert.NotEmpty(t, r.AgentVersion) - assert.Nil(t, err) -} - -func TestLatestAgentTagError(t *testing.T) { - r := &runtimeConfig{agentVersionGitOrg: "", agentVersionGitRepo: ""} - err := latestAgentTag(r) - assert.Empty(t, r.AgentVersion) - assert.Nil(t, err) -} diff --git a/checks/runtime_config.go b/checks/runtime_config.go deleted file mode 100644 index 639740e..0000000 --- a/checks/runtime_config.go +++ /dev/null @@ -1,58 +0,0 @@ -package checks - -var ( - layerAgentPathNode = []string{"/opt/nodejs/node_modules/newrelic"} - layerAgentPathsPython = []string{ - "/opt/python/lib/python2.7/site-packages/newrelic", - "/opt/python/lib/python3.6/site-packages/newrelic", - "/opt/python/lib/python3.7/newrelic", - "/opt/python/lib/python3.8/site-packages/newrelic", - "/opt/python/lib/python3.9/site-packages/newrelic", - } - vendorAgentPathNode = "/var/task/node_modules/newrelic" - vendorAgentPathPython = "/var/task/newrelic" - runtimeLookupPath = "/var/lang/bin" -) - -type runtimeConfig struct { - AgentVersion string - agentVersionGitOrg string - agentVersionGitRepo string - agentVersionFile string - fileType string - language Runtime - layerAgentPaths []string - vendorAgentPath string - wrapperName string -} - -type Runtime string - -const ( - Python Runtime = "python" - Node Runtime = "node" -) - -// Runtime static values -var runtimeConfigs = map[Runtime]runtimeConfig{ - Node: { - language: Node, - wrapperName: "newrelic-lambda-wrapper.handler", - fileType: "js", - layerAgentPaths: layerAgentPathNode, - vendorAgentPath: vendorAgentPathNode, - agentVersionFile: "package.json", - agentVersionGitOrg: "newrelic", - agentVersionGitRepo: "node-newrelic", - }, - Python: { - language: Python, - wrapperName: "newrelic_lambda_wrapper.handler", - fileType: "py", - layerAgentPaths: layerAgentPathsPython, - vendorAgentPath: vendorAgentPathPython, - agentVersionFile: "version.txt", - agentVersionGitOrg: "newrelic", - agentVersionGitRepo: "newrelic-python-agent", - }, -} diff --git a/checks/sanity_check.go b/checks/sanity_check.go deleted file mode 100644 index 64dd066..0000000 --- a/checks/sanity_check.go +++ /dev/null @@ -1,35 +0,0 @@ -package checks - -import ( - "context" - "fmt" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/credentials" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -var ( - awsLogIngestionEnvVars = []string{ - "DEBUG_LOGGING_ENABLED", - "INFRA_ENABLED", - "LICENSE_KEY", - "LOGGING_ENABLED", - "NR_INFRA_ENDPOINT", - "NR_LOGGING_ENDPOINT", - } -) - -// sanityCheck checks for configuration that is either misplaced or in conflict -func sanityCheck(ctx context.Context, conf *config.Configuration, res *api.RegistrationResponse, _ runtimeConfig) error { - if util.AnyEnvVarsExist(awsLogIngestionEnvVars) { - return fmt.Errorf("Environment varaible '%s' is used by aws-log-ingestion and has no effect here. Recommend unsetting this environment variable within this function.", util.AnyEnvVarsExistString(awsLogIngestionEnvVars)) - } - - if credentials.IsSecretConfigured(ctx, conf) && util.EnvVarExists("NEW_RELIC_LICENSE_KEY") { - return fmt.Errorf("There is both a AWS Secrets Manager secret and a NEW_RELIC_LICENSE_KEY environment variable set. Recommend removing the NEW_RELIC_LICENSE_KEY environment variable and using the AWS Secrets Manager secret.") - } - - return nil -} diff --git a/checks/sanity_check_test.go b/checks/sanity_check_test.go deleted file mode 100644 index 0919d9c..0000000 --- a/checks/sanity_check_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package checks - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/credentials" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" - "github.com/stretchr/testify/assert" -) - -type mockSecretManager struct { - secretsmanageriface.SecretsManagerAPI -} - -func (mockSecretManager) GetSecretValueWithContext(context.Context, *secretsmanager.GetSecretValueInput, ...request.Option) (*secretsmanager.GetSecretValueOutput, error) { - return &secretsmanager.GetSecretValueOutput{ - SecretString: aws.String(`{"LicenseKey": "foo"}`), - }, nil -} - -type mockSecretManagerErr struct { - secretsmanageriface.SecretsManagerAPI -} - -func (mockSecretManagerErr) GetSecretValueWithContext(context.Context, *secretsmanager.GetSecretValueInput, ...request.Option) (*secretsmanager.GetSecretValueOutput, error) { - return nil, fmt.Errorf("Something went wrong") -} - -func TestSanityCheck(t *testing.T) { - ctx := context.Background() - - if util.AnyEnvVarsExist(awsLogIngestionEnvVars) { - assert.Error(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) - } else { - assert.Nil(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) - } - - os.Setenv("DEBUG_LOGGING_ENABLED", "1") - assert.Error(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) - os.Unsetenv("DEBUG_LOGGING_ENABLED") - - os.Unsetenv("NEW_RELIC_LICENSE_KEY") - credentials.OverrideSecretsManager(&mockSecretManager{}) - assert.Nil(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) - - os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - credentials.OverrideSecretsManager(&mockSecretManager{}) - assert.Error(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) - - credentials.OverrideSecretsManager(&mockSecretManagerErr{}) - assert.Nil(t, sanityCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, runtimeConfig{})) -} diff --git a/checks/startup_check.go b/checks/startup_check.go deleted file mode 100644 index df600a7..0000000 --- a/checks/startup_check.go +++ /dev/null @@ -1,57 +0,0 @@ -package checks - -import ( - "context" - "fmt" - "time" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/lambda/logserver" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -type checkFn func(context.Context, *config.Configuration, *api.RegistrationResponse, runtimeConfig) error - -type LogSender interface { - SendFunctionLogs(ctx context.Context, invokedFunctionARN string, lines []logserver.LogLine) error -} - -/// Register checks here -var checks = []checkFn{ - agentVersionCheck, - handlerCheck, - sanityCheck, - vendorCheck, -} - -func RunChecks(ctx context.Context, conf *config.Configuration, reg *api.RegistrationResponse, logSender LogSender) { - runtimeConfig, err := checkAndReturnRuntime() - if err != nil { - errLog := fmt.Sprintf("There was an issue querying for the latest agent version: %v", err) - util.Logln(errLog) - } - - for _, check := range checks { - runCheck(ctx, conf, reg, runtimeConfig, logSender, check) - } -} - -func runCheck(ctx context.Context, conf *config.Configuration, reg *api.RegistrationResponse, r runtimeConfig, logSender LogSender, check checkFn) error { - err := check(ctx, conf, reg, r) - if err != nil { - errLog := fmt.Sprintf("Startup check failed: %v", err) - util.Logln(errLog) - - //Send a log line to NR as well - logSender.SendFunctionLogs(ctx, "", []logserver.LogLine{ - { - Time: time.Now(), - RequestID: "0", - Content: []byte(errLog), - }, - }) - } - - return err -} diff --git a/checks/startup_check_test.go b/checks/startup_check_test.go deleted file mode 100644 index f66359c..0000000 --- a/checks/startup_check_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package checks - -import ( - "context" - "errors" - "fmt" - "net/http" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/lambda/logserver" - "github.com/stretchr/testify/assert" -) - -type mockClientError struct{} - -func (c *mockClientError) Get(string) (*http.Response, error) { - return nil, errors.New("Something went wrong") -} - -type TestLogSender struct { - sent []logserver.LogLine -} - -func (c *TestLogSender) SendFunctionLogs(ctx context.Context, invokedFunctionARN string, lines []logserver.LogLine) error { - c.sent = append(c.sent, lines...) - return nil -} - -func TestRunCheck(t *testing.T) { - conf := config.Configuration{} - resp := api.RegistrationResponse{} - r := runtimeConfig{} - client := TestLogSender{} - ctx := context.Background() - - tested := false - testCheck := func(ctx context.Context, conf *config.Configuration, resp *api.RegistrationResponse, r runtimeConfig) error { - tested = true - return nil - } - - result := runCheck(ctx, &conf, &resp, r, &client, testCheck) - - assert.Equal(t, true, tested) - assert.Nil(t, result) -} - -func TestRunCheckErr(t *testing.T) { - conf := config.Configuration{} - resp := api.RegistrationResponse{} - r := runtimeConfig{} - logSender := TestLogSender{} - ctx := context.Background() - - tested := false - testCheck := func(ctx context.Context, conf *config.Configuration, resp *api.RegistrationResponse, r runtimeConfig) error { - tested = true - return fmt.Errorf("Failure Test") - } - - result := runCheck(ctx, &conf, &resp, r, &logSender, testCheck) - - assert.Equal(t, true, tested) - assert.NotNil(t, result) - - assert.Equal(t, "Startup check failed: Failure Test", string(logSender.sent[0].Content)) -} - -func TestRunChecks(t *testing.T) { - c := &config.Configuration{} - r := &api.RegistrationResponse{} - l := &TestLogSender{} - - client = &mockClientError{} - - ctx := context.Background() - RunChecks(ctx, c, r, l) -} diff --git a/checks/vendor_check.go b/checks/vendor_check.go deleted file mode 100644 index 59b12ac..0000000 --- a/checks/vendor_check.go +++ /dev/null @@ -1,21 +0,0 @@ -package checks - -import ( - "context" - "fmt" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -// vendorCheck checks to see if the user included a vendored copy of the agent along -// with their function while also using a layer that includes the agent -func vendorCheck(ctx context.Context, _ *config.Configuration, _ *api.RegistrationResponse, r runtimeConfig) error { - - if util.PathExists(r.vendorAgentPath) && util.AnyPathsExist(r.layerAgentPaths) { - return fmt.Errorf("Vendored agent found at '%s', a layer already includes this agent at '%s'. Recommend using the layer agent to avoid unexpected agent behavior.", r.vendorAgentPath, util.AnyPathsExistString(r.layerAgentPaths)) - } - - return nil -} diff --git a/checks/vendor_check_test.go b/checks/vendor_check_test.go deleted file mode 100644 index 9e037cd..0000000 --- a/checks/vendor_check_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package checks - -import ( - "context" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" - "github.com/stretchr/testify/assert" -) - -func TestVendorCheck(t *testing.T) { - n := runtimeConfigs[Node] - ctx := context.Background() - - if !util.AnyPathsExist(n.layerAgentPaths) && !util.PathExists(n.vendorAgentPath) { - assert.Nil(t, vendorCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, n)) - } - - if util.PathExists(n.layerAgentPaths[0]) && util.PathExists(n.vendorAgentPath) { - assert.Error(t, vendorCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, n)) - } - - p := runtimeConfigs[Python] - - if !util.AnyPathsExist(p.layerAgentPaths) && !util.PathExists(p.vendorAgentPath) { - assert.Nil(t, vendorCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, n)) - } - - if util.AnyPathsExist(p.layerAgentPaths) && util.PathExists(p.vendorAgentPath) { - assert.Error(t, vendorCheck(ctx, &config.Configuration{}, &api.RegistrationResponse{}, n)) - } -} diff --git a/config/config.go b/config/config.go index c35ba66..4c75ff6 100644 --- a/config/config.go +++ b/config/config.go @@ -2,145 +2,115 @@ package config import ( "os" + "path" "strconv" "strings" "time" -) -const ( - DefaultRipeMillis = 7_000 - DefaultRotMillis = 12_000 - DefaultLogLevel = "INFO" - DebugLogLevel = "DEBUG" - defaultLogServerHost = "sandbox.localdomain" - DefaultClientTimeout = 10 * time.Second + log "github.com/sirupsen/logrus" ) -var EmptyNRWrapper = "Undefined" - -type Configuration struct { - ExtensionEnabled bool - LogsEnabled bool - SendFunctionLogs bool - CollectTraceID bool - TelemetryAPIEnabled bool - RipeMillis uint32 - RotMillis uint32 - LicenseKey string - LicenseKeySecretId string - NRHandler string - TelemetryEndpoint string - LogEndpoint string - LogLevel string - LogServerHost string - ClientTimeout time.Duration +type Config struct { + DataCollectionTimeout time.Duration + LogLevel log.Level + AgentTelemetryBatchSize int + TelemetryAPIBatchSize int64 + AgentTelemetryRegion string + LicenseKey string + AccountID string + ExtensionName string + CollectAgentData bool } -func ConfigurationFromEnvironment() *Configuration { - enabledStr, extensionEnabledOverride := os.LookupEnv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED") - licenseKey, lkOverride := os.LookupEnv("NEW_RELIC_LICENSE_KEY") - licenseKeySecretId, lkSecretOverride := os.LookupEnv("NEW_RELIC_LICENSE_KEY_SECRET") - nrHandler, nrOverride := os.LookupEnv("NEW_RELIC_LAMBDA_HANDLER") - telemetryEndpoint, teOverride := os.LookupEnv("NEW_RELIC_TELEMETRY_ENDPOINT") - logEndpoint, leOverride := os.LookupEnv("NEW_RELIC_LOG_ENDPOINT") - clientTimeout, ctOverride := os.LookupEnv("NEW_RELIC_DATA_COLLECTION_TIMEOUT") - ripeMillisStr, ripeMillisOverride := os.LookupEnv("NEW_RELIC_HARVEST_RIPE_MILLIS") - rotMillisStr, rotMillisOverride := os.LookupEnv("NEW_RELIC_HARVEST_ROT_MILLIS") - logLevelStr, logLevelOverride := os.LookupEnv("NEW_RELIC_EXTENSION_LOG_LEVEL") - logsEnabledStr, logsEnabledOverride := os.LookupEnv("NEW_RELIC_EXTENSION_LOGS_ENABLED") - sendFunctionLogsStr, sendFunctionLogsOverride := os.LookupEnv("NEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS") - logServerHostStr, logServerHostOverride := os.LookupEnv("NEW_RELIC_LOG_SERVER_HOST") - collectTraceIDStr, collectTraceIDOverride := os.LookupEnv("NEW_RELIC_COLLECT_TRACE_ID") - telemetryAPIEnabledStr, telemetryAPIOverride := os.LookupEnv("NEW_RELIC_TELEMETRY_API_EXTENSION_ENABLED") - - extensionEnabled := true - if extensionEnabledOverride && strings.ToLower(enabledStr) == "false" { - extensionEnabled = false - } - - logsEnabled := true - if logsEnabledOverride && strings.ToLower(logsEnabledStr) == "false" { - logsEnabled = false - } +const ( + defaultCollectionTimeout = 10 * time.Second + minimumCollectionTimeout = 600 * time.Millisecond + defaultAgentTelemtryBatchSize = 1 + defaultTelemtryAPIBatchSize = 1 + + // Optional Environment variables that can be used to talior the user experience to your needs + agentDataEnabledVariable = "NEW_RELIC_EXTENSION_AGENT_DATA_COLLECTION_ENABLED" + agentDataBatchSizeVariable = "NEW_RELIC_EXTENSION_AGENT_DATA_BATCH_SIZE" + clientRetryTimeoutVariable = "NEW_RELIC_EXTENSION_DATA_COLLECTION_TIMEOUT" + agentTelemetryRegionVariable = "NEW_RELIC_EXTENSION_COLLECTOR_OVERRIDE" + extensionLogLevelVariable = "NEW_RELIC_EXTENSION_LOG_LEVEL" + telAPIBatchSizeVariable = "NEW_RELIC_EXTENSION_TELEMETRY_API_BATCH_SIZE" + NrAccountIDVariable = "NEW_RELIC_ACCOUNT_ID" +) - ret := &Configuration{ExtensionEnabled: extensionEnabled, LogsEnabled: logsEnabled} +var l = log.WithFields(log.Fields{"pkg": "config"}) - ret.ClientTimeout = DefaultClientTimeout - if ctOverride && clientTimeout != "" { - clientTimeout, err := time.ParseDuration(clientTimeout) - if err == nil { - ret.ClientTimeout = clientTimeout - } +// simplifies testing +func defaultConfig() Config { + return Config{ + CollectAgentData: true, + DataCollectionTimeout: defaultCollectionTimeout, + AgentTelemetryBatchSize: defaultAgentTelemtryBatchSize, + TelemetryAPIBatchSize: defaultTelemtryAPIBatchSize, + LogLevel: log.InfoLevel, + ExtensionName: path.Base(os.Args[0]), } +} - if lkOverride { - ret.LicenseKey = licenseKey - } else if lkSecretOverride { - ret.LicenseKeySecretId = licenseKeySecretId - } +func GetConfig() Config { + conf := defaultConfig() - if nrOverride { - ret.NRHandler = nrHandler - } else { - ret.NRHandler = EmptyNRWrapper - } + conf.AccountID = os.Getenv("NEW_RELIC_ACCOUNT_ID") + conf.AgentTelemetryRegion = os.Getenv(agentTelemetryRegionVariable) - if teOverride { - ret.TelemetryEndpoint = telemetryEndpoint + // Enable or disable collection of agent telemetry data + enableAgent := os.Getenv(agentDataEnabledVariable) + if strings.ToLower(enableAgent) == "false" { + conf.CollectAgentData = false } - - if leOverride { - ret.LogEndpoint = logEndpoint - } - - if ripeMillisOverride { - ripeMillis, err := strconv.ParseUint(ripeMillisStr, 10, 32) - if err == nil { - ret.RipeMillis = uint32(ripeMillis) + // How long agent will try to resend + clientTimeout := os.Getenv(clientRetryTimeoutVariable) + if clientTimeout != "" { + dur, err := time.ParseDuration(clientTimeout) + if err != nil { + environmentVariableError(clientRetryTimeoutVariable, err) + l.Warnf("client retry timeout will be set to default value: %s", defaultCollectionTimeout.String()) } - } - - if ret.RipeMillis == 0 { - ret.RipeMillis = DefaultRipeMillis - } - - if rotMillisOverride { - rotMillis, err := strconv.ParseUint(rotMillisStr, 10, 32) - if err == nil { - ret.RotMillis = uint32(rotMillis) + if dur >= minimumCollectionTimeout { + conf.DataCollectionTimeout = dur + } else { + l.Warnf("configured client retry duration is too short, setting it to minimum value: %s", minimumCollectionTimeout.String()) + conf.DataCollectionTimeout = minimumCollectionTimeout } } - if ret.RotMillis == 0 { - ret.RotMillis = DefaultRotMillis - } - - if logLevelOverride && logLevelStr == DebugLogLevel { - ret.LogLevel = DebugLogLevel - } else { - ret.LogLevel = DefaultLogLevel - } - - if logServerHostOverride { - ret.LogServerHost = logServerHostStr + telApiBatchSize, err := strconv.ParseInt(os.Getenv(telAPIBatchSizeVariable), 0, 16) + if err != nil { + environmentVariableError(telAPIBatchSizeVariable, err) + l.Warnf("telemetry api batch size will be set to default value: %d", telApiBatchSize) } else { - ret.LogServerHost = defaultLogServerHost + conf.TelemetryAPIBatchSize = telApiBatchSize } - if telemetryAPIOverride && strings.ToLower(telemetryAPIEnabledStr) == "true" { - ret.TelemetryAPIEnabled = true + buffer := os.Getenv(agentDataBatchSizeVariable) + if buffer != "" { + val, err := strconv.Atoi(buffer) + if err != nil { + environmentVariableError(agentDataBatchSizeVariable, err) + l.Warnf("agent data batch size will be set to default value: %d", defaultAgentTelemtryBatchSize) + } else { + conf.AgentTelemetryBatchSize = val + } } - // if telemetry API is enabled, disable logAPI to avoid duplicating log data - // telemetry API replaces and improves on logs api - // https://aws.amazon.com/blogs/compute/introducing-the-aws-lambda-telemetry-api/ - if !telemetryAPIOverride && sendFunctionLogsOverride && strings.ToLower(sendFunctionLogsStr) == "true" { - ret.SendFunctionLogs = true + logLevel := strings.ToLower(os.Getenv(extensionLogLevelVariable)) + switch logLevel { + case "debug": + conf.LogLevel = log.DebugLevel + case "warn": + conf.LogLevel = log.WarnLevel + case "info": + conf.LogLevel = log.InfoLevel } - if collectTraceIDOverride && strings.ToLower(collectTraceIDStr) == "true" { - ret.CollectTraceID = true - } + return conf +} - return ret +func environmentVariableError(variable string, err error) { + l.Warnf("[config] error parsing environment variable \"%s\": %v", variable, err) } diff --git a/config/config_test.go b/config/config_test.go index 64fff68..2f95410 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2,89 +2,126 @@ package config import ( "os" + "path" + "reflect" "testing" + "time" - "github.com/stretchr/testify/assert" + log "github.com/sirupsen/logrus" ) -func TestConfigurationFromEnvironmentZero(t *testing.T) { - conf := ConfigurationFromEnvironment() - expected := &Configuration{ - ExtensionEnabled: true, - RipeMillis: DefaultRipeMillis, - RotMillis: DefaultRotMillis, - LogLevel: DefaultLogLevel, - LogsEnabled: true, - NRHandler: EmptyNRWrapper, - LogServerHost: defaultLogServerHost, - ClientTimeout: DefaultClientTimeout, - } - assert.Equal(t, expected, conf) +type envVariables struct { + agentData string + agentBatch string + telemBatch string + timeout string + region string + logLevel string + acctId string } -func TestConfigurationFromEnvironment(t *testing.T) { - os.Unsetenv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED") - - conf := ConfigurationFromEnvironment() - - assert.Equal(t, conf.ExtensionEnabled, true) - assert.Equal(t, conf.LogsEnabled, true) - - os.Setenv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED", "false") - os.Setenv("NEW_RELIC_LAMBDA_HANDLER", "newrelic_lambda_wrapper.handler") - os.Setenv("NEW_RELIC_LICENSE_KEY", "lk") - os.Setenv("NEW_RELIC_LICENSE_KEY_SECRET", "secretId") - os.Setenv("NEW_RELIC_LOG_ENDPOINT", "endpoint") - os.Setenv("NEW_RELIC_TELEMETRY_ENDPOINT", "endpoint") - os.Setenv("NEW_RELIC_HARVEST_RIPE_MILLIS", "0") - os.Setenv("NEW_RELIC_HARVEST_ROT_MILLIS", "0") - os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - os.Setenv("NEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS", "true") - os.Setenv("NEW_RELIC_EXTENSION_LOGS_ENABLED", "false") - os.Setenv("NEW_RELIC_DATA_COLLECTION_TIMEOUT", "5s") - - defer func() { - os.Unsetenv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED") - os.Unsetenv("NEW_RELIC_LAMBDA_HANDLER") - os.Unsetenv("NEW_RELIC_LICENSE_KEY") - os.Unsetenv("NEW_RELIC_LICENSE_KEY_SECRET") - os.Unsetenv("NEW_RELIC_LOG_ENDPOINT") - os.Unsetenv("NEW_RELIC_TELEMETRY_ENDPOINT") - os.Unsetenv("NEW_RELIC_HARVEST_RIPE_MILLIS") - os.Unsetenv("NEW_RELIC_HARVEST_ROT_MILLIS") - os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - os.Unsetenv("NEW_RELIC_EXTENSION_SEND_FUNCTION_LOGS") - os.Unsetenv("NEW_RELIC_EXTENSION_LOGS_ENABLED") - os.Unsetenv("NEW_RELIC_DATA_COLLECTION_TIMEOUT") - }() - - conf = ConfigurationFromEnvironment() - - assert.Equal(t, conf.ExtensionEnabled, false) - assert.Equal(t, "newrelic_lambda_wrapper.handler", conf.NRHandler) - assert.Equal(t, "lk", conf.LicenseKey) - assert.Empty(t, conf.LicenseKeySecretId) - assert.Equal(t, "endpoint", conf.LogEndpoint) - assert.Equal(t, "endpoint", conf.TelemetryEndpoint) - assert.Equal(t, uint32(DefaultRipeMillis), conf.RipeMillis) - assert.Equal(t, uint32(DefaultRotMillis), conf.RotMillis) - assert.Equal(t, "DEBUG", conf.LogLevel) - assert.Equal(t, true, conf.SendFunctionLogs) - assert.Equal(t, false, conf.LogsEnabled) -} - -func TestConfigurationFromEnvironmentSecretId(t *testing.T) { - os.Setenv("NEW_RELIC_LICENSE_KEY_SECRET", "secretId") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY_SECRET") - - conf := ConfigurationFromEnvironment() - assert.Equal(t, "secretId", conf.LicenseKeySecretId) -} +func TestGetConfig(t *testing.T) { + tests := []struct { + name string + want Config + vars envVariables + }{ + { + name: "default", + want: defaultConfig(), + vars: envVariables{ + agentData: "", + agentBatch: "", + telemBatch: "", + timeout: "", + region: "", + logLevel: "", + acctId: "", + }, + }, + { + name: "default env vars", + want: defaultConfig(), + vars: envVariables{ + agentData: "true", + agentBatch: "1", + telemBatch: "1", + timeout: "10s", + region: "", + logLevel: "info", + acctId: "", + }, + }, + { + name: "override all defaults", + want: func() Config { + return Config{ + CollectAgentData: false, + DataCollectionTimeout: 600 * time.Millisecond, + AgentTelemetryBatchSize: 5, + TelemetryAPIBatchSize: 8, + LogLevel: log.WarnLevel, + AgentTelemetryRegion: "test", + AccountID: "12", + ExtensionName: path.Base(os.Args[0]), + } + }(), + vars: envVariables{ + agentData: "false", + agentBatch: "5", + telemBatch: "8", + timeout: "600ms", + region: "test", + logLevel: "warn", + acctId: "12", + }, + }, + { + name: "timeout too low", + want: func() Config { + return Config{ + CollectAgentData: false, + DataCollectionTimeout: 600 * time.Millisecond, + AgentTelemetryBatchSize: 5, + TelemetryAPIBatchSize: 8, + LogLevel: log.WarnLevel, + AgentTelemetryRegion: "test", + AccountID: "12", + ExtensionName: path.Base(os.Args[0]), + } + }(), + vars: envVariables{ + agentData: "false", + agentBatch: "5", + telemBatch: "8", + timeout: "300ms", + region: "test", + logLevel: "warn", + acctId: "12", + }, + }, + } + for _, tt := range tests { + os.Setenv(agentDataEnabledVariable, tt.vars.agentData) + os.Setenv(agentDataBatchSizeVariable, tt.vars.agentBatch) + os.Setenv(clientRetryTimeoutVariable, tt.vars.timeout) + os.Setenv(agentTelemetryRegionVariable, tt.vars.region) + os.Setenv(extensionLogLevelVariable, tt.vars.logLevel) + os.Setenv(telAPIBatchSizeVariable, tt.vars.telemBatch) + os.Setenv(NrAccountIDVariable, tt.vars.acctId) -func TestConfigurationFromEnvironmentLogServerHost(t *testing.T) { - os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "foobar") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") + t.Run(tt.name, func(t *testing.T) { + if got := GetConfig(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetConfig() = %v, want %v", got, tt.want) + } + }) - conf := ConfigurationFromEnvironment() - assert.Equal(t, "foobar", conf.LogServerHost) + os.Unsetenv(agentDataEnabledVariable) + os.Unsetenv(agentDataBatchSizeVariable) + os.Unsetenv(clientRetryTimeoutVariable) + os.Unsetenv(agentTelemetryRegionVariable) + os.Unsetenv(extensionLogLevelVariable) + os.Unsetenv(telAPIBatchSizeVariable) + os.Unsetenv(NrAccountIDVariable) + } } diff --git a/credentials/credentials.go b/credentials/credentials.go deleted file mode 100644 index bc520ed..0000000 --- a/credentials/credentials.go +++ /dev/null @@ -1,99 +0,0 @@ -package credentials - -import ( - "context" - "encoding/json" - "fmt" - "os" - - "github.com/newrelic/newrelic-lambda-extension/util" - - "github.com/newrelic/newrelic-lambda-extension/config" - - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" -) - -type licenseKeySecret struct { - LicenseKey string -} - -var ( - sess = session.Must(session.NewSessionWithOptions(session.Options{ - SharedConfigState: session.SharedConfigEnable, - })) - secrets secretsmanageriface.SecretsManagerAPI -) - -const defaultSecretId = "NEW_RELIC_LICENSE_KEY" - -func init() { - secrets = secretsmanager.New(sess) -} - -func getLicenseKeySecretId(conf *config.Configuration) string { - if conf.LicenseKeySecretId != "" { - util.Logln("Fetching license key from secret id " + conf.LicenseKeySecretId) - return conf.LicenseKeySecretId - } - - return defaultSecretId -} - -func decodeLicenseKey(rawJson *string) (string, error) { - var secrets licenseKeySecret - - err := json.Unmarshal([]byte(*rawJson), &secrets) - if err != nil { - return "", err - } - if secrets.LicenseKey == "" { - return "", fmt.Errorf("malformed license key secret; missing \"LicenseKey\" attribute") - } - - return secrets.LicenseKey, nil -} - -// IsSecretConfigured returns true if the Secrets Maanger secret is configured, false -// otherwise -func IsSecretConfigured(ctx context.Context, conf *config.Configuration) bool { - secretId := getLicenseKeySecretId(conf) - secretValueInput := secretsmanager.GetSecretValueInput{SecretId: &secretId} - - _, err := secrets.GetSecretValueWithContext(ctx, &secretValueInput) - if err != nil { - return false - } - - return true -} - -// GetNewRelicLicenseKey fetches the license key from AWS Secrets Manager, falling back -// to the NEW_RELIC_LICENSE_KEY environment variable if set. -func GetNewRelicLicenseKey(ctx context.Context, conf *config.Configuration) (string, error) { - if conf.LicenseKey != "" { - util.Logln("Using license key from environment variable") - return conf.LicenseKey, nil - } - - secretId := getLicenseKeySecretId(conf) - secretValueInput := secretsmanager.GetSecretValueInput{SecretId: &secretId} - - secretValueOutput, err := secrets.GetSecretValueWithContext(ctx, &secretValueInput) - if err != nil { - envLicenseKey, found := os.LookupEnv(defaultSecretId) - if found { - return envLicenseKey, nil - } - - return "", err - } - - return decodeLicenseKey(secretValueOutput.SecretString) -} - -// OverrideSecretsManager overrides the default Secrets Manager implementation -func OverrideSecretsManager(override secretsmanageriface.SecretsManagerAPI) { - secrets = override -} diff --git a/credentials/credentials_test.go b/credentials/credentials_test.go deleted file mode 100644 index baead03..0000000 --- a/credentials/credentials_test.go +++ /dev/null @@ -1,98 +0,0 @@ -package credentials - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/config" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface" - "github.com/stretchr/testify/assert" -) - -func TestGetLicenseKeySecretId(t *testing.T) { - secretId := getLicenseKeySecretId(&config.Configuration{}) - assert.Equal(t, defaultSecretId, secretId) - - var testSecretId = "testSecretName" - var conf = &config.Configuration{LicenseKeySecretId: testSecretId} - secretId = getLicenseKeySecretId(conf) - assert.Equal(t, testSecretId, secretId) -} - -type mockSecretManager struct { - secretsmanageriface.SecretsManagerAPI -} - -func (mockSecretManager) GetSecretValueWithContext(context.Context, *secretsmanager.GetSecretValueInput, ...request.Option) (*secretsmanager.GetSecretValueOutput, error) { - return &secretsmanager.GetSecretValueOutput{ - SecretString: aws.String(`{"LicenseKey": "foo"}`), - }, nil -} - -type mockSecretManagerErr struct { - secretsmanageriface.SecretsManagerAPI -} - -func (mockSecretManagerErr) GetSecretValueWithContext(context.Context, *secretsmanager.GetSecretValueInput, ...request.Option) (*secretsmanager.GetSecretValueOutput, error) { - return nil, fmt.Errorf("Something went wrong") -} - -func TestIsSecretConfigured(t *testing.T) { - OverrideSecretsManager(mockSecretManager{}) - ctx := context.Background() - assert.True(t, IsSecretConfigured(ctx, &config.Configuration{})) - - OverrideSecretsManager(mockSecretManagerErr{}) - assert.False(t, IsSecretConfigured(ctx, &config.Configuration{})) -} - -func TestGetNewRelicLicenseKey(t *testing.T) { - OverrideSecretsManager(mockSecretManager{}) - ctx := context.Background() - lk, err := GetNewRelicLicenseKey(ctx, &config.Configuration{}) - assert.Nil(t, err) - assert.Equal(t, "foo", lk) - - os.Unsetenv("NEW_RELIC_LICENSE_KEY") - OverrideSecretsManager(mockSecretManagerErr{}) - lk, err = GetNewRelicLicenseKey(ctx, &config.Configuration{}) - assert.Error(t, err) - assert.Empty(t, lk) - - os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - lk, err = GetNewRelicLicenseKey(ctx, &config.Configuration{}) - assert.Nil(t, err) - assert.Equal(t, "foobar", lk) -} - -func TestGetNewRelicLicenseKeyConfigValue(t *testing.T) { - licenseKey := "test_value" - ctx := context.Background() - resultKey, err := GetNewRelicLicenseKey(ctx, &config.Configuration{ - LicenseKey: licenseKey, - }) - - assert.Nil(t, err) - assert.Equal(t, licenseKey, resultKey) -} - -func TestDecodeLicenseKey(t *testing.T) { - invalidJson := "invalid json" - decoded, err := decodeLicenseKey(&invalidJson) - assert.Empty(t, decoded) - assert.Error(t, err) -} - -func TestDecodeLicenseKeyValidButWrong(t *testing.T) { - badJson := "{\"some\": \"garbage\"}" - decoded, err := decodeLicenseKey(&badJson) - assert.Empty(t, decoded) - assert.Error(t, err) -} diff --git a/examples/sam/go/go.mod b/examples/sam/go/go.mod new file mode 100644 index 0000000..e6af00d --- /dev/null +++ b/examples/sam/go/go.mod @@ -0,0 +1,19 @@ +module example-app + +go 1.19 + +require ( + github.com/newrelic/go-agent/v3 v3.20.3 + github.com/newrelic/go-agent/v3/integrations/nrlambda v1.2.1 +) + +require ( + github.com/aws/aws-lambda-go v1.11.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + golang.org/x/net v0.0.0-20201021035429-f5854403a974 // indirect + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect + google.golang.org/grpc v1.49.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect +) diff --git a/examples/sam/go/main.go b/examples/sam/go/main.go index 96fd602..9962e23 100644 --- a/examples/sam/go/main.go +++ b/examples/sam/go/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/newrelic/go-agent/v3/integrations/nrlambda" "github.com/newrelic/go-agent/v3/newrelic" ) diff --git a/AwsLambdaExtension/extensionApi/client.go b/extensionApi/client.go similarity index 100% rename from AwsLambdaExtension/extensionApi/client.go rename to extensionApi/client.go diff --git a/go.mod b/go.mod index 97d758f..1835b0f 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,20 @@ -module github.com/newrelic/newrelic-lambda-extension +module newrelic-lambda-extension -go 1.14 +go 1.19 require ( - github.com/aws/aws-lambda-go v1.19.1 // indirect - github.com/aws/aws-sdk-go v1.34.21 - github.com/golang/protobuf v1.4.2 // indirect - github.com/google/go-github/v44 v44.1.0 - github.com/google/uuid v1.1.2 - github.com/newrelic/go-agent/v3 v3.9.0 - github.com/newrelic/go-agent/v3/integrations/nrlambda v1.2.0 - github.com/stretchr/testify v1.6.1 - golang.org/x/mod v0.4.1 - google.golang.org/genproto v0.0.0-20200910191746-8ad3c7ee2cd1 // indirect - google.golang.org/grpc v1.32.0 // indirect - google.golang.org/protobuf v1.25.0 // indirect + github.com/aws/aws-sdk-go v1.44.176 + github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 + github.com/google/uuid v1.3.0 + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.7.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.1.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) diff --git a/go.sum b/go.sum index ede4b67..679a256 100644 --- a/go.sum +++ b/go.sum @@ -1,145 +1,58 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.11.0/go.mod h1:Rr2SMTLeSMKgD45uep9V/NP8tnbCcySgu04cx0k/6cw= -github.com/aws/aws-lambda-go v1.19.1 h1:5iUHbIZ2sG6Yq/J1IN3sWm3+vAB1CWwhI21NffLNuNI= -github.com/aws/aws-lambda-go v1.19.1/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/aws/aws-sdk-go v1.34.21 h1:M97FXuiJgDHwD4mXhrIZ7RJ4xXV6uZVPvIC2qb+HfYE= -github.com/aws/aws-sdk-go v1.34.21/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/aws/aws-sdk-go v1.44.176 h1:mxcfI3IWHemX+5fEKt5uqIS/hdbaR7qzGfJYo5UyjJE= +github.com/aws/aws-sdk-go v1.44.176/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v44 v44.1.0 h1:shWPaufgdhr+Ad4eo/pZv9ORTxFpsxPEPEuuXAKIQGA= -github.com/google/go-github/v44 v44.1.0/go.mod h1:iWn00mWcP6PRWHhXm0zuFJ8wbEjE5AGO5D5HXYM4zgw= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/newrelic/go-agent/v3 v3.4.0/go.mod h1:H28zDNUC0U/b7kLoY4EFOhuth10Xu/9dchozUiOseQQ= -github.com/newrelic/go-agent/v3 v3.9.0 h1:5bcTbdk/Up5cIYIkQjCG92Y+uNoett9wmhuz4kPiFlM= -github.com/newrelic/go-agent/v3 v3.9.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= -github.com/newrelic/go-agent/v3/integrations/nrlambda v1.2.0 h1:pVLK1gx8YsOoI3EpEZ44HOL5GAnOVNkFx50ZJNKxUBk= -github.com/newrelic/go-agent/v3/integrations/nrlambda v1.2.0/go.mod h1:IZemD4LiJXNBAV652z2x3Awa1Z9Rlx7hEO4OUyqnr+U= +github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259 h1:ZHJ7+IGpuOXtVf6Zk/a3WuHQgkC+vXwaqfUBDFwahtI= +github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200910191746-8ad3c7ee2cd1 h1:Oi/dETbxPPblvoi4hgkzJun62A4dwuBsTM0UcZYpN3U= -google.golang.org/genproto v0.0.0-20200910191746-8ad3c7ee2cd1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/lambda/extension/api/api.go b/lambda/extension/api/api.go deleted file mode 100644 index 9e1c77a..0000000 --- a/lambda/extension/api/api.go +++ /dev/null @@ -1,116 +0,0 @@ -// Package api contains types and constants for interacting with the AWS Lambda Extension API. -package api - -import ( - "fmt" - "time" -) - -// LifecycleEvent represents lifecycle events that the extension can express interest in -type LifecycleEvent string -type ShutdownReason string -type LogEventType string - -const ( - Invoke LifecycleEvent = "INVOKE" - Shutdown LifecycleEvent = "SHUTDOWN" - - Spindown ShutdownReason = "spindown" - Timeout ShutdownReason = "timeout" - Failure ShutdownReason = "failure" - - Platform LogEventType = "platform" - Function = "function" - Extension = "extension" - - Version string = "2020-01-01" - LogsApiVersion = "2020-08-15" - - LambdaHostPortEnvVar = "AWS_LAMBDA_RUNTIME_API" - - ExtensionNameHeader = "Lambda-Extension-Name" - ExtensionIdHeader = "Lambda-Extension-Identifier" - ExtensionErrorTypeHeader = "Lambda-Extension-Function-Error-Type" - - LogBufferDefaultBytes uint32 = 1024 * 1024 - LogBufferDefaultItems uint32 = 10_000 - LogBufferDefaultTimeout uint32 = 500 -) - -type InvocationEvent struct { - // Either INVOKE or SHUTDOWN. - EventType LifecycleEvent `json:"eventType"` - // The instant that the invocation times out, as epoch milliseconds - DeadlineMs int64 `json:"deadlineMs"` - // The AWS Request ID, for INVOKE events. - RequestID string `json:"requestId"` - // The ARN of the function being invoked, for INVOKE events. - InvokedFunctionARN string `json:"invokedFunctionArn"` - // XRay trace ID, for INVOKE events. - Tracing map[string]string `json:"tracing"` - // The reason for termination, if this is a shutdown event - ShutdownReason ShutdownReason `json:"shutdownReason"` -} - -type RegistrationRequest struct { - Events []LifecycleEvent `json:"events"` -} - -type RegistrationResponse struct { - FunctionName string `json:"functionName"` - FunctionVersion string `json:"functionVersion"` - Handler string `json:"handler"` -} - -type LogSubscription struct { - Buffering BufferingCfg `json:"buffering"` - Destination DestinationCfg `json:"destination"` - Types []LogEventType `json:"types"` -} - -func NewLogSubscription(bufferingCfg BufferingCfg, destinationCfg DestinationCfg, types []LogEventType) *LogSubscription { - return &LogSubscription{ - Buffering: bufferingCfg, - Destination: destinationCfg, - Types: types, - } -} - -func DefaultLogSubscription(types []LogEventType, port uint16) *LogSubscription { - endpoint := formatLogsEndpoint(port) - - return NewLogSubscription( - BufferingCfg{ - MaxBytes: LogBufferDefaultBytes, - MaxItems: LogBufferDefaultItems, - TimeoutMs: LogBufferDefaultTimeout, - }, - DestinationCfg{ - URI: endpoint, - Protocol: "HTTP", - }, - types, - ) -} - -func formatLogsEndpoint(port uint16) string { - return fmt.Sprintf("http://sandbox:%d", port) -} - -type BufferingCfg struct { - MaxBytes uint32 `json:"maxBytes"` - MaxItems uint32 `json:"maxItems"` - TimeoutMs uint32 `json:"timeoutMs"` -} - -type DestinationCfg struct { - URI string `json:"URI"` - Protocol string `json:"protocol"` - //Port uint16 `json:"port"` //Not used by us -} - -type LogEvent struct { - Time time.Time `json:"time"` - Type string `json:"type"` - Record interface{} `json:"record"` -} diff --git a/lambda/extension/api/api_test.go b/lambda/extension/api/api_test.go deleted file mode 100644 index c539b56..0000000 --- a/lambda/extension/api/api_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package api - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func Test_formatLogsEndpoint(t *testing.T) { - endpoint := formatLogsEndpoint(1234) - - assert.Equal(t, "http://sandbox:1234", endpoint) -} - -func Test_DefaultLogSubscription(t *testing.T) { - types := []LogEventType{Platform} - sub := DefaultLogSubscription(types, 2345) - - assert.Equal(t, LogBufferDefaultBytes, sub.Buffering.MaxBytes) - assert.Equal(t, LogBufferDefaultItems, sub.Buffering.MaxItems) - assert.Equal(t, LogBufferDefaultTimeout, sub.Buffering.TimeoutMs) - - assert.Equal(t, "http://sandbox:2345", sub.Destination.URI) - assert.Equal(t, "HTTP", sub.Destination.Protocol) - assert.Equal(t, types, sub.Types) -} diff --git a/lambda/extension/client/client.go b/lambda/extension/client/client.go deleted file mode 100644 index 61d4b6d..0000000 --- a/lambda/extension/client/client.go +++ /dev/null @@ -1,274 +0,0 @@ -// Package client is a generic client for the AWS Lambda Extension API. -// The API's lifecycle begins with execution of the extension binary, which is expected to register. -// The extension then makes blocking requests for the next event. The response to the next event request -// is either a notification of the next event, or a shutdown notification. -package client - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "path/filepath" - - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -// InvocationClient is used to poll for invocation events. It is produced as a result of successful -// registration. The zero value is not usable. -type InvocationClient struct { - version string - baseUrl string - httpClient http.Client - extensionId string -} - -// RegistrationClient is used to register, and acquire an InvocationClient. The zero value is not usable. -type RegistrationClient struct { - extensionName string - version string - baseUrl string - httpClient http.Client -} - -// Constructs a new RegistrationClient. This is the entry point. -func New(httpClient http.Client) *RegistrationClient { - exePath, err := os.Executable() - if err != nil { - util.Fatal(err) - } - - exeName := filepath.Base(exePath) - - return &RegistrationClient{ - extensionName: exeName, - version: api.Version, - baseUrl: os.Getenv(api.LambdaHostPortEnvVar), - httpClient: httpClient, - } -} - -// getRegisterURL returns the Lambda Extension register URL -func (rc *RegistrationClient) getRegisterURL() string { - return fmt.Sprintf("http://%s/%s/extension/register", rc.baseUrl, rc.version) -} - -// RegisterDefault registers for Invoke and Shutdown events, with no configuration parameters. -func (rc *RegistrationClient) RegisterDefault(ctx context.Context) (*InvocationClient, *api.RegistrationResponse, error) { - defaultEvents := []api.LifecycleEvent{api.Invoke, api.Shutdown} - defaultRequest := api.RegistrationRequest{Events: defaultEvents} - return rc.Register(ctx, defaultRequest) -} - -// Register registers, with custom registration parameters. -func (rc *RegistrationClient) Register(ctx context.Context, registrationRequest api.RegistrationRequest) (*InvocationClient, *api.RegistrationResponse, error) { - registrationRequestJson, err := json.Marshal(registrationRequest) - if err != nil { - return nil, nil, fmt.Errorf("error occurred while marshaling registration request %s", err) - } - - req, err := http.NewRequestWithContext(ctx, "POST", rc.getRegisterURL(), bytes.NewBuffer(registrationRequestJson)) - if err != nil { - return nil, nil, fmt.Errorf("error occurred while creating registration request %s", err) - } - - req.Header.Set(api.ExtensionNameHeader, rc.extensionName) - - res, err := rc.httpClient.Do(req) - if err != nil { - return nil, nil, fmt.Errorf("error occurred while making registration request %s", err) - } - - defer util.Close(res.Body) - - if res.StatusCode == http.StatusInternalServerError { - util.Panic("error occurred while making registration request: ", res.Status) - } - - if res.StatusCode != http.StatusOK { - return nil, nil, fmt.Errorf("error occurred while making registration request: %s", res.Status) - } - - bodyBytes, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, nil, err - } - - util.Debugf("Registration response: %s", bodyBytes) - - var registrationResponse api.RegistrationResponse - err = json.Unmarshal(bodyBytes, ®istrationResponse) - if err != nil { - return nil, nil, err - } - - id, exists := res.Header[api.ExtensionIdHeader] - if !exists { - return nil, nil, fmt.Errorf("missing extension identifier. Response body %s", bodyBytes) - } - - invocationClient := InvocationClient{rc.version, rc.baseUrl, rc.httpClient, id[0]} - return &invocationClient, ®istrationResponse, nil -} - -// getNextEventURL returns the Lambda Extension next event URL -func (ic *InvocationClient) getNextEventURL() string { - return fmt.Sprintf("http://%s/%s/extension/event/next", ic.baseUrl, ic.version) -} - -// getInitErrorURL returns the Lambda Extension initialization error URL -func (ic *InvocationClient) getInitErrorURL() string { - return fmt.Sprintf("http://%s/%s/extension/init/error", ic.baseUrl, ic.version) -} - -// getExitErrorURL returns the Lambda exit error URL -func (ic *InvocationClient) getExitErrorURL() string { - return fmt.Sprintf("http://%s/%s/extension/exit/error", ic.baseUrl, ic.version) -} - -// getLogRegistrationURL returns the Lambda Log Registration URL -func (ic *InvocationClient) getLogRegistrationURL() string { - return fmt.Sprintf("http://%s/%s/logs", ic.baseUrl, api.LogsApiVersion) -} - -// LogRegister registers for log events -func (ic *InvocationClient) LogRegister(ctx context.Context, subscriptionRequest *api.LogSubscription) error { - subscriptionRequestJson, err := json.Marshal(subscriptionRequest) - if err != nil { - return fmt.Errorf("error occurred while marshaling subscription request %s", err) - } - - util.Debugln("Log registration with request ", string(subscriptionRequestJson)) - - req, err := http.NewRequestWithContext(ctx, "PUT", ic.getLogRegistrationURL(), bytes.NewBuffer(subscriptionRequestJson)) - if err != nil { - return fmt.Errorf("error occurred while creating subscription request %s", err) - } - - req.Header.Set(api.ExtensionIdHeader, ic.extensionId) - - res, err := ic.httpClient.Do(req) - if err != nil { - return fmt.Errorf("error occurred while making log subscription request %s", err) - } - - defer util.Close(res.Body) - - if res.StatusCode == http.StatusInternalServerError { - util.Panic("error occurred while making log subscription request: ", res.Status) - } - - if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusAccepted { - return fmt.Errorf("error occurred while making log subscription request: %s", res.Status) - } - - responseBody, err := ioutil.ReadAll(res.Body) - if err != nil { - return err - } - - util.Debugln("Registered for logs. Got response code ", res.StatusCode, string(responseBody)) - - return nil -} - -// NextEvent awaits the next event. -func (ic *InvocationClient) NextEvent(ctx context.Context) (*api.InvocationEvent, error) { - req, err := http.NewRequestWithContext(ctx, "GET", ic.getNextEventURL(), nil) - if err != nil { - return nil, fmt.Errorf("error occurred when creating next request %s", err) - } - - req.Header.Set(api.ExtensionIdHeader, ic.extensionId) - - res, err := ic.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("error occurred when calling extension/event/next %s", err) - } - - defer util.Close(res.Body) - - if res.StatusCode == http.StatusInternalServerError { - util.Panic("error occurred when calling extension/event/next: ", res.Status) - } - - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("error occurred when calling extension/event/next: %s", res.Status) - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("error occurred while reading extension/event/next response body %s", err) - } - - var event api.InvocationEvent - err = json.Unmarshal(body, &event) - if err != nil { - return nil, fmt.Errorf("error occurred while unmarshaling extension/event/next response body %s", err) - } - - return &event, nil -} - -// InitError sends an initialization error to the lambda platform -func (ic *InvocationClient) InitError(ctx context.Context, errorEnum string, initError error) error { - errorBuf := bytes.NewBufferString(initError.Error()) - - req, err := http.NewRequestWithContext(ctx, "POST", ic.getInitErrorURL(), errorBuf) - if err != nil { - return fmt.Errorf("error occurred when creating init error request %s", err) - } - - req.Header.Set(api.ExtensionIdHeader, ic.extensionId) - req.Header.Set(api.ExtensionErrorTypeHeader, errorEnum) - - res, err := ic.httpClient.Do(req) - if err != nil { - return fmt.Errorf("error occurred when calling extension/init/error %s", err) - } - - defer util.Close(res.Body) - - if res.StatusCode == http.StatusInternalServerError { - util.Panic("error occurred while making init error request: ", res.Status) - } - - if res.StatusCode != http.StatusAccepted { - return fmt.Errorf("error occurred while making init error request: %s", res.Status) - } - - return nil -} - -// ExitError sends an exit error to the lambda platform -func (ic *InvocationClient) ExitError(ctx context.Context, errorEnum string, exitError error) error { - errorBuf := bytes.NewBufferString(exitError.Error()) - req, err := http.NewRequestWithContext(ctx, "POST", ic.getExitErrorURL(), errorBuf) - if err != nil { - return fmt.Errorf("error occurred when creating exit error request %s", err) - } - - req.Header.Set(api.ExtensionIdHeader, ic.extensionId) - req.Header.Set(api.ExtensionErrorTypeHeader, errorEnum) - - res, err := ic.httpClient.Do(req) - if err != nil { - return fmt.Errorf("error occurred when calling extension/exit/error %s", err) - } - - defer util.Close(res.Body) - - if res.StatusCode == http.StatusInternalServerError { - util.Panic("error occurred while making exit error request: ", res.Status) - } - - if res.StatusCode != http.StatusAccepted { - return fmt.Errorf("error occurred while making exit error request: %s", res.Status) - } - - return nil -} diff --git a/lambda/extension/client/client_test.go b/lambda/extension/client/client_test.go deleted file mode 100644 index c4e49c2..0000000 --- a/lambda/extension/client/client_test.go +++ /dev/null @@ -1,448 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "errors" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" - - "github.com/stretchr/testify/assert" -) - -var exePath, _ = os.Executable() -var exeName = filepath.Base(exePath) - -func TestNew(t *testing.T) { - _ = os.Setenv(api.LambdaHostPortEnvVar, "127.0.0.1:8123") - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - client := New(http.Client{}) - - assert.Equal(t, exeName, client.extensionName) -} - -func TestRegistrationClient_GetRegisterURL(t *testing.T) { - _ = os.Setenv(api.LambdaHostPortEnvVar, "127.0.0.1:8123") - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - client := New(http.Client{}) - - assert.Equal(t, "http://127.0.0.1:8123/2020-01-01/extension/register", client.getRegisterURL()) -} - -func TestRegistrationClient_RegisterDefault(t *testing.T) { - rc := RegistrationClient{} - ctx := context.Background() - ic, res, err := rc.RegisterDefault(ctx) - assert.Nil(t, ic) - assert.Nil(t, res) - assert.Error(t, err) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodPost) - assert.NotEmpty(t, r.Header.Get(api.ExtensionNameHeader)) - - reqBytes, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - defer util.Close(r.Body) - - assert.NotEmpty(t, reqBytes) - - var reqData api.RegistrationRequest - assert.NoError(t, json.Unmarshal(reqBytes, &reqData)) - assert.Equal(t, []api.LifecycleEvent{api.Invoke, api.Shutdown}, reqData.Events) - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - respBytes, _ := json.Marshal(api.RegistrationResponse{}) - _, _ = w.Write(respBytes) - })) - - defer srv.Close() - - url := srv.URL[7:] - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - client := New(*srv.Client()) - invocationClient, rr, err := client.RegisterDefault(ctx) - - assert.NoError(t, err) - assert.Equal(t, "test-ext-id", invocationClient.extensionId) - assert.NotNil(t, rr) - assert.NotEmpty(t, invocationClient.getInitErrorURL()) - assert.NotEmpty(t, invocationClient.getExitErrorURL()) - assert.NotEmpty(t, invocationClient.getLogRegistrationURL()) -} - -func TestRegistrationClient_RegisterError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(400) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - client := New(*srv.Client()) - ctx := context.Background() - ic, rr, err := client.RegisterDefault(ctx) - - assert.Nil(t, ic) - assert.Nil(t, rr) - assert.Error(t, err) -} - -func TestRegistrationClient_RegisterPanic(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(500) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - client := New(*srv.Client()) - ctx := context.Background() - - assert.Panics(t, func() { - client.RegisterDefault(ctx) - }) -} - -func TestInvocationClient_LogRegister(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodPut) - - assert.NotEmpty(t, r.Header.Get(api.ExtensionIdHeader)) - defer util.Close(r.Body) - - w.WriteHeader(200) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - eventTypes := []api.LogEventType{api.Platform} - subscriptionRequest := api.DefaultLogSubscription(eventTypes, 12345) - - ctx := context.Background() - err := client.LogRegister(ctx, subscriptionRequest) - - assert.NoError(t, err) -} - -func TestInvocationClient_LogRegisterError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(400) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - eventTypes := []api.LogEventType{api.Platform} - subscriptionRequest := api.DefaultLogSubscription(eventTypes, 12345) - - ctx := context.Background() - err := client.LogRegister(ctx, subscriptionRequest) - - assert.Error(t, err) -} - -func TestInvocationClient_LogRegisterEPanic(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(500) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - eventTypes := []api.LogEventType{api.Platform} - subscriptionRequest := api.DefaultLogSubscription(eventTypes, 12345) - - ctx := context.Background() - assert.Panics(t, func() { - client.LogRegister(ctx, subscriptionRequest) - }) -} - -func TestInvocationClient_InitError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodPost) - - assert.NotEmpty(t, r.Header.Get(api.ExtensionIdHeader)) - defer util.Close(r.Body) - - w.WriteHeader(202) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - err := client.InitError(ctx, "foo.bar", errors.New("something went wrong")) - - assert.NoError(t, err) -} - -func TestInvocationClient_InitErrorError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(400) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - err := client.InitError(ctx, "foo.bar", errors.New("something went wrong")) - - assert.Error(t, err) -} - -func TestInvocationClient_InitErrorPanic(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(500) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - assert.Panics(t, func() { - client.InitError(ctx, "foo.bar", errors.New("something went wrong")) - }) -} - -func TestInvocationClient_ExitError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodPost) - - assert.NotEmpty(t, r.Header.Get(api.ExtensionIdHeader)) - defer util.Close(r.Body) - - w.WriteHeader(202) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - err := client.ExitError(ctx, "foo.bar", errors.New("something went wrong")) - - assert.NoError(t, err) -} - -func TestInvocationClient_ExitErrorError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(400) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - err := client.ExitError(ctx, "foo.bar", errors.New("something went wrong")) - - assert.Error(t, err) -} - -func TestInvocationClient_ExitErrorPanic(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(500) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - assert.Panics(t, func() { - client.ExitError(ctx, "foo.bar", errors.New("something went wrong")) - }) -} - -func TestInvocationClient_NextEvent(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodGet) - - assert.NotEmpty(t, r.Header.Get(api.ExtensionIdHeader)) - defer util.Close(r.Body) - - w.WriteHeader(200) - respBytes, _ := json.Marshal(api.InvocationEvent{ - EventType: api.Invoke, - DeadlineMs: 1234, - RequestID: "5678", - InvokedFunctionARN: "arn:aws:test", - Tracing: nil, - }) - _, _ = w.Write(respBytes) - })) - - defer srv.Close() - - url := srv.URL[7:] - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - ctx := context.Background() - invocationEvent, err := client.NextEvent(ctx) - - assert.NoError(t, err) - assert.NotNil(t, invocationEvent) -} - -func TestInvocationClient_NextEventError(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(400) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - event, err := client.NextEvent(ctx) - - assert.Error(t, err) - assert.Nil(t, event) -} - -func TestInvocationClient_NextEventPanic(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - w.WriteHeader(500) - _, _ = w.Write(nil) - })) - defer srv.Close() - - url := srv.URL[7:] - - client := InvocationClient{ - version: api.Version, - baseUrl: url, - httpClient: *srv.Client(), - extensionId: "test-ext-id", - } - - ctx := context.Background() - assert.Panics(t, func() { - client.NextEvent(ctx) - }) -} diff --git a/lambda/logserver/logserver.go b/lambda/logserver/logserver.go deleted file mode 100644 index cc2ae92..0000000 --- a/lambda/logserver/logserver.go +++ /dev/null @@ -1,221 +0,0 @@ -package logserver - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "regexp" - "strconv" - "sync" - "time" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" -) - -const ( - platformLogBufferSize = 100 -) - -type LogLine struct { - Time time.Time - RequestID string - Content []byte -} - -type LogServer struct { - listenString string - server *http.Server - platformLogChan chan LogLine - functionLogChan chan []LogLine - lastRequestId string - lastRequestIdLock *sync.Mutex -} - -func (ls *LogServer) Port() uint16 { - _, portStr, _ := net.SplitHostPort(ls.listenString) - port, _ := strconv.ParseUint(portStr, 10, 16) - return uint16(port) -} - -func (ls *LogServer) Close() error { - // Pause briefly to allow final platform logs to arrive - time.Sleep(200 * time.Millisecond) - - ret := ls.server.Close() - close(ls.platformLogChan) - close(ls.functionLogChan) - return ret -} - -func (ls *LogServer) PollPlatformChannel() []LogLine { - var ret []LogLine - - for { - select { - case report, more := <-ls.platformLogChan: - if more { - ret = append(ret, report) - } else { - return ret - } - default: - return ret - } - } -} - -func (ls *LogServer) AwaitFunctionLogs() ([]LogLine, bool) { - ll, more := <-ls.functionLogChan - return ll, more -} - -func formatReport(metrics map[string]interface{}) string { - ret := "" - - if val, ok := metrics["durationMs"]; ok { - ret += fmt.Sprintf("\tDuration: %.2f ms", val) - } - - if val, ok := metrics["billedDurationMs"]; ok { - ret += fmt.Sprintf("\tBilled Duration: %.0f ms", val) - } - - if val, ok := metrics["memorySizeMB"]; ok { - ret += fmt.Sprintf("\tMemory Size: %.0f MB", val) - } - - if val, ok := metrics["maxMemoryUsedMB"]; ok { - ret += fmt.Sprintf("\tMax Memory Used: %.0f MB", val) - } - - if val, ok := metrics["initDurationMs"]; ok { - ret += fmt.Sprintf("\tInit Duration: %.2f ms", val) - } - - return ret -} - -var reportStringRegExp, _ = regexp.Compile("RequestId: ([a-fA-F0-9-]+)(.*)") - -func (ls *LogServer) handler(res http.ResponseWriter, req *http.Request) { - defer util.Close(req.Body) - - bodyBytes, err := ioutil.ReadAll(req.Body) - if err != nil { - util.Logf("Error processing log request: %v", err) - } - - var logEvents []api.LogEvent - err = json.Unmarshal(bodyBytes, &logEvents) - if err != nil { - util.Logf("Error parsing log payload: %v", err) - } - - var functionLogs []LogLine - - for _, event := range logEvents { - switch event.Type { - case "platform.start": - ls.lastRequestIdLock.Lock() - switch event.Record.(type) { - case map[string]interface{}: - ls.lastRequestId = event.Record.(map[string]interface{})["requestId"].(string) - case string: - recordString := event.Record.(string) - results := reportStringRegExp.FindStringSubmatch(recordString) - if len(results) > 1 { - ls.lastRequestId = results[1] - } - } - ls.lastRequestIdLock.Unlock() - case "platform.report": - metricString := "" - requestId := "" - switch event.Record.(type) { - case map[string]interface{}: - record := event.Record.(map[string]interface{}) - metrics := record["metrics"].(map[string]interface{}) - metricString = formatReport(metrics) - requestId = record["requestId"].(string) - case string: - recordString := event.Record.(string) - results := reportStringRegExp.FindStringSubmatch(recordString) - if len(results) > 1 { - requestId = results[1] - if len(results) > 2 { - metricString = results[2] - } - } else { - util.Debugf("Unknown platform log: %s", recordString) - } - } - - reportStr := fmt.Sprintf( - "REPORT RequestId: %v%s", - requestId, - metricString, - ) - reportLine := LogLine{ - Time: event.Time, - RequestID: requestId, - Content: []byte(reportStr), - } - ls.platformLogChan <- reportLine - case "platform.logsDropped": - util.Logf("Platform dropped logs: %v", event.Record) - case "function": - record := event.Record.(string) - ls.lastRequestIdLock.Lock() - functionLogs = append(functionLogs, LogLine{ - Time: event.Time, - RequestID: ls.lastRequestId, - Content: []byte(record), - }) - ls.lastRequestIdLock.Unlock() - default: - //util.Debugln("Ignored log event of type ", event.Type, string(bodyBytes)) - } - } - - if len(functionLogs) > 0 { - ls.functionLogChan <- functionLogs - } - - _, _ = res.Write(nil) -} - -func Start(conf *config.Configuration) (*LogServer, error) { - return startInternal(conf.LogServerHost) -} - -func startInternal(host string) (*LogServer, error) { - listener, err := net.Listen("tcp", host+":") - if err != nil { - return nil, err - } - - server := &http.Server{} - - logServer := &LogServer{ - listenString: listener.Addr().String(), - server: server, - platformLogChan: make(chan LogLine, platformLogBufferSize), - functionLogChan: make(chan []LogLine), - lastRequestIdLock: &sync.Mutex{}, - } - - mux := http.NewServeMux() - mux.HandleFunc("/", logServer.handler) - server.Handler = mux - - go func() { - util.Logln("Starting log server.") - util.Logf("Log server terminating: %v\n", server.Serve(listener)) - }() - - return logServer, nil -} diff --git a/lambda/logserver/logserver_test.go b/lambda/logserver/logserver_test.go deleted file mode 100644 index dd38754..0000000 --- a/lambda/logserver/logserver_test.go +++ /dev/null @@ -1,205 +0,0 @@ -//go:build !race -// +build !race - -package logserver - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "testing" - "time" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/stretchr/testify/assert" -) - -func TestLogServer(t *testing.T) { - logs, err := startInternal("localhost") - assert.NoError(t, err) - - testEvents := []api.LogEvent{ - { - Time: time.Now(), - Type: "platform.report", - Record: map[string]interface{}{ - "metrics": map[string]float64{ - "durationMs": 25.3, - "billedDurationMs": 100.0, - "memorySizeMB": 128.0, - "maxMemoryUsedMB": 73.5, - "initDurationMs": 202.0, - }, - "requestId": "testRequestId", - }, - }, - } - - testEventBytes, err := json.Marshal(testEvents) - assert.NoError(t, err) - - realEndpoint := fmt.Sprintf("http://localhost:%d", logs.Port()) - req, err := http.NewRequest("POST", realEndpoint, bytes.NewBuffer(testEventBytes)) - assert.NoError(t, err) - - client := http.Client{} - res, err := client.Do(req) - - assert.NoError(t, err) - assert.Equal(t, 200, res.StatusCode) - assert.Equal(t, http.NoBody, res.Body) - - logLines := logs.PollPlatformChannel() - - assert.Equal(t, 1, len(logLines)) - assert.Equal(t, "REPORT RequestId: testRequestId\tDuration: 25.30 ms\tBilled Duration: 100 ms\tMemory Size: 128 MB\tMax Memory Used: 74 MB\tInit Duration: 202.00 ms", string(logLines[0].Content)) - - assert.Nil(t, logs.Close()) -} - -func TestFunctionLogs(t *testing.T) { - logs, err := startInternal("localhost") - assert.NoError(t, err) - - testEvents := []api.LogEvent{ - { - Time: time.Now().Add(-100 * time.Millisecond), - Type: "platform.start", - Record: map[string]interface{}{ - "requestId": "testRequestId", - }, - }, - { - Time: time.Now().Add(-50 * time.Millisecond), - Type: "function", - Record: "log line 1", - }, - } - - testEventBytes, err := json.Marshal(testEvents) - assert.NoError(t, err) - - realEndpoint := fmt.Sprintf("http://localhost:%d", logs.Port()) - req, err := http.NewRequest("POST", realEndpoint, bytes.NewBuffer(testEventBytes)) - assert.NoError(t, err) - - client := http.Client{} - go func() { - res, err := client.Do(req) - - assert.NoError(t, err) - assert.Equal(t, 200, res.StatusCode) - assert.Equal(t, http.NoBody, res.Body) - }() - - logLines, _ := logs.AwaitFunctionLogs() - - assert.Equal(t, 1, len(logLines)) - assert.Equal(t, "log line 1", string(logLines[0].Content)) - assert.Equal(t, "testRequestId", logLines[0].RequestID) - - testEvents2 := []api.LogEvent{ - { - Time: time.Now().Add(500 * time.Millisecond), - Type: "function", - Record: "log line 2", - }, - } - - testEventBytes, err = json.Marshal(testEvents2) - assert.NoError(t, err) - - req, err = http.NewRequest("POST", realEndpoint, bytes.NewBuffer(testEventBytes)) - assert.NoError(t, err) - - go func() { - res, err := client.Do(req) - assert.NoError(t, err) - assert.Equal(t, 200, res.StatusCode) - assert.Equal(t, http.NoBody, res.Body) - }() - - logLines2, _ := logs.AwaitFunctionLogs() - - assert.Equal(t, 1, len(logLines2)) - assert.Equal(t, "log line 2", string(logLines2[0].Content)) - assert.Equal(t, "testRequestId", logLines2[0].RequestID) - - testRequestId := "abcdef01-a2b3-4321-cd89-0123456789ab" - - testEvents3 := []api.LogEvent{ - { - Time: time.Now().Add(600 * time.Millisecond), - Type: "platform.start", - Record: "RequestId: " + testRequestId, - }, - { - Time: time.Now().Add(700 * time.Millisecond), - Type: "function", - Record: "log line 3, for testing start line record as string", - }, - } - - testEventBytes, err = json.Marshal(testEvents3) - assert.NoError(t, err) - - req, err = http.NewRequest("POST", realEndpoint, bytes.NewBuffer(testEventBytes)) - assert.NoError(t, err) - - go func() { - res, err := client.Do(req) - assert.NoError(t, err) - assert.Equal(t, 200, res.StatusCode) - assert.Equal(t, http.NoBody, res.Body) - }() - - logLines3, _ := logs.AwaitFunctionLogs() - - assert.Equal(t, 1, len(logLines3)) - assert.Equal(t, "log line 3, for testing start line record as string", string(logLines3[0].Content)) - assert.Equal(t, testRequestId, logLines3[0].RequestID) - - platformMetricString := "REPORT RequestId: " + testRequestId + "\tDuration: 25.30 ms\tBilled Duration: 100 ms\tMemory Size: 128 MB\tMax Memory Used: 74 MB\tInit Duration: 202.00 ms" - - testEvents4 := []api.LogEvent{ - { - Time: time.Now().Add(800 * time.Millisecond), - Type: "platform.report", - Record: platformMetricString, - }, - { - Time: time.Now().Add(900 * time.Millisecond), - Type: "function", - Record: "log line 4, testing platform metrics as string", - }, - } - - testEventBytes, err = json.Marshal(testEvents4) - assert.NoError(t, err) - - req, err = http.NewRequest("POST", realEndpoint, bytes.NewBuffer(testEventBytes)) - assert.NoError(t, err) - - go func() { - res, err := client.Do(req) - assert.NoError(t, err) - assert.Equal(t, 200, res.StatusCode) - assert.Equal(t, http.NoBody, res.Body) - }() - - logLines4, _ := logs.AwaitFunctionLogs() - - assert.Equal(t, 1, len(logLines4)) - assert.Equal(t, "log line 4, testing platform metrics as string", string(logLines4[0].Content)) - assert.Equal(t, testRequestId, logLines4[0].RequestID) - - assert.Nil(t, logs.Close()) -} - -func TestLogServerStart(t *testing.T) { - logs, err := Start(&config.Configuration{LogServerHost: "localhost"}) - assert.NoError(t, err) - assert.Nil(t, logs.Close()) -} diff --git a/local-build.sh b/local-build.sh deleted file mode 100755 index a8656f1..0000000 --- a/local-build.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -set -x - -echo "Building newrelic-lambda-extension..." - -cat << EOF > Dockerfile -FROM golang:latest -WORKDIR /newrelic-lambda-extension -ENTRYPOINT [ "./build-arm64.sh" ] -EOF - -cat << EOF > build-arm64.sh -#!/bin/sh -set -xeu - -go mod tidy -rm -rf extensions -rm -f preview-extensions-ggqizro707 -rm -f /tmp/newrelic-lambda-extension.x86_64.zip -rm -f /tmp/newrelic-lambda-extension.arm64.zip -env GOARCH=arm64 GOOS=linux go build -ldflags="-s -w" -o ./extensions/newrelic-lambda-extension -touch preview-extensions-ggqizro707 -EOF - -chmod +x build-arm64.sh - -docker build . -t build-lambda-extension-m1:latest -docker run -v /Users/emilio/Dev/newrelic-lambda-extension:/newrelic-lambda-extension build-lambda-extension-m1:latest - -rm Dockerfile -rm build-arm64.sh -echo "Done" - -echo "Building Telemetry API Extension..." -cd AwsLambdaExtension - -cat << EOF > Dockerfile -FROM golang:latest -WORKDIR /AwsLambdaExtension -ENTRYPOINT [ "./build-deploy.sh" ] -EOF - -docker build . -t build-telemetry-extension-m1:latest -docker run -v /Users/emilio/Dev/newrelic-lambda-extension/AwsLambdaExtension:/AwsLambdaExtension build-telemetry-extension-m1:latest - -rm Dockerfile -zip -r extension.zip ./extensions/ -mv extension.zip ../extensions -mv extensions/AwsLambdaExtension ../extensions -echo "Done" -exit 0 diff --git a/main.go b/main.go index 03da484..b295773 100644 --- a/main.go +++ b/main.go @@ -1,358 +1,117 @@ package main +/* +Notes: +- Because of the asynchronous nature of the system, it is possible that telemetry for one invoke will be + processed during the next invoke slice. Likewise, it is possible that telemetry for the last invoke will + be processed during the SHUTDOWN event. +*/ + import ( "context" - "encoding/base64" - "fmt" - "net/http" + "newrelic-lambda-extension/agentTelemetry" + "newrelic-lambda-extension/config" + "newrelic-lambda-extension/extensionApi" + "newrelic-lambda-extension/telemetryApi" + "os" "os/signal" - "sync" "syscall" - "time" - "github.com/newrelic/newrelic-lambda-extension/checks" - "github.com/newrelic/newrelic-lambda-extension/lambda/logserver" - "github.com/newrelic/newrelic-lambda-extension/util" - - "github.com/newrelic/newrelic-lambda-extension/config" - "github.com/newrelic/newrelic-lambda-extension/credentials" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/client" - "github.com/newrelic/newrelic-lambda-extension/telemetry" + log "github.com/sirupsen/logrus" ) var ( - invokedFunctionARN string - lastEventStart time.Time - lastRequestId string - rootCtx context.Context + l = log.WithFields(log.Fields{"pkg": "main"}) ) -func init() { - rootCtx = context.Background() -} - func main() { - extensionStartup := time.Now() - - ctx, cancel := context.WithCancel(rootCtx) - defer cancel() - - // exit cleanly on SIGTERM or SIGINT + // Handle User Configured Settings + conf := config.GetConfig() + log.SetLevel(conf.LogLevel) + log.SetFormatter(&log.TextFormatter{ + DisableTimestamp: true, + }) + + l.Info("[main] Starting the New Relic Telemetry API extension") + ctx, cancel := context.WithCancel(context.Background()) sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) go func() { + // ctrl + c escape s := <-sigs cancel() - util.Logf("Received %v Exiting", s) + l.Info("[main] Received", s) + l.Info("[main] Exiting") }() - // Allow extension to be interrupted with CTRL-C - ctrlCChan := make(chan os.Signal, 1) - signal.Notify(ctrlCChan, os.Interrupt) - go func() { - for range ctrlCChan { - cancel() - util.Fatal("Exiting...") - } - }() - - // Parse various env vars for our config - conf := config.ConfigurationFromEnvironment() - - // Optionally enable debug logging, disabled by default - util.ConfigLogger(conf.LogsEnabled, conf.LogLevel == config.DebugLogLevel) - - // Extensions must register - registrationClient := client.New(http.Client{}) - - regReq := api.RegistrationRequest{ - Events: []api.LifecycleEvent{api.Invoke, api.Shutdown}, - } - - invocationClient, registrationResponse, err := registrationClient.Register(ctx, regReq) + // Step 1 - Register the extension with Extensions API + l.Debug("[main] Registering extension") + extensionApiClient := extensionApi.NewClient(conf.LogLevel) + extensionId, err := extensionApiClient.Register(ctx, conf.ExtensionName) if err != nil { - util.Panic(err) + l.Fatal(err) } + l.Debug("[main] Registation success with extensionId", extensionId) - // If extension disabled, go into no op mode - if !conf.ExtensionEnabled { - util.Logln("Extension telemetry processing disabled") - noopLoop(ctx, invocationClient) - return - } - - // Attempt to find the license key for telemetry sending - licenseKey, err := credentials.GetNewRelicLicenseKey(ctx, conf) + // Step 2 - Start the local http listener which will receive data from Telemetry API + l.Debug("[main] Starting the Telemetry listener") + telemetryListener := telemetryApi.NewTelemetryApiListener() + telemetryListenerUri, err := telemetryListener.Start() if err != nil { - util.Logln("Failed to retrieve New Relic license key", err) - // We fail open; telemetry will go to CloudWatch instead - noopLoop(ctx, invocationClient) - return + l.Fatal(err) } - // Set up the telemetry buffer - batch := telemetry.NewBatch(int64(conf.RipeMillis), int64(conf.RotMillis), conf.CollectTraceID) - - // Init the telemetry sending client - telemetryClient := telemetry.New(registrationResponse.FunctionName, licenseKey, conf.TelemetryEndpoint, conf.LogEndpoint, batch, conf.CollectTraceID, conf.ClientTimeout) - telemetryChan, err := telemetry.InitTelemetryChannel() + // Step 3 - Subscribe the listener to Telemetry API + l.Debug("[main] Subscribing to the Telemetry API") + telemetryApiClient := telemetryApi.NewClient(conf.LogLevel) + _, err = telemetryApiClient.Subscribe(ctx, extensionId, telemetryListenerUri) if err != nil { - err2 := invocationClient.InitError(ctx, "telemetryClient.init", err) - if err2 != nil { - util.Logln(err2) - } - util.Panic("telemetry pipe init failed: ", err) + l.Fatal(err) } + l.Debug("[main] Subscription success") + dispatcher := telemetryApi.NewDispatcher( + extensionApiClient.GetFunctionName(), + &conf, + ctx, + conf.TelemetryAPIBatchSize, + ) - // Run startup checks - go func() { - checks.RunChecks(ctx, conf, registrationResponse, telemetryClient) - }() - - backgroundTasks := &sync.WaitGroup{} - var logServer *logserver.LogServer + // Set up new relic agent telemetry dispatcher + agentDispatcher := agentTelemetry.NewDispatcher(conf) - // When telemetry API is enabled, do not subscribe to log API events or run the log server - if !conf.TelemetryAPIEnabled { - // Start the Logs API server, and register it - logServer, err = logserver.Start(conf) - if err != nil { - err2 := invocationClient.InitError(ctx, "logServer.start", err) - if err2 != nil { - util.Logln(err2) - } - util.Panic("Failed to start logs HTTP server", err) - } - - eventTypes := []api.LogEventType{api.Platform} - if conf.SendFunctionLogs { - eventTypes = append(eventTypes, api.Function) - } - subscriptionRequest := api.DefaultLogSubscription(eventTypes, logServer.Port()) - err = invocationClient.LogRegister(ctx, subscriptionRequest) - if err != nil { - err2 := invocationClient.InitError(ctx, "logServer.register", err) - if err2 != nil { - util.Logln(err2) - } - util.Panic("Failed to register with Logs API", err) - } - - // Send function logs as they arrive. When disabled, function logs aren't delivered to the extension. - backgroundTasks.Add(1) - - go func() { - defer backgroundTasks.Done() - logShipLoop(ctx, logServer, telemetryClient) - }() - } - - // Call next, and process telemetry, until we're shut down - eventCounter := mainLoop(ctx, invocationClient, batch, telemetryChan, logServer, telemetryClient) - - util.Logf("New Relic Extension shutting down after %v events\n", eventCounter) - - if !conf.TelemetryAPIEnabled { - err = logServer.Close() - if err != nil { - util.Logln("Error shutting down Log API server", err) - } - - pollLogServer(logServer, batch) - } - - finalHarvest := batch.Close() - shipHarvest(ctx, finalHarvest, telemetryClient) - - util.Debugln("Waiting for background tasks to complete") - backgroundTasks.Wait() - - shutdownAt := time.Now() - ranFor := shutdownAt.Sub(extensionStartup) - util.Logf("Extension shutdown after %vms", ranFor.Milliseconds()) -} - -// logShipLoop ships function logs to New Relic as they arrive. -func logShipLoop(ctx context.Context, logServer *logserver.LogServer, telemetryClient *telemetry.Client) { - for { - functionLogs, more := logServer.AwaitFunctionLogs() - if !more { - return - } - - err := telemetryClient.SendFunctionLogs(ctx, invokedFunctionARN, functionLogs) - if err != nil { - util.Logf("Failed to send %d function logs", len(functionLogs)) - } - } -} - -// mainLoop repeatedly calls the /next api, and processes telemetry and platform logs. The timing is rather complicated. -func mainLoop(ctx context.Context, invocationClient *client.InvocationClient, batch *telemetry.Batch, telemetryChan chan []byte, logServer *logserver.LogServer, telemetryClient *telemetry.Client) int { - eventCounter := 0 - probablyTimeout := false - - for { - select { - case <-ctx.Done(): - // We're already done - return eventCounter - default: - // Our call to next blocks. It is likely that the container is frozen immediately after we call NextEvent. - event, err := invocationClient.NextEvent(ctx) - - // We've thawed. - eventStart := time.Now() - - if err != nil { - util.Logln(err) - err = invocationClient.ExitError(ctx, "NextEventError.Main", err) - if err != nil { - util.Logln(err) - } - continue - } - - eventCounter++ - - if probablyTimeout { - // We suspect a timeout. Either way, we've gotten to the next event, so telemetry will - // have arrived for the last request if it's going to. Non-blocking poll for telemetry. - // If we have indeed timed out, there's a chance we got telemetry out anyway. If we haven't - // timed out, this will catch us up to the current state of telemetry, allowing us to resume. - select { - case telemetryBytes := <-telemetryChan: - // We received telemetry - util.Debugf("Agent telemetry bytes: %s", base64.URLEncoding.EncodeToString(telemetryBytes)) - batch.AddTelemetry(lastRequestId, telemetryBytes) - util.Logf("We suspected a timeout for request %s but got telemetry anyway", lastRequestId) - default: - } - } - - if event.EventType == api.Shutdown { - if event.ShutdownReason == api.Timeout && lastRequestId != "" { - // Synthesize the timeout error message that the platform produces, and LLC parses - timestamp := eventStart.UTC() - timeoutSecs := eventStart.Sub(lastEventStart).Seconds() - timeoutMessage := fmt.Sprintf( - "%s %s Task timed out after %.2f seconds", - timestamp.Format(time.RFC3339), - lastRequestId, - timeoutSecs, - ) - batch.AddTelemetry(lastRequestId, []byte(timeoutMessage)) - } else if event.ShutdownReason == api.Failure && lastRequestId != "" { - // Synthesize a generic platform error. Probably an OOM, though it could be any runtime crash. - errorMessage := fmt.Sprintf("RequestId: %s A platform error caused a shutdown", lastRequestId) - batch.AddTelemetry(lastRequestId, []byte(errorMessage)) - } - - return eventCounter - } else { - // Reset probablyTimeout if the event after the suspected timeout wasn't a timeout shutdown. - probablyTimeout = false - } - - // Note: shutdown events do not have these properties; we now know this is an invocation event. - invokedFunctionARN = event.InvokedFunctionARN - lastRequestId = event.RequestID - - // Create an invocation record to hold telemetry - batch.AddInvocation(lastRequestId, eventStart) - - // Await agent telemetry, which may time out. - - // timeoutInstant is when the invocation will time out - timeoutInstant := time.Unix(0, event.DeadlineMs*int64(time.Millisecond)) - - // Set the timeout timer for a smidge before the actual timeout; we can recover from false timeouts. - timeoutWatchBegins := 200 * time.Millisecond - timeLimitContext, timeLimitCancel := context.WithDeadline(ctx, timeoutInstant.Add(-timeoutWatchBegins)) - - // Before we begin to await telemetry, harvest and ship. Ripe telemetry will mostly be handled here. Even that is a - // minority of invocations. Putting this here lets us run the HTTP request to send to NR in parallel with the Lambda - // handler, reducing or eliminating our latency impact. - pollLogServer(logServer, batch) - shipHarvest(ctx, batch.Harvest(time.Now()), telemetryClient) - - select { - case <-timeLimitContext.Done(): - timeLimitCancel() - - // We are about to timeout - probablyTimeout = true - continue - case telemetryBytes := <-telemetryChan: - timeLimitCancel() - - // We received telemetry - util.Debugf("Agent telemetry bytes: %s", base64.URLEncoding.EncodeToString(telemetryBytes)) - inv := batch.AddTelemetry(lastRequestId, telemetryBytes) - if inv == nil { - util.Logf("Failed to add telemetry for request %v", lastRequestId) - } - - // Opportunity for an aggressive harvest, in which case, we definitely want to wait for the HTTP POST - // to complete. Mostly, nothing really happens here. - pollLogServer(logServer, batch) - shipHarvest(ctx, batch.Harvest(time.Now()), telemetryClient) - } - - lastEventStart = eventStart - } - } -} - -// pollLogServer polls for platform logs, and annotates telemetry -func pollLogServer(logServer *logserver.LogServer, batch *telemetry.Batch) { - if logServer == nil { - return - } - - for _, platformLog := range logServer.PollPlatformChannel() { - inv := batch.AddTelemetry(platformLog.RequestID, platformLog.Content) - if inv == nil { - util.Debugf("Skipping platform log for request %v", platformLog.RequestID) - } - } -} - -func shipHarvest(ctx context.Context, harvested []*telemetry.Invocation, telemetryClient *telemetry.Client) { - if len(harvested) > 0 { - telemetrySlice := make([][]byte, 0, 2*len(harvested)) - for _, inv := range harvested { - telemetrySlice = append(telemetrySlice, inv.Telemetry...) - } - - err, _ := telemetryClient.SendTelemetry(ctx, invokedFunctionARN, telemetrySlice) - if err != nil { - util.Logf("Failed to send harvested telemetry for %d invocations %s", len(harvested), err) - } - } -} - -func noopLoop(ctx context.Context, invocationClient *client.InvocationClient) { - util.Logln("Starting no-op mode, no telemetry will be sent") + l.Info("[main] New Relic Telemetry API Extension succesfully registered and subscribed") + // Will block until invoke or shutdown event is received or cancelled via the context. for { select { case <-ctx.Done(): return default: - event, err := invocationClient.NextEvent(ctx) + l.Debug("[main] Waiting for next event...") + + // This is a blocking action + res, err := extensionApiClient.NextEvent(ctx) if err != nil { - util.Logln(err) - errErr := invocationClient.ExitError(ctx, "NextEventError.Noop", err) - if errErr != nil { - util.Logln(errErr) - } - continue + l.Errorf("[main] Exiting. Error: %v", err) + return } - - if event.EventType == api.Shutdown { + l.Debugf("[main] Received event %+v", res) + + // Dispatching log events from previous invocations + agentDispatcher.AddEvent(res) + dispatcher.Dispatch(ctx, telemetryListener.LogEventsQueue, false) + agentDispatcher.Dispatch(ctx, res, false) + + if res.EventType == extensionApi.Invoke { + l.Debug("[handleInvoke]") + // we no longer care about this but keep it here just in case + } else if res.EventType == extensionApi.Shutdown { + // force dispatch all remaining telemetry, handle shutdown + l.Debug("[handleShutdown]") + dispatcher.Dispatch(ctx, telemetryListener.LogEventsQueue, true) + agentDispatcher.Dispatch(ctx, res, true) + l.Info("[main] New Relic Telemetry API Extension successfully shut down") return } } diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 3f625ad..0000000 --- a/main_test.go +++ /dev/null @@ -1,753 +0,0 @@ -//go:build !race -// +build !race - -package main - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "os" - "testing" - "time" - - "github.com/newrelic/newrelic-lambda-extension/lambda/extension/api" - "github.com/newrelic/newrelic-lambda-extension/util" - - "github.com/stretchr/testify/assert" -) - -// TODO: These tests are very repetitive. Helpers would be useful here. - -func TestMainRegisterFail(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(400) - _, _ = w.Write(nil) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - assert.Panics(t, main) -} - -func TestMainLogServerInitFail(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - ) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - // Shouldn't be able to bind to this locally - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "sandbox.localdomain") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.Panics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 1, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 0, logRegisterRequestCount) -} - -func TestMainLogServerRegisterFail(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - ) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(400) - _, _ = w.Write(nil) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "localhost") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.Panics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 1, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 1, logRegisterRequestCount) -} - -func TestMainShutdown(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - nextEventRequestCount int - ) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - util.Logln("Path: ", r.URL.Path) - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - nextEventRequestCount++ - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Shutdown, - DeadlineMs: 1, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: api.Timeout, - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "localhost") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.NotPanics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 0, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 1, logRegisterRequestCount) - assert.Equal(t, 1, nextEventRequestCount) -} - -func TestMainNoLicenseKey(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - nextEventRequestCount int - ) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - util.Logln("Path: ", r.URL.Path) - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - nextEventRequestCount++ - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Shutdown, - DeadlineMs: 1, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: api.Timeout, - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.NotPanics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 0, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 0, logRegisterRequestCount) - assert.Equal(t, 1, nextEventRequestCount) -} - -func TestMainExtensionDisabled(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - nextEventRequestCount int - ) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - util.Logln("Path: ", r.URL.Path) - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - nextEventRequestCount++ - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Shutdown, - DeadlineMs: 1, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: api.Timeout, - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED", "false") - defer os.Unsetenv("NEW_RELIC_LAMBDA_EXTENSION_ENABLED") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.NotPanics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 0, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 0, logRegisterRequestCount) - assert.Equal(t, 1, nextEventRequestCount) -} - -func TestMainTimeout(t *testing.T) { - var ( - registerRequestCount int - initErrorRequestCount int - exitErrorRequestCount int - logRegisterRequestCount int - nextEventRequestCount int - ) - - ctx, cancel := context.WithCancel(context.Background()) - overrideContext(ctx) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - registerRequestCount++ - - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - initErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - exitErrorRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-08-15/logs" { - logRegisterRequestCount++ - - w.WriteHeader(200) - _, _ = w.Write(nil) - - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - nextEventRequestCount++ - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Invoke, - DeadlineMs: 1000, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: "", - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - - cancel() - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "localhost") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - assert.NotPanics(t, main) - - assert.Equal(t, 1, registerRequestCount) - assert.Equal(t, 0, initErrorRequestCount) - assert.Equal(t, 0, exitErrorRequestCount) - assert.Equal(t, 1, logRegisterRequestCount) - assert.Equal(t, 1, nextEventRequestCount) -} - -func TestMainTimeoutUnreachable(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(200*time.Millisecond)) - defer cancel() - overrideContext(ctx) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "$latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - w.WriteHeader(200) - _, _ = w.Write(nil) - } - - if r.URL.Path == "/2020-08-15/logs" { - w.WriteHeader(200) - _, _ = w.Write(nil) - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - time.Sleep(25 * time.Millisecond) - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Invoke, - DeadlineMs: 100, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: "", - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/aws/lambda/v1" { - time.Sleep(5 * time.Second) - - w.WriteHeader(200) - _, _ = w.Write(nil) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "localhost") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - _ = os.Setenv("NEW_RELIC_TELEMETRY_ENDPOINT", fmt.Sprintf("%s/aws/lambda/v1", srv.URL)) - defer os.Unsetenv("NEW_RELIC_TELEMETRY_ENDPOINT") - - _ = os.Remove("/tmp/newrelic-telemetry") - - go func() { - pipeOpened := false - - for { - select { - case <-ctx.Done(): - return - default: - if _, err := os.Stat("/tmp/newrelic-telemetry"); os.IsNotExist(err) { - if pipeOpened { - return - } else { - continue - } - } else { - pipeOpened = true - } - - pipe, err := os.OpenFile("/tmp/newrelic-telemetry", os.O_WRONLY, 0) - assert.Nil(t, err) - defer pipe.Close() - - pipe.WriteString("foobar\n") - pipe.Close() - time.Sleep(100 * time.Millisecond) - } - } - }() - - assert.NotPanics(t, main) -} - -func TestMainTimeoutNoPipeWrite(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(200*time.Millisecond)) - defer cancel() - overrideContext(ctx) - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer util.Close(r.Body) - - if r.URL.Path == "/2020-01-01/extension/register" { - w.Header().Add(api.ExtensionIdHeader, "test-ext-id") - w.WriteHeader(200) - res, err := json.Marshal(api.RegistrationResponse{ - FunctionName: "foobar", - FunctionVersion: "$latest", - Handler: "lambda.handler", - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - - if r.URL.Path == "/2020-01-01/extension/init/error" { - w.WriteHeader(200) - _, _ = w.Write([]byte("")) - } - - if r.URL.Path == "/2020-01-01/extension/exit/error" { - w.WriteHeader(200) - _, _ = w.Write(nil) - } - - if r.URL.Path == "/2020-08-15/logs" { - w.WriteHeader(200) - _, _ = w.Write(nil) - } - - if r.URL.Path == "/2020-01-01/extension/event/next" { - time.Sleep(25 * time.Millisecond) - - w.WriteHeader(200) - res, err := json.Marshal(api.InvocationEvent{ - EventType: api.Invoke, - DeadlineMs: 100, - RequestID: "12345", - InvokedFunctionARN: "arn:aws:lambda:us-east-1:12345:foobar", - ShutdownReason: "", - Tracing: nil, - }) - assert.Nil(t, err) - _, _ = w.Write(res) - } - })) - defer srv.Close() - - url := srv.URL[7:] - - _ = os.Setenv(api.LambdaHostPortEnvVar, url) - defer os.Unsetenv(api.LambdaHostPortEnvVar) - - _ = os.Setenv("NEW_RELIC_LICENSE_KEY", "foobar") - defer os.Unsetenv("NEW_RELIC_LICENSE_KEY") - - _ = os.Setenv("NEW_RELIC_LOG_SERVER_HOST", "localhost") - defer os.Unsetenv("NEW_RELIC_LOG_SERVER_HOST") - - _ = os.Setenv("NEW_RELIC_EXTENSION_LOG_LEVEL", "DEBUG") - defer os.Unsetenv("NEW_RELIC_EXTENSION_LOG_LEVEL") - - _ = os.Remove("/tmp/newrelic-telemetry") - - go func() { - pipeOpened := false - - for { - select { - case <-ctx.Done(): - return - default: - if _, err := os.Stat("/tmp/newrelic-telemetry"); os.IsNotExist(err) { - if pipeOpened { - return - } else { - continue - } - } else { - pipeOpened = true - } - - pipe, err := os.OpenFile("/tmp/newrelic-telemetry", os.O_WRONLY, 0) - assert.Nil(t, err) - defer pipe.Close() - - time.Sleep(200 * time.Millisecond) - pipe.Close() - } - } - }() - - assert.NotPanics(t, main) -} - -func overrideContext(ctx context.Context) { - rootCtx = ctx -} diff --git a/telemetry/batch.go b/telemetry/batch.go deleted file mode 100644 index 7b9a573..0000000 --- a/telemetry/batch.go +++ /dev/null @@ -1,178 +0,0 @@ -package telemetry - -import ( - "math" - "sync" - "time" - - "github.com/newrelic/newrelic-lambda-extension/util" -) - -// The Unix epoch instant; used as a nil time for eldest and lastHarvest -var epochStart = time.Unix(0, 0) - -// Batch represents the unsent invocations and their telemetry, along with timing data. -type Batch struct { - extractTraceID bool - lastHarvest time.Time - eldest time.Time - ripeDuration time.Duration - veryOldDuration time.Duration - invocations map[string]*Invocation - lock sync.RWMutex -} - -// NewBatch constructs a new batch. -func NewBatch(ripeMillis, rotMillis int64, extractTraceID bool) *Batch { - initialSize := uint32(math.Min(float64(ripeMillis)/100, 100)) - return &Batch{ - lastHarvest: epochStart, - eldest: epochStart, - invocations: make(map[string]*Invocation, initialSize), - ripeDuration: time.Duration(ripeMillis) * time.Millisecond, - veryOldDuration: time.Duration(rotMillis) * time.Millisecond, - extractTraceID: extractTraceID, - } -} - -// AddInvocation should be called just after the next API response. It creates the Invocation record so that we can attach telemetry later. -func (b *Batch) AddInvocation(requestId string, start time.Time) { - b.lock.Lock() - defer b.lock.Unlock() - - invocation := NewInvocation(requestId, start) - b.invocations[requestId] = &invocation -} - -// AddTelemetry attaches telemetry to an existing Invocation, identified by requestId -func (b *Batch) AddTelemetry(requestId string, telemetry []byte) *Invocation { - b.lock.Lock() - defer b.lock.Unlock() - - inv, ok := b.invocations[requestId] - if ok { - inv.Telemetry = append(inv.Telemetry, telemetry) - if b.eldest.Equal(epochStart) { - b.eldest = inv.Start - } - if b.extractTraceID { - traceId, err := ExtractTraceID(telemetry) - if err != nil { - util.Debugln(err) - } - // We don't want to unset a previously set trace ID - if traceId != "" { - inv.TraceId = traceId - } - } - return inv - } - return nil -} - -// Harvest checks to see if it's time to harvest, and returns harvested invocations, or nil. The caller must ensure that harvested invocations are sent. -func (b *Batch) Harvest(now time.Time) []*Invocation { - b.lock.Lock() - defer b.lock.Unlock() - - if len(b.invocations) == 0 { - return nil - } - - veryOldTime := now.Add(-b.veryOldDuration) - if b.lastHarvest.Before(veryOldTime) { - return b.aggressiveHarvest(now) - } - - ripeTime := now.Add(-b.ripeDuration) - if b.eldest.Before(ripeTime) { - return b.ripeHarvest(now) - } - - return nil -} - -// Close aggressively harvests all telemetry from the Batch. The Batch is no longer valid. -func (b *Batch) Close() []*Invocation { - b.lock.Lock() - defer b.lock.Unlock() - - return b.aggressiveHarvest(time.Now()) -} - -// aggressiveHarvest harvests all invocations, ripe or not. It removes harvested invocations from the batch and updates the lastHarvest timestamp. -func (b *Batch) aggressiveHarvest(now time.Time) []*Invocation { - ret := make([]*Invocation, 0, len(b.invocations)) - for k, v := range b.invocations { - if !v.IsEmpty() { - ret = append(ret, v) - delete(b.invocations, k) - } - } - if len(ret) > 0 { - b.lastHarvest = now - b.eldest = epochStart - } - util.Debugf("Aggressive harvest yielded %d invocations\n", len(ret)) - return ret -} - -// ripeHarvest harvests all ripe invocations. It removes harvested invocations from the batch and updates the lastHarvest and eldest timestamps. -func (b *Batch) ripeHarvest(now time.Time) []*Invocation { - ret := make([]*Invocation, 0, len(b.invocations)) - newEldest := epochStart - for k, v := range b.invocations { - if v.IsRipe() { - ret = append(ret, v) - delete(b.invocations, k) - } else if newEldest.Equal(epochStart) || v.Start.Before(newEldest) { - newEldest = v.Start - } - } - b.eldest = newEldest - if len(ret) > 0 { - b.lastHarvest = now - } - util.Debugf("Ripe harvest yielded %d invocations\n", len(ret)) - return ret -} - -// RetrieveTraceID looks up a trace ID using the provided request ID -func (b *Batch) RetrieveTraceID(requestId string) string { - b.lock.RLock() - defer b.lock.RUnlock() - - inv, ok := b.invocations[requestId] - if ok { - return inv.TraceId - } - return "" -} - -// An Invocation holds telemetry for a request, and knows when the request began. -// Invocations are parts of a Batch, and should only be used by the batch object. -type Invocation struct { - Start time.Time - RequestId string - TraceId string - Telemetry [][]byte -} - -// NewInvocation creates an Invocation, which can hold telemetry -func NewInvocation(requestId string, start time.Time) Invocation { - return Invocation{ - Start: start, - RequestId: requestId, - Telemetry: make([][]byte, 0, 2), - } -} - -// IsRipe indicates that an Invocation has all the telemetry it's likely to get. Sending a ripe invocation won't omit data. -func (inv *Invocation) IsRipe() bool { - return len(inv.Telemetry) >= 2 -} - -// IsEmpty is true when the invocation has no telemetry. The invocation has begun, but has received no agent payload, nor platform logs. -func (inv *Invocation) IsEmpty() bool { - return len(inv.Telemetry) == 0 -} diff --git a/telemetry/batch_test.go b/telemetry/batch_test.go deleted file mode 100644 index f8f56a0..0000000 --- a/telemetry/batch_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package telemetry - -import ( - "bytes" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -const ( - testTelemetry = "test_telemetry" - moreTestTelemetry = "more_test_telemetry" - testRequestId = "test_a" - testRequestId2 = "test_b" - testRequestId3 = "test_c" - testNoSuchRequestId = "test_z" - ripe = 1000 - rot = 10000 -) - -var ( - requestStart = time.Unix(1603821157, 0) -) - -func TestMissingInvocation(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - invocation := batch.AddTelemetry(testNoSuchRequestId, bytes.NewBufferString(testTelemetry).Bytes()) - assert.Nil(t, invocation) -} - -func TestEmptyHarvest(t *testing.T) { - batch := NewBatch(ripe, rot, false) - res := batch.Harvest(requestStart) - - assert.Nil(t, res) -} - -func TestEmptyRotHarvest(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.AddInvocation("test", requestStart) - - res := batch.Harvest(requestStart) - - assert.Empty(t, res) -} - -func TestEmptyRipeHarvest(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.lastHarvest = requestStart.Add(-ripe) - batch.AddInvocation("test", requestStart) - - res := batch.Harvest(requestStart) - - assert.Empty(t, res) -} - -func TestWithInvocationRipeHarvest(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.lastHarvest = requestStart - - batch.AddInvocation(testRequestId, requestStart) - batch.AddInvocation(testRequestId2, requestStart.Add(100*time.Millisecond)) - batch.AddInvocation(testRequestId3, requestStart.Add(200*time.Millisecond)) - - invocation := batch.AddTelemetry(testRequestId, bytes.NewBufferString(testTelemetry).Bytes()) - assert.NotNil(t, invocation) - - invocation2 := batch.AddTelemetry(testRequestId, bytes.NewBufferString(moreTestTelemetry).Bytes()) - assert.Equal(t, invocation, invocation2) - - batch.AddTelemetry(testRequestId2, bytes.NewBufferString(testTelemetry).Bytes()) - - harvested := batch.Harvest(requestStart.Add(ripe*time.Millisecond + time.Millisecond)) - assert.Equal(t, 1, len(harvested)) - assert.Equal(t, testRequestId, harvested[0].RequestId) - assert.Equal(t, 2, len(harvested[0].Telemetry)) -} - -func TestWithInvocationAggressiveHarvest(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.AddInvocation(testRequestId, requestStart) - batch.AddInvocation(testRequestId2, requestStart.Add(100*time.Millisecond)) - batch.AddInvocation(testRequestId3, requestStart.Add(200*time.Millisecond)) - - invocation := batch.AddTelemetry(testRequestId, bytes.NewBufferString(testTelemetry).Bytes()) - assert.NotNil(t, invocation) - - invocation2 := batch.AddTelemetry(testRequestId, bytes.NewBufferString(moreTestTelemetry).Bytes()) - assert.Equal(t, invocation, invocation2) - - batch.AddTelemetry(testRequestId2, bytes.NewBufferString(testTelemetry).Bytes()) - - harvested := batch.Harvest(requestStart.Add(ripe*time.Millisecond + time.Millisecond)) - assert.Equal(t, 2, len(harvested)) -} - -func TestBatch_Close(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.AddInvocation(testRequestId, requestStart) - batch.AddInvocation(testRequestId2, requestStart.Add(100*time.Millisecond)) - batch.AddInvocation(testRequestId3, requestStart.Add(200*time.Millisecond)) - - invocation := batch.AddTelemetry(testRequestId, bytes.NewBufferString(testTelemetry).Bytes()) - assert.NotNil(t, invocation) - - invocation2 := batch.AddTelemetry(testRequestId, bytes.NewBufferString(moreTestTelemetry).Bytes()) - assert.Equal(t, invocation, invocation2) - - batch.AddTelemetry(testRequestId2, bytes.NewBufferString(testTelemetry).Bytes()) - - harvested := batch.Close() - assert.Equal(t, 2, len(harvested)) -} - -func TestBatchAsync(t *testing.T) { - batch := NewBatch(ripe, rot, false) - - batch.lastHarvest = requestStart - - wg := sync.WaitGroup{} - wg.Add(3) - - go func() { - batch.AddInvocation(testRequestId, requestStart) - wg.Done() - }() - go func() { - batch.AddInvocation(testRequestId2, requestStart.Add(100*time.Millisecond)) - wg.Done() - }() - go func() { - batch.AddInvocation(testRequestId3, requestStart.Add(200*time.Millisecond)) - wg.Done() - }() - - // Doing this to try to trigger a panic - go batch.RetrieveTraceID(testRequestId) - - wg.Wait() - - var invocation, invocation2 *Invocation - wg.Add(2) - - go func() { - invocation = batch.AddTelemetry(testRequestId, bytes.NewBufferString(testTelemetry).Bytes()) - wg.Done() - }() - go func() { - invocation2 = batch.AddTelemetry(testRequestId, bytes.NewBufferString(moreTestTelemetry).Bytes()) - wg.Done() - }() - - // Doing this to try to trigger a panic - go batch.RetrieveTraceID(testRequestId) - - wg.Wait() - assert.NotNil(t, invocation) - assert.Equal(t, invocation, invocation2) - - batch.AddTelemetry(testRequestId2, bytes.NewBufferString(testTelemetry).Bytes()) - - harvested := batch.Harvest(requestStart.Add(ripe*time.Millisecond + time.Millisecond)) - go assert.Equal(t, 1, len(harvested)) - go assert.Equal(t, testRequestId, harvested[0].RequestId) - go assert.Equal(t, 2, len(harvested[0].Telemetry)) -} diff --git a/telemetry/client.go b/telemetry/client.go deleted file mode 100644 index 5a4415c..0000000 --- a/telemetry/client.go +++ /dev/null @@ -1,307 +0,0 @@ -package telemetry - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "io/ioutil" - "log" - "net" - "net/http" - "strings" - "time" - - crypto_rand "crypto/rand" - math_rand "math/rand" - - "github.com/newrelic/newrelic-lambda-extension/lambda/logserver" - - "github.com/newrelic/newrelic-lambda-extension/util" -) - -const ( - InfraEndpointEU string = "https://cloud-collector.eu01.nr-data.net/aws/lambda/v1" - InfraEndpointUS string = "https://cloud-collector.newrelic.com/aws/lambda/v1" - LogEndpointEU string = "https://log-api.eu.newrelic.com/log/v1" - LogEndpointUS string = "https://log-api.newrelic.com/log/v1" - - // WIP (configuration options?) - SendTimeoutRetryBase time.Duration = 200 * time.Millisecond - SendTimeoutMaxRetries int = 20 - SendTimeoutMaxBackOff time.Duration = 5 * time.Second -) - -type Client struct { - httpClient *http.Client - batch *Batch - timeout time.Duration - licenseKey string - telemetryEndpoint string - logEndpoint string - functionName string - collectTraceID bool -} - -// New creates a telemetry client with sensible defaults -func New(functionName string, licenseKey string, telemetryEndpointOverride string, logEndpointOverride string, batch *Batch, collectTraceID bool, clientTimeout time.Duration) *Client { - httpClient := &http.Client{ - Timeout: 2400 * time.Millisecond, - } - - // Create random seed for timeout to avoid instances created at the same time - // from creating a wall of retry requests to the collector - var b [8]byte - _, err := crypto_rand.Read(b[:]) - if err != nil { - log.Fatal("cannot seed math/rand package with cryptographically secure random number generator") - } - math_rand.Seed(int64(binary.LittleEndian.Uint64(b[:]))) - - return NewWithHTTPClient(httpClient, functionName, licenseKey, telemetryEndpointOverride, logEndpointOverride, batch, collectTraceID, clientTimeout) -} - -// NewWithHTTPClient is just like New, but the HTTP client can be overridden -func NewWithHTTPClient(httpClient *http.Client, functionName string, licenseKey string, telemetryEndpointOverride string, logEndpointOverride string, batch *Batch, collectTraceID bool, clientTimeout time.Duration) *Client { - telemetryEndpoint := getInfraEndpointURL(licenseKey, telemetryEndpointOverride) - logEndpoint := getLogEndpointURL(licenseKey, logEndpointOverride) - return &Client{ - httpClient: httpClient, - licenseKey: licenseKey, - telemetryEndpoint: telemetryEndpoint, - logEndpoint: logEndpoint, - functionName: functionName, - batch: batch, - collectTraceID: collectTraceID, - timeout: clientTimeout, - } -} - -// getInfraEndpointURL returns the Vortex endpoint for the provided license key -func getInfraEndpointURL(licenseKey string, telemetryEndpointOverride string) string { - if telemetryEndpointOverride != "" { - return telemetryEndpointOverride - } - - if strings.HasPrefix(licenseKey, "eu") { - return InfraEndpointEU - } - - return InfraEndpointUS -} - -// getLogEndpointURL returns the Vortex endpoint for the provided license key -func getLogEndpointURL(licenseKey string, logEndpointOverride string) string { - if logEndpointOverride != "" { - return logEndpointOverride - } - - if strings.HasPrefix(licenseKey, "eu") { - return LogEndpointEU - } - - return LogEndpointUS -} - -func (c *Client) SendTelemetry(ctx context.Context, invokedFunctionARN string, telemetry [][]byte) (error, int) { - start := time.Now() - logEvents := make([]LogsEvent, 0, len(telemetry)) - for _, payload := range telemetry { - logEvent := LogsEventForBytes(payload) - logEvents = append(logEvents, logEvent) - } - - compressedPayloads, err := CompressedPayloadsForLogEvents(logEvents, c.functionName, invokedFunctionARN) - if err != nil { - return err, 0 - } - - var builder requestBuilder = func(buffer *bytes.Buffer) (*http.Request, error) { - return BuildVortexRequest(ctx, c.telemetryEndpoint, buffer, util.Name, c.licenseKey) - } - - transmitStart := time.Now() - successCount, sentBytes := c.sendPayloads(compressedPayloads, builder) - end := time.Now() - totalTime := end.Sub(start) - transmissionTime := end.Sub(transmitStart) - util.Logf( - "Sent %d/%d New Relic payload batches with %d log events successfully in %.3fms (%dms to transmit %.1fkB).\n", - successCount, - len(compressedPayloads), - len(telemetry), - float64(totalTime.Microseconds())/1000.0, - transmissionTime.Milliseconds(), - float64(sentBytes)/1024.0, - ) - - return nil, successCount -} - -type requestBuilder func(buffer *bytes.Buffer) (*http.Request, error) - -func (c *Client) sendPayloads(compressedPayloads []*bytes.Buffer, builder requestBuilder) (successCount int, sentBytes int) { - successCount = 0 - sentBytes = 0 - for _, p := range compressedPayloads { - sentBytes += p.Len() - currentPayloadBytes := p.Bytes() - - var response AttemptData - - timer := time.NewTimer(c.timeout) - - quit := make(chan bool, 1) - data := make(chan AttemptData) - go c.attemptSend(currentPayloadBytes, builder, data, quit) - - select { - case <-timer.C: - response.Error = fmt.Errorf("failed to send data within user defined timeout period: %s", c.timeout.String()) - quit <- true - case response = <-data: - timer.Stop() - } - - if response.Error != nil { - util.Logf("Telemetry client error: %s", response.Error) - sentBytes -= p.Len() - } else if response.Response.StatusCode >= 300 { - util.Logf("Telemetry client response: [%s] %s", response.Response.Status, response.ResponseBody) - } else { - successCount += 1 - } - } - - return successCount, sentBytes -} - -type AttemptData struct { - Error error - ResponseBody string - Response *http.Response -} - -func (c *Client) attemptSend(currentPayloadBytes []byte, builder requestBuilder, dataChan chan AttemptData, quit chan bool) { - baseSleepTime := SendTimeoutRetryBase - - for attempts := 0; attempts < SendTimeoutMaxRetries; attempts++ { - select { - case <-quit: - return - default: - // Construct request for this try - req, err := builder(bytes.NewBuffer(currentPayloadBytes)) - if err != nil { - dataChan <- AttemptData{ - Error: err, - } - return - } - //Make request, check for timeout - res, err := c.httpClient.Do(req) - - // send response data and exit - if err == nil { - // Success. Process response and exit retry loop - defer util.Close(res.Body) - - bodyBytes, err := ioutil.ReadAll(res.Body) - if err != nil { - dataChan <- AttemptData{ - Error: err, - } - return - } - - // Successfully sent bytes - dataChan <- AttemptData{ - Error: nil, - ResponseBody: string(bodyBytes), - Response: res, - } - return - } - - // if error is http timeout, retry - if err, ok := err.(net.Error); ok && err.Timeout() { - util.Debugln("Retrying after timeout", err) - time.Sleep(baseSleepTime + time.Duration(math_rand.Intn(400))) - - // double wait time after 3 timed out attempts - if attempts%3 == 0 { - baseSleepTime *= 2 - } - if baseSleepTime > SendTimeoutMaxBackOff { - baseSleepTime = SendTimeoutMaxBackOff - } - } else { - // All other error types are fatal - dataChan <- AttemptData{ - Error: err, - } - return - } - } - } -} - -func (c *Client) SendFunctionLogs(ctx context.Context, invokedFunctionARN string, lines []logserver.LogLine) error { - start := time.Now() - - common := map[string]interface{}{ - "plugin": util.Id, - "faas.arn": invokedFunctionARN, - "faas.name": c.functionName, - } - - logMessages := make([]FunctionLogMessage, 0, len(lines)) - for _, l := range lines { - // Unix time in ms - ts := l.Time.UnixNano() / 1e6 - var traceId string - if c.batch != nil && c.collectTraceID { - // There is a race condition here. Telemetry batch may be late, so the trace - // ID would be blank. This would require a lock to handle, which would delay - // logs being sent. Not sure if worth the performance hit yet. - traceId = c.batch.RetrieveTraceID(l.RequestID) - } - logMessages = append(logMessages, NewFunctionLogMessage(ts, l.RequestID, traceId, string(l.Content))) - util.Debugf("Sending function logs for request %s", l.RequestID) - } - // The Log API expects an array - logData := []DetailedFunctionLog{NewDetailedFunctionLog(common, logMessages)} - - // Since the Log API won't send us more than 1MB, we shouldn't have any issues with payload size. - compressedPayload, err := CompressedJsonPayload(logData) - if err != nil { - return err - } - compressedPayloads := []*bytes.Buffer{compressedPayload} - - var builder requestBuilder = func(buffer *bytes.Buffer) (*http.Request, error) { - req, err := BuildVortexRequest(ctx, c.logEndpoint, buffer, util.Name, c.licenseKey) - if err != nil { - return nil, err - } - - req.Header.Add("X-Event-Source", "logs") - return req, err - } - - transmitStart := time.Now() - successCount, sentBytes := c.sendPayloads(compressedPayloads, builder) - end := time.Now() - totalTime := end.Sub(start) - transmissionTime := end.Sub(transmitStart) - util.Logf( - "Sent %d/%d New Relic function log batches successfully in %.3fms (%dms to transmit %.1fkB).\n", - successCount, - len(compressedPayloads), - float64(totalTime.Microseconds())/1000.0, - transmissionTime.Milliseconds(), - float64(sentBytes)/1024.0, - ) - - return nil -} diff --git a/telemetry/client_test.go b/telemetry/client_test.go deleted file mode 100644 index d303fea..0000000 --- a/telemetry/client_test.go +++ /dev/null @@ -1,176 +0,0 @@ -package telemetry - -import ( - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "sync/atomic" - "testing" - "time" - - "github.com/newrelic/newrelic-lambda-extension/util" - "github.com/stretchr/testify/assert" -) - -const ( - clientTestingTimeout = 800 * time.Millisecond -) - -func TestClientSend(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, r.Method, http.MethodPost) - - assert.Equal(t, r.Header.Get("Content-Encoding"), "gzip") - assert.Equal(t, r.Header.Get("Content-Type"), "application/json") - assert.Equal(t, r.Header.Get("User-Agent"), "newrelic-lambda-extension") - assert.Equal(t, r.Header.Get("X-License-Key"), "a mock license key") - - reqBytes, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - defer util.Close(r.Body) - assert.NotEmpty(t, reqBytes) - - reqBody, err := util.Uncompress(reqBytes) - assert.NoError(t, err) - assert.NotEmpty(t, reqBody) - - var reqData RequestData - assert.NoError(t, json.Unmarshal(reqBody, &reqData)) - assert.NotEmpty(t, reqData) - - w.WriteHeader(200) - w.Write([]byte("")) - })) - - defer srv.Close() - - client := NewWithHTTPClient(srv.Client(), "", "a mock license key", srv.URL, srv.URL, &Batch{}, false, clientTestingTimeout) - - ctx := context.Background() - bytes := []byte("foobar") - err, successCount := client.SendTelemetry(ctx, "arn:aws:lambda:us-east-1:1234:function:newrelic-example-go", [][]byte{bytes}) - - assert.NoError(t, err) - assert.Equal(t, 1, successCount) - - client = New("", "mock license key", srv.URL, srv.URL, &Batch{}, false, clientTestingTimeout) - assert.NotNil(t, client) -} - -func TestClientSendRetry(t *testing.T) { - var count int32 = 0 - - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - if atomic.LoadInt32(&count) == 0 { - time.Sleep(50 * time.Millisecond) - } else { - assert.Equal(t, r.Method, http.MethodPost) - - assert.Equal(t, r.Header.Get("Content-Encoding"), "gzip") - assert.Equal(t, r.Header.Get("Content-Type"), "application/json") - assert.Equal(t, r.Header.Get("User-Agent"), "newrelic-lambda-extension") - assert.Equal(t, r.Header.Get("X-License-Key"), "a mock license key") - - reqBytes, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) - defer util.Close(r.Body) - assert.NotEmpty(t, reqBytes) - - reqBody, err := util.Uncompress(reqBytes) - assert.NoError(t, err) - assert.NotEmpty(t, reqBody) - - var reqData RequestData - assert.NoError(t, json.Unmarshal(reqBody, &reqData)) - assert.NotEmpty(t, reqData) - - w.WriteHeader(200) - w.Write([]byte("")) - } - atomic.AddInt32(&count, 1) - })) - - defer srv.Close() - - httpClient := srv.Client() - httpClient.Timeout = 50 * time.Millisecond - client := NewWithHTTPClient(httpClient, "", "a mock license key", srv.URL, srv.URL, &Batch{}, false, clientTestingTimeout) - - ctx := context.Background() - bytes := []byte("foobar") - err, successCount := client.SendTelemetry(ctx, "arn:aws:lambda:us-east-1:1234:function:newrelic-example-go", [][]byte{bytes}) - - assert.NoError(t, err) - assert.Equal(t, 1, successCount) - assert.Equal(t, int32(2), atomic.LoadInt32(&count)) -} - -func TestClientReachesDataTimeout(t *testing.T) { - startTime := time.Now() - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - time.Sleep(400 * time.Millisecond) - })) - - defer srv.Close() - - httpClient := srv.Client() - httpClient.Timeout = 100 * time.Millisecond - client := NewWithHTTPClient(httpClient, "", "a mock license key", srv.URL, srv.URL, &Batch{}, false, clientTestingTimeout) - - ctx := context.Background() - bytes := []byte("foobar") - err, successCount := client.SendTelemetry(ctx, "arn:aws:lambda:us-east-1:1234:function:newrelic-example-go", [][]byte{bytes}) - assert.LessOrEqual(t, int(time.Since(startTime)), int(clientTestingTimeout+250*time.Millisecond)) - assert.NoError(t, err) - assert.Equal(t, 0, successCount) -} - -func TestClientUnreachableEndpoint(t *testing.T) { - httpClient := &http.Client{ - Timeout: time.Millisecond * 1, - } - - client := NewWithHTTPClient(httpClient, "", "a mock license key", "http://10.123.123.123:12345", "http://10.123.123.123:12345", &Batch{}, false, clientTestingTimeout) - - ctx := context.Background() - bytes := []byte("foobar") - err, successCount := client.SendTelemetry(ctx, "arn:aws:lambda:us-east-1:1234:function:newrelic-example-go", [][]byte{bytes}) - - assert.Nil(t, err) - assert.Equal(t, 0, successCount) -} - -func TestClientGetsHTTPError(t *testing.T) { - startTime := time.Now() - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(500) - })) - - defer srv.Close() - - httpClient := srv.Client() - httpClient.Timeout = 100 * time.Millisecond - client := NewWithHTTPClient(httpClient, "", "a mock license key", srv.URL, srv.URL, &Batch{}, false, clientTestingTimeout) - - ctx := context.Background() - bytes := []byte("foobar") - err, successCount := client.SendTelemetry(ctx, "arn:aws:lambda:us-east-1:1234:function:newrelic-example-go", [][]byte{bytes}) - assert.Less(t, int(time.Since(startTime)), int(clientTestingTimeout)) // should exit as soon as a non-timeout error occurs without retrying - assert.NoError(t, err) - assert.Equal(t, 0, successCount) -} - -func TestGetInfraEndpointURL(t *testing.T) { - assert.Equal(t, "barbaz", getInfraEndpointURL("foobar", "barbaz")) - assert.Equal(t, InfraEndpointUS, getInfraEndpointURL("us license key", "")) - assert.Equal(t, InfraEndpointEU, getInfraEndpointURL("eu license key", "")) -} - -func TestGetLogEndpointURL(t *testing.T) { - assert.Equal(t, "barbaz", getLogEndpointURL("foobar", "barbaz")) - assert.Equal(t, LogEndpointUS, getLogEndpointURL("us mock license key", "")) - assert.Equal(t, LogEndpointEU, getLogEndpointURL("eu mock license key", "")) -} diff --git a/telemetry/ipc.go b/telemetry/ipc.go deleted file mode 100644 index 1ce805b..0000000 --- a/telemetry/ipc.go +++ /dev/null @@ -1,49 +0,0 @@ -package telemetry - -import ( - "io/ioutil" - "log" - "os" - "syscall" - - "github.com/newrelic/newrelic-lambda-extension/util" -) - -const telemetryNamedPipePath = "/tmp/newrelic-telemetry" - -func InitTelemetryChannel() (chan []byte, error) { - _ = os.Remove(telemetryNamedPipePath) - - err := syscall.Mkfifo(telemetryNamedPipePath, 0666) - if err != nil { - return nil, err - } - - telemetryChan := make(chan []byte) - - go func() { - for { - telemetryChan <- pollForTelemetry() - } - }() - - return telemetryChan, nil -} - -func pollForTelemetry() []byte { - // Opening a pipe will block, until the write side has been opened as well - telemetryPipe, err := os.OpenFile(telemetryNamedPipePath, os.O_RDONLY, 0) - if err != nil { - log.Panic("failed to open telemetry pipe", err) - } - - defer util.Close(telemetryPipe) - - // When the write side closes, we get an EOF. - bytes, err := ioutil.ReadAll(telemetryPipe) - if err != nil { - log.Panic("failed to read telemetry pipe", err) - } - - return bytes -} diff --git a/telemetry/ipc_test.go b/telemetry/ipc_test.go deleted file mode 100644 index b8704f5..0000000 --- a/telemetry/ipc_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package telemetry - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestInitTelemetryChannel(t *testing.T) { - channel, err := InitTelemetryChannel() - - assert.Nil(t, err) - assert.Empty(t, channel) -} diff --git a/telemetry/payload.go b/telemetry/payload.go deleted file mode 100644 index 5b96bb9..0000000 --- a/telemetry/payload.go +++ /dev/null @@ -1,123 +0,0 @@ -package telemetry - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "strings" -) - -type uncompressedData map[string]map[string]json.RawMessage - -func parsePayload(data []byte) (uncompressedData uncompressedData, err error) { - var arr [3]json.RawMessage - - if err = json.Unmarshal(data, &arr); err != nil { - err = fmt.Errorf("unable to unmarshal payload data array: %v", err) - return - } - - var dataJSON []byte - compressed := strings.Trim(string(arr[2]), `"`) - - if dataJSON, err = decodeUncompress(compressed); err != nil { - err = fmt.Errorf("unable to uncompress payload: %v", err) - return - } - - if err = json.Unmarshal(dataJSON, &uncompressedData); err != nil { - err = fmt.Errorf("unable to unmarshal uncompressed payload: %v", err) - return - } - - return -} - -func decodeUncompress(input string) ([]byte, error) { - decoded, err := base64.StdEncoding.DecodeString(input) - if err != nil { - return nil, err - } - - buf := bytes.NewBuffer(decoded) - gz, err := gzip.NewReader(buf) - if err != nil { - return nil, err - } - - var out bytes.Buffer - io.Copy(&out, gz) - gz.Close() - - return out.Bytes(), nil -} - -// ExtractTraceID extracts the trace ID within a payload, if present -func ExtractTraceID(data []byte) (string, error) { - decoded, err := base64.StdEncoding.DecodeString(string(data)) - if err != nil { - return "", err - } - - if !bytes.Contains(decoded, []byte("NR_LAMBDA_MONITORING")) { - return "", nil - } - - segments, err := parsePayload(decoded) - if err != nil { - return "", err - } - - dataSegment, ok := segments["data"] - if !ok { - return "", errors.New("No trace ID found in payload") - } - - analyticEvents, ok := dataSegment["analytic_event_data"] - if ok { - var parsedAnalyticEvents []json.RawMessage - if err := json.Unmarshal(analyticEvents, &parsedAnalyticEvents); err != nil { - return "", err - } - - if len(parsedAnalyticEvents) > 2 { - var analyticEvent [][]struct { - TraceID string `json:"traceId"` - } - if err := json.Unmarshal(parsedAnalyticEvents[2], &analyticEvent); err != nil { - return "", err - } - if len(analyticEvent) > 0 && len(analyticEvent[0]) > 0 { - return analyticEvent[0][0].TraceID, nil - } - } - } - - spanEvents, ok := dataSegment["span_event_data"] - if ok { - var parsedSpanEvents []json.RawMessage - if err := json.Unmarshal(spanEvents, &parsedSpanEvents); err != nil { - return "", err - } - - if len(parsedSpanEvents) > 2 { - var spanEvent [][]struct { - TraceID string `json:"traceId"` - } - - if err := json.Unmarshal(parsedSpanEvents[2], &spanEvent); err != nil { - return "", err - } - - if len(spanEvent) > 0 && len(spanEvent[0]) > 0 { - return spanEvent[0][0].TraceID, nil - } - } - } - - return "", errors.New("No trace ID found in payload") -} diff --git a/telemetry/payload_test.go b/telemetry/payload_test.go deleted file mode 100644 index 486069c..0000000 --- a/telemetry/payload_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package telemetry - -import ( - "encoding/base64" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParsePayload(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"H4sIAHEQsmIC/6VXaXPiOBP+K5Q/zWwSbNnyNfvOW2Xu+w6QZLco2Ra2wRe2TIBU/vtKhjAwk8nM1lYlgFstdevpfrrbL1yACbIRQdyXwguHkpB+c8ZsvOgY3VLFWNTue+VJs99bGKMed1vg4iQikRX5iy1OUi9i6kCh8mUWWoQ+X8g5ILMdeIetLF/C4dZLojDAITlbQYFpo0W8J24USkWFbUAOVbg8BxbFolAEgsBW3/y9UBDPm3wUOhn9yXYdz+Re6eL5fnRz4lmL0/NTmPn+bQHIkqoLgqpJRUmBoqZdiCAVybno6emFC1GQHz3Ij+Zn43qT72ckzghf2hOcMv9SK4pzJWb5Cdwy785/8O+/qfDbQQ1C4oqXxohYLk7e3S7l1n/8BIIGBB3SE9mZHAqRvyf0bnjLgLi+4QuX4BQn28hLFql3YOdTNAWBRYepp4sUY4ak9nq8KNtuohSXke+XoyyPFzgByeSVLEHkBH5RBKqoi6omKxDKEhDxnSAz3W86LHiaommaBCRRl6Cs0nUn82x2URHaggp0oGCwFASMGQpv+MywOUlQmKI8t/jaKcn4xSJAXrhYfPFCG+/yvIwS5qQmCCDPUi9KPLJnbhd1VWOyFAWxj5lJkmSYCogX4JRQKdNiAdckVZWhzmAhEUH+xMu9eNd7kiALN396AbI/RvHCexbQl9djMDYZtVyk2ehG+RH16oTtelvIEo9J+aMsjaMwxUUXI5umfNGKQkJj1sGhQ1zmOvhAa3Lyg+Ad4V0S+H8WLBclKSZfM7K8064sUDBIluZXOnINPafFk0/Hq2KsikBfqncAmuod1IB4Z4qWdSeKuq2ogq7L+LzRz6ld/HVJuVCmhcUeE5SHkkXp9ZjcVpaSKPhJal9n8AmM75Kd3ueU2d8FXZOBrEhQVdWLoJVzc1V2bh40bhlFTG6ihDt5hJMkSv6DQ2d/rlgiCJoMBeqWLEgQqLqiv5kqWj5K89CYmecTL0y/TJGf4Spb5M5a9G7pqfwxny9Ypsm6jkQdIxkoumzbOcuSIvmWn/UPND9gFy0jsvIb9GJIq1ASoXTNnvfMXXjV+91KgN+QeId6R5R+4N8bV5Bl4Zhc0eQWxbHvWXlo+B2T3Oy+l1Iybb4KRf3WCyjm/DM249NPFIfO7R/8H/m6dsnsN5NulOYG/chCPnv4wrB9TzOjqWM4p5bZjQ6e7yNeLgqFT11keSGJUvfPQpOS3S9QQaE/Lsxpei2AtFA+FwzqL6bQtT3C0wpHm1nhU7sx6XZuC763xoU6ttbR50LZTaIA84pO26wEdZX12sIYLVHinbZdevbLsnUOxS9rl6j/q+L1k2olH6vVc+p4xShvx0Uzb8dvBi5XKN5+Wtx72LdP7LxcTTG1bacnNgoy0AVJFSGtbRAIQDmxnzbs8D+R/439NJWwEyWMSRyNMabDCfdj61SAoqlQ0aEmQlp+L1i9NGVVEQV7qVlIQwK87J0fNEtKfOplsh9EXki+Mfa6a9L7ir/VNvPRiU5O17yGULGWoqBAW5fAEuLveP2B0inqYwrymbNvwG/8RW7jjPvTE9cndHb6uDjQ/KQg0wPovyjpkIKmizrUoQwVTabTFDeudqrlSSF2FqmPcfyJ4v6ZbahQO7QbJJinc9kxKvyA8tWhYeVT7GOLUDXA5jGxCCUZAKDTNgjoTPT7Mg639mS9Bomxqg7drd7eV/3SoQ2XzVqzE5tI99ru/divtroTAhx9vZ6Y7XR84/OqshqCdXe7s7LhY00N8KR7aEjDRK7ISvOh2p/yExlXvJt19dEd7oe41XKbca+MUKnmWYJr6rVa3KynG1r8VbwG5qicVlp9EXiVzjpOSo8b3BBHGp4Z850DHSBMnVVl56NZafrQO/TudwSiTsNWHhxrU5nGzVasr0bPqWoKwVDzkjIPyxO7qqz2yKjQFFHH65u9XqrxvCTsMnWiPE+q411tWIdldSaYZClX8GY5nBvG7B7BupeYD143G/akdX9TWjlKd+O0+PpyFVa0nSG7sN8Oe72ep3VKCFW7O9F9Hk0fhW24e9i2Oo9yXWkj83nbbA2VKmkPs0p9FbeEtTox637DNd1prd2O57VlaygoM1SR5817YVzXJ4fkoM0MBY822mFghIdt0gwfHJnISlIx5vo8oYhiXazXGo67aT7vWmJjWLnhjYEG1NnwK3c5IVyXh6enYyNUVFUpMjqzqeM30pd9nbL3J/3/5fgCZBD6fmNmx9rHiMNRgidU27NyyblwWLZki8ikAystICoQuO/pDyQovt/WryZjWsrEfCimqaxqoigq+TBwUQfes0Rdihc/ceT1W0NZHBvKETuO1hxrfWQ/Q5T1dwubVFb4FFBCFhJsUQQKrL4X6LBEPn9hpmqejwt/cbRxF+P9XxzreyGmJLwteGHhf0FkZz7+/4UiO9BJ6CuPvaDEX19vkvJdzwk97fi29v7pSq6WB41lQt7BrwNzKmcX9XBxBPpnCUNh1YFWhDqt/rQ0QQAge2HRRDqCy/8qiU5o4tYOTASQuBVjyI/1Q2VlPtZLma5PO9F4vMKjamsGpQdnlPoNNA1p/wGkI87UbqVSCfgl3vZ9tZU490k7rk4nvUe/Me0lWzIajIUoGt4n9fFWfthL/thsT5oDWkVdoFVv4E7cusGyj0FZ2nSzzqYh34xuSpv72epeieVg9ViiBQzVBoat9AN5MC/dDwnynuuoftPTrLGRzSZbjWx2jdpBVJx6dzYvNwaB1mzV41J31TwMUmVshTe4ZTzU4SgQ1Hl3507Gge7NQUtXRqWOnAI88vWuMxRXyrZxY6x77YM3dQL7kAjLg7bRtZZibHfpLAz6ffv+MGoAYx88DHZLs8TvXa073/celbbx9SvLAA3IFpCWS02QJHNpgzPCS+Sn+O2BfdKQv77+A81iGHhwEQAA\"]") - - data, err := parsePayload(payload) - - assert.NotNil(t, data) - assert.Nil(t, err) -} - -func TestParsePayloadBlank(t *testing.T) { - payload := []byte("") - - data, err := parsePayload(payload) - - assert.Nil(t, data) - assert.Error(t, err) -} - -func TestParsePayloadInvalidCompressedData(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"foobar\"]") - - data, err := parsePayload(payload) - - assert.Nil(t, data) - assert.Error(t, err) -} - -func TestParsePayloadInvalidData(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"H4sIAK6pdWIC/0vLz09KLAIAlR/2ngYAAAA=\"]") - - data, err := parsePayload(payload) - - assert.Nil(t, data) - assert.Error(t, err) -} - -func TestExtractTraceIDAnalyticEvent(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"H4sIAHEQsmIC/6VXaXPiOBP+K5Q/zWwSbNnyNfvOW2Xu+w6QZLco2Ra2wRe2TIBU/vtKhjAwk8nM1lYlgFstdevpfrrbL1yACbIRQdyXwguHkpB+c8ZsvOgY3VLFWNTue+VJs99bGKMed1vg4iQikRX5iy1OUi9i6kCh8mUWWoQ+X8g5ILMdeIetLF/C4dZLojDAITlbQYFpo0W8J24USkWFbUAOVbg8BxbFolAEgsBW3/y9UBDPm3wUOhn9yXYdz+Re6eL5fnRz4lmL0/NTmPn+bQHIkqoLgqpJRUmBoqZdiCAVybno6emFC1GQHz3Ij+Zn43qT72ckzghf2hOcMv9SK4pzJWb5Cdwy785/8O+/qfDbQQ1C4oqXxohYLk7e3S7l1n/8BIIGBB3SE9mZHAqRvyf0bnjLgLi+4QuX4BQn28hLFql3YOdTNAWBRYepp4sUY4ak9nq8KNtuohSXke+XoyyPFzgByeSVLEHkBH5RBKqoi6omKxDKEhDxnSAz3W86LHiaommaBCRRl6Cs0nUn82x2URHaggp0oGCwFASMGQpv+MywOUlQmKI8t/jaKcn4xSJAXrhYfPFCG+/yvIwS5qQmCCDPUi9KPLJnbhd1VWOyFAWxj5lJkmSYCogX4JRQKdNiAdckVZWhzmAhEUH+xMu9eNd7kiALN396AbI/RvHCexbQl9djMDYZtVyk2ehG+RH16oTtelvIEo9J+aMsjaMwxUUXI5umfNGKQkJj1sGhQ1zmOvhAa3Lyg+Ad4V0S+H8WLBclKSZfM7K8064sUDBIluZXOnINPafFk0/Hq2KsikBfqncAmuod1IB4Z4qWdSeKuq2ogq7L+LzRz6ld/HVJuVCmhcUeE5SHkkXp9ZjcVpaSKPhJal9n8AmM75Kd3ueU2d8FXZOBrEhQVdWLoJVzc1V2bh40bhlFTG6ihDt5hJMkSv6DQ2d/rlgiCJoMBeqWLEgQqLqiv5kqWj5K89CYmecTL0y/TJGf4Spb5M5a9G7pqfwxny9Ypsm6jkQdIxkoumzbOcuSIvmWn/UPND9gFy0jsvIb9GJIq1ASoXTNnvfMXXjV+91KgN+QeId6R5R+4N8bV5Bl4Zhc0eQWxbHvWXlo+B2T3Oy+l1Iybb4KRf3WCyjm/DM249NPFIfO7R/8H/m6dsnsN5NulOYG/chCPnv4wrB9TzOjqWM4p5bZjQ6e7yNeLgqFT11keSGJUvfPQpOS3S9QQaE/Lsxpei2AtFA+FwzqL6bQtT3C0wpHm1nhU7sx6XZuC763xoU6ttbR50LZTaIA84pO26wEdZX12sIYLVHinbZdevbLsnUOxS9rl6j/q+L1k2olH6vVc+p4xShvx0Uzb8dvBi5XKN5+Wtx72LdP7LxcTTG1bacnNgoy0AVJFSGtbRAIQDmxnzbs8D+R/439NJWwEyWMSRyNMabDCfdj61SAoqlQ0aEmQlp+L1i9NGVVEQV7qVlIQwK87J0fNEtKfOplsh9EXki+Mfa6a9L7ir/VNvPRiU5O17yGULGWoqBAW5fAEuLveP2B0inqYwrymbNvwG/8RW7jjPvTE9cndHb6uDjQ/KQg0wPovyjpkIKmizrUoQwVTabTFDeudqrlSSF2FqmPcfyJ4v6ZbahQO7QbJJinc9kxKvyA8tWhYeVT7GOLUDXA5jGxCCUZAKDTNgjoTPT7Mg639mS9Bomxqg7drd7eV/3SoQ2XzVqzE5tI99ru/divtroTAhx9vZ6Y7XR84/OqshqCdXe7s7LhY00N8KR7aEjDRK7ISvOh2p/yExlXvJt19dEd7oe41XKbca+MUKnmWYJr6rVa3KynG1r8VbwG5qicVlp9EXiVzjpOSo8b3BBHGp4Z850DHSBMnVVl56NZafrQO/TudwSiTsNWHhxrU5nGzVasr0bPqWoKwVDzkjIPyxO7qqz2yKjQFFHH65u9XqrxvCTsMnWiPE+q411tWIdldSaYZClX8GY5nBvG7B7BupeYD143G/akdX9TWjlKd+O0+PpyFVa0nSG7sN8Oe72ep3VKCFW7O9F9Hk0fhW24e9i2Oo9yXWkj83nbbA2VKmkPs0p9FbeEtTox637DNd1prd2O57VlaygoM1SR5817YVzXJ4fkoM0MBY822mFghIdt0gwfHJnISlIx5vo8oYhiXazXGo67aT7vWmJjWLnhjYEG1NnwK3c5IVyXh6enYyNUVFUpMjqzqeM30pd9nbL3J/3/5fgCZBD6fmNmx9rHiMNRgidU27NyyblwWLZki8ikAystICoQuO/pDyQovt/WryZjWsrEfCimqaxqoigq+TBwUQfes0Rdihc/ceT1W0NZHBvKETuO1hxrfWQ/Q5T1dwubVFb4FFBCFhJsUQQKrL4X6LBEPn9hpmqejwt/cbRxF+P9XxzreyGmJLwteGHhf0FkZz7+/4UiO9BJ6CuPvaDEX19vkvJdzwk97fi29v7pSq6WB41lQt7BrwNzKmcX9XBxBPpnCUNh1YFWhDqt/rQ0QQAge2HRRDqCy/8qiU5o4tYOTASQuBVjyI/1Q2VlPtZLma5PO9F4vMKjamsGpQdnlPoNNA1p/wGkI87UbqVSCfgl3vZ9tZU490k7rk4nvUe/Me0lWzIajIUoGt4n9fFWfthL/thsT5oDWkVdoFVv4E7cusGyj0FZ2nSzzqYh34xuSpv72epeieVg9ViiBQzVBoat9AN5MC/dDwnynuuoftPTrLGRzSZbjWx2jdpBVJx6dzYvNwaB1mzV41J31TwMUmVshTe4ZTzU4SgQ1Hl3507Gge7NQUtXRqWOnAI88vWuMxRXyrZxY6x77YM3dQL7kAjLg7bRtZZibHfpLAz6ffv+MGoAYx88DHZLs8TvXa073/celbbx9SvLAA3IFpCWS02QJHNpgzPCS+Sn+O2BfdKQv77+A81iGHhwEQAA\"]") - payload = []byte(base64.StdEncoding.EncodeToString(payload)) - - traceId, err := ExtractTraceID(payload) - - assert.Nil(t, err) - assert.Equal(t, "24d071916e1f00ee", traceId) -} - -func TestExtractTraceIDSpanEvent(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"H4sIANsRsmIC/6VW+Y/iuBL+V6L8NLO0SJw7s2+eFO77vrp7V8hJTBLIhePQQKv/92cHmqHn0j6tBISUy1Xlqvq+8isfIQJdSCD/hXvlIY7pk7eW03XP6ldq1roxH1Rn7eFgbU0G/APHpzghiZOE6wPCWZAwdaBR+SaPHULf7+Q8UNkOdEROXiyh+BDgJI5QTG5eYGS7cJ2eiJ/EclljG6BHFe7tKGWpLJaBKLLV93jvFKTbphDGXk7/sl0Xm/wbXbydj27GgbO+vj/HeRg+cECVdVMUdUMuy5oiGcadSKEitRA9P7/yMYwK06PCtLCcNtvCMCdpToTKiaCMxZc5SVooMc/P4IFFd/sof/9Nhd8MtQhJa0GWQuL4CP90u1x4//EXiAYQTYVaZDZ5J89IEq3RgaXh4/le+UKarTOEinpRdYwyhA9JgNdZcGb+gCSKb5dTkiBCGYFRysSqbKhA1WRF13W6j5wu0VULd3VmlwX6ym+ShMltiPm3S0QI4wT/i4Bu8bg5huRSabEsioaqiDQsVZQVoJua+e6q7IQwy4og8iAkQZx9WcAwR3W2yN+06Nmya4ewmKncywOXvRqqaULJRFAFmqm6LluLcZlgGGewaO7mbzTTBLOuNkQRFDAJEhyQEzsIjVlUGUYymtMQMQsE54hl88dM64osKTJbw9BB7V+6u4tqcG2mJbJn36RC44pIYb2OYBCv11/Qeybeq3infskSK2VRToz2OQ2t7CPoUpyVoeOgtEAtQUci+CQKH2CahoFTlEY4Mknp+L00Cv/cfxXL5kMQ0ZwLL8hOr39hGnsPfwh/FOsGX3TAR5d+khUOw8SBIXv5wnL7M82cto7lXVmln5yDMISCWha5T33oBDFJMv9Prh0TFHJUwA2n3Iq21xrIa+0zZ9F4EU1dNyACRT3FO/ep25r1ew9cGOwQ10TOLvnMVX2cREjQTMpEsmLqjI64KdxAHFy33UdGecZPito167P7hRwHTCrcSkH7Pk3iDN0O4yQ00Jj0UOwRn5GbCX6jNrtW8laVDzZpb5G8gIR6Ic+XzAvKScFYZbtgrHcH9ys032FWPgUodK/ovF/NEPXtZlc0iiowRVmXFE0XFSAC7Yp+ymnxvwL/O/ppKyEvwQxJPK0xovzNTvKBFICoAc3QFc1UDEmRgHmH6o2t6pokuhvDgQYUlQLVV8T8CJEgdtHxCnwaJT6NkqDoqytiP+DapOeV/gGur9OFDpePuFYUzdlIoqa4pgw2CvoO179RulZ9SpN8w+x74vfhuvBxy/vzMz8kdLz8nhxof9IkUwP0K8mmQpNmSqZiKqqiGSodOPy03qtXZ1zqrbMQofQTzftntqFG/dBpgJFAR9elKsKI4tWjZRUyFCKHUDXARpZUVmQVAGCaJn3o0j+X8ahzIrsdwNa2PvYPZvdUDyvnrrJpN9q91IZm0PXn07De6c8I8MzdbmZ3s2kpFHRtOwa7/uHo5OOnhh6hWf/cksdYrala+7E+XAgzFdWC0q7+5I9PY9Tp+O10UIWw0ggc0bfNRiNtN7M9JX8d7YA9qWa1zlACQa23S3HlaY9a0sRAS2t19BQPiAtvWzuGcFlZPA7Og/mRKLDXcrVHz9nXFmm7k5rbyUum22I0NgJcFZTqzK1r2xO0arRF9OmudDIrDUGQxWOuz7SXWX16bIybSlVfijbZqDW034xXlrWcQ6UZYPsx6Ofjgbwb7itbT+vvvY7Q3GzjmnG0VF8ZduPBYBAYvQqE9f5R8l8miyfxEB8fD53ek9rUutB+ObQ7Y61OuuO81tymHXGnz+xm2PJtf9HodtNVY9MZi9oS1tRVey5Om+bsjM/G0tLQZG+cR1Z8PuB2/OipRNVwzVqZK0wzikyp2Wh5/r79cuxIrXGtJFgjA+jL8Vf+/obwkR6eny+DUNN1rczgzG4d/6B92ePavb+Y/6+XO6JF6BXQzi/cx4DDU4Bjqh04heRGHI4ruxK0wUakBKIDkf8e/kBWpJ+P9YTAcBYUHFNwpCRLJh0aKtANSZK04jJwxwM/80RDSte/COTt20BZXwbKJXc85Rxnd0E/yyib7w6yqYz7FFFAchg5NAMc43eOXpbI5y/MVSMIEfcXTwd3OT39xbO5FyMKwgcuiLn/RImbh+i/d4rMoIeTPHbXFPi7j5vkYtcLptYuF9qfW9cKtaJorBOKCf6xMFc6u+PD9SXRv2oYmlYTGGXFpOxPqUkBQKHMaxiSAST1/2qiazZR5whmIsB+zRoLU/Nc29pPzUpumoteMp1u0aTeWSryozfJwhZcxHT+ANKTlnq/VqtFwgYdhqHewd4cd9P6YjZ4CluLAT6QyWgqJsl4jpvTg/p4ksOp3Z21R5RFfWDUS8pROvjRZohAVd73896+pZYmpcp+vtzOtVSNtk8VSmCwMbJcbRipo1VlPiYweGnCZmlgOFMrX84OBtkfW42zpHnN/nJVbY0io91pppX+tn0eZdrUiUuoYz02lUkk6qv+0Z9NIzNYgY6pTSo9NQNoEpp9byxttUOrZO0G3XOw8CL3jMXN2dibRkezDsdsGUfDoTs/T1rAOkWPo+PGrggn3+ivToMnrWt9/co6wACqA+TNxhBl2d644JbhDQwz9P7CfmnJ397+ByG+irKTDgAA\"]") - payload = []byte(base64.StdEncoding.EncodeToString(payload)) - - traceId, err := ExtractTraceID(payload) - - assert.Nil(t, err) - assert.Equal(t, "446cf2064d931f4e", traceId) -} - -func TestExtractTraceIDInvalid(t *testing.T) { - payload := []byte("[1, \"NR_LAMBDA_MONITORING\", \"H4sIAK6pdWIC/0vLz09KLAIAlR/2ngYAAAA=\"]") - payload = []byte(base64.StdEncoding.EncodeToString(payload)) - - traceId, err := ExtractTraceID(payload) - - assert.Error(t, err) - assert.Empty(t, traceId) - - payload2 := []byte("[foobar]") - payload2 = []byte(base64.StdEncoding.EncodeToString(payload)) - - traceId, err = ExtractTraceID(payload2) - - assert.Nil(t, err) - assert.Empty(t, traceId) - - payload3 := []byte("[foobar]") - - traceId, err = ExtractTraceID(payload3) - - assert.Error(t, err) - assert.Empty(t, traceId) -} diff --git a/telemetry/request.go b/telemetry/request.go deleted file mode 100644 index d72f82f..0000000 --- a/telemetry/request.go +++ /dev/null @@ -1,164 +0,0 @@ -package telemetry - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - - "github.com/newrelic/newrelic-lambda-extension/util" -) - -const ( - maxCompressedPayloadLen = 1000 * 1024 -) - -// DetailedFunctionLog is the Logs API payload -type DetailedFunctionLog struct { - Common CommonLogAttrs `json:"common"` - Logs []FunctionLogMessage `json:"logs"` -} - -type CommonLogAttrs struct { - Attributes map[string]interface{} `json:"attributes"` -} - -type FunctionLogMessage struct { - Message string `json:"message"` - Timestamp int64 `json:"timestamp"` - Attributes map[string]interface{} `json:"attributes"` -} - -func NewFunctionLogMessage(timestamp int64, requestId, traceId, message string) FunctionLogMessage { - return FunctionLogMessage{ - Message: message, - Timestamp: timestamp, - Attributes: map[string]interface{}{ - "aws": map[string]string{ - "lambda_request_id": requestId, - }, - "faas.execution": requestId, - "trace.id": traceId, - }, - } -} - -func NewDetailedFunctionLog(common map[string]interface{}, logs []FunctionLogMessage) DetailedFunctionLog { - return DetailedFunctionLog{ - Common: CommonLogAttrs{ - Attributes: common, - }, - Logs: logs, - } -} - -// RequestContext is the Vortex request context -type RequestContext struct { - FunctionName string `json:"function_name"` - InvokedFunctionARN string `json:"invoked_function_arn"` - // Below are not relevant to Lambda Extensions, but ingest requires these to be present - LogGroupName string `json:"log_group_name"` - LogStreamName string `json:"log_stream_name"` -} - -// RequestData is the body of the Vortex request -type RequestData struct { - Context RequestContext `json:"context"` - Entry string `json:"entry"` -} - -// LogsEntry is a CloudWatch Logs entry -type LogsEntry struct { - LogEvents []LogsEvent `json:"logEvents"` - // Below are not relevant to Lambda Extensions, but ingest expects these to be present - LogGroup string `json:"logGroup"` - LogStream string `json:"logStream"` - MessageType string `json:"messageType"` - Owner string `json:"owner"` -} - -// LogsEvent is a CloudWatch Logs event -type LogsEvent struct { - ID string `json:"id"` - Message string `json:"message"` - Timestamp int64 `json:"timestamp"` -} - -func LogsEventForBytes(payload []byte) LogsEvent { - return LogsEvent{ID: util.UUID(), Message: string(payload), Timestamp: util.Timestamp()} -} - -func CompressedPayloadsForLogEvents(logsEvents []LogsEvent, functionName string, invokedFunctionARN string) ([]*bytes.Buffer, error) { - logGroupName := fmt.Sprintf("/aws/lambda/%s", functionName) - logEntry := LogsEntry{ - LogEvents: logsEvents, - LogGroup: logGroupName, - } - - entry, err := json.Marshal(logEntry) - if err != nil { - return nil, err - } - - context := RequestContext{ - FunctionName: functionName, - InvokedFunctionARN: invokedFunctionARN, - LogGroupName: logGroupName, - LogStreamName: util.Id, - } - data := RequestData{Context: context, Entry: string(entry)} - - compressed, err := CompressedJsonPayload(data) - if err != nil { - return nil, err - } - - if compressed.Len() <= maxCompressedPayloadLen { - ret := []*bytes.Buffer{compressed} - return ret, nil - } else { - // Payload is too large, split in half, recursively - split := len(logsEvents) / 2 - leftRet, err := CompressedPayloadsForLogEvents(logsEvents[0:split], functionName, invokedFunctionARN) - if err != nil { - return nil, err - } - - rightRet, err := CompressedPayloadsForLogEvents(logsEvents[split:], functionName, invokedFunctionARN) - if err != nil { - return nil, err - } - - return append(leftRet, rightRet...), nil - } -} - -// BuildVortexRequest builds a Vortex HTTP request -func BuildVortexRequest(ctx context.Context, url string, compressed *bytes.Buffer, userAgent string, licenseKey string) (*http.Request, error) { - req, err := http.NewRequestWithContext(ctx, "POST", url, compressed) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - - req.Header.Add("Content-Encoding", "gzip") - req.Header.Add("Content-Type", "application/json") - req.Header.Add("User-Agent", userAgent) - req.Header.Add("X-License-Key", licenseKey) - - return req, nil -} - -func CompressedJsonPayload(payload interface{}) (*bytes.Buffer, error) { - uncompressed, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - compressed, err := util.Compress(uncompressed) - if err != nil { - return nil, fmt.Errorf("error compressing data: %v", err) - } - - return compressed, nil -} diff --git a/telemetry/request_test.go b/telemetry/request_test.go deleted file mode 100644 index beaf1cb..0000000 --- a/telemetry/request_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package telemetry - -import ( - "encoding/json" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestSerialize_DetailedFunctionLog(t *testing.T) { - commons := map[string]interface{}{ - "foo": "bar", - } - messages := []FunctionLogMessage{ - NewFunctionLogMessage(1234, "test1", "123456789", "message1"), - NewFunctionLogMessage(1235, "test2", "123456789", "message2"), - } - dfl := NewDetailedFunctionLog(commons, messages) - - json_bytes, err := json.Marshal(dfl) - assert.NoError(t, err) - assert.Equal(t, "{\"common\":{\"attributes\":{\"foo\":\"bar\"}},\"logs\":[{\"message\":\"message1\",\"timestamp\":1234,\"attributes\":{\"aws\":{\"lambda_request_id\":\"test1\"},\"faas.execution\":\"test1\",\"trace.id\":\"123456789\"}},{\"message\":\"message2\",\"timestamp\":1235,\"attributes\":{\"aws\":{\"lambda_request_id\":\"test2\"},\"faas.execution\":\"test2\",\"trace.id\":\"123456789\"}}]}", string(json_bytes)) -} diff --git a/AwsLambdaExtension/telemetryApi/client.go b/telemetryApi/client.go similarity index 100% rename from AwsLambdaExtension/telemetryApi/client.go rename to telemetryApi/client.go diff --git a/AwsLambdaExtension/telemetryApi/dispatcher.go b/telemetryApi/dispatcher.go similarity index 87% rename from AwsLambdaExtension/telemetryApi/dispatcher.go rename to telemetryApi/dispatcher.go index cc5ba42..cedac92 100644 --- a/AwsLambdaExtension/telemetryApi/dispatcher.go +++ b/telemetryApi/dispatcher.go @@ -3,10 +3,11 @@ package telemetryApi import ( "context" "net/http" - "newrelic-lambda-extension/AwsLambdaExtension/agentTelemetry" "os" "github.com/golang-collections/go-datastructures/queue" + + "newrelic-lambda-extension/config" ) type Dispatcher struct { @@ -17,7 +18,7 @@ type Dispatcher struct { functionName string } -func NewDispatcher(functionName string, config *agentTelemetry.Config, ctx context.Context, batchSize int64) *Dispatcher { +func NewDispatcher(functionName string, config *config.Config, ctx context.Context, batchSize int64) *Dispatcher { var licenseKey string licenseKey = os.Getenv("NEW_RELIC_LICENSE_KEY") if len(licenseKey) == 0 { diff --git a/AwsLambdaExtension/telemetryApi/listener.go b/telemetryApi/listener.go similarity index 100% rename from AwsLambdaExtension/telemetryApi/listener.go rename to telemetryApi/listener.go diff --git a/AwsLambdaExtension/telemetryApi/send_to_new_relic.go b/telemetryApi/send_to_new_relic.go similarity index 100% rename from AwsLambdaExtension/telemetryApi/send_to_new_relic.go rename to telemetryApi/send_to_new_relic.go