From 8ac3eef33ac254c821490220f93767090cc833d3 Mon Sep 17 00:00:00 2001 From: Gustavo Maronato Date: Thu, 21 Sep 2023 16:19:17 -0300 Subject: [PATCH] implemented tracing --- cmd/main.go | 10 ++ go.mod | 39 +++++- go.sum | 95 ++++++++++--- internal/server/api/handler.go | 125 ++++++++++++++++-- internal/server/healthcheck/handler.go | 7 +- internal/server/middleware/auth/auth.go | 16 ++- internal/server/middleware/logging.go | 5 + internal/server/middleware/session/session.go | 5 +- internal/server/middleware/tracing/tracing.go | 33 +++++ internal/server/server.go | 6 + internal/server/short/handler.go | 7 +- internal/server/static/handler.go | 4 + internal/storage/bun/storage.go | 6 + internal/util/tracing/tracing.go | 89 +++++++++++++ 14 files changed, 408 insertions(+), 39 deletions(-) create mode 100644 internal/server/middleware/tracing/tracing.go create mode 100644 internal/util/tracing/tracing.go diff --git a/cmd/main.go b/cmd/main.go index d389d85..c9cbae0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,6 +16,7 @@ import ( "git.maronato.dev/maronato/goshort/cmd/shared" "git.maronato.dev/maronato/goshort/internal/config" "git.maronato.dev/maronato/goshort/internal/util/logging" + "git.maronato.dev/maronato/goshort/internal/util/tracing" "github.com/peterbourgon/ff/v3/ffcli" ) @@ -78,6 +79,15 @@ func Run(version string) error { l := logging.NewLogger(cfg) ctx = logging.WithLogger(ctx, l) + // Initialize tracing + ctx, stopTracing, err := tracing.InitTracer(ctx, cfg, version) + if err != nil { + return fmt.Errorf("%w", err) + } + + // Close the tracer when the application exits + defer stopTracing() + // Run the command. if err := rootCmd.Run(ctx); err != nil { if errors.Is(err, flag.ErrHelp) { diff --git a/go.mod b/go.mod index 38eede2..3a77f1b 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,15 @@ require ( github.com/go-chi/render v1.0.3 github.com/peterbourgon/ff/v3 v3.4.0 github.com/stretchr/testify v1.8.4 - github.com/uptrace/bun v1.1.14 - github.com/uptrace/bun/dialect/sqlitedialect v1.1.14 - github.com/uptrace/bun/extra/bundebug v1.1.14 + github.com/uptrace/bun v1.1.16 + github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 + github.com/uptrace/bun/extra/bundebug v1.1.16 + github.com/uptrace/bun/extra/bunotel v1.1.16 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 + go.opentelemetry.io/otel v1.18.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0 + go.opentelemetry.io/otel/sdk v1.18.0 golang.org/x/crypto v0.12.0 golang.org/x/sync v0.3.0 modernc.org/sqlite v1.25.0 @@ -19,25 +25,46 @@ require ( require ( github.com/ajg/form v1.5.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fatih/color v1.15.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + go.opentelemetry.io/contrib/propagators/autoprop v0.44.0 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.19.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.19.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.19.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.19.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect + go.opentelemetry.io/otel/metric v1.18.0 // indirect + go.opentelemetry.io/otel/trace v1.18.0 // indirect + go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.12.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // 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.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect diff --git a/go.sum b/go.sum index fd5edaf..8beda49 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alexedwards/scs/v2 v2.5.1 h1:EhAz3Kb3OSQzD8T+Ub23fKsiuvE0GzbF5Lgn0uTwM3Y= github.com/alexedwards/scs/v2 v2.5.1/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -10,27 +11,39 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= +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/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= 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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -42,42 +55,94 @@ github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.1.14 h1:S5vvNnjEynJ0CvnrBOD7MIRW7q/WbtvFXrdfy0lddAM= -github.com/uptrace/bun v1.1.14/go.mod h1:RHk6DrIisO62dv10pUOJCz5MphXThuOTpVNYEYv7NI8= -github.com/uptrace/bun/dialect/sqlitedialect v1.1.14 h1:SlwXLxr+N1kEo8Q0cheRlnIZLZlWniEB1OI+jkiLgWE= -github.com/uptrace/bun/dialect/sqlitedialect v1.1.14/go.mod h1:9RTEj1l4bB9a4l1Mnc9y4COTwWlFYe1dh6fyxq1rR7A= -github.com/uptrace/bun/extra/bundebug v1.1.14 h1:9OCGfP9ZDlh41u6OLerWdhBtJAVGXHr0xtxO4xWi6t0= -github.com/uptrace/bun/extra/bundebug v1.1.14/go.mod h1:lto3guzS2v6mnQp1+akyE+ecBLOltevDDe324NXEYdw= +github.com/uptrace/bun v1.1.16 h1:cn9cgEMFwcyYRsQLfxCRMUxyK1WaHwOVrR3TvzEFZ/A= +github.com/uptrace/bun v1.1.16/go.mod h1:7HnsMRRvpLFUcquJxp22JO8PsWKpFQO/gNXqqsuGWg8= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.16 h1:gbc9BP/e4sNOB9VBj+Si46dpOz2oktmZPidkda92GYY= +github.com/uptrace/bun/dialect/sqlitedialect v1.1.16/go.mod h1:YNezpK7fIn5Wa2WGmTCZ/nEyiswcXmuT4iNWADeL1x4= +github.com/uptrace/bun/extra/bundebug v1.1.16 h1:SgicRQGtnjhrIhlYOxdkOm1Em4s6HykmT3JblHnoTBM= +github.com/uptrace/bun/extra/bundebug v1.1.16/go.mod h1:SkiOkfUirBiO1Htc4s5bQKEq+JSeU1TkBVpMsPz2ePM= +github.com/uptrace/bun/extra/bunotel v1.1.16 h1:qkLTaTZK3FZk3b2P/stO/krS7KX9Fq5wSOj7Hlb2HG8= +github.com/uptrace/bun/extra/bunotel v1.1.16/go.mod h1:JwEH0kdXFnzYuK8D6eXUrf9HKsYy5wmB+lqQ/+dvH4E= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2 h1:USRngIQppxeyb39XzkVHXwQesKK0+JSwnHE/1c7fgic= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.2.2/go.mod h1:1frv9RN1rlTq0jzCq+mVuEQisubZCQ4OU6S/8CaHzGY= 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= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48= +go.opentelemetry.io/contrib/propagators/autoprop v0.44.0 h1:HgXKc1D1PrpsYKdO8fc5XuyVmYFxcY2mLVAHq8XBZMU= +go.opentelemetry.io/contrib/propagators/autoprop v0.44.0/go.mod h1:B3RxUeb5nXP5iTp9NH1fGTrAbzCTXjvUIyS4pDxqwks= +go.opentelemetry.io/contrib/propagators/aws v1.19.0 h1:fXXcgurRq5CbEKxHg8Ge9pgTMSaCX9KcBnELHe9bHbc= +go.opentelemetry.io/contrib/propagators/aws v1.19.0/go.mod h1:W1bbfg19rs+luEUEYKSR65H2psL2YFutZmPWOdaswJg= +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/jaeger v1.19.0 h1:mGrx7XEAE+7ybCLM0T6iRl/jUTuHg6qKUJAtsAlknec= +go.opentelemetry.io/contrib/propagators/jaeger v1.19.0/go.mod h1:cHWVPhYWMZOanEf1qexqMIRhr4TKVjZWBKwZTL/tdR4= +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.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +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/stdout/stdouttrace v1.18.0 h1:hSWWvDjXHVLq9DkmB+77fl8v7+t+yYiS+eNkiplDK54= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.18.0/go.mod h1:zG7KQql1WjZCaUJd+L/ReSYx4bjbYJxg5ws9ws+mYes= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +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/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= +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.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= +google.golang.org/grpc v1.58.0/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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= diff --git a/internal/server/api/handler.go b/internal/server/api/handler.go index 6b330c2..e8afb28 100644 --- a/internal/server/api/handler.go +++ b/internal/server/api/handler.go @@ -17,6 +17,7 @@ import ( userservice "git.maronato.dev/maronato/goshort/internal/service/user" "git.maronato.dev/maronato/goshort/internal/storage/models" "git.maronato.dev/maronato/goshort/internal/util/logging" + "git.maronato.dev/maronato/goshort/internal/util/tracing" "github.com/go-chi/chi/v5" "github.com/go-chi/render" ) @@ -43,19 +44,25 @@ func NewAPIHandler( } func (h *APIHandler) Me(w http.ResponseWriter, r *http.Request) { + _, span := tracing.StartSpan(r.Context(), "api.Me") + defer span.End() + // Get user from context user, ok := h.findUserOrRespond(w, r) if !ok { return } + span.AddEvent("found user") + // Respond with the user render.Status(r, http.StatusOK) render.JSON(w, r, user) } func (h *APIHandler) DeleteMe(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.DeleteMe") + defer span.End() // Get user from context user, ok := h.findUserOrRespond(w, r) @@ -63,12 +70,16 @@ func (h *APIHandler) DeleteMe(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found user") + // Delete all user's sessions err := authmiddleware.DeleteAllUserSessions(ctx, user) if err != nil { server.RenderServerError(w, r, err) } + span.AddEvent("deleted all user sessions") + // Delete the user err = h.users.DeleteUser(ctx, user) if err != nil { @@ -77,13 +88,19 @@ func (h *APIHandler) DeleteMe(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("deleted user") + // Logout and return authmiddleware.LogoutUser(ctx) + span.AddEvent("logged out user") + render.NoContent(w, r) } func (h *APIHandler) Login(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.Login") + defer span.End() + l := logging.FromCtx(ctx) type loginForm struct { @@ -119,8 +136,11 @@ func (h *APIHandler) Login(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("authenticated user") + // Login user authmiddleware.LoginUser(ctx, user, r) + span.AddEvent("logged in user") // Render the response render.Status(r, http.StatusOK) @@ -128,10 +148,12 @@ func (h *APIHandler) Login(w http.ResponseWriter, r *http.Request) { } func (h *APIHandler) Logout(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.Logout") + defer span.End() // Logout user authmiddleware.LogoutUser(ctx) + span.AddEvent("logged out user") // Render the response render.Status(r, http.StatusOK) @@ -139,7 +161,9 @@ func (h *APIHandler) Logout(w http.ResponseWriter, r *http.Request) { } func (h *APIHandler) Signup(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.Signup") + defer span.End() + l := logging.FromCtx(ctx) // Get the user from the json body @@ -156,6 +180,8 @@ func (h *APIHandler) Signup(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("parsed form") + // Get user and pass from form user := &form.User pass := form.Password @@ -171,6 +197,8 @@ func (h *APIHandler) Signup(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("hashed password") + // Create user newUser, err := h.users.CreateUser(ctx, user) if err != nil { @@ -188,13 +216,16 @@ func (h *APIHandler) Signup(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("created user") + // Render the response render.Status(r, http.StatusCreated) render.JSON(w, r, newUser) } func (h *APIHandler) CreateShort(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.CreateShort") + defer span.End() // Get the URL from the json body var short *models.Short @@ -205,12 +236,16 @@ func (h *APIHandler) CreateShort(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("parsed form") + // Get user from context user, ok := h.findUserOrRespond(w, r) if !ok { return } + span.AddEvent("found user") + // Set the user short.UserID = &user.ID @@ -226,15 +261,20 @@ func (h *APIHandler) CreateShort(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("created short") + shortResponse := newShortResponse(r, newShort) + span.AddEvent("created shorted response") + // Render the response render.Status(r, http.StatusCreated) render.JSON(w, r, shortResponse) } func (h *APIHandler) ListShorts(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.ListShorts") + defer span.End() // Get user from context user, ok := h.findUserOrRespond(w, r) @@ -242,6 +282,8 @@ func (h *APIHandler) ListShorts(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found user") + // Get shorts shorts, err := h.shorts.ListShorts(ctx, user) if err != nil { @@ -250,29 +292,41 @@ func (h *APIHandler) ListShorts(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("listed shorts") + shortsResponse := newShortResponseList(r, shorts) + span.AddEvent("created shorts response") + // Render the response render.Status(r, http.StatusOK) render.JSON(w, r, shortsResponse) } func (h *APIHandler) FindShort(w http.ResponseWriter, r *http.Request) { + _, span := tracing.StartSpan(r.Context(), "api.FindShort") + defer span.End() + // Find own short or respond short, ok := h.findShortOrRespond(w, r) if !ok { return } + span.AddEvent("found short") + shortResponse := newShortResponse(r, short) + span.AddEvent("created short response") + // Render the short render.Status(r, http.StatusOK) render.JSON(w, r, shortResponse) } func (h *APIHandler) DeleteShort(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.DeleteShort") + defer span.End() // Find own short or respond short, ok := h.findShortOrRespond(w, r) @@ -280,6 +334,8 @@ func (h *APIHandler) DeleteShort(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found short") + // Delete short err := h.shorts.DeleteShort(ctx, short) if err != nil { @@ -288,12 +344,15 @@ func (h *APIHandler) DeleteShort(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("deleted short") + // Deleted, return no content render.NoContent(w, r) } func (h *APIHandler) ListShortLogs(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.ListShortLogs") + defer span.End() // Find own short or respond short, ok := h.findShortOrRespond(w, r) @@ -301,6 +360,8 @@ func (h *APIHandler) ListShortLogs(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found short") + // Get logs logs, err := h.shortLogs.ListLogs(ctx, short) if err != nil { @@ -309,13 +370,15 @@ func (h *APIHandler) ListShortLogs(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("listed logs") + // Render the response render.Status(r, http.StatusOK) render.JSON(w, r, logs) } func (h *APIHandler) ListSessions(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.ListSessions") // Get user from context user, ok := h.findUserOrRespond(w, r) @@ -323,18 +386,24 @@ func (h *APIHandler) ListSessions(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found user") + sessions, err := authmiddleware.ListUserSessions(ctx, user) if err != nil { server.RenderServerError(w, r, err) } + span.AddEvent("listed sessions") + // Render the response render.Status(r, http.StatusOK) render.JSON(w, r, sessions) } func (h *APIHandler) DeleteSession(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.DeleteSession") + defer span.End() + l := logging.FromCtx(ctx) // Get user from context @@ -343,6 +412,8 @@ func (h *APIHandler) DeleteSession(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found user") + // Get session token from request sessionToken := chi.URLParam(r, "id") @@ -360,13 +431,16 @@ func (h *APIHandler) DeleteSession(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("deleted session") + // Render the response render.NoContent(w, r) } // ListTokens lists all tokens belonging to the user. func (h *APIHandler) ListTokens(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.ListTokens") + defer span.End() // Get user from context user, ok := h.findUserOrRespond(w, r) @@ -374,12 +448,16 @@ func (h *APIHandler) ListTokens(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found user") + // Get tokens tokens, err := h.tokens.ListTokens(ctx, user) if err != nil { server.RenderServerError(w, r, err) } + span.AddEvent("listed tokens") + // Render the response render.Status(r, http.StatusOK) render.JSON(w, r, tokens) @@ -387,7 +465,8 @@ func (h *APIHandler) ListTokens(w http.ResponseWriter, r *http.Request) { // CreateToken creates a new token for the user. func (h *APIHandler) CreateToken(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.CreateToken") + defer span.End() type tokenNameForm struct { Name string `json:"name"` @@ -402,12 +481,16 @@ func (h *APIHandler) CreateToken(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("parsed form") + // Get user from context user, ok := h.findUserOrRespond(w, r) if !ok { return } + span.AddEvent("found user") + token, err := h.tokens.CreateToken(ctx, user, form.Name) if err != nil { server.RenderServerError(w, r, err) @@ -415,6 +498,8 @@ func (h *APIHandler) CreateToken(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("created token") + // Render the response render.Status(r, http.StatusCreated) render.JSON(w, r, token) @@ -422,7 +507,8 @@ func (h *APIHandler) CreateToken(w http.ResponseWriter, r *http.Request) { // ChangeTokenName changes a token's name belonging to the user. func (h *APIHandler) ChangeTokenName(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.ChangeTokenName") + defer span.End() type tokenNameForm struct { Name string `json:"name"` @@ -435,12 +521,16 @@ func (h *APIHandler) ChangeTokenName(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("parsed form") + // Find token or respond token, ok := h.findTokenOrRespond(w, r) if !ok { return } + span.AddEvent("found token") + // Rename token newToken, err := h.tokens.ChangeTokenName(ctx, token, form.Name) if err != nil { @@ -449,6 +539,8 @@ func (h *APIHandler) ChangeTokenName(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("changed token name") + // Changed. Return the token render.Status(r, http.StatusOK) render.JSON(w, r, newToken) @@ -456,7 +548,8 @@ func (h *APIHandler) ChangeTokenName(w http.ResponseWriter, r *http.Request) { // DeleteToken deletes a token belonging to the user. func (h *APIHandler) DeleteToken(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "api.DeleteToken") + defer span.End() // Find token or respond token, ok := h.findTokenOrRespond(w, r) @@ -464,6 +557,8 @@ func (h *APIHandler) DeleteToken(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("found token") + // Delete token err := h.tokens.DeleteToken(ctx, token) if err != nil { @@ -472,6 +567,8 @@ func (h *APIHandler) DeleteToken(w http.ResponseWriter, r *http.Request) { return } + span.AddEvent("deleted token") + // Deleted, return no content render.NoContent(w, r) } diff --git a/internal/server/healthcheck/handler.go b/internal/server/healthcheck/handler.go index 494849e..10d80a8 100644 --- a/internal/server/healthcheck/handler.go +++ b/internal/server/healthcheck/handler.go @@ -5,6 +5,7 @@ import ( "git.maronato.dev/maronato/goshort/internal/storage" "git.maronato.dev/maronato/goshort/internal/util/logging" + "git.maronato.dev/maronato/goshort/internal/util/tracing" "github.com/go-chi/render" ) @@ -27,7 +28,9 @@ func NewHealthcheckHandler(strg storage.Storage) *HealthcheckHandler { } func (h *HealthcheckHandler) CheckHealth(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "healthcheck.CheckHealth") + defer span.End() + l := logging.FromCtx(ctx) // Defaults @@ -41,6 +44,8 @@ func (h *HealthcheckHandler) CheckHealth(w http.ResponseWriter, r *http.Request) databaseOk = false } + span.AddEvent("database check done") + // All checks must pass overallStatus := HealthCheckStatusOk statusCode := http.StatusOK diff --git a/internal/server/middleware/auth/auth.go b/internal/server/middleware/auth/auth.go index 66f7a90..f20d01a 100644 --- a/internal/server/middleware/auth/auth.go +++ b/internal/server/middleware/auth/auth.go @@ -7,6 +7,8 @@ import ( tokenservice "git.maronato.dev/maronato/goshort/internal/service/token" userservice "git.maronato.dev/maronato/goshort/internal/service/user" "git.maronato.dev/maronato/goshort/internal/storage/models" + "git.maronato.dev/maronato/goshort/internal/util/tracing" + "go.opentelemetry.io/otel/attribute" ) type userCtxKey struct{} @@ -15,15 +17,23 @@ func Auth(userService *userservice.UserService, tokenService *tokenservice.Token ) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "authmiddleware.Auth") + defer span.End() + + // Set default span attributes + span.SetAttributes(attribute.String("auth_method", "none")) // Authenticate user authMethod := TokenAuth user, err := authenticateViaToken(r, tokenService) + span.AddEvent("queried token auth") + if err != nil { // Failed to authenticate via token. Try to authenticate via session. authMethod = SessionAuth user, err = authenticateUserViaSession(r, userService) + span.AddEvent("queried session auth") + if err != nil { // Failed to authenticate via session. Call the next handler. next.ServeHTTP(w, r) @@ -37,6 +47,10 @@ func Auth(userService *userservice.UserService, tokenService *tokenservice.Token // Register the auth method used ctx = context.WithValue(ctx, authMethodCtxKey{}, authMethod) + // Set span attributes + span.SetAttributes(attribute.String("auth_method", string(authMethod))) + span.SetAttributes(attribute.String("user_id", user.ID)) + // Call the next handler next.ServeHTTP(w, r.WithContext(ctx)) }) diff --git a/internal/server/middleware/logging.go b/internal/server/middleware/logging.go index eaa77aa..a79dfd0 100644 --- a/internal/server/middleware/logging.go +++ b/internal/server/middleware/logging.go @@ -8,6 +8,7 @@ import ( "git.maronato.dev/maronato/goshort/internal/config" "git.maronato.dev/maronato/goshort/internal/util/logging" "github.com/go-chi/chi/v5/middleware" + "go.opentelemetry.io/otel/trace" ) type RequestLogFormatter struct { @@ -41,6 +42,8 @@ func (rl *RequestLogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry var requestGroup slog.Attr + traceID := trace.SpanContextFromContext(ctx).TraceID().String() + if rl.verbose >= config.VerboseLevelAccessLogs { scheme := "http" if r.TLS != nil { @@ -48,6 +51,7 @@ func (rl *RequestLogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry } requestGroup = slog.Group("request", + slog.String("trace_id", traceID), slog.String("id", reqID), slog.String("method", r.Method), slog.String("path", r.URL.Path), @@ -61,6 +65,7 @@ func (rl *RequestLogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry ) } else { requestGroup = slog.Group("request", + slog.String("trace_id", traceID), slog.String("id", reqID), slog.String("method", r.Method), slog.String("path", r.URL.Path), diff --git a/internal/server/middleware/session/session.go b/internal/server/middleware/session/session.go index 2353684..95c3b9b 100644 --- a/internal/server/middleware/session/session.go +++ b/internal/server/middleware/session/session.go @@ -5,6 +5,7 @@ import ( "net/http" "git.maronato.dev/maronato/goshort/internal/config" + "git.maronato.dev/maronato/goshort/internal/util/tracing" "github.com/alexedwards/scs/v2" ) @@ -29,7 +30,9 @@ func SessionManager(cfg *config.Config) func(http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Add the session manager to the context. - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "sessionmiddleware.SessionManager") + defer span.End() + ctx = context.WithValue(ctx, sessionContextKey{}, sessionManager) // Call the next handler. diff --git a/internal/server/middleware/tracing/tracing.go b/internal/server/middleware/tracing/tracing.go new file mode 100644 index 0000000..bd07dcf --- /dev/null +++ b/internal/server/middleware/tracing/tracing.go @@ -0,0 +1,33 @@ +package tracingmiddleware + +import ( + "fmt" + "net/http" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + semconv "go.opentelemetry.io/otel/semconv/v1.18.0" + "go.opentelemetry.io/otel/trace" +) + +func Tracer() func(http.Handler) http.Handler { + middleware := otelhttp.NewMiddleware("serve", + otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string { + return fmt.Sprintf("%s %s", r.Method, r.URL.Path) + }), + ) + + return func(next http.Handler) http.Handler { + return middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Set some extra attributes to the span + span := trace.SpanFromContext(r.Context()) + span.SetAttributes( + semconv.HTTPRoute(r.URL.Path), + semconv.HTTPRequestContentLength(int(r.ContentLength)), + semconv.HTTPURL(r.URL.String()), + semconv.HTTPClientIP(r.RemoteAddr), + ) + + next.ServeHTTP(w, r) + })) + } +} diff --git a/internal/server/server.go b/internal/server/server.go index ad67807..8a4d51e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -10,6 +10,7 @@ import ( "git.maronato.dev/maronato/goshort/internal/config" servermiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware" sessionmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/session" + tracingmiddleware "git.maronato.dev/maronato/goshort/internal/server/middleware/tracing" "git.maronato.dev/maronato/goshort/internal/util/logging" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -35,9 +36,14 @@ func NewServer(cfg *config.Config) *Server { mux.Use(middleware.RealIP) mux.Use(middleware.StripSlashes) + // Register tracing middleware + mux.Use(tracingmiddleware.Tracer()) + + // Register logging middleware requestLogger := servermiddleware.NewLogFormatter(cfg) mux.Use(middleware.RequestLogger(requestLogger)) + // Register secondary middlewares mux.Use(middleware.Recoverer) mux.Use(sessionmiddleware.SessionManager(cfg)) mux.Use(middleware.Timeout(config.RequestTimeout)) diff --git a/internal/server/short/handler.go b/internal/server/short/handler.go index de28e68..41f21e9 100644 --- a/internal/server/short/handler.go +++ b/internal/server/short/handler.go @@ -8,6 +8,7 @@ import ( "git.maronato.dev/maronato/goshort/internal/server" shortservice "git.maronato.dev/maronato/goshort/internal/service/short" shortlogservice "git.maronato.dev/maronato/goshort/internal/service/shortlog" + "git.maronato.dev/maronato/goshort/internal/util/tracing" "github.com/go-chi/chi/v5" ) @@ -24,7 +25,8 @@ func NewShortHandler(shorts *shortservice.ShortService, shortLogs *shortlogservi } func (h *ShortHandler) FindShort(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + ctx, span := tracing.StartSpan(r.Context(), "short.FindShort") + defer span.End() // Get the short URL from the request name := chi.URLParam(r, "short") @@ -34,8 +36,10 @@ func (h *ShortHandler) FindShort(w http.ResponseWriter, r *http.Request) { switch { case err == nil: + span.AddEvent("short found") // If there's no error, log the access and redirect to the URL h.shortLogs.LogShortAccess(ctx, short, r) + span.AddEvent("submitted access log") http.Redirect(w, r, short.URL, http.StatusSeeOther) case errors.Is(err, errs.ErrInvalidShort): @@ -43,6 +47,7 @@ func (h *ShortHandler) FindShort(w http.ResponseWriter, r *http.Request) { // If the short name is invalid, do nothing and let the static handler // take care of it. case errors.Is(err, errs.ErrShortDoesNotExist): + span.AddEvent("short not found") // If the short doesn't exist, do nothing and let the static handler // take care of it. diff --git a/internal/server/static/handler.go b/internal/server/static/handler.go index 593b666..6b53908 100644 --- a/internal/server/static/handler.go +++ b/internal/server/static/handler.go @@ -7,6 +7,7 @@ import ( "os" "git.maronato.dev/maronato/goshort/internal/config" + "git.maronato.dev/maronato/goshort/internal/util/tracing" ) type StaticHandler struct { @@ -29,6 +30,9 @@ func NewStaticHandler(_ *config.Config, prefix string, assets fs.FS) *StaticHand } func (h *StaticHandler) ServeFiles(w http.ResponseWriter, r *http.Request) { + _, span := tracing.StartSpan(r.Context(), "static.ServeFiles") + defer span.End() + // Serve the files h.assetServer.ServeHTTP(w, r) } diff --git a/internal/storage/bun/storage.go b/internal/storage/bun/storage.go index 0dc5363..f65dd45 100644 --- a/internal/storage/bun/storage.go +++ b/internal/storage/bun/storage.go @@ -12,6 +12,7 @@ import ( "git.maronato.dev/maronato/goshort/internal/storage/models" "github.com/uptrace/bun" "github.com/uptrace/bun/extra/bundebug" + "github.com/uptrace/bun/extra/bunotel" ) type BunStorage struct { @@ -29,6 +30,11 @@ func NewBunStorage(cfg *config.Config, db *bun.DB) *BunStorage { db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) } + // Enable tracing. + db.AddQueryHook(bunotel.NewQueryHook( + bunotel.WithDBName("goshort")), + ) + return &BunStorage{ db: db, started: false, diff --git a/internal/util/tracing/tracing.go b/internal/util/tracing/tracing.go new file mode 100644 index 0000000..f2e0979 --- /dev/null +++ b/internal/util/tracing/tracing.go @@ -0,0 +1,89 @@ +package tracing + +import ( + "context" + + "git.maronato.dev/maronato/goshort/internal/config" + "git.maronato.dev/maronato/goshort/internal/errs" + "git.maronato.dev/maronato/goshort/internal/util/logging" + "go.opentelemetry.io/contrib/propagators/autoprop" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.18.0" + "go.opentelemetry.io/otel/trace" +) + +const tracerName = "goshort" + +type tracerKey struct{} + +func InitTracer(ctx context.Context, cfg *config.Config, version string) (context.Context, func(), error) { + var ex sdktrace.SpanExporter + + shutdown := func() {} + + ex, err := otlptracegrpc.New(ctx) + if err != nil { + return ctx, shutdown, errs.Errorf("failed to initialize otlptracegrpc exporter %w", err) + } + + // Create a new resource with service name + env := "development" + if cfg.Prod { + env = "production" + } + + r := resource.NewSchemaless( + semconv.ServiceNameKey.String("goshort"), + semconv.ServiceVersion(version), + semconv.DeploymentEnvironment(env), + semconv.ServiceName("goshort"), + ) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(ex), + sdktrace.WithResource(r), + ) + + // Set the global trace provider + otel.SetTracerProvider(tp) + + tracer := tp.Tracer(tracerName) + + // Set context tracer + ctx = context.WithValue(ctx, tracerKey{}, tracer) + + // Set the global propagator + p := autoprop.NewTextMapPropagator() + otel.SetTextMapPropagator(p) + + // Define shutdown function + shutdown = func() { + l := logging.FromCtx(ctx) + + if err := tp.ForceFlush(ctx); err != nil { + l.Error("Failed to flush tracer provider", err) + + return + } + + if err := tp.Shutdown(context.WithoutCancel(ctx)); err != nil { + l.Error("Failed to shutdown tracer provider", err) + + return + } + } + + return ctx, shutdown, nil +} + +func StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { //nolint:ireturn // Span is only available as an interface + tracer, ok := ctx.Value(tracerKey{}).(trace.Tracer) + if !ok { + tracer = trace.SpanFromContext(ctx).TracerProvider().Tracer("goshort") + } + + return tracer.Start(ctx, name, opts...) +}