diff --git a/config/config.go b/config/config.go index e0d6ac25..56a535b0 100644 --- a/config/config.go +++ b/config/config.go @@ -112,6 +112,9 @@ type Config struct { // The list of HTTP headers to extract from a HTTP request/response. HTTPHeadersToExtract []string + + // Event Handler type to use for sending events. + EventHandlerType string } // NewConfig returns a new Config struct. @@ -149,6 +152,7 @@ func NewConfig() Config { AdditionalAttributes: utils.LookupEnvAsStringMap("ADDITIONAL_ATTRIBUTES"), IncludeRequestURL: utils.LookupEnvOrBool("INCLUDE_REQUEST_URL", true), HTTPHeadersToExtract: getHTTPHeadersToExtract(), + EventHandlerType: utils.LookupEnvOrString("HANDLER_TYPE", "libhoney"), } } @@ -246,7 +250,7 @@ var defaultHeadersToExtract = []string{ } // getHTTPHeadersToExtract returns the list of HTTP headers to extract from a HTTP request/response -// based on a user-defined list in HTTP_HEADERS, or the default headers if no list is given. +// based on a user-defined list in HTTP_HEADERS, or the default headers if no list is given. func getHTTPHeadersToExtract() []string { if headers, found := utils.LookupEnvAsStringSlice("HTTP_HEADERS"); found { return headers diff --git a/go.mod b/go.mod index cf5a42a4..f1998777 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,46 @@ require ( github.com/rs/zerolog v1.31.0 github.com/stretchr/testify v1.8.4 go.opentelemetry.io/otel v1.19.0 + go.opentelemetry.io/otel/trace v1.19.0 k8s.io/api v0.28.2 k8s.io/apimachinery v0.28.2 k8s.io/client-go v0.28.2 ) +require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect + github.com/honeycombio/otel-config-go v1.12.1 + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/sethvargo/go-envconfig v0.9.0 // indirect + github.com/shirou/gopsutil/v3 v3.23.8 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/contrib/instrumentation/host v0.44.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.19.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 // indirect + go.opentelemetry.io/otel/metric v1.19.0 // indirect + go.opentelemetry.io/otel/sdk v1.18.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.41.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.1 // indirect +) + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect @@ -46,13 +81,13 @@ require ( github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/alexcesaro/statsd.v2 v2.0.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index baec887f..2733ccf7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -22,8 +24,13 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= @@ -35,6 +42,8 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -42,6 +51,7 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -51,10 +61,14 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/honeycombio/gopacket v1.1.1 h1:J7hvYGbDc0OvCRSmONOby6voqz7TeI93MnhOC1Frv7c= github.com/honeycombio/gopacket v1.1.1/go.mod h1:HavMeONEl7W9036of9LbSWoonqhH7HA1+ZRO+rMIvFs= github.com/honeycombio/libhoney-go v1.20.0 h1:PL54R0P9vxIyb28H3twbLb+DCqQlJdMQM55VZg1abKA= github.com/honeycombio/libhoney-go v1.20.0/go.mod h1:RIaurCpfg5NDWSEV8t3QLcda9dUAiVNyWeHRAaSpN90= +github.com/honeycombio/otel-config-go v1.12.1 h1:7PiKBjXStElCvM95lphJEShNJGA2Ep8wbucC6yuYzQw= +github.com/honeycombio/otel-config-go v1.12.1/go.mod h1:6L4w8t0ttG+jacDhjFAn7TnaKUm/uqdA7QWokJLW8DY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -70,6 +84,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -92,6 +109,9 @@ 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pyroscope-io/godeltaprof v0.1.2 h1:MdlEmYELd5w+lvIzmZvXGNMVzW2Qc9jDMuJaPOR75g4= github.com/pyroscope-io/godeltaprof v0.1.2/go.mod h1:psMITXp90+8pFenXkKIpNhrfmI9saQnPbba27VIaiQE= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -101,6 +121,14 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/sethvargo/go-envconfig v0.9.0 h1:Q6FQ6hVEeTECULvkJZakq3dZMeBQ3JUpcKMfPQbKMDE= +github.com/sethvargo/go-envconfig v0.9.0/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0= +github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= +github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -113,14 +141,56 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/contrib/detectors/aws/lambda v0.44.0 h1:s+4DQOUrFZwKOS2cOQf9RwVgg0ZtUSSsi17PdDOz96g= +go.opentelemetry.io/contrib/detectors/aws/lambda v0.44.0/go.mod h1:DtJoiPxV7q/w2hqGPZSOfjfIigK/tkgihpsQhQFDl/Q= +go.opentelemetry.io/contrib/instrumentation/host v0.44.0 h1:SNqDjPpQmwFYvDipyJJxDbU5zKNWiYSMii864ubzIuQ= +go.opentelemetry.io/contrib/instrumentation/host v0.44.0/go.mod h1:bZcqg3yy0riQLNkx8dJWV4J3tbfL+6LQ5lIbI+vmarE= +go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0 h1:TXu20nL4yYfJlQeqG/D3Ia6b0p2HZmLfJto9hqJTQ/c= +go.opentelemetry.io/contrib/instrumentation/runtime v0.44.0/go.mod h1:tQ5gBnfjndV1su3+DiLuu6rnd9hBBzg4rkRILnjSNFg= +go.opentelemetry.io/contrib/propagators/b3 v1.19.0 h1:ulz44cpm6V5oAeg5Aw9HyqGFMS6XM7untlMEhD7YzzA= +go.opentelemetry.io/contrib/propagators/b3 v1.19.0/go.mod h1:OzCmE2IVS+asTI+odXQstRGVfXQ4bXv9nMBRK0nNyqQ= +go.opentelemetry.io/contrib/propagators/ot v1.19.0 h1:vODRLMlKN4ApM8ri0UDk8nnEeISuwxpf67sE7PmOHhE= +go.opentelemetry.io/contrib/propagators/ot v1.19.0/go.mod h1:S2Uc7th2ZmLiHu0lrCmDCgTQ/y5Nbbis+TNjR1jjm4Q= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0 h1:k0k7hFNDd8K4iOMJXj7s8sHaC4mhTlAeppRmZXLgZ6k= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.41.0/go.mod h1:hG4Fj/y8TR/tlEDREo8tWstl9fO9gcFkn4xrx0Io8xU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0 h1:HgbDTD8pioFdY3NRc/YCvsWjqQPtweGyXxa32LgnTOw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.41.0/go.mod h1:tmvt/yK5Es5d6lHYWerLSOna8lCEfrBVX/a9M0ggqss= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0 h1:iV3BOgW4fry1Riw9dwypigqlIYWXvSRVT2RJmblzo40= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.41.0/go.mod h1:7PGzqlKrxIRmbj5tlNW0nTkYZ5fHXDgk6Fy8/KjR0CI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0 h1:6pu8ttx76BxHf+xz/H77AUZkPF3cwWzXqAUsXhVKI18= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.18.0/go.mod h1:IOmXxPrxoxFMXdNy7lfDmE8MzE61YPcurbUm0SMjerI= +go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= +go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= +go.opentelemetry.io/otel/sdk/metric v0.41.0 h1:c3sAt9/pQ5fSIUfl0gPtClV3HhE18DCVzByD33R/zsk= +go.opentelemetry.io/otel/sdk/metric v0.41.0/go.mod h1:PmOmSt+iOklKtIg5O4Vz9H/ttcRFSNTgii+E1KGyn1w= +go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -133,16 +203,20 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -167,10 +241,18 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= +google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= +google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= +google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alexcesaro/statsd.v2 v2.0.0 h1:FXkZSCZIH17vLCO5sO2UucTHsH9pc+17F6pl3JVCwMc= gopkg.in/alexcesaro/statsd.v2 v2.0.0/go.mod h1:i0ubccKGzBVNBpdGV5MocxyA/XlLUJzA7SLonnE4drU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/handlers/event_handler.go b/handlers/event_handler.go index 96bc81aa..9887faeb 100644 --- a/handlers/event_handler.go +++ b/handlers/event_handler.go @@ -5,6 +5,9 @@ import ( "sync" "github.com/honeycombio/honeycomb-network-agent/assemblers" + "github.com/honeycombio/honeycomb-network-agent/config" + "github.com/honeycombio/honeycomb-network-agent/utils" + "github.com/rs/zerolog/log" ) // EventHandler is an interface for event handlers @@ -13,3 +16,18 @@ type EventHandler interface { Close() handleEvent(event assemblers.Event) } + +// NewEventHandler returns an event handler based on the config's selected handler type. +func NewEventHandler(config config.Config, cachedK8sClient *utils.CachedK8sClient, eventsChannel chan assemblers.Event, version string) EventHandler { + var eventHandler EventHandler + switch config.EventHandlerType { + case "libhoney": + eventHandler = NewLibhoneyEventHandler(config, cachedK8sClient, eventsChannel, version) + case "otel": + eventHandler = NewOtelHandler(config, cachedK8sClient, eventsChannel, version) + default: + log.Warn().Str("event_handler_type", config.EventHandlerType).Msg("Unknown event handler type. Using libhoney.") + eventHandler = NewLibhoneyEventHandler(config, cachedK8sClient, eventsChannel, version) + } + return eventHandler +} diff --git a/handlers/libhoney_event_handler.go b/handlers/libhoney_event_handler.go index 4f66b91f..cc43021e 100644 --- a/handlers/libhoney_event_handler.go +++ b/handlers/libhoney_event_handler.go @@ -23,6 +23,8 @@ type libhoneyEventHandler struct { eventsChan chan assemblers.Event } +var _ EventHandler = (*libhoneyEventHandler)(nil) + // NewLibhoneyEventHandler creates a new event handler that sends events using libhoney func NewLibhoneyEventHandler(config config.Config, k8sClient *utils.CachedK8sClient, eventsChan chan assemblers.Event, version string) EventHandler { initLibhoney(config, version) @@ -96,7 +98,7 @@ func (handler *libhoneyEventHandler) handleEvent(event assemblers.Event) { // the telemetry event to send var ev *libhoney.Event = libhoney.NewEvent() - setTimestampsAndDurationIfValid(ev, event) + handler.setTimestampsAndDurationIfValid(ev, event) ev.AddField("meta.stream.ident", event.StreamIdent()) ev.AddField("meta.seqack", event.RequestId()) @@ -133,7 +135,7 @@ func (handler *libhoneyEventHandler) handleEvent(event assemblers.Event) { // // It only sets timestamps if they are present in the captured event, and only // computes and includes durations for which there are correct timestamps to based them upon. -func setTimestampsAndDurationIfValid(honeyEvent *libhoney.Event, event assemblers.Event) { +func (handler *libhoneyEventHandler) setTimestampsAndDurationIfValid(honeyEvent *libhoney.Event, event assemblers.Event) { honeyEvent.AddField("meta.event_handled_at", time.Now()) switch { case event.RequestTimestamp().IsZero() && event.ResponseTimestamp().IsZero(): diff --git a/handlers/libhoney_event_handler_test.go b/handlers/libhoney_event_handler_test.go index 3edbf956..cbd6ab53 100644 --- a/handlers/libhoney_event_handler_test.go +++ b/handlers/libhoney_event_handler_test.go @@ -113,11 +113,11 @@ func Test_libhoneyEventHandler_handleEvent(t *testing.T) { "source.k8s.resource.type": "pod", "source.k8s.namespace.name": "unit-tests", "source.k8s.pod.name": "src-pod", - "source.k8s.pod.uid": srcPod.UID, + "source.k8s.pod.uid": string(srcPod.UID), "destination.k8s.resource.type": "pod", "destination.k8s.namespace.name": "unit-tests", "destination.k8s.pod.name": "dest-pod", - "destination.k8s.pod.uid": destPod.UID, + "destination.k8s.pod.uid": string(destPod.UID), } assert.Equal(t, expectedAttrs, attrs) @@ -262,11 +262,11 @@ func Test_libhoneyEventHandler_handleEvent_routed_to_service(t *testing.T) { "source.k8s.resource.type": "pod", "source.k8s.namespace.name": "unit-tests", "source.k8s.pod.name": "src-pod", - "source.k8s.pod.uid": srcPod.UID, + "source.k8s.pod.uid": string(srcPod.UID), "destination.k8s.resource.type": "service", "destination.k8s.namespace.name": "unit-tests", "destination.k8s.service.name": "dest-service", - "destination.k8s.service.uid": destService.UID, + "destination.k8s.service.uid": string(destService.UID), } assert.Equal(t, expectedAttrs, attrs) @@ -323,12 +323,13 @@ func Test_reportingTimesAndDurations(t *testing.T) { expectedTelemetryTime: nowish, }, } + handler := &libhoneyEventHandler{} for _, tC := range testCases { t.Run(tC.desc, func(t *testing.T) { ev := libhoney.NewEvent() event := createTestHttpEvent(tC.reqTime, tC.respTime) - setTimestampsAndDurationIfValid(ev, event) + handler.setTimestampsAndDurationIfValid(ev, event) if tC.expectedTelemetryTime != nowish { assert.Equal(t, tC.expectedTelemetryTime, ev.Timestamp) diff --git a/handlers/otel_handler.go b/handlers/otel_handler.go new file mode 100644 index 00000000..df60881b --- /dev/null +++ b/handlers/otel_handler.go @@ -0,0 +1,252 @@ +package handlers + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "github.com/honeycombio/honeycomb-network-agent/assemblers" + "github.com/honeycombio/honeycomb-network-agent/config" + "github.com/honeycombio/honeycomb-network-agent/utils" + "github.com/honeycombio/otel-config-go/otelconfig" + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" + "go.opentelemetry.io/otel/trace" +) + +type otelHandler struct { + config config.Config + k8sClient *utils.CachedK8sClient + eventsChan chan assemblers.Event + tracer trace.Tracer + otelShutdown func() +} + +var _ EventHandler = (*otelHandler)(nil) + +// NewOtelHandler creates a new event handler that sends events using OpenTelemetry +func NewOtelHandler(config config.Config, k8sClient *utils.CachedK8sClient, eventsChan chan assemblers.Event, version string) EventHandler { + otelShutdown, err := otelconfig.ConfigureOpenTelemetry( + otelconfig.WithServiceName(config.Dataset), + otelconfig.WithServiceVersion(version), + otelconfig.WithExporterEndpoint(config.Endpoint), + otelconfig.WithHeaders(map[string]string{ + "x-honeycomb-team": config.APIKey, + }), + otelconfig.WithResourceAttributes(map[string]string{ + "honeycomb.agent_version": version, + "meta.agent.node.ip": config.AgentNodeIP, + "meta.agent.node.name": config.AgentNodeName, + "meta.agent.serviceaccount.name": config.AgentServiceAccount, + "meta.agent.pod.ip": config.AgentPodIP, + "meta.agent.pod.name": config.AgentPodName, + }), + otelconfig.WithMetricsEnabled(false), + ) + if err != nil { + log.Fatal().Err(err).Msg("Failed to configure OpenTelemetry") + } + + return &otelHandler{ + config: config, + k8sClient: k8sClient, + eventsChan: eventsChan, + tracer: otel.Tracer(config.Dataset), + otelShutdown: otelShutdown, + } +} + +// Start starts the event handler and begins handling events from the events channel +// When the context is cancelled, the event handler will stop handling events +func (handler *otelHandler) Start(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + + var event assemblers.Event + for { + select { + case <-ctx.Done(): + return + case event = <-handler.eventsChan: + handler.handleEvent(event) + } + } +} + +// Close closes the libhoney client, flushing any pending events. +func (handler *otelHandler) Close() { + handler.otelShutdown() +} + +// handleEvent transforms a captured event into a libhoney event and sends it +func (handler *otelHandler) handleEvent(event assemblers.Event) { + log.Debug(). + Str("stream_ident", event.StreamIdent()). + Int64("request_id", event.RequestId()). + Msg("Event sent") + + // Get event start/end timestamps and attributes + startTime, endTime, attrs := handler.getEventStartEndTimestamps(event) + + switch event.(type) { + case *assemblers.HttpEvent: + handler.createHTTPSpan(event.(*assemblers.HttpEvent), startTime, endTime, attrs) + default: + log.Warn().Msg("Unknown event type") + return + } +} + +func (handler *otelHandler) createHTTPSpan(event *assemblers.HttpEvent, startTime, endTime time.Time, attrs []attribute.KeyValue) { + var spanName string + if event.Request() == nil { + spanName = "HTTP" + } else { + spanName = fmt.Sprintf("HTTP %s", event.Request().Method) + } + + _, span := handler.tracer.Start( + context.Background(), + spanName, + trace.WithTimestamp(startTime), + trace.WithAttributes( + attribute.String("meta.stream.ident", event.StreamIdent()), + attribute.Int64("meta.seqack", event.RequestId()), + attribute.Int("meta.request.packet_count", event.RequestPacketCount()), + attribute.Int("meta.response.packet_count", event.ResponsePacketCount()), + semconv.ClientSocketAddress(event.SrcIp()), + semconv.ServerSocketAddress(event.DstIp()), + ), + trace.WithAttributes(attrs...), + ) + defer span.End(trace.WithTimestamp(endTime)) + + // request attributes + if event.Request() != nil { + span.SetAttributes( + semconv.HTTPRequestMethodKey.String(event.Request().Method), + semconv.HTTPRequestBodySize(int(event.Request().ContentLength)), + ) + // by this point, we've already extracted headers based on HTTP_HEADERS list + // so we can safely add the headers to the event + span.SetAttributes(headerToAttributes(true, event.Request().Header)...) + if handler.config.IncludeRequestURL { + url, err := url.ParseRequestURI(event.Request().RequestURI) + if err == nil { + span.SetAttributes( + semconv.URLPath(url.Path), + ) + } + } + } else { + span.SetAttributes( + attribute.String("http.request.missing", "no request on this event"), + ) + } + + // response attributes + if event.Response() != nil { + span.SetAttributes( + semconv.HTTPResponseStatusCode(event.Response().StatusCode), + semconv.HTTPResponseBodySize(int(event.Response().ContentLength)), + ) + // by this point, we've already extracted headers based on HTTP_HEADERS list + // so we can safely add the headers to the event + span.SetAttributes(headerToAttributes(false, event.Response().Header)...) + // We cannot quite follow the OTel spec for HTTP instrumentation and OK/Error Status. + // https://github.com/open-telemetry/opentelemetry-specification/blob/v1.25.0/specification/trace/semantic_conventions/http.md#status + // We don't (yet?) have a way to determine the client-or-server perspective of the event, + // so we'll set the "error" field to the general category of the error status codes. + if event.Response().StatusCode >= 500 { + span.SetAttributes( + attribute.String("error", "HTTP server error"), + ) + } else if event.Response().StatusCode >= 400 { + span.SetAttributes( + attribute.String("error", "HTTP client error"), + ) + } + } else { + span.SetAttributes( + attribute.String("http.response.missing", "no response on this event"), + ) + } + + // TODO: it would be nicer if the k8s attrs were returned as otel attrs instead of a map + // so that we don't need to loop over them again here + // Add k8s attributes for source and destination IPs + for key, val := range handler.k8sClient.GetK8sAttrsForSourceIP(handler.config.AgentPodIP, event.SrcIp()) { + span.SetAttributes(attribute.String(key, val)) + } + for key, val := range handler.k8sClient.GetK8sAttrsForDestinationIP(handler.config.AgentPodIP, event.DstIp()) { + span.SetAttributes(attribute.String(key, val)) + } +} + +// getEventStartEndTimestamps sets time-related fields in the emitted telemetry +// about the request/response cycle. +// +// It only sets timestamps if they are present in the captured event, and only +// computes and includes durations for which there are correct timestamps to based them upon. +func (handler *otelHandler) getEventStartEndTimestamps(event assemblers.Event) (time.Time, time.Time, []attribute.KeyValue) { + var startTime, endTime time.Time + attrs := []attribute.KeyValue{ + attribute.String("meta.event_handled_at", time.Now().String()), + } + + switch { + case event.RequestTimestamp().IsZero() && event.ResponseTimestamp().IsZero(): // no request or response + attrs = append(attrs, attribute.String("meta.timestamps_missing", "request, response")) + startTime = time.Now() + endTime = startTime + + case event.RequestTimestamp().IsZero(): // have response, no request + attrs = append(attrs, attribute.String("meta.timestamps_missing", "request")) + startTime = event.ResponseTimestamp() + endTime = startTime + attrs = append(attrs, attribute.String("http.response.timestamp", event.ResponseTimestamp().String())) + attrs = append(attrs, attribute.Int64("meta.response.capture_to_handle.latency_ms", time.Since(event.ResponseTimestamp()).Milliseconds())) + + case event.ResponseTimestamp().IsZero(): // have request, no response + attrs = append(attrs, attribute.String("meta.timestamps_missing", "response")) + startTime = event.RequestTimestamp() + endTime = startTime + attrs = append(attrs, attribute.String("http.request.timestamp", event.RequestTimestamp().String())) + attrs = append(attrs, attribute.Int64("meta.request.capture_to_handle.latency_ms", time.Since(event.RequestTimestamp()).Milliseconds())) + + default: // the happiest of paths, we have both request and response + startTime = event.RequestTimestamp() + endTime = event.ResponseTimestamp() + attrs = append(attrs, attribute.String("http.request.timestamp", event.RequestTimestamp().String())) + attrs = append(attrs, attribute.String("http.response.timestamp", event.ResponseTimestamp().String())) + attrs = append(attrs, attribute.Int64("meta.request.capture_to_handle.latency_ms", time.Since(event.RequestTimestamp()).Milliseconds())) + attrs = append(attrs, attribute.Int64("meta.response.capture_to_handle.latency_ms", time.Since(event.ResponseTimestamp()).Milliseconds())) + attrs = append(attrs, attribute.Int64("duration_ms", event.ResponseTimestamp().Sub(event.RequestTimestamp()).Milliseconds())) + + } + return startTime, endTime, attrs +} + +// headerToAttributes converts a http.Header into a slice of OpenTelemetry attributes +func headerToAttributes(isRequest bool, header http.Header) []attribute.KeyValue { + var prefix string + if isRequest { + prefix = "http.request.header" + } else { + prefix = "http.response.header" + } + attrs := []attribute.KeyValue{} + for key, val := range header { + // semantic conventions suggest lowercase, with - characters replaced by _ + semconvKey := strings.ToLower(strings.Replace(key, "-", "_", -1)) + for _, v := range val { + attrs = append(attrs, attribute.String(fmt.Sprintf("%s.%s", prefix, semconvKey), v)) + } + } + return attrs +} diff --git a/handlers/otel_handler_test.go b/handlers/otel_handler_test.go new file mode 100644 index 00000000..2c3cb1f9 --- /dev/null +++ b/handlers/otel_handler_test.go @@ -0,0 +1,58 @@ +package handlers + +import ( + "net/http" + "testing" + "time" + + "github.com/honeycombio/honeycomb-network-agent/assemblers" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" +) + +// test that headerToAttributes correctly sets attributes from headers +func TestHeaderToAttributes(t *testing.T) { + requestTimestamp := time.Now() + responseTimestamp := requestTimestamp.Add(3 * time.Millisecond) + event := createTestOtelEvent(requestTimestamp, responseTimestamp) + + reqAttrs := (headerToAttributes(true, event.Request().Header)) + + expectedReqAttrs := []attribute.KeyValue{ + attribute.String("http.request.header.user_agent", "teapot-checker/1.0"), + attribute.String("http.request.header.connection", "keep-alive"), + } + + assert.Equal(t, expectedReqAttrs, reqAttrs) + + resAttrs := (headerToAttributes(false, event.Response().Header)) + expectedResAttrs := []attribute.KeyValue{ + attribute.String("http.response.header.content_type", "text/plain; charset=utf-8"), + attribute.String("http.response.header.x_custom_header", "tea-party"), + } + assert.Equal(t, expectedResAttrs, resAttrs) +} + +func createTestOtelEvent(requestTimestamp, responseTimestamp time.Time) *assemblers.HttpEvent { + return assemblers.NewHttpEvent( + "c->s:1->2", + 0, + requestTimestamp, + responseTimestamp, + 2, + 3, + "1.2.3.4", + "5.6.7.8", + &http.Request{ + Method: "GET", + RequestURI: "/check?teapot=true", + ContentLength: 42, + Header: http.Header{"User-Agent": []string{"teapot-checker/1.0"}, "Connection": []string{"keep-alive"}}, + }, + &http.Response{ + StatusCode: 418, + ContentLength: 84, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}, "X-Custom-Header": []string{"tea-party"}}, + }, + ) +} diff --git a/main.go b/main.go index b462f82d..ecf81b26 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ func main() { // create event handler that sends events to backend (eg Honeycomb) // TODO: move version outside of main package so it can be used directly in the eventHandler - eventHandler := handlers.NewLibhoneyEventHandler(config, cachedK8sClient, eventsChannel, Version) + eventHandler := handlers.NewEventHandler(config, cachedK8sClient, eventsChannel, Version) wgServices.Add(1) go eventHandler.Start(ctx, &wgServices) diff --git a/smoke-tests/deployment.yaml b/smoke-tests/deployment.yaml index 779d97d5..e950a103 100644 --- a/smoke-tests/deployment.yaml +++ b/smoke-tests/deployment.yaml @@ -112,10 +112,14 @@ spec: secretKeyRef: name: honeycomb key: api-key - ## uncomment this to change the endpoint for events (uncommon) + ## uncomment this to change the endpoint for events # - name: HONEYCOMB_API_ENDPOINT # value: $HONEYCOMB_API_ENDPOINT + ## uncomment this to send to insecure endpoint like collector + # - name: OTEL_EXPORTER_OTLP_INSECURE + # value: "true" ## uncomment this to set the destination dataset for events + ## this will represent service.name in honeycomb when using otel # - name: HONEYCOMB_DATASET # value: $HONEYCOMB_DATASET ## uncomment this to set the destination dataset for agent performance stats @@ -130,15 +134,21 @@ spec: ## uncomment this to enable profiling # - name: DEBUG # value: "$DEBUG" - ## uncomment this to add extra attributes to all events + ## uncomment this to add extra attributes to all events when using libhoney handler # - name: ADDITIONAL_ATTRIBUTES - # value: "key1=value1,key2=value2" + # value: "handler=libhoney,environment=dev" + ## uncomment this to add extra attributes to all events when using otel handler + # - name: OTEL_RESOURCE_ATTRIBUTES + # value: "handler=otel,environment=dev" ## uncomment this to configure a list of HTTP headers to be recorded from requests/responses. + ## this will show as http.request.header.user_agent and http.response.header.x_custom_header # - name: HTTP_HEADERS # value: "User-Agent,X-Custom-Header" ## uncomment this to disable including the request URL in events # - name: INCLUDE_REQUEST_URL # value: "false" + # - name: HANDLER_TYPE + # value: "otel" securityContext: capabilities: add: diff --git a/utils/cached_k8s_client.go b/utils/cached_k8s_client.go index 23b867e9..0916d105 100644 --- a/utils/cached_k8s_client.go +++ b/utils/cached_k8s_client.go @@ -130,13 +130,13 @@ func (c *CachedK8sClient) GetNodeForPod(pod *v1.Pod) *v1.Node { // GetK8sAttrsForSourceIP returns a map of kubernetes metadata attributes for // a given IP address. Attribute names will be prefixed with "source.". -func (c *CachedK8sClient) GetK8sAttrsForSourceIP(agentIP string, ip string) map[string]any { +func (c *CachedK8sClient) GetK8sAttrsForSourceIP(agentIP string, ip string) map[string]string { return c.getK8sAttrsForIp(agentIP, ip, "source") } // GetK8sAttrsForDestinationIP returns a map of kubernetes metadata attributes for // a given IP address. Attribute names will be prefixed with "destination.". -func (c *CachedK8sClient) GetK8sAttrsForDestinationIP(agentIP string, ip string) map[string]any { +func (c *CachedK8sClient) GetK8sAttrsForDestinationIP(agentIP string, ip string) map[string]string { return c.getK8sAttrsForIp(agentIP, ip, "destination") } @@ -145,8 +145,8 @@ func (c *CachedK8sClient) GetK8sAttrsForDestinationIP(agentIP string, ip string) // Provide a prefix to prepend to the attribute names, example: "source" or "destination". // // If the IP address is not found in the kubernetes cache, an empty map is returned. -func (client *CachedK8sClient) getK8sAttrsForIp(agentIP string, ip string, prefix string) map[string]any { - k8sAttrs := map[string]any{} +func (client *CachedK8sClient) getK8sAttrsForIp(agentIP string, ip string, prefix string) map[string]string { + k8sAttrs := map[string]string{} if ip == "" { return k8sAttrs @@ -166,7 +166,7 @@ func (client *CachedK8sClient) getK8sAttrsForIp(agentIP string, ip string, prefi if pod := client.GetPodByIPAddr(ip); pod != nil { k8sAttrs[prefix+k8sResourceType] = k8sResourceTypePod k8sAttrs[prefix+string(semconv.K8SPodNameKey)] = pod.Name - k8sAttrs[prefix+string(semconv.K8SPodUIDKey)] = pod.UID + k8sAttrs[prefix+string(semconv.K8SPodUIDKey)] = string(pod.UID) k8sAttrs[prefix+string(semconv.K8SNamespaceNameKey)] = pod.Namespace if len(pod.Spec.Containers) > 0 { @@ -179,20 +179,20 @@ func (client *CachedK8sClient) getK8sAttrsForIp(agentIP string, ip string, prefi if node := client.GetNodeForPod(pod); node != nil { k8sAttrs[prefix+string(semconv.K8SNodeNameKey)] = node.Name - k8sAttrs[prefix+string(semconv.K8SNodeUIDKey)] = node.UID + k8sAttrs[prefix+string(semconv.K8SNodeUIDKey)] = string(node.UID) } if service := client.GetServiceForPod(pod); service != nil { // no semconv for service yet k8sAttrs[prefix+k8sServiceName] = service.Name - k8sAttrs[prefix+k8sServiceUID] = service.UID + k8sAttrs[prefix+k8sServiceUID] = string(service.UID) } } else if service := client.GetServiceByIPAddr(ip); service != nil { k8sAttrs[prefix+k8sResourceType] = k8sResourceTypeService k8sAttrs[prefix+string(semconv.K8SNamespaceNameKey)] = service.Namespace // no semconv for service yet k8sAttrs[prefix+k8sServiceName] = service.Name - k8sAttrs[prefix+k8sServiceUID] = service.UID + k8sAttrs[prefix+k8sServiceUID] = string(service.UID) } return k8sAttrs } diff --git a/utils/cached_k8s_client_test.go b/utils/cached_k8s_client_test.go index 3c650e62..ad215909 100644 --- a/utils/cached_k8s_client_test.go +++ b/utils/cached_k8s_client_test.go @@ -80,73 +80,73 @@ func Test_GetAttrs(t *testing.T) { name string agentIP string srcIP string - expectedSrcAttrs map[string]interface{} + expectedSrcAttrs map[string]string destIP string - expectedDestAttrs map[string]interface{} + expectedDestAttrs map[string]string }{ { name: "src & dest pods", agentIP: "1.1.1.1", srcIP: srcPod.Status.PodIP, - expectedSrcAttrs: map[string]interface{}{ + expectedSrcAttrs: map[string]string{ "source.k8s.resource.type": "pod", "source.k8s.namespace.name": srcPod.Namespace, "source.k8s.pod.name": srcPod.Name, - "source.k8s.pod.uid": srcPod.UID, + "source.k8s.pod.uid": string(srcPod.UID), "source.k8s.container.name": "src-pod-container-1,src-pod-container-2", "source.k8s.node.name": node.Name, - "source.k8s.node.uid": node.UID, + "source.k8s.node.uid": string(node.UID), "source.k8s.service.name": service.Name, - "source.k8s.service.uid": service.UID, + "source.k8s.service.uid": string(service.UID), }, destIP: destPod.Status.PodIP, - expectedDestAttrs: map[string]interface{}{ + expectedDestAttrs: map[string]string{ "destination.k8s.resource.type": "pod", "destination.k8s.namespace.name": destPod.Namespace, "destination.k8s.pod.name": destPod.Name, - "destination.k8s.pod.uid": destPod.UID, + "destination.k8s.pod.uid": string(destPod.UID), "destination.k8s.container.name": "dest-pod-container-1,dest-pod-container-2", "destination.k8s.node.name": node.Name, - "destination.k8s.node.uid": node.UID, + "destination.k8s.node.uid": string(node.UID), "destination.k8s.service.name": service.Name, - "destination.k8s.service.uid": service.UID, + "destination.k8s.service.uid": string(service.UID), }, }, { name: "src IP matches agent IP - no src pod attrs", agentIP: srcPod.Status.PodIP, srcIP: srcPod.Status.PodIP, - expectedSrcAttrs: map[string]interface{}{}, + expectedSrcAttrs: map[string]string{}, destIP: destPod.Status.PodIP, - expectedDestAttrs: map[string]interface{}{ + expectedDestAttrs: map[string]string{ "destination.k8s.resource.type": "pod", "destination.k8s.namespace.name": destPod.Namespace, "destination.k8s.pod.name": destPod.Name, - "destination.k8s.pod.uid": destPod.UID, + "destination.k8s.pod.uid": string(destPod.UID), "destination.k8s.container.name": "dest-pod-container-1,dest-pod-container-2", "destination.k8s.node.name": node.Name, - "destination.k8s.node.uid": node.UID, + "destination.k8s.node.uid": string(node.UID), "destination.k8s.service.name": service.Name, - "destination.k8s.service.uid": service.UID, + "destination.k8s.service.uid": string(service.UID), }, }, { name: "dest IP matches agent IP - no dest pod attrs", agentIP: destPod.Status.PodIP, srcIP: srcPod.Status.PodIP, - expectedSrcAttrs: map[string]interface{}{ + expectedSrcAttrs: map[string]string{ "source.k8s.resource.type": "pod", "source.k8s.namespace.name": srcPod.Namespace, "source.k8s.pod.name": srcPod.Name, - "source.k8s.pod.uid": srcPod.UID, + "source.k8s.pod.uid": string(srcPod.UID), "source.k8s.container.name": "src-pod-container-1,src-pod-container-2", "source.k8s.node.name": node.Name, - "source.k8s.node.uid": node.UID, + "source.k8s.node.uid": string(node.UID), "source.k8s.service.name": service.Name, - "source.k8s.service.uid": service.UID, + "source.k8s.service.uid": string(service.UID), }, destIP: destPod.Status.PodIP, - expectedDestAttrs: map[string]interface{}{}, + expectedDestAttrs: map[string]string{}, }, }