From d61f4085410ba76dc2a53c90a9cdc89bc5c06278 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 30 May 2024 14:38:36 -0700 Subject: [PATCH 01/26] First pass at adding support for Lambda --- examples/grpc/go.mod | 8 +-- examples/grpc/go.sum | 4 ++ examples/http/go.mod | 8 +-- examples/http/go.sum | 4 ++ go.mod | 32 +++++---- go.sum | 71 ++++++++++++++++--- .../aws/aws-lambda-go/swolambda/go.mod | 35 +++++++++ .../aws/aws-lambda-go/swolambda/go.sum | 66 +++++++++++++++++ .../aws/aws-lambda-go/swolambda/swolambda.go | 58 +++++++++++++++ internal/metrics/otel.go | 63 ++++++++++++++++ swo/agent.go | 70 ++++++++++++++++++ 11 files changed, 390 insertions(+), 29 deletions(-) create mode 100644 instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod create mode 100644 instrumentation/github.com/aws/aws-lambda-go/swolambda/go.sum create mode 100644 instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go create mode 100644 internal/metrics/otel.go diff --git a/examples/grpc/go.mod b/examples/grpc/go.mod index c2500c9a..44771d06 100644 --- a/examples/grpc/go.mod +++ b/examples/grpc/go.mod @@ -22,7 +22,7 @@ replace github.com/solarwinds/apm-go => ../.. require ( github.com/solarwinds/apm-go v0.1.1 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 - go.opentelemetry.io/otel v1.25.0 + go.opentelemetry.io/otel v1.26.0 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 ) @@ -38,9 +38,9 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect - go.opentelemetry.io/otel/trace v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect diff --git a/examples/grpc/go.sum b/examples/grpc/go.sum index 095ef44e..051ab6ac 100644 --- a/examples/grpc/go.sum +++ b/examples/grpc/go.sum @@ -44,12 +44,16 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= diff --git a/examples/http/go.mod b/examples/http/go.mod index bc0a007a..e01a28b3 100644 --- a/examples/http/go.mod +++ b/examples/http/go.mod @@ -24,8 +24,8 @@ require ( github.com/mattn/go-sqlite3 v1.14.18 github.com/solarwinds/apm-go v0.1.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 - go.opentelemetry.io/otel v1.25.0 - go.opentelemetry.io/otel/trace v1.25.0 + go.opentelemetry.io/otel v1.26.0 + go.opentelemetry.io/otel/trace v1.26.0 ) require ( @@ -40,8 +40,8 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - go.opentelemetry.io/otel/sdk v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect diff --git a/examples/http/go.sum b/examples/http/go.sum index 157b4c1a..f0608677 100644 --- a/examples/http/go.sum +++ b/examples/http/go.sum @@ -40,14 +40,18 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= diff --git a/go.mod b/go.mod index 51209eb8..459c656e 100644 --- a/go.mod +++ b/go.mod @@ -25,32 +25,40 @@ require ( github.com/pkg/errors v0.9.1 github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 github.com/stretchr/testify v1.9.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 + go.opentelemetry.io/otel/metric v1.27.0 + go.opentelemetry.io/otel/sdk/metric v1.27.0 go.uber.org/atomic v1.11.0 - google.golang.org/grpc v1.63.2 + google.golang.org/grpc v1.64.0 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/yaml.v2 v2.4.0 ) require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.opentelemetry.io/otel/metric v1.25.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect - google.golang.org/protobuf v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 // indirect + go.opentelemetry.io/proto/otlp v1.2.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect + google.golang.org/protobuf v1.34.1 // indirect ) require ( github.com/stretchr/objx v0.5.2 // indirect - go.opentelemetry.io/otel v1.25.0 - go.opentelemetry.io/otel/sdk v1.25.0 - go.opentelemetry.io/otel/trace v1.25.0 - golang.org/x/sys v0.19.0 // indirect + go.opentelemetry.io/otel v1.27.0 + go.opentelemetry.io/otel/sdk v1.27.0 + go.opentelemetry.io/otel/trace v1.27.0 + golang.org/x/sys v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6efbded8..4f171d78 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYh github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -16,16 +18,27 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 h1:Oyzwjp7RN7X8q3K4iK1B5+XmQL2ou993u9CGXOBkEpk= github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6/go.mod h1:CN4fCYBnxyOJlBV0CYNXLz6lzNH8SCfNqcCBbpai76c= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -34,30 +47,70 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= -go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= -go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= -go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= -go.opentelemetry.io/otel/sdk v1.25.0 h1:PDryEJPC8YJZQSyLY5eqLeafHtG+X7FWnf3aXMtxbqo= -go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= -go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= +go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= +go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= +go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= +google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod new file mode 100644 index 00000000..81e0f8fb --- /dev/null +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod @@ -0,0 +1,35 @@ +module github.com/solarwinds/apm-go/instrumentation/github.com/aws/aws-lambda-go/swolambda + +go 1.22.3 + +require ( + github.com/aws/aws-lambda-go v1.47.0 + // TODO this has to be updated to a version that actually has support for Lambda + github.com/solarwinds/apm-go v1.0.0 + go.opentelemetry.io/otel v1.27.0 +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect + github.com/aws/smithy-go v1.20.2 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coocood/freecache v1.2.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect + go.opentelemetry.io/otel/sdk v1.27.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.sum b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.sum new file mode 100644 index 00000000..03a92d4f --- /dev/null +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.sum @@ -0,0 +1,66 @@ +github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI= +github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M= +github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= +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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/solarwinds/apm-go v1.0.0 h1:oSF7fRCQE4yhvyZxwQqfFLtCbpqQCWTJrpJ822T/LCo= +github.com/solarwinds/apm-go v1.0.0/go.mod h1:NcXCeVxcj1O6/2UH8iPiWYY9Xd+g+/u04taW3CMw2zg= +github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 h1:Oyzwjp7RN7X8q3K4iK1B5+XmQL2ou993u9CGXOBkEpk= +github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6/go.mod h1:CN4fCYBnxyOJlBV0CYNXLz6lzNH8SCfNqcCBbpai76c= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= +go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= +go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go new file mode 100644 index 00000000..1eeeed90 --- /dev/null +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -0,0 +1,58 @@ +package swolambda + +import ( + "context" + "github.com/aws/aws-lambda-go/lambda" + "github.com/aws/aws-lambda-go/lambdacontext" + "github.com/solarwinds/apm-go/internal/log" + "github.com/solarwinds/apm-go/swo" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.24.0" + "go.opentelemetry.io/otel/trace" + "os" + "sync" +) + +var flusher swo.Flusher +var tracer trace.Tracer +var once sync.Once + +type wrappedHandler struct { + base lambda.Handler +} + +var _ lambda.Handler = &wrappedHandler{} + +func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { + var attrs []attribute.KeyValue + if lc, ok := lambdacontext.FromContext(ctx); !ok { + log.Error("could not obtain lambda context") + } else if lc != nil { + attrs = append(attrs, semconv.FaaSInvocationID(lc.AwsRequestID)) + } + name := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + ctx, span := tracer.Start(ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) + defer func() { + span.End() + if flusher != nil { + if err := flusher.Flush(context.Background()); err != nil { + log.Error("could not flush lambda metrics", err) + } + } + }() + return w.base.Invoke(ctx, payload) +} + +func WrapHandler(f interface{}) lambda.Handler { + once.Do(func() { + var err error + if flusher, err = swo.StartLambda(); err != nil { + log.Error("could not initialize SWO lambda instrumentation", err) + } + tracer = otel.GetTracerProvider().Tracer("swolambda") + }) + return &wrappedHandler{ + base: lambda.NewHandler(f), + } +} diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go new file mode 100644 index 00000000..7e01b346 --- /dev/null +++ b/internal/metrics/otel.go @@ -0,0 +1,63 @@ +package metrics + +import ( + "context" + "github.com/solarwinds/apm-go/internal/log" + "github.com/solarwinds/apm-go/internal/utils" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/metric" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.24.0" +) + +type otelRegistry struct { + meterProvider metric.MeterProvider +} + +func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, isAppoptics bool) { + // TODO DRY with legacy registry? + var attrs = []attribute.KeyValue{ + attribute.Bool("sw.is_error", span.Status().Code == codes.Error), + attribute.String("sw.transaction", utils.GetTransactionName(span)), + } + for _, attr := range span.Attributes() { + // TODO use semconv? + if attr.Key == semconv.HTTPMethodKey { + attrs = append(attrs, attribute.String("http.method", attr.Value.AsString())) + } else if attr.Key == semconv.HTTPStatusCodeKey { + attrs = append(attrs, attribute.Int64("http.status_code", attr.Value.AsInt64())) + } else if attr.Key == semconv.HTTPRouteKey { + attrs = append(attrs, attribute.String("http.route", attr.Value.AsString())) + } + } + // TODO service.name? + meter := o.meterProvider.Meter("sw.apm.request.metrics") + histo, err := meter.Int64Histogram( + "trace.service.response_time", + metric.WithExplicitBucketBoundaries(), + metric.WithUnit("ms"), + ) + if err != nil { + log.Error(err) + } else { + duration := span.EndTime().Sub(span.StartTime()) + histo.Record( + context.Background(), + duration.Milliseconds(), + metric.WithAttributes(attrs...), + ) + } +} + +var _ MetricRegistry = &otelRegistry{} + +func NewOtelRegistry() MetricRegistry { + return &otelRegistry{} +} + +func TemporalitySelector(sdkmetric.InstrumentKind) metricdata.Temporality { + return metricdata.DeltaTemporality +} diff --git a/swo/agent.go b/swo/agent.go index f5846d5e..38e3f89b 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -32,10 +32,17 @@ import ( "github.com/solarwinds/apm-go/internal/sampler" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" + "io" + stdlog "log" + "strings" + "time" "github.com/pkg/errors" ) @@ -95,6 +102,7 @@ func Start(resourceAttrs ...attribute.KeyValue) (func(), error) { if err != nil { return func() {}, err } + exprtr := exporter.NewExporter(_reporter) smplr, err := sampler.NewSampler(o) if err != nil { @@ -137,3 +145,65 @@ func SetTransactionName(ctx context.Context, name string) error { } return entryspans.SetTransactionName(sc.TraceID(), name) } + +type Flusher interface { + Flush(ctx context.Context) error +} + +type lambdaFlusher struct { + Reader *metric.PeriodicReader +} + +func (l lambdaFlusher) Flush(ctx context.Context) error { + return l.Reader.ForceFlush(ctx) +} + +var _ Flusher = &lambdaFlusher{} + +func StartLambda() (Flusher, error) { + ctx := context.Background() + var err error + var tpOpts []sdktrace.TracerProviderOption + registry := metrics.NewOtelRegistry() + var metExp metric.Exporter + if metExp, err = otlpmetricgrpc.New(ctx, + otlpmetricgrpc.WithTemporalitySelector(metrics.TemporalitySelector), + ); err != nil { + return nil, err + } + reader := metric.NewPeriodicReader( + metExp, + metric.WithInterval(1*time.Second), + ) + flusher := &lambdaFlusher{ + Reader: reader, + } + mp := metric.NewMeterProvider( + metric.WithReader(reader), + ) + otel.SetMeterProvider(mp) + if exprtr, err := otlptracegrpc.New(ctx); err != nil { + return nil, err + } else { + tpOpts = append(tpOpts, sdktrace.WithSyncer(exprtr)) + } + + proc := processor.NewInboundMetricsSpanProcessor(registry, false) + prop := propagation.NewCompositeTextMapPropagator( + &propagation.TraceContext{}, + &propagation.Baggage{}, + &propagator.SolarwindsPropagator{}, + ) + o := oboe.NewOboe() + smplr, err := sampler.NewSampler(o) + if err != nil { + return nil, err + } + otel.SetTextMapPropagator(prop) + tpOpts = append(tpOpts, + sdktrace.WithSampler(smplr), + sdktrace.WithSpanProcessor(proc), + ) + otel.SetTracerProvider(sdktrace.NewTracerProvider(tpOpts...)) + return flusher, nil +} From 688a8dd199ec08d91d821587a0e37a209f561713 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 30 May 2024 15:41:44 -0700 Subject: [PATCH 02/26] Add some missing resource attributes --- .../aws/aws-lambda-go/swolambda/swolambda.go | 2 +- swo/agent.go | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index 1eeeed90..c065f118 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -47,7 +47,7 @@ func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, er func WrapHandler(f interface{}) lambda.Handler { once.Do(func() { var err error - if flusher, err = swo.StartLambda(); err != nil { + if flusher, err = swo.StartLambda(lambdacontext.LogStreamName); err != nil { log.Error("could not initialize SWO lambda instrumentation", err) } tracer = otel.GetTracerProvider().Tracer("swolambda") diff --git a/swo/agent.go b/swo/agent.go index 38e3f89b..43486af0 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -30,6 +30,7 @@ import ( "github.com/solarwinds/apm-go/internal/propagator" "github.com/solarwinds/apm-go/internal/reporter" "github.com/solarwinds/apm-go/internal/sampler" + "github.com/solarwinds/apm-go/internal/utils" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" @@ -160,7 +161,7 @@ func (l lambdaFlusher) Flush(ctx context.Context) error { var _ Flusher = &lambdaFlusher{} -func StartLambda() (Flusher, error) { +func StartLambda(lambdaLogStreamName string) (Flusher, error) { ctx := context.Background() var err error var tpOpts []sdktrace.TracerProviderOption @@ -200,7 +201,22 @@ func StartLambda() (Flusher, error) { return nil, err } otel.SetTextMapPropagator(prop) + // Default resource detection plus our required attributes + var resrc *resource.Resource + resrc, err = resource.Merge( + resource.Default(), + resource.NewSchemaless( + attribute.String("sw.data.module", "apm"), + attribute.String("sw.apm.version", utils.Version()), + attribute.String("faas.instance", lambdaLogStreamName), + ), + ) + if err != nil { + return nil, err + } + tpOpts = append(tpOpts, + sdktrace.WithResource(resrc), sdktrace.WithSampler(smplr), sdktrace.WithSpanProcessor(proc), ) From 1ef1b2a40a60b06373d5078db8cbc2cee8da85f9 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 30 May 2024 15:44:46 -0700 Subject: [PATCH 03/26] license check fixes --- .../github.com/aws/aws-lambda-go/swolambda/go.mod | 14 ++++++++++++++ .../aws/aws-lambda-go/swolambda/swolambda.go | 14 ++++++++++++++ internal/metrics/otel.go | 14 ++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod index 81e0f8fb..88f1dfdf 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/go.mod @@ -1,3 +1,17 @@ +// © 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + module github.com/solarwinds/apm-go/instrumentation/github.com/aws/aws-lambda-go/swolambda go 1.22.3 diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index c065f118..816da3b2 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -1,3 +1,17 @@ +// © 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package swolambda import ( diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go index 7e01b346..412818ff 100644 --- a/internal/metrics/otel.go +++ b/internal/metrics/otel.go @@ -1,3 +1,17 @@ +// © 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package metrics import ( From 12f680829628fada8e4c8dff609066563cce1aba Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Fri, 31 May 2024 08:16:27 -0700 Subject: [PATCH 04/26] satisfy linter --- internal/reporter/reporter_grpc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/reporter/reporter_grpc.go b/internal/reporter/reporter_grpc.go index b0ced062..af102ea1 100644 --- a/internal/reporter/reporter_grpc.go +++ b/internal/reporter/reporter_grpc.go @@ -1280,7 +1280,7 @@ func (d *DefaultDialer) Dial(p DialParams) (*grpc.ClientConn, error) { opts = append(opts, grpc.WithContextDialer(newGRPCProxyDialer(p))) } - return grpc.Dial(p.Address, opts...) + return grpc.NewClient(p.Address, opts...) } func newGRPCProxyDialer(p DialParams) func(context.Context, string) (net.Conn, error) { From 239b2cf723c817462b4831b9916cdcd66c431f40 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Fri, 31 May 2024 08:21:57 -0700 Subject: [PATCH 05/26] go mod tidy --- go.sum | 52 ++++++++++++---------------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/go.sum b/go.sum index 4f171d78..038804fb 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -26,8 +24,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -37,45 +34,30 @@ 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/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6 h1:Oyzwjp7RN7X8q3K4iK1B5+XmQL2ou993u9CGXOBkEpk= github.com/solarwinds/apm-proto v0.0.0-20231107001908-432e697887b6/go.mod h1:CN4fCYBnxyOJlBV0CYNXLz6lzNH8SCfNqcCBbpai76c= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0 h1:bFgvUr3/O4PHj3VQcFEuYKvRZJX1SJDQ+11JXuSB3/w= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.27.0/go.mod h1:xJntEd2KL6Qdg5lwp97HMLQDVeAhrYxmzFseAMDPQ8I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= -go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= @@ -84,29 +66,19 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ= google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI= google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= From db3e074c92e08bbb7e25068822593074da864436 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 11 Jun 2024 11:29:02 -0700 Subject: [PATCH 06/26] checkpoint --- .../aws/aws-lambda-go/swolambda/swolambda.go | 19 ++++-- internal/config/config.go | 9 +-- internal/entryspans/entryspans.go | 58 +++++++++++++++---- internal/entryspans/entryspans_test.go | 10 +++- internal/exporter/exporter.go | 4 +- internal/metrics/otel.go | 9 +-- internal/metrics/registry.go | 4 +- internal/{utils/otel.go => txn/txn.go} | 14 +++-- .../{utils/otel_test.go => txn/txn_test.go} | 2 +- swo/agent.go | 1 - 10 files changed, 95 insertions(+), 35 deletions(-) rename internal/{utils/otel.go => txn/txn.go} (91%) rename internal/{utils/otel_test.go => txn/txn_test.go} (99%) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index 816da3b2..4f846d3c 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -18,6 +18,7 @@ import ( "context" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-lambda-go/lambdacontext" + "github.com/solarwinds/apm-go/internal/config" "github.com/solarwinds/apm-go/internal/log" "github.com/solarwinds/apm-go/swo" "go.opentelemetry.io/otel" @@ -25,6 +26,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.24.0" "go.opentelemetry.io/otel/trace" "os" + "strings" "sync" ) @@ -33,7 +35,9 @@ var tracer trace.Tracer var once sync.Once type wrappedHandler struct { - base lambda.Handler + base lambda.Handler + fnName string + txnName string } var _ lambda.Handler = &wrappedHandler{} @@ -45,8 +49,8 @@ func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, er } else if lc != nil { attrs = append(attrs, semconv.FaaSInvocationID(lc.AwsRequestID)) } - name := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") - ctx, span := tracer.Start(ctx, name, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) + attrs = append(attrs, attribute.String("sw.transaction", w.txnName)) + ctx, span := tracer.Start(ctx, w.fnName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) defer func() { span.End() if flusher != nil { @@ -66,7 +70,14 @@ func WrapHandler(f interface{}) lambda.Handler { } tracer = otel.GetTracerProvider().Tracer("swolambda") }) + fnName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") + txnName := strings.TrimSpace(config.GetTransactionName()) + if txnName == "" { + txnName = fnName + } return &wrappedHandler{ - base: lambda.NewHandler(f), + base: lambda.NewHandler(f), + fnName: fnName, + txnName: txnName, } } diff --git a/internal/config/config.go b/internal/config/config.go index c9ef73d0..cc7fe0c0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + // Package config is responsible for loading the configuration from various // sources, e.g., environment variables, configuration files and user input. // It also accepts dynamic settings from the collector server. @@ -340,8 +341,8 @@ const ( may be different from your setting.` ) -// hasLambdaEnv checks if the AWS Lambda env var is set. -func hasLambdaEnv() bool { +// HasLambdaEnv checks if the AWS Lambda env var is set. +func HasLambdaEnv() bool { return os.Getenv("AWS_LAMBDA_FUNCTION_NAME") != "" && os.Getenv("LAMBDA_TASK_ROOT") != "" } @@ -362,12 +363,12 @@ func (c *Config) validate() error { c.Ec2MetadataTimeout = t } - if c.TransactionName != "" && !hasLambdaEnv() { + if c.TransactionName != "" && !HasLambdaEnv() { log.Info(InvalidEnv("TransactionName", c.TransactionName)) c.TransactionName = getFieldDefaultValue(c, "TransactionName") } - if !hasLambdaEnv() { + if !HasLambdaEnv() { if c.ServiceKey != "" { c.ServiceKey = ToServiceKey(c.ServiceKey) if ok := IsValidServiceKey(c.ServiceKey); !ok { diff --git a/internal/entryspans/entryspans.go b/internal/entryspans/entryspans.go index 29a0cce9..26b61c2d 100644 --- a/internal/entryspans/entryspans.go +++ b/internal/entryspans/entryspans.go @@ -17,34 +17,62 @@ package entryspans import ( "fmt" "github.com/pkg/errors" + "github.com/solarwinds/apm-go/internal/config" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "sync" ) var ( - state = &entrySpans{ - spans: make(map[trace.TraceID][]*entrySpan), - } + state = makeManagerFromEnv() - NotEntrySpan = errors.New("span is not an entry span") + NotEntrySpan = errors.New("span is not an entry span") + CannotSetTransaction = errors.New("cannot set transaction, likely due to lambda enviroment") nullSpanID = trace.SpanID{} nullEntrySpan = &entrySpan{spanId: nullSpanID} ) +type manager interface { + push(tid trace.TraceID, sid trace.SpanID) + delete(tid trace.TraceID, sid trace.SpanID) error + current(tid trace.TraceID) (*entrySpan, bool) + setTransactionName(tid trace.TraceID, name string) error +} + type entrySpan struct { spanId trace.SpanID txnName string } -type entrySpans struct { +type stdManager struct { mut sync.RWMutex spans map[trace.TraceID][]*entrySpan } -func (e *entrySpans) push(tid trace.TraceID, sid trace.SpanID) { +type noopManager struct{} + +func (n noopManager) push(trace.TraceID, trace.SpanID) {} + +func (n noopManager) delete(trace.TraceID, trace.SpanID) error { + return nil +} + +func (n noopManager) current(trace.TraceID) (*entrySpan, bool) { + return nil, false +} + +func (n noopManager) setTransactionName(trace.TraceID, string) error { + return CannotSetTransaction +} + +var ( + _ manager = &stdManager{} + _ manager = &noopManager{} +) + +func (e *stdManager) push(tid trace.TraceID, sid trace.SpanID) { e.mut.Lock() defer e.mut.Unlock() var list []*entrySpan @@ -56,14 +84,14 @@ func (e *entrySpans) push(tid trace.TraceID, sid trace.SpanID) { e.spans[tid] = list } -func (e *entrySpans) current(tid trace.TraceID) (*entrySpan, bool) { +func (e *stdManager) current(tid trace.TraceID) (*entrySpan, bool) { e.mut.Lock() defer e.mut.Unlock() a, ok := e.currentUnsafe(tid) return a, ok } -func (e *entrySpans) currentUnsafe(tid trace.TraceID) (*entrySpan, bool) { +func (e *stdManager) currentUnsafe(tid trace.TraceID) (*entrySpan, bool) { if list, ok := e.spans[tid]; ok { l := len(list) if len(list) == 0 { @@ -85,7 +113,7 @@ func Push(span sdktrace.ReadOnlySpan) error { return nil } -func (e *entrySpans) delete(tid trace.TraceID, sid trace.SpanID) error { +func (e *stdManager) delete(tid trace.TraceID, sid trace.SpanID) error { e.mut.Lock() defer e.mut.Unlock() @@ -125,7 +153,7 @@ func Current(tid trace.TraceID) (trace.SpanID, bool) { return curr.spanId, ok } -func (e *entrySpans) setTransactionName(tid trace.TraceID, name string) error { +func (e *stdManager) setTransactionName(tid trace.TraceID, name string) error { e.mut.Lock() defer e.mut.Unlock() @@ -152,3 +180,13 @@ func IsEntrySpan(span sdktrace.ReadOnlySpan) bool { parent := span.Parent() return !parent.IsValid() || parent.IsRemote() } + +func makeManagerFromEnv() manager { + if config.HasLambdaEnv() { + return &noopManager{} + } else { + return &stdManager{ + spans: make(map[trace.TraceID][]*entrySpan), + } + } +} diff --git a/internal/entryspans/entryspans_test.go b/internal/entryspans/entryspans_test.go index e8823da1..9d45b54c 100644 --- a/internal/entryspans/entryspans_test.go +++ b/internal/entryspans/entryspans_test.go @@ -33,7 +33,7 @@ var ( span4 = trace.SpanID{0x4} ) -func (e *entrySpans) pop(tid trace.TraceID) (trace.SpanID, bool) { +func (e *stdManager) pop(tid trace.TraceID) (trace.SpanID, bool) { e.mut.Lock() defer e.mut.Unlock() @@ -57,6 +57,7 @@ func (e *entrySpans) pop(tid trace.TraceID) (trace.SpanID, bool) { } func TestCurrent(t *testing.T) { + state := state.(*stdManager) sid, ok := Current(traceA) require.False(t, ok) require.False(t, sid.IsValid()) @@ -109,6 +110,7 @@ func TestCurrent(t *testing.T) { } func TestPush(t *testing.T) { + state := state.(*stdManager) var err error tr, teardown := testutils.TracerSetup() defer teardown() @@ -133,7 +135,8 @@ func TestPush(t *testing.T) { func TestSetTransactionName(t *testing.T) { // reset state - state = &entrySpans{spans: make(map[trace.TraceID][]*entrySpan)} + state = &stdManager{spans: make(map[trace.TraceID][]*entrySpan)} + state := state.(*stdManager) err := SetTransactionName(traceA, "foo bar") require.Error(t, err) @@ -174,7 +177,8 @@ func TestSetTransactionName(t *testing.T) { func TestDelete(t *testing.T) { // reset state - state = &entrySpans{spans: make(map[trace.TraceID][]*entrySpan)} + state = &stdManager{spans: make(map[trace.TraceID][]*entrySpan)} + state := state.(*stdManager) err := state.delete(traceA, span1) require.Error(t, err) diff --git a/internal/exporter/exporter.go b/internal/exporter/exporter.go index f950ff8d..e88d13ca 100644 --- a/internal/exporter/exporter.go +++ b/internal/exporter/exporter.go @@ -22,7 +22,7 @@ import ( "github.com/solarwinds/apm-go/internal/log" "github.com/solarwinds/apm-go/internal/reporter" "github.com/solarwinds/apm-go/internal/swotel/semconv" - "github.com/solarwinds/apm-go/internal/utils" + "github.com/solarwinds/apm-go/internal/txn" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -45,7 +45,7 @@ func (e *exporter) exportSpan(_ context.Context, s sdktrace.ReadOnlySpan) { attribute.String("otel.scope.version", s.InstrumentationScope().Version), }) if entryspans.IsEntrySpan(s) { - evt.AddKV(attribute.String("TransactionName", utils.GetTransactionName(s))) + evt.AddKV(attribute.String("TransactionName", txn.GetTransactionName(s))) // We MUST clear the entry span here. The SpanProcessor only clears entry spans when they are `RecordOnly` if err := entryspans.Delete(s); err != nil { log.Warningf( diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go index 412818ff..daa39bc4 100644 --- a/internal/metrics/otel.go +++ b/internal/metrics/otel.go @@ -17,7 +17,8 @@ package metrics import ( "context" "github.com/solarwinds/apm-go/internal/log" - "github.com/solarwinds/apm-go/internal/utils" + "github.com/solarwinds/apm-go/internal/txn" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" @@ -28,14 +29,13 @@ import ( ) type otelRegistry struct { - meterProvider metric.MeterProvider } func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, isAppoptics bool) { // TODO DRY with legacy registry? var attrs = []attribute.KeyValue{ attribute.Bool("sw.is_error", span.Status().Code == codes.Error), - attribute.String("sw.transaction", utils.GetTransactionName(span)), + attribute.String("sw.transaction", txn.GetTransactionName(span)), } for _, attr := range span.Attributes() { // TODO use semconv? @@ -48,7 +48,8 @@ func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, isAppoptics bool) } } // TODO service.name? - meter := o.meterProvider.Meter("sw.apm.request.metrics") + + meter := otel.GetMeterProvider().Meter("sw.apm.request.metrics") histo, err := meter.Int64Histogram( "trace.service.response_time", metric.WithExplicitBucketBoundaries(), diff --git a/internal/metrics/registry.go b/internal/metrics/registry.go index 4d25db5d..9dec47c9 100644 --- a/internal/metrics/registry.go +++ b/internal/metrics/registry.go @@ -19,7 +19,7 @@ import ( "github.com/solarwinds/apm-go/internal/bson" "github.com/solarwinds/apm-go/internal/log" "github.com/solarwinds/apm-go/internal/swotel/semconv" - "github.com/solarwinds/apm-go/internal/utils" + "github.com/solarwinds/apm-go/internal/txn" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/sdk/trace" trace2 "go.opentelemetry.io/otel/trace" @@ -192,7 +192,7 @@ func (r *registry) RecordSpan(span trace.ReadOnlySpan, isAppoptics bool) { } swoTags["sw.is_error"] = strconv.FormatBool(isError) - txnName := utils.GetTransactionName(span) + txnName := txn.GetTransactionName(span) swoTags["sw.transaction"] = txnName duration := span.EndTime().Sub(span.StartTime()) diff --git a/internal/utils/otel.go b/internal/txn/txn.go similarity index 91% rename from internal/utils/otel.go rename to internal/txn/txn.go index 9e10eaf0..5571204b 100644 --- a/internal/utils/otel.go +++ b/internal/txn/txn.go @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils +package txn import ( + "github.com/solarwinds/apm-go/internal/config" "github.com/solarwinds/apm-go/internal/entryspans" "github.com/solarwinds/apm-go/internal/swotel/semconv" "go.opentelemetry.io/otel/attribute" @@ -34,8 +35,13 @@ func GetTransactionName(span sdktrace.ReadOnlySpan) string { } // deriveTransactionName returns transaction name from given span name and attributes, falling back to "unknown" -func deriveTransactionName(name string, attrs []attribute.KeyValue) string { - var httpRoute, httpUrl, txnName = "", "", "" +func deriveTransactionName(name string, attrs []attribute.KeyValue) (txnName string) { + // TODO: add test + if txnName = config.GetTransactionName(); txnName != "" { + return + } + + var httpRoute, httpUrl = "", "" for _, attr := range attrs { if attr.Key == semconv.HTTPRouteKey { httpRoute = attr.Value.AsString() @@ -69,5 +75,5 @@ func deriveTransactionName(name string, attrs []attribute.KeyValue) string { if len(txnName) > 255 { txnName = txnName[:255] } - return txnName + return } diff --git a/internal/utils/otel_test.go b/internal/txn/txn_test.go similarity index 99% rename from internal/utils/otel_test.go rename to internal/txn/txn_test.go index aaf79e73..b579a24c 100644 --- a/internal/utils/otel_test.go +++ b/internal/txn/txn_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils +package txn import ( "context" diff --git a/swo/agent.go b/swo/agent.go index 43486af0..4895c2ff 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -188,7 +188,6 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { } else { tpOpts = append(tpOpts, sdktrace.WithSyncer(exprtr)) } - proc := processor.NewInboundMetricsSpanProcessor(registry, false) prop := propagation.NewCompositeTextMapPropagator( &propagation.TraceContext{}, From 87e3da995af3e0b53ce61fe3fa99ed35376da0fc Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 11 Jun 2024 15:15:58 -0700 Subject: [PATCH 07/26] checkpoint --- internal/oboe/file_watcher.go | 37 +++++++++++++++++++++----------- internal/oboe/settings_lambda.go | 13 +++++------ swo/agent.go | 8 +++---- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/internal/oboe/file_watcher.go b/internal/oboe/file_watcher.go index add94858..896e9e12 100644 --- a/internal/oboe/file_watcher.go +++ b/internal/oboe/file_watcher.go @@ -15,13 +15,14 @@ package oboe import ( + "os" "time" "github.com/solarwinds/apm-go/internal/log" ) const ( - settingsCheckSeconds = 10 + settingsCheckDuration time.Duration = 10 * time.Second ) var exit = make(chan bool, 1) @@ -33,9 +34,9 @@ type FileBasedWatcher interface { // NewFileBasedWatcher returns a FileBasedWatcher that periodically // reads lambda settings from file -func NewFileBasedWatcher(oboe *Oboe) FileBasedWatcher { +func NewFileBasedWatcher(oboe Oboe) FileBasedWatcher { return &fileBasedWatcher{ - *oboe, + oboe, } } @@ -44,22 +45,33 @@ type fileBasedWatcher struct { } // readSettingFromFile parses, normalizes, and print settings from file -func (fbw *fileBasedWatcher) readSettingFromFile() { - settingLambda, err := newSettingLambdaFromFile() - if err != nil { +func (w *fileBasedWatcher) readSettingFromFile() { + s, err := newSettingLambdaFromFile() + if os.IsNotExist(err) { + log.Debug("Settings file does not yet exist") + return + } else if err != nil { log.Errorf("Could not read setting from file: %s", err) return } log.Debugf( - "Got lambda settings from file:\n%v", - settingLambda, + "Got lambda settings from file:\n%+v", + s, + ) + w.o.UpdateSetting( + s.sType, + s.layer, + s.flags, + s.value, + s.ttl, + s.args, ) } // Start runs a ticker that checks settings expiry from cache // and, if expired, updates cache and oboe settings. -func (fbw *fileBasedWatcher) Start() { - ticker := time.NewTicker(settingsCheckSeconds * time.Second) +func (w *fileBasedWatcher) Start() { + ticker := time.NewTicker(settingsCheckDuration) go func() { defer ticker.Stop() for { @@ -67,13 +79,14 @@ func (fbw *fileBasedWatcher) Start() { case <-exit: return case <-ticker.C: - fbw.readSettingFromFile() + w.readSettingFromFile() } } }() + w.readSettingFromFile() } -func (fbw *fileBasedWatcher) Stop() { +func (w *fileBasedWatcher) Stop() { log.Info("Stopping settings file watcher.") exit <- true } diff --git a/internal/oboe/settings_lambda.go b/internal/oboe/settings_lambda.go index b574ed8a..e873f6bb 100644 --- a/internal/oboe/settings_lambda.go +++ b/internal/oboe/settings_lambda.go @@ -44,7 +44,7 @@ type settingArguments struct { } type settingLambdaNormalized struct { - sType int32 + sType settingType layer string flags []byte value int64 @@ -71,11 +71,11 @@ func newSettingLambdaNormalized(fromFile *settingLambdaFromFile) *settingLambdaN ) settingNorm := &settingLambdaNormalized{ - 1, // always DEFAULT_SAMPLE_RATE - "", // not set since type is always DEFAULT_SAMPLE_RATE + TypeDefault, // always DEFAULT_SAMPLE_RATE + "", // not set since type is always DEFAULT_SAMPLE_RATE flags, - int64(fromFile.Value), - int64(fromFile.Ttl), + fromFile.Value, + fromFile.Ttl, args, } @@ -86,7 +86,8 @@ func newSettingLambdaNormalized(fromFile *settingLambdaFromFile) *settingLambdaN // specific path in a specific format then returns values normalized for // oboe UpdateSetting, else returns error. func newSettingLambdaFromFile() (*settingLambdaNormalized, error) { - settingFile, err := os.Open("/tmp/solarwinds-apm-settings.json") + fn := "/tmp/solarwinds-apm-settings.json" + settingFile, err := os.Open(fn) if err != nil { return nil, err } diff --git a/swo/agent.go b/swo/agent.go index 4895c2ff..3fbe51ce 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -16,9 +16,6 @@ package swo import ( "context" - "io" - stdlog "log" - "strings" "github.com/solarwinds/apm-go/internal/config" "github.com/solarwinds/apm-go/internal/entryspans" @@ -163,6 +160,10 @@ var _ Flusher = &lambdaFlusher{} func StartLambda(lambdaLogStreamName string) (Flusher, error) { ctx := context.Background() + o := oboe.NewOboe() + settingsWatcher := oboe.NewFileBasedWatcher(o) + settingsWatcher.Start() + // TODO where do we shut this down? var err error var tpOpts []sdktrace.TracerProviderOption registry := metrics.NewOtelRegistry() @@ -194,7 +195,6 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { &propagation.Baggage{}, &propagator.SolarwindsPropagator{}, ) - o := oboe.NewOboe() smplr, err := sampler.NewSampler(o) if err != nil { return nil, err From 29a06a4f2e45b09a186dca2ae3a9bc399b52d53a Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 11 Jun 2024 15:19:21 -0700 Subject: [PATCH 08/26] various fixes --- internal/oboe/file_watcher.go | 2 +- internal/oboe/settings_lambda_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/oboe/file_watcher.go b/internal/oboe/file_watcher.go index 896e9e12..0e4290ef 100644 --- a/internal/oboe/file_watcher.go +++ b/internal/oboe/file_watcher.go @@ -59,7 +59,7 @@ func (w *fileBasedWatcher) readSettingFromFile() { s, ) w.o.UpdateSetting( - s.sType, + int32(s.sType), s.layer, s.flags, s.value, diff --git a/internal/oboe/settings_lambda_test.go b/internal/oboe/settings_lambda_test.go index 67e075ad..a6f763d5 100644 --- a/internal/oboe/settings_lambda_test.go +++ b/internal/oboe/settings_lambda_test.go @@ -46,7 +46,7 @@ func TestNewSettingLambdaNormalized(t *testing.T) { } result := newSettingLambdaNormalized(&fromFile) - assert.Equal(t, int32(1), result.sType) + assert.Equal(t, TypeDefault, result.sType) assert.Equal(t, "", result.layer) assert.Equal( t, @@ -140,7 +140,7 @@ func TestNewSettingLambdaFromFile(t *testing.T) { require.NoError(t, os.WriteFile(SettingsFile, content, 0644)) result, err := newSettingLambdaFromFile() assert.Nil(t, err) - assert.Equal(t, int32(1), result.sType) + assert.Equal(t, TypeDefault, result.sType) assert.Equal(t, "", result.layer) assert.Equal( t, From 1662062a3de635c0af66ea0745a4f3f5853e5536 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 08:57:17 -0700 Subject: [PATCH 09/26] misc cleanups --- internal/entryspans/entryspans_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/entryspans/entryspans_test.go b/internal/entryspans/entryspans_test.go index 9d45b54c..97242926 100644 --- a/internal/entryspans/entryspans_test.go +++ b/internal/entryspans/entryspans_test.go @@ -56,6 +56,12 @@ func (e *stdManager) pop(tid trace.TraceID) (trace.SpanID, bool) { } } +func (e *stdManager) reset() { + e.mut.Lock() + defer e.mut.Unlock() + clear(e.spans) +} + func TestCurrent(t *testing.T) { state := state.(*stdManager) sid, ok := Current(traceA) @@ -134,9 +140,8 @@ func TestPush(t *testing.T) { } func TestSetTransactionName(t *testing.T) { - // reset state - state = &stdManager{spans: make(map[trace.TraceID][]*entrySpan)} state := state.(*stdManager) + state.reset() err := SetTransactionName(traceA, "foo bar") require.Error(t, err) @@ -176,9 +181,8 @@ func TestSetTransactionName(t *testing.T) { } func TestDelete(t *testing.T) { - // reset state - state = &stdManager{spans: make(map[trace.TraceID][]*entrySpan)} state := state.(*stdManager) + state.reset() err := state.delete(traceA, span1) require.Error(t, err) From df141b26c2b797372ee24e1d4840e0d924538a65 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 10:02:53 -0700 Subject: [PATCH 10/26] cleanups --- internal/metrics/otel.go | 54 +++++++++++++++++++--------------------- swo/agent.go | 5 +++- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go index daa39bc4..9fa56aa0 100644 --- a/internal/metrics/otel.go +++ b/internal/metrics/otel.go @@ -16,9 +16,7 @@ package metrics import ( "context" - "github.com/solarwinds/apm-go/internal/log" "github.com/solarwinds/apm-go/internal/txn" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/metric" @@ -29,50 +27,48 @@ import ( ) type otelRegistry struct { + histo metric.Int64Histogram } -func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, isAppoptics bool) { - // TODO DRY with legacy registry? +var searchSet = map[attribute.Key]bool{ + semconv.HTTPMethodKey: true, + semconv.HTTPStatusCodeKey: true, + semconv.HTTPRouteKey: true, +} + +func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, _ bool) { var attrs = []attribute.KeyValue{ attribute.Bool("sw.is_error", span.Status().Code == codes.Error), attribute.String("sw.transaction", txn.GetTransactionName(span)), } for _, attr := range span.Attributes() { - // TODO use semconv? - if attr.Key == semconv.HTTPMethodKey { - attrs = append(attrs, attribute.String("http.method", attr.Value.AsString())) - } else if attr.Key == semconv.HTTPStatusCodeKey { - attrs = append(attrs, attribute.Int64("http.status_code", attr.Value.AsInt64())) - } else if attr.Key == semconv.HTTPRouteKey { - attrs = append(attrs, attribute.String("http.route", attr.Value.AsString())) + if searchSet[attr.Key] { + attrs = append(attrs, attr) } } - // TODO service.name? + duration := span.EndTime().Sub(span.StartTime()) + o.histo.Record( + context.Background(), + duration.Milliseconds(), + metric.WithAttributes(attrs...), + ) +} + +var _ MetricRegistry = &otelRegistry{} - meter := otel.GetMeterProvider().Meter("sw.apm.request.metrics") - histo, err := meter.Int64Histogram( +func NewOtelRegistry(p metric.MeterProvider) (MetricRegistry, error) { + meter := p.Meter("sw.apm.request.metrics") + if histo, err := meter.Int64Histogram( "trace.service.response_time", metric.WithExplicitBucketBoundaries(), metric.WithUnit("ms"), - ) - if err != nil { - log.Error(err) + ); err != nil { + return nil, err } else { - duration := span.EndTime().Sub(span.StartTime()) - histo.Record( - context.Background(), - duration.Milliseconds(), - metric.WithAttributes(attrs...), - ) + return &otelRegistry{histo: histo}, nil } } -var _ MetricRegistry = &otelRegistry{} - -func NewOtelRegistry() MetricRegistry { - return &otelRegistry{} -} - func TemporalitySelector(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality } diff --git a/swo/agent.go b/swo/agent.go index 3fbe51ce..0b81d522 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -166,7 +166,6 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { // TODO where do we shut this down? var err error var tpOpts []sdktrace.TracerProviderOption - registry := metrics.NewOtelRegistry() var metExp metric.Exporter if metExp, err = otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithTemporalitySelector(metrics.TemporalitySelector), @@ -189,6 +188,10 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { } else { tpOpts = append(tpOpts, sdktrace.WithSyncer(exprtr)) } + registry, err := metrics.NewOtelRegistry(mp) + if err != nil { + return nil, err + } proc := processor.NewInboundMetricsSpanProcessor(registry, false) prop := propagation.NewCompositeTextMapPropagator( &propagation.TraceContext{}, From 197788726f88fcce6f984ffc420729bed67ba4cf Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 10:11:23 -0700 Subject: [PATCH 11/26] refactors --- internal/metrics/metrics_test.go | 34 +++++++++++++++------------- internal/metrics/otel.go | 2 +- internal/metrics/registry.go | 12 ++++++---- internal/processor/processor.go | 10 ++++---- internal/processor/processor_test.go | 11 ++++----- internal/reporter/reporter_test.go | 10 ++++---- swo/agent.go | 8 +++---- 7 files changed, 44 insertions(+), 43 deletions(-) diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 5ea6b4fc..f15cfc7b 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -381,7 +381,7 @@ func TestAddHistogramToBSON(t *testing.T) { } func TestGenerateMetricsMessage(t *testing.T) { - reg := NewLegacyRegistry().(*registry) + reg := NewLegacyRegistry(false).(*registry) flushInterval := int32(60) bbuf := bson.WithBuf(reg.BuildBuiltinMetricsMessage(flushInterval, &EventQueueStats{}, map[string]*RateCounts{ // requested, sampled, limited, traced, through @@ -467,7 +467,7 @@ func TestGenerateMetricsMessage(t *testing.T) { assert.Nil(t, m["TransactionNameOverflow"]) - reg = NewLegacyRegistry().(*registry) + reg = NewLegacyRegistry(false).(*registry) for i := 0; i <= metricsTransactionsMaxDefault; i++ { if !reg.apmMetrics.txnMap.isWithinLimit("Transaction-" + strconv.Itoa(i)) { break @@ -546,9 +546,9 @@ func TestRecordSpan(t *testing.T) { ), ) span.End(trace.WithTimestamp(now.Add(1 * time.Second))) - reg := NewLegacyRegistry().(*registry) + reg := NewLegacyRegistry(false).(*registry) - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), false) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) m := reg.apmMetrics.CopyAndReset(60) assert.NotEmpty(t, m.m) @@ -567,7 +567,7 @@ func TestRecordSpan(t *testing.T) { assert.Equal(t, responseTime, v.Name) h := reg.apmHistograms.histograms - reg = NewLegacyRegistry().(*registry) + reg = NewLegacyRegistry(false).(*registry) assert.NotEmpty(t, h) globalHisto := h[""] granularHisto := h["my cool route"] @@ -579,8 +579,9 @@ func TestRecordSpan(t *testing.T) { assert.Equal(t, 1.001472e+06, granularHisto.hist.Mean()) assert.Equal(t, int64(1), granularHisto.hist.TotalCount()) + reg = NewLegacyRegistry(true).(*registry) // Now test for AO - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), true) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) m = reg.apmMetrics.CopyAndReset(60) assert.NotEmpty(t, m.m) @@ -637,8 +638,8 @@ func TestRecordSpanErrorStatus(t *testing.T) { ) span.End(trace.WithTimestamp(now.Add(1 * time.Second))) - reg := NewLegacyRegistry().(*registry) - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), false) + reg := NewLegacyRegistry(false).(*registry) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) m := reg.apmMetrics.CopyAndReset(60) assert.NotEmpty(t, m.m) @@ -657,7 +658,7 @@ func TestRecordSpanErrorStatus(t *testing.T) { assert.Equal(t, responseTime, v.Name) h := reg.apmHistograms.histograms - reg = NewLegacyRegistry().(*registry) + reg = NewLegacyRegistry(false).(*registry) assert.NotEmpty(t, h) globalHisto := h[""] granularHisto := h["my cool route"] @@ -670,7 +671,8 @@ func TestRecordSpanErrorStatus(t *testing.T) { assert.Equal(t, int64(1), granularHisto.hist.TotalCount()) // Now test for AO - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), true) + reg = NewLegacyRegistry(true).(*registry) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) m = reg.apmMetrics.CopyAndReset(60) assert.NotEmpty(t, m.m) @@ -740,14 +742,14 @@ func TestRecordSpanOverflow(t *testing.T) { ) span2.End(trace.WithTimestamp(now.Add(1 * time.Second))) - reg := NewLegacyRegistry().(*registry) + reg := NewLegacyRegistry(false).(*registry) // The cap only takes affect after the following reset reg.SetApmMetricsCap(1) reg.apmMetrics.CopyAndReset(60) assert.Equal(t, int32(1), reg.ApmMetricsCap()) - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), false) - reg.RecordSpan(span2.(sdktrace.ReadOnlySpan), false) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) + reg.RecordSpan(span2.(sdktrace.ReadOnlySpan)) m := reg.apmMetrics.CopyAndReset(60) // We expect to have a record for `my cool route` and one for `other` @@ -826,13 +828,13 @@ func TestRecordSpanOverflowAppoptics(t *testing.T) { // The cap only takes affect after the following reset // Appoptics-style will generate 3 metrics, so we'll set the cap to that here - reg := NewLegacyRegistry().(*registry) + reg := NewLegacyRegistry(true).(*registry) reg.SetApmMetricsCap(3) reg.apmMetrics.CopyAndReset(60) assert.Equal(t, int32(3), reg.ApmMetricsCap()) - reg.RecordSpan(span.(sdktrace.ReadOnlySpan), true) - reg.RecordSpan(span2.(sdktrace.ReadOnlySpan), true) + reg.RecordSpan(span.(sdktrace.ReadOnlySpan)) + reg.RecordSpan(span2.(sdktrace.ReadOnlySpan)) m := reg.apmMetrics.CopyAndReset(60) // We expect to have 3 records for `my cool route` and 3 for `other` diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go index 9fa56aa0..e9316f94 100644 --- a/internal/metrics/otel.go +++ b/internal/metrics/otel.go @@ -36,7 +36,7 @@ var searchSet = map[attribute.Key]bool{ semconv.HTTPRouteKey: true, } -func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan, _ bool) { +func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan) { var attrs = []attribute.KeyValue{ attribute.Bool("sw.is_error", span.Status().Code == codes.Error), attribute.String("sw.transaction", txn.GetTransactionName(span)), diff --git a/internal/metrics/registry.go b/internal/metrics/registry.go index 9dec47c9..f1fa5c70 100644 --- a/internal/metrics/registry.go +++ b/internal/metrics/registry.go @@ -31,11 +31,12 @@ type registry struct { apmHistograms *histograms apmMetrics *measurements customMetrics *measurements + isAppoptics bool } var _ LegacyRegistry = ®istry{} -func NewLegacyRegistry() LegacyRegistry { +func NewLegacyRegistry(isAppoptics bool) LegacyRegistry { return ®istry{ apmHistograms: &histograms{ histograms: make(map[string]*histogram), @@ -43,11 +44,12 @@ func NewLegacyRegistry() LegacyRegistry { }, apmMetrics: newMeasurements(false, metricsTransactionsMaxDefault), customMetrics: newMeasurements(true, metricsCustomMetricsMaxDefault), + isAppoptics: isAppoptics, } } type MetricRegistry interface { - RecordSpan(span trace.ReadOnlySpan, isAppoptics bool) + RecordSpan(span trace.ReadOnlySpan) } type LegacyRegistry interface { @@ -163,7 +165,7 @@ func (r *registry) BuildBuiltinMetricsMessage(flushInterval int32, qs *EventQueu return bbuf.GetBuf() } -func (r *registry) RecordSpan(span trace.ReadOnlySpan, isAppoptics bool) { +func (r *registry) RecordSpan(span trace.ReadOnlySpan) { method := "" status := int64(0) isError := span.Status().Code == codes.Error @@ -207,7 +209,7 @@ func (r *registry) RecordSpan(span trace.ReadOnlySpan, isAppoptics bool) { var tagsList []map[string]string var metricName string - if !isAppoptics { + if !r.isAppoptics { tagsList = []map[string]string{swoTags} metricName = responseTime } else { @@ -217,7 +219,7 @@ func (r *registry) RecordSpan(span trace.ReadOnlySpan, isAppoptics bool) { r.apmHistograms.recordHistogram("", duration) if err := s.processMeasurements(metricName, tagsList, r.apmMetrics); errors.Is(err, ErrExceedsMetricsCountLimit) { - if isAppoptics { + if r.isAppoptics { s.Transaction = OtherTransactionName tagsList = s.appOpticsTagsList() } else { diff --git a/internal/processor/processor.go b/internal/processor/processor.go index f581d389..de7a0118 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -22,18 +22,16 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" ) -func NewInboundMetricsSpanProcessor(registry metrics.MetricRegistry, isAppoptics bool) sdktrace.SpanProcessor { +func NewInboundMetricsSpanProcessor(registry metrics.MetricRegistry) sdktrace.SpanProcessor { return &inboundMetricsSpanProcessor{ - registry: registry, - isAppoptics: isAppoptics, + registry: registry, } } var _ sdktrace.SpanProcessor = &inboundMetricsSpanProcessor{} type inboundMetricsSpanProcessor struct { - registry metrics.MetricRegistry - isAppoptics bool + registry metrics.MetricRegistry } func (s *inboundMetricsSpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) { @@ -62,7 +60,7 @@ func maybeClearEntrySpan(span sdktrace.ReadOnlySpan) { func (s *inboundMetricsSpanProcessor) OnEnd(span sdktrace.ReadOnlySpan) { if entryspans.IsEntrySpan(span) { - s.registry.RecordSpan(span, s.isAppoptics) + s.registry.RecordSpan(span) maybeClearEntrySpan(span) } } diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index c6edba46..bdd76919 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -31,9 +31,8 @@ type recordMock struct { called bool } -func (r *recordMock) RecordSpan(span sdktrace.ReadOnlySpan, isAppoptics bool) { +func (r *recordMock) RecordSpan(span sdktrace.ReadOnlySpan) { r.span = span - r.isAppoptics = isAppoptics r.called = true } @@ -65,7 +64,7 @@ var _ metrics.LegacyRegistry = &recordMock{} func TestInboundMetricsSpanProcessorOnEnd(t *testing.T) { mock := &recordMock{} - sp := NewInboundMetricsSpanProcessor(mock, false) + sp := NewInboundMetricsSpanProcessor(mock) tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sp), sdktrace.WithSampler(sdktrace.AlwaysSample()), @@ -104,7 +103,7 @@ func (ro recordOnlySampler) Description() string { func TestInboundMetricsSpanProcessorOnEndRecordOnly(t *testing.T) { mock := &recordMock{} - sp := NewInboundMetricsSpanProcessor(mock, false) + sp := NewInboundMetricsSpanProcessor(mock) tp := sdktrace.NewTracerProvider( sdktrace.WithSpanProcessor(sp), sdktrace.WithSampler(recordOnlySampler{}), @@ -130,7 +129,7 @@ func TestInboundMetricsSpanProcessorOnEndRecordOnly(t *testing.T) { func TestInboundMetricsSpanProcessorOnEndWithLocalParent(t *testing.T) { mock := &recordMock{} - sp := NewInboundMetricsSpanProcessor(mock, false) + sp := NewInboundMetricsSpanProcessor(mock) tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sp)) tracer := tp.Tracer("foo") ctx, s1 := tracer.Start(context.Background(), "span name") @@ -153,7 +152,7 @@ func TestInboundMetricsSpanProcessorOnEndWithLocalParent(t *testing.T) { func TestInboundMetricsSpanProcessorOnEndWithRemoteParent(t *testing.T) { mock := &recordMock{} - sp := NewInboundMetricsSpanProcessor(mock, false) + sp := NewInboundMetricsSpanProcessor(mock) tp := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sp)) tracer := tp.Tracer("foo") ctx := context.Background() diff --git a/internal/reporter/reporter_test.go b/internal/reporter/reporter_test.go index b187dc10..f3c1548d 100644 --- a/internal/reporter/reporter_test.go +++ b/internal/reporter/reporter_test.go @@ -89,7 +89,7 @@ func TestGRPCReporter(t *testing.T) { setEnv("SW_APM_COLLECTOR", addr) setEnv("SW_APM_TRUSTEDPATH", testCertFile) config.Load() - registry := metrics.NewLegacyRegistry() + registry := metrics.NewLegacyRegistry(false) o := oboe.NewOboe() r := newGRPCReporter("myservice", registry, o).(*grpcReporter) @@ -176,7 +176,7 @@ func TestShutdownGRPCReporter(t *testing.T) { setEnv("SW_APM_COLLECTOR", addr) setEnv("SW_APM_TRUSTEDPATH", testCertFile) config.Load() - registry := metrics.NewLegacyRegistry() + registry := metrics.NewLegacyRegistry(false) o := oboe.NewOboe() r := newGRPCReporter("myservice", registry, o).(*grpcReporter) r.ShutdownNow() @@ -236,7 +236,7 @@ func TestInvalidKey(t *testing.T) { config.Load() log.SetLevel(log.INFO) - registry := metrics.NewLegacyRegistry() + registry := metrics.NewLegacyRegistry(false) o := oboe.NewOboe() r := newGRPCReporter("myservice", registry, o).(*grpcReporter) @@ -447,7 +447,7 @@ func TestInitReporter(t *testing.T) { // Test disable agent setEnv("SW_APM_ENABLED", "false") config.Load() - registry := metrics.NewLegacyRegistry() + registry := metrics.NewLegacyRegistry(false) o := oboe.NewOboe() r := initReporter(resource.Empty(), registry, o) require.IsType(t, &nullReporter{}, r) @@ -493,7 +493,7 @@ func testProxy(t *testing.T, proxyUrl string) { server := StartTestGRPCServer(t, addr) time.Sleep(100 * time.Millisecond) - registry := metrics.NewLegacyRegistry() + registry := metrics.NewLegacyRegistry(false) o := oboe.NewOboe() r := newGRPCReporter("myservice", registry, o).(*grpcReporter) diff --git a/swo/agent.go b/swo/agent.go index 0b81d522..841a7c18 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -94,7 +94,8 @@ func Start(resourceAttrs ...attribute.KeyValue) (func(), error) { // return a no-op func so that we don't cause a nil-deref for the end-user }, err } - registry := metrics.NewLegacyRegistry() + isAppoptics := strings.Contains(strings.ToLower(config.GetCollector()), "appoptics.com") + registry := metrics.NewLegacyRegistry(isAppoptics) o := oboe.NewOboe() _reporter, err := reporter.Start(resrc, registry, o) if err != nil { @@ -107,8 +108,7 @@ func Start(resourceAttrs ...attribute.KeyValue) (func(), error) { return func() {}, err } config.Load() - isAppoptics := strings.Contains(strings.ToLower(config.GetCollector()), "appoptics.com") - proc := processor.NewInboundMetricsSpanProcessor(registry, isAppoptics) + proc := processor.NewInboundMetricsSpanProcessor(registry) prop := propagation.NewCompositeTextMapPropagator( &propagation.TraceContext{}, &propagation.Baggage{}, @@ -192,7 +192,7 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { if err != nil { return nil, err } - proc := processor.NewInboundMetricsSpanProcessor(registry, false) + proc := processor.NewInboundMetricsSpanProcessor(registry) prop := propagation.NewCompositeTextMapPropagator( &propagation.TraceContext{}, &propagation.Baggage{}, From 899233afb97b2bf832ecd62b90eff20e02b39a17 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 10:19:57 -0700 Subject: [PATCH 12/26] refactors --- internal/processor/processor_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index bdd76919..704e33b0 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -26,9 +26,8 @@ import ( ) type recordMock struct { - span sdktrace.ReadOnlySpan - isAppoptics bool - called bool + span sdktrace.ReadOnlySpan + called bool } func (r *recordMock) RecordSpan(span sdktrace.ReadOnlySpan) { @@ -85,7 +84,6 @@ func TestInboundMetricsSpanProcessorOnEnd(t *testing.T) { require.True(t, ok) require.Equal(t, s.SpanContext().SpanID(), es) assert.True(t, mock.called) - assert.False(t, mock.isAppoptics) } type recordOnlySampler struct{} @@ -124,7 +122,6 @@ func TestInboundMetricsSpanProcessorOnEndRecordOnly(t *testing.T) { require.False(t, ok) require.False(t, es.IsValid()) assert.True(t, mock.called) - assert.False(t, mock.isAppoptics) } func TestInboundMetricsSpanProcessorOnEndWithLocalParent(t *testing.T) { From 89931e70b3b54eeab1ad3bcf189f3e49c5f63453 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 10:59:17 -0700 Subject: [PATCH 13/26] add test --- internal/txn/txn.go | 1 - internal/txn/txn_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/txn/txn.go b/internal/txn/txn.go index 5571204b..43c37ebe 100644 --- a/internal/txn/txn.go +++ b/internal/txn/txn.go @@ -36,7 +36,6 @@ func GetTransactionName(span sdktrace.ReadOnlySpan) string { // deriveTransactionName returns transaction name from given span name and attributes, falling back to "unknown" func deriveTransactionName(name string, attrs []attribute.KeyValue) (txnName string) { - // TODO: add test if txnName = config.GetTransactionName(); txnName != "" { return } diff --git a/internal/txn/txn_test.go b/internal/txn/txn_test.go index b579a24c..a83a8335 100644 --- a/internal/txn/txn_test.go +++ b/internal/txn/txn_test.go @@ -16,11 +16,13 @@ package txn import ( "context" + "github.com/solarwinds/apm-go/internal/config" "github.com/solarwinds/apm-go/internal/entryspans" "github.com/solarwinds/apm-go/internal/testutils" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" + "os" "strings" "testing" ) @@ -87,3 +89,25 @@ func TestDeriveTransactionName(t *testing.T) { expected := strings.Repeat("a", 255) require.Equal(t, expected, deriveTransactionName(name, attrs)) } + +func TestDeriveTxnFromEnv(t *testing.T) { + envTxn := "env-provided" + name := "span name" + var attrs []attribute.KeyValue + // `SW_APM_TRANSACTION_NAME` only takes effect in Lambda + require.NoError(t, os.Setenv("SW_APM_TRANSACTION_NAME", envTxn)) + config.Load() + require.Equal(t, "", config.GetTransactionName()) + require.Equal(t, "span name", deriveTransactionName(name, attrs)) + + require.NoError(t, os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "foo")) + require.NoError(t, os.Setenv("LAMBDA_TASK_ROOT", "bar")) + defer func() { + _ = os.Unsetenv("SW_APM_TRANSACTION_NAME") + _ = os.Unsetenv("AWS_LAMBDA_FUNCTION_NAME") + _ = os.Unsetenv("LAMBDA_TASK_ROOT") + }() + config.Load() + require.Equal(t, envTxn, config.GetTransactionName()) + require.Equal(t, envTxn, deriveTransactionName(name, attrs)) +} From 250af6d72e48237d78d953875f74ede793a1e5f8 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 11:12:55 -0700 Subject: [PATCH 14/26] misc --- internal/entryspans/entryspans.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/entryspans/entryspans.go b/internal/entryspans/entryspans.go index 26b61c2d..b8b0d2a8 100644 --- a/internal/entryspans/entryspans.go +++ b/internal/entryspans/entryspans.go @@ -27,7 +27,7 @@ var ( state = makeManagerFromEnv() NotEntrySpan = errors.New("span is not an entry span") - CannotSetTransaction = errors.New("cannot set transaction, likely due to lambda enviroment") + CannotSetTransaction = errors.New("cannot set transaction, likely due to lambda environment") nullSpanID = trace.SpanID{} nullEntrySpan = &entrySpan{spanId: nullSpanID} @@ -183,6 +183,8 @@ func IsEntrySpan(span sdktrace.ReadOnlySpan) bool { func makeManagerFromEnv() manager { if config.HasLambdaEnv() { + // In Lambda, we cannot modify the outgoing spans for transaction naming, + // thus we do not want to track entry spans. return &noopManager{} } else { return &stdManager{ From a63d6da45abbc137ffbfdf627c2fa1ef2bd99975 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Wed, 12 Jun 2024 11:52:34 -0700 Subject: [PATCH 15/26] misc cleanups --- internal/oboe/file_watcher.go | 3 ++- internal/oboe/settings_lambda.go | 9 ++++----- internal/oboe/settings_lambda_test.go | 22 ++++++++++------------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/internal/oboe/file_watcher.go b/internal/oboe/file_watcher.go index 0e4290ef..f7a8a05a 100644 --- a/internal/oboe/file_watcher.go +++ b/internal/oboe/file_watcher.go @@ -22,7 +22,8 @@ import ( ) const ( - settingsCheckDuration time.Duration = 10 * time.Second + settingsCheckDuration = 10 * time.Second + settingsFileName = "/tmp/solarwinds-apm-settings.json" ) var exit = make(chan bool, 1) diff --git a/internal/oboe/settings_lambda.go b/internal/oboe/settings_lambda.go index e873f6bb..365ffa58 100644 --- a/internal/oboe/settings_lambda.go +++ b/internal/oboe/settings_lambda.go @@ -86,8 +86,7 @@ func newSettingLambdaNormalized(fromFile *settingLambdaFromFile) *settingLambdaN // specific path in a specific format then returns values normalized for // oboe UpdateSetting, else returns error. func newSettingLambdaFromFile() (*settingLambdaNormalized, error) { - fn := "/tmp/solarwinds-apm-settings.json" - settingFile, err := os.Open(fn) + settingFile, err := os.Open(settingsFileName) if err != nil { return nil, err } @@ -96,8 +95,8 @@ func newSettingLambdaFromFile() (*settingLambdaNormalized, error) { return nil, err } // Settings file should be an array with a single settings object - var settingLambdas []settingLambdaFromFile - if err := json.Unmarshal(settingBytes, &settingLambdas); err != nil { + var settingLambdas []*settingLambdaFromFile + if err = json.Unmarshal(settingBytes, &settingLambdas); err != nil { return nil, err } if len(settingLambdas) != 1 { @@ -106,5 +105,5 @@ func newSettingLambdaFromFile() (*settingLambdaNormalized, error) { settingLambda := settingLambdas[0] - return newSettingLambdaNormalized(&settingLambda), nil + return newSettingLambdaNormalized(settingLambda), nil } diff --git a/internal/oboe/settings_lambda_test.go b/internal/oboe/settings_lambda_test.go index a6f763d5..cf41686a 100644 --- a/internal/oboe/settings_lambda_test.go +++ b/internal/oboe/settings_lambda_test.go @@ -23,8 +23,6 @@ import ( "github.com/stretchr/testify/require" ) -const SettingsFile = "/tmp/solarwinds-apm-settings.json" - func TestNewSettingLambdaNormalized(t *testing.T) { settingArgs := settingArguments{ 1, @@ -103,41 +101,41 @@ func TestNewSettingLambdaNormalized(t *testing.T) { } func TestNewSettingLambdaFromFileErrorOpen(t *testing.T) { - require.NoFileExists(t, SettingsFile) + require.NoFileExists(t, settingsFileName) res, err := newSettingLambdaFromFile() assert.Nil(t, res) assert.Error(t, err) } func TestNewSettingLambdaFromFileErrorUnmarshal(t *testing.T) { - require.NoFileExists(t, SettingsFile) + require.NoFileExists(t, settingsFileName) content := []byte("hello\ngo\n") - require.NoError(t, os.WriteFile(SettingsFile, content, 0644)) + require.NoError(t, os.WriteFile(settingsFileName, content, 0644)) res, err := newSettingLambdaFromFile() assert.Nil(t, res) assert.Error(t, err) - os.Remove(SettingsFile) + os.Remove(settingsFileName) } func TestNewSettingLambdaFromFileErrorLen(t *testing.T) { - require.NoFileExists(t, SettingsFile) + require.NoFileExists(t, settingsFileName) content := []byte("[]") - require.NoError(t, os.WriteFile(SettingsFile, content, 0644)) + require.NoError(t, os.WriteFile(settingsFileName, content, 0644)) res, err := newSettingLambdaFromFile() assert.Nil(t, res) assert.Error(t, err) - os.Remove(SettingsFile) + os.Remove(settingsFileName) } func TestNewSettingLambdaFromFile(t *testing.T) { - require.NoFileExists(t, SettingsFile) + require.NoFileExists(t, settingsFileName) content := []byte("[{\"arguments\":{\"BucketCapacity\":1,\"BucketRate\":1,\"MetricsFlushInterval\":1,\"TriggerRelaxedBucketCapacity\":1,\"TriggerRelaxedBucketRate\":1,\"TriggerStrictBucketCapacity\":1,\"TriggerStrictBucketRate\":1},\"flags\":\"SAMPLE_START,SAMPLE_THROUGH_ALWAYS,SAMPLE_BUCKET_ENABLED,TRIGGER_TRACE\",\"layer\":\"\",\"timestamp\":1715900164,\"ttl\":120,\"type\":0,\"value\":1000000}]") - require.NoError(t, os.WriteFile(SettingsFile, content, 0644)) + require.NoError(t, os.WriteFile(settingsFileName, content, 0644)) result, err := newSettingLambdaFromFile() assert.Nil(t, err) assert.Equal(t, TypeDefault, result.sType) @@ -195,5 +193,5 @@ func TestNewSettingLambdaFromFile(t *testing.T) { result.args[constants.KvSignatureKey], ) - os.Remove(SettingsFile) + os.Remove(settingsFileName) } From 30d4947a685f6d918aa9e331b3d3ef951edea29c Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 13 Jun 2024 08:12:59 -0700 Subject: [PATCH 16/26] linter fixes --- internal/metrics/metrics_test.go | 2 -- swo/agent.go | 15 +++++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index f15cfc7b..e3e0d15a 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -567,7 +567,6 @@ func TestRecordSpan(t *testing.T) { assert.Equal(t, responseTime, v.Name) h := reg.apmHistograms.histograms - reg = NewLegacyRegistry(false).(*registry) assert.NotEmpty(t, h) globalHisto := h[""] granularHisto := h["my cool route"] @@ -658,7 +657,6 @@ func TestRecordSpanErrorStatus(t *testing.T) { assert.Equal(t, responseTime, v.Name) h := reg.apmHistograms.histograms - reg = NewLegacyRegistry(false).(*registry) assert.NotEmpty(t, h) globalHisto := h[""] granularHisto := h["my cool route"] diff --git a/swo/agent.go b/swo/agent.go index 841a7c18..bc5ab0bb 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -17,6 +17,7 @@ package swo import ( "context" + "github.com/pkg/errors" "github.com/solarwinds/apm-go/internal/config" "github.com/solarwinds/apm-go/internal/entryspans" "github.com/solarwinds/apm-go/internal/exporter" @@ -40,9 +41,6 @@ import ( "io" stdlog "log" "strings" - "time" - - "github.com/pkg/errors" ) var ( @@ -162,8 +160,8 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { ctx := context.Background() o := oboe.NewOboe() settingsWatcher := oboe.NewFileBasedWatcher(o) + // settingsWatcher is started but never stopped in Lambda settingsWatcher.Start() - // TODO where do we shut this down? var err error var tpOpts []sdktrace.TracerProviderOption var metExp metric.Exporter @@ -172,10 +170,10 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { ); err != nil { return nil, err } - reader := metric.NewPeriodicReader( - metExp, - metric.WithInterval(1*time.Second), - ) + // The reader is flushed manually + reader := metric.NewPeriodicReader(metExp) + // The flusher is called after every invocation. We only need to flush + // metrics here because traces are sent synchronously. flusher := &lambdaFlusher{ Reader: reader, } @@ -186,6 +184,7 @@ func StartLambda(lambdaLogStreamName string) (Flusher, error) { if exprtr, err := otlptracegrpc.New(ctx); err != nil { return nil, err } else { + // Use WithSyncer to flush all spans each invocation tpOpts = append(tpOpts, sdktrace.WithSyncer(exprtr)) } registry, err := metrics.NewOtelRegistry(mp) From 9387e14a9d2eb3973438dfc634c80ee5dcf81455 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 13 Jun 2024 10:23:33 -0700 Subject: [PATCH 17/26] Override unset OTEL_EXPORTER_OTLP_ENDPOINT --- swo/agent.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/swo/agent.go b/swo/agent.go index bc5ab0bb..d9298ffa 100644 --- a/swo/agent.go +++ b/swo/agent.go @@ -16,6 +16,7 @@ package swo import ( "context" + "os" "github.com/pkg/errors" "github.com/solarwinds/apm-go/internal/config" @@ -157,6 +158,14 @@ func (l lambdaFlusher) Flush(ctx context.Context) error { var _ Flusher = &lambdaFlusher{} func StartLambda(lambdaLogStreamName string) (Flusher, error) { + // By default, the Go OTEL SDK sets this to `https://localhost:4317`, however + // we do not use https for the local collector in Lambda. We override if not + // already set. + if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") == "" { + if err := os.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"); err != nil { + log.Warningf("could not override unset OTEL_EXPORTER_OTLP_ENDPOINT %s", err) + } + } ctx := context.Background() o := oboe.NewOboe() settingsWatcher := oboe.NewFileBasedWatcher(o) From b0d407ea43572ae96a55b4cc485195305ded638d Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 13 Jun 2024 11:59:41 -0700 Subject: [PATCH 18/26] Add some (but not all) required semconv span attributes --- .../aws/aws-lambda-go/swolambda/swolambda.go | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index 4f846d3c..bf361b52 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -28,16 +28,21 @@ import ( "os" "strings" "sync" + "sync/atomic" ) -var flusher swo.Flusher -var tracer trace.Tracer -var once sync.Once +var ( + flusher swo.Flusher + tracer trace.Tracer + initHandlerOnce sync.Once + warmStart atomic.Bool +) type wrappedHandler struct { base lambda.Handler fnName string txnName string + region string } var _ lambda.Handler = &wrappedHandler{} @@ -49,7 +54,16 @@ func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, er } else if lc != nil { attrs = append(attrs, semconv.FaaSInvocationID(lc.AwsRequestID)) } - attrs = append(attrs, attribute.String("sw.transaction", w.txnName)) + // Note: We need to figure out how to determine `faas.trigger` attribute + // which is required by semconv + attrs = append( + attrs, + attribute.String("sw.transaction", w.txnName), + semconv.FaaSColdstart(!warmStart.Swap(true)), + semconv.FaaSInvokedName(w.fnName), + semconv.FaaSInvokedProviderAWS, + semconv.FaaSInvokedRegion(w.region), + ) ctx, span := tracer.Start(ctx, w.fnName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) defer func() { span.End() @@ -63,7 +77,7 @@ func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, er } func WrapHandler(f interface{}) lambda.Handler { - once.Do(func() { + initHandlerOnce.Do(func() { var err error if flusher, err = swo.StartLambda(lambdacontext.LogStreamName); err != nil { log.Error("could not initialize SWO lambda instrumentation", err) @@ -79,5 +93,6 @@ func WrapHandler(f interface{}) lambda.Handler { base: lambda.NewHandler(f), fnName: fnName, txnName: txnName, + region: os.Getenv("AWS_REGION"), } } From ccf2017cc86ae66747016ce0c91dc02b26c749da Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Thu, 13 Jun 2024 13:34:22 -0700 Subject: [PATCH 19/26] misc fix --- .../aws/aws-lambda-go/swolambda/swolambda.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index bf361b52..2e808708 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -48,22 +48,20 @@ type wrappedHandler struct { var _ lambda.Handler = &wrappedHandler{} func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, error) { - var attrs []attribute.KeyValue - if lc, ok := lambdacontext.FromContext(ctx); !ok { - log.Error("could not obtain lambda context") - } else if lc != nil { - attrs = append(attrs, semconv.FaaSInvocationID(lc.AwsRequestID)) - } // Note: We need to figure out how to determine `faas.trigger` attribute // which is required by semconv - attrs = append( - attrs, + attrs := []attribute.KeyValue{ attribute.String("sw.transaction", w.txnName), semconv.FaaSColdstart(!warmStart.Swap(true)), semconv.FaaSInvokedName(w.fnName), semconv.FaaSInvokedProviderAWS, semconv.FaaSInvokedRegion(w.region), - ) + } + if lc, ok := lambdacontext.FromContext(ctx); !ok { + log.Error("could not obtain lambda context") + } else if lc != nil { + attrs = append(attrs, semconv.FaaSInvocationID(lc.AwsRequestID)) + } ctx, span := tracer.Start(ctx, w.fnName, trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes(attrs...)) defer func() { span.End() From c7b7d7e7adecf039a599e10c920abd9044a0271f Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Fri, 14 Jun 2024 10:06:22 -0700 Subject: [PATCH 20/26] Wait for settings file --- internal/oboe/file_watcher.go | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/oboe/file_watcher.go b/internal/oboe/file_watcher.go index f7a8a05a..8184d298 100644 --- a/internal/oboe/file_watcher.go +++ b/internal/oboe/file_watcher.go @@ -15,6 +15,7 @@ package oboe import ( + "context" "os" "time" @@ -24,6 +25,8 @@ import ( const ( settingsCheckDuration = 10 * time.Second settingsFileName = "/tmp/solarwinds-apm-settings.json" + + timeoutEnv = "SW_APM_INITIAL_SETTINGS_FILE_TIMEOUT" ) var exit = make(chan bool, 1) @@ -73,6 +76,7 @@ func (w *fileBasedWatcher) readSettingFromFile() { // and, if expired, updates cache and oboe settings. func (w *fileBasedWatcher) Start() { ticker := time.NewTicker(settingsCheckDuration) + waitForSettingsFile() go func() { defer ticker.Stop() for { @@ -91,3 +95,45 @@ func (w *fileBasedWatcher) Stop() { log.Info("Stopping settings file watcher.") exit <- true } + +func waitForSettingsFile() { + var timeout = 1 * time.Second + if timeoutStr := os.Getenv(timeoutEnv); timeoutStr != "" { + if override, err := time.ParseDuration(timeoutStr); err != nil { + log.Errorf("could not parse duration from %s '%s': %s", timeoutEnv, timeoutStr, err) + } else if int64(override) < 1 { + log.Infof("%s was 0 or negative, skipping wait for settings file", timeoutEnv) + return + } else { + timeout = override + } + } + log.Debugf("Waiting for settings file for up to %s (override with %s; set to 0 to skip)", timeout, timeoutEnv) + // We could use something like fsnotify, but that's overkill for something this simple + waitTicker := time.NewTicker(10 * time.Millisecond) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + defer waitTicker.Stop() + for { + select { + case <-waitTicker.C: + { + _, err := os.Stat(settingsFileName) + if err == nil { + log.Info("Settings file found") + return + } else if os.IsNotExist(err) { + log.Debug("Settings file does not yet exist") + } else { + log.Errorf("Could not read settings from file: %s", err) + return + } + } + case <-ctx.Done(): + { + log.Info("timed out waiting for settings file") + return + } + } + } +} From 78316fadf2a83a4ff1d3323b74cb1638c8213754 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Mon, 17 Jun 2024 09:42:21 -0700 Subject: [PATCH 21/26] Truncate derived transaction names --- internal/txn/txn.go | 56 ++++++++++++++++++++-------------------- internal/txn/txn_test.go | 17 ++++++++++++ 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/internal/txn/txn.go b/internal/txn/txn.go index 43c37ebe..45b8a6ed 100644 --- a/internal/txn/txn.go +++ b/internal/txn/txn.go @@ -35,37 +35,37 @@ func GetTransactionName(span sdktrace.ReadOnlySpan) string { } // deriveTransactionName returns transaction name from given span name and attributes, falling back to "unknown" -func deriveTransactionName(name string, attrs []attribute.KeyValue) (txnName string) { - if txnName = config.GetTransactionName(); txnName != "" { - return - } - - var httpRoute, httpUrl = "", "" - for _, attr := range attrs { - if attr.Key == semconv.HTTPRouteKey { - httpRoute = attr.Value.AsString() - } else if attr.Key == semconv.HTTPURLKey { - httpUrl = attr.Value.AsString() +func deriveTransactionName(name string, attrs []attribute.KeyValue) string { + txnName := config.GetTransactionName() + if txnName == "" { + var httpRoute, httpUrl = "", "" + for _, attr := range attrs { + if attr.Key == semconv.HTTPRouteKey { + httpRoute = attr.Value.AsString() + } else if attr.Key == semconv.HTTPURLKey { + httpUrl = attr.Value.AsString() + } } - } - if httpRoute != "" { - txnName = httpRoute - } else if name != "" { - txnName = name - } - if httpUrl != "" && strings.TrimSpace(txnName) == "" { - parsed, err := url.Parse(httpUrl) - if err != nil { - // We can't import internal logger in the util package, so we default to "log". However, this should be - // infrequent. - log.Println("could not parse URL from span", httpUrl) - } else { - // Clear user/password - parsed.User = nil - txnName = parsed.String() + if httpRoute != "" { + txnName = httpRoute + } else if name != "" { + txnName = name + } + if httpUrl != "" && strings.TrimSpace(txnName) == "" { + parsed, err := url.Parse(httpUrl) + if err != nil { + // We can't import internal logger in the util package, so we default to "log". However, this should be + // infrequent. + log.Println("could not parse URL from span", httpUrl) + } else { + // Clear user/password + parsed.User = nil + txnName = parsed.String() + } } } + txnName = strings.TrimSpace(txnName) if txnName == "" { txnName = "unknown" @@ -74,5 +74,5 @@ func deriveTransactionName(name string, attrs []attribute.KeyValue) (txnName str if len(txnName) > 255 { txnName = txnName[:255] } - return + return txnName } diff --git a/internal/txn/txn_test.go b/internal/txn/txn_test.go index a83a8335..b1ddbd1b 100644 --- a/internal/txn/txn_test.go +++ b/internal/txn/txn_test.go @@ -111,3 +111,20 @@ func TestDeriveTxnFromEnv(t *testing.T) { require.Equal(t, envTxn, config.GetTransactionName()) require.Equal(t, envTxn, deriveTransactionName(name, attrs)) } +func TestDeriveTxnFromEnvTruncated(t *testing.T) { + envTxn := strings.Repeat("a", 1024) + expected := strings.Repeat("a", 255) + name := "span name" + var attrs []attribute.KeyValue + require.NoError(t, os.Setenv("SW_APM_TRANSACTION_NAME", envTxn)) + require.NoError(t, os.Setenv("AWS_LAMBDA_FUNCTION_NAME", "foo")) + require.NoError(t, os.Setenv("LAMBDA_TASK_ROOT", "bar")) + defer func() { + _ = os.Unsetenv("SW_APM_TRANSACTION_NAME") + _ = os.Unsetenv("AWS_LAMBDA_FUNCTION_NAME") + _ = os.Unsetenv("LAMBDA_TASK_ROOT") + }() + config.Load() + require.Equal(t, envTxn, config.GetTransactionName()) + require.Equal(t, expected, deriveTransactionName(name, attrs)) +} From 75b401b5a1d2bb17dc108795cc9c44f1c8a7c6e3 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Mon, 17 Jun 2024 10:48:42 -0700 Subject: [PATCH 22/26] Fix error name --- internal/entryspans/entryspans.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/entryspans/entryspans.go b/internal/entryspans/entryspans.go index b8b0d2a8..fc242d0f 100644 --- a/internal/entryspans/entryspans.go +++ b/internal/entryspans/entryspans.go @@ -26,8 +26,8 @@ import ( var ( state = makeManagerFromEnv() - NotEntrySpan = errors.New("span is not an entry span") - CannotSetTransaction = errors.New("cannot set transaction, likely due to lambda environment") + NotEntrySpan = errors.New("span is not an entry span") + CannotSetTransactionName = errors.New("cannot set transaction, likely due to lambda environment") nullSpanID = trace.SpanID{} nullEntrySpan = &entrySpan{spanId: nullSpanID} @@ -64,7 +64,7 @@ func (n noopManager) current(trace.TraceID) (*entrySpan, bool) { } func (n noopManager) setTransactionName(trace.TraceID, string) error { - return CannotSetTransaction + return CannotSetTransactionName } var ( From 067790fdc0ee1906a1bec04fa280c8d52f9af443 Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Mon, 17 Jun 2024 13:47:24 -0700 Subject: [PATCH 23/26] move trimspace --- .../github.com/aws/aws-lambda-go/swolambda/swolambda.go | 3 +-- internal/config/config.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index 2e808708..6562749b 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -26,7 +26,6 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.24.0" "go.opentelemetry.io/otel/trace" "os" - "strings" "sync" "sync/atomic" ) @@ -83,7 +82,7 @@ func WrapHandler(f interface{}) lambda.Handler { tracer = otel.GetTracerProvider().Tracer("swolambda") }) fnName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME") - txnName := strings.TrimSpace(config.GetTransactionName()) + txnName := config.GetTransactionName() if txnName == "" { txnName = fnName } diff --git a/internal/config/config.go b/internal/config/config.go index cc7fe0c0..2b855daf 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -900,7 +900,7 @@ func (c *Config) GetTransactionFiltering() []TransactionFilter { func (c *Config) GetTransactionName() string { c.RLock() defer c.RUnlock() - return c.TransactionName + return strings.TrimSpace(c.TransactionName) } // GetSQLSanitize returns the SQL sanitization level. From 686b90d13bcc535ef7d5c79081014ca53dc8d2aa Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 18 Jun 2024 13:04:37 -0700 Subject: [PATCH 24/26] First pass at instrumentation README --- .../aws/aws-lambda-go/swolambda/README.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 instrumentation/github.com/aws/aws-lambda-go/swolambda/README.md diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/README.md b/instrumentation/github.com/aws/aws-lambda-go/swolambda/README.md new file mode 100644 index 00000000..d2a68da6 --- /dev/null +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/README.md @@ -0,0 +1,51 @@ +# Instrumentation for AWS Lambda + +This package instruments the AWS Lambda `Handler` interface. + +## Usage + +### Add the `Otelcol` extension layer + +Follow the [SolarWinds Observability +documentation](https://documentation.solarwinds.com/en/success_center/observability/content/intro/services/aws-lambda-overview.htm) +to add the Otelcol extension layer. + +**Note**: Unlike other languages, Golang does not require an additional +extension, so the "Instrumentation extension" section on that page does not +apply. + +### Modify your code + +First, install the dependency: +```shell +go get -u github.com/solarwinds/apm-go/instrumentation/github.com/aws/aws-lambda-go/swolambda +``` + +Then, wrap your handler with `swolambda.WrapHandler`: + +```go +package main +import ( + "context" + "github.com/aws/aws-lambda-go/lambda" + "github.com/solarwinds/apm-go/instrumentation/github.com/aws/aws-lambda-go/swolambda" +) + +// Example incoming type +type MyEvent struct {} + +// This is an example handler, yours may have a different signature and a +// different name. It will work ass long as it adheres to what the Lambda SDK +// expects. (See "Valid handler signatures"[0]) +// [0] https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html +func ExampleHandler(ctx context.Context, event *MyEvent) (string, error) { + return "hello world", nil +} +func main() { + // We wrap our handler here and pass the result to `lambda.Start` + lambda.Start(swolambda.WrapHandler(ExampleHandler)) +} +``` + +Now that you've instrumented your code, you should be able to send requests and +see the resulting metrics and traces in SWO. \ No newline at end of file From 1967feef8716f8bf9b6a345c7e1f857c89c9e2da Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 18 Jun 2024 13:10:17 -0700 Subject: [PATCH 25/26] Fix RecordSpan to only add http attributes when span kind is server --- internal/metrics/otel.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/metrics/otel.go b/internal/metrics/otel.go index e9316f94..083f5ba0 100644 --- a/internal/metrics/otel.go +++ b/internal/metrics/otel.go @@ -24,6 +24,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" + "go.opentelemetry.io/otel/trace" ) type otelRegistry struct { @@ -41,9 +42,11 @@ func (o *otelRegistry) RecordSpan(span sdktrace.ReadOnlySpan) { attribute.Bool("sw.is_error", span.Status().Code == codes.Error), attribute.String("sw.transaction", txn.GetTransactionName(span)), } - for _, attr := range span.Attributes() { - if searchSet[attr.Key] { - attrs = append(attrs, attr) + if span.SpanKind() == trace.SpanKindServer { + for _, attr := range span.Attributes() { + if searchSet[attr.Key] { + attrs = append(attrs, attr) + } } } duration := span.EndTime().Sub(span.StartTime()) From 3e18a256df1aba93fe862e6e463c80183344fb8b Mon Sep 17 00:00:00 2001 From: Jared Harper Date: Tue, 18 Jun 2024 13:14:09 -0700 Subject: [PATCH 26/26] Set status to error and record error when error is returned from Invoke --- .../github.com/aws/aws-lambda-go/swolambda/swolambda.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go index 6562749b..e84dad8d 100644 --- a/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go +++ b/instrumentation/github.com/aws/aws-lambda-go/swolambda/swolambda.go @@ -23,6 +23,7 @@ import ( "github.com/solarwinds/apm-go/swo" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" "go.opentelemetry.io/otel/trace" "os" @@ -70,7 +71,12 @@ func (w *wrappedHandler) Invoke(ctx context.Context, payload []byte) ([]byte, er } } }() - return w.base.Invoke(ctx, payload) + res, err := w.base.Invoke(ctx, payload) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + span.RecordError(err) + } + return res, err } func WrapHandler(f interface{}) lambda.Handler {