diff --git a/.cspell.json b/.cspell.json index d64540d..9c49407 100644 --- a/.cspell.json +++ b/.cspell.json @@ -15,7 +15,6 @@ "jaegertracing", "pprof", "zpages", - "fluentbit", "chainguard", "hadolint", "GOARCH", diff --git a/.github/workflows/image-scan.yaml b/.github/workflows/image-scan.yaml index af90260..b0484b9 100644 --- a/.github/workflows/image-scan.yaml +++ b/.github/workflows/image-scan.yaml @@ -25,7 +25,7 @@ jobs: go-version-file: go.mod - uses: ko-build/setup-ko@v0.8 with: - version: v0.17.1 + version: v0.18.0 - run: | ko build . --push=false --tarball "${RUNNER_TEMP}/image.tar" \ --bare \ @@ -37,5 +37,7 @@ jobs: | sh -s -- -b /usr/local/bin - name: Run trufflehog on image.tar run: | - trufflehog --fail --no-update --github-actions \ + trufflehog --github-actions \ + --fail --no-update \ + --results=verified,unknown \ filesystem "${RUNNER_TEMP}/image.tar" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 8a55024..dcf2b83 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -26,7 +26,7 @@ jobs: go-version-file: go.mod - uses: ko-build/setup-ko@v0.8 with: - version: v0.17.1 + version: v0.18.0 - run: | ko build . \ --bare \ diff --git a/0_config.go b/0_config.go index 79c0291..514154d 100644 --- a/0_config.go +++ b/0_config.go @@ -7,11 +7,11 @@ package main // spell-checker:disable import ( "fmt" + "log/slog" "os" "strings" "github.com/joho/godotenv" - "github.com/sirupsen/logrus" ) // spell-checker:enable @@ -38,12 +38,14 @@ func init() { cfg.Auth0Tenant = os.Getenv("AUTH0_TENANT") if cfg.Auth0Tenant == "" { - logrus.Fatalln("AUTH0_TENANT not set") + slog.Error("AUTH0_TENANT not set") + os.Exit(1) } if strings.ContainsAny(strings.TrimSuffix(cfg.Auth0Tenant, ".us"), "./:") { // .us is allowed, but otherwise AUTH0_TENANT cannot contain anything // looking like a domain name or URL. - logrus.Fatalln("invalid AUTH0_TENANT") + slog.Error("invalid AUTH0_TENANT") + os.Exit(1) } cfg.Auth0Domain = os.Getenv("AUTH0_DOMAIN") if cfg.Auth0Domain == "" { @@ -51,16 +53,19 @@ func init() { } cfg.ClientID = os.Getenv("CLIENT_ID") if cfg.ClientID == "" { - logrus.Fatalln("CLIENT_ID not set") + slog.Error("CLIENT_ID not set") + os.Exit(1) } cfg.ClientSecret = os.Getenv("CLIENT_SECRET") if cfg.ClientSecret == "" { - logrus.Fatalln("CLIENT_SECRET not set") + slog.Error("CLIENT_SECRET not set") + os.Exit(1) } cfg.CookieSecret = os.Getenv("COOKIE_SECRET") if cfg.CookieSecret == "" { - logrus.Fatalln("COOKIE_SECRET not set") + slog.Error("COOKIE_SECRET not set") + os.Exit(1) } insecureCookie := os.Getenv("INSECURE_COOKIE") diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bdf7b63 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# AGENTS.md + +## Build/Test Commands +- `make` or `make bin/auth0-cas-server-go` - Build binary +- `make all` - Build binary and Docker container +- `make lint` - Run mega-linter with Go linting rules +- `make docker-build` - Build Docker container +- `go run .` - Run directly with Go + +## Code Style Guidelines +- **License Header**: All files must start with Linux Foundation MIT license header +- **Package**: Single `main` package for this service +- **Imports**: Standard library first, then third-party, separated by blank lines +- **Naming**: Use camelCase for private, PascalCase for public; descriptive variable names +- **Error Handling**: Use slog for logging with structured fields; fatal errors use `slog.Error` with `os.Exit(1)` +- **Comments**: Spell-checker disable/enable blocks around imports; function comments for public APIs +- **Global Variables**: Minimal use (cfg for config, store for sessions) +- **Context**: Pass context through request handlers for logging and tracing +- **Types**: Define custom types for constants (e.g., `contextID int`) +- **Environment**: Use godotenv for optional .env file loading in init() +- **Linting**: Uses mega-linter with revive (not golangci-lint), excludes spell/link checkers + +## Key Patterns +- Global config in `cfg` variable populated via init() +- Request-scoped logging with context injection +- OpenTelemetry instrumentation throughout +- Gorilla sessions for cookie management diff --git a/auth0_clients.go b/auth0_clients.go index 782a99f..5cb4678 100644 --- a/auth0_clients.go +++ b/auth0_clients.go @@ -19,7 +19,6 @@ import ( "github.com/bmatcuk/doublestar/v4" "github.com/patrickmn/go-cache" - "github.com/sirupsen/logrus" "golang.org/x/oauth2/clientcredentials" ) @@ -111,10 +110,9 @@ func getAuth0Clients(ctx context.Context) ([]auth0ClientStub, error) { } if resp.StatusCode != http.StatusOK { - appLogger(ctx).WithFields(logrus.Fields{ - "status": resp.StatusCode, - "body": string(bodyBytes), - }).Error("Auth0 get_users error") + appLogger(ctx).Error("Auth0 get_users error", + "status", resp.StatusCode, + "body", string(bodyBytes)) return nil, errors.New("Auth0 get_clients error") } @@ -163,10 +161,9 @@ func getAuth0ClientByService(ctx context.Context, serviceURL string) (*auth0Clie var match bool match, err = doublestar.Match(glob, serviceURL) if err != nil { - appLogger(ctx).WithFields(logrus.Fields{ - "pattern": glob, - logrus.ErrorKey: err, - }).Warning("unexpected bad cas_service glob in cache") + appLogger(ctx).Warn("unexpected bad cas_service glob in cache", + "pattern", glob, + "error", err) continue } if !match { @@ -174,7 +171,7 @@ func getAuth0ClientByService(ctx context.Context, serviceURL string) (*auth0Clie } // There is a match - appLogger(ctx).WithFields(logrus.Fields{"service": serviceURL, "glob": glob, "auth0_client": client.Name}).Debug("matched service in glob cache") + appLogger(ctx).Debug("matched service in glob cache", "service", serviceURL, "glob", glob, "auth0_client", client.Name) auth0Cache.Set("cas-service-url/"+url.PathEscape(serviceURL), client, cache.NoExpiration) return &client, nil } @@ -198,10 +195,9 @@ func getAuth0ClientByService(ctx context.Context, serviceURL string) (*auth0Clie } if client.TokenEndpointAuthMethod != "client_secret_post" && client.TokenEndpointAuthMethod != "client_secret_basic" { - appLogger(ctx).WithFields(logrus.Fields{ - "token_endpoint_auth_method": client.TokenEndpointAuthMethod, - "auth0_client": client.Name, - }).Warning("client with cas_service has unsupported token_endpoint_auth_method") + appLogger(ctx).Warn("client with cas_service has unsupported token_endpoint_auth_method", + "token_endpoint_auth_method", client.TokenEndpointAuthMethod, + "auth0_client", client.Name) continue } @@ -212,10 +208,9 @@ func getAuth0ClientByService(ctx context.Context, serviceURL string) (*auth0Clie for _, glob := range serviceGlobs { match, err := doublestar.Match(glob, serviceURL) if err != nil { - appLogger(ctx).WithFields(logrus.Fields{ - "pattern": glob, - logrus.ErrorKey: err, - }).Warning("ignoring bad cas_service glob") + appLogger(ctx).Warn("ignoring bad cas_service glob", + "pattern", glob, + "error", err) continue } // Store the glob-to-client lookup (for cache). @@ -225,7 +220,7 @@ func getAuth0ClientByService(ctx context.Context, serviceURL string) (*auth0Clie continue } - appLogger(ctx).WithFields(logrus.Fields{"service": serviceURL, "glob": glob, "auth0_client": client.Name}).Debug("matched service") + appLogger(ctx).Debug("matched service", "service", serviceURL, "glob", glob, "auth0_client", client.Name) // If the glob matches, save the match, but keep processing remaining // comma-delimited globs AND clients to complete the glob-to-client cache // update. diff --git a/cas.go b/cas.go index bf6c0da..220119c 100644 --- a/cas.go +++ b/cas.go @@ -18,7 +18,6 @@ import ( "strings" "github.com/gorilla/sessions" - "github.com/sirupsen/logrus" "golang.org/x/oauth2" ) @@ -62,31 +61,31 @@ func casLogin(w http.ResponseWriter, r *http.Request) { service := params.Get("service") if service == "" { - appLogger(r.Context()).Warning("service parameter is required") + appLogger(r.Context()).Warn("service parameter is required") http.Error(w, "service parameter is required", http.StatusBadRequest) return } if _, err := url.Parse(service); err != nil { // We don't use this now, but better to catch here than in oauth2Callback. - appLogger(r.Context()).Warning("invalid service URL") + appLogger(r.Context()).Warn("invalid service URL") http.Error(w, "invalid service URL", http.StatusBadRequest) return } casClient, err := getAuth0ClientByService(r.Context(), service) if err != nil { - appLogger(r.Context()).WithError(err).Error("error looking up service") + appLogger(r.Context()).Error("error looking up service", "error", err) http.Error(w, "error looking up service", http.StatusInternalServerError) return } if casClient == nil { - appLogger(r.Context()).Warning("unknown service") + appLogger(r.Context()).Warn("unknown service") http.Error(w, "unknown service", http.StatusForbidden) return } - appLogger(r.Context()).WithField("auth0_client", casClient).Debug("found client") + appLogger(r.Context()).Debug("found client", "auth0_client", casClient) renew := params.Get("renew") @@ -108,13 +107,13 @@ func casLogin(w http.ResponseWriter, r *http.Request) { if err != nil && err.Error() == "securecookie: the value is too long" { // The cookie can get too big if the user tries 10+ logins in the day // without returning from any of them. - appLogger(r.Context()).Warning("cookie too large (bot or other bad client)") + appLogger(r.Context()).Warn("cookie too large (bot or other bad client)") w.Header().Set("Retry-After", "86400") http.Error(w, "429 too many requests", http.StatusTooManyRequests) return } if err != nil { - appLogger(r.Context()).WithError(err).Error("error saving session") + appLogger(r.Context()).Error("error saving session", "error", err) http.Error(w, "500 internal server error", http.StatusInternalServerError) return } @@ -235,7 +234,7 @@ func casServiceValidate(w http.ResponseWriter, r *http.Request) { return } - appLogger(r.Context()).WithField("auth0_client", casClient).Debug("found client") + appLogger(r.Context()).Debug("found client", "auth0_client", casClient) // Construct an OAuth2 config that lets us complete the authorization code // handshake to to get an access token. @@ -254,11 +253,10 @@ func casServiceValidate(w http.ResponseWriter, r *http.Request) { // the client is configured for it), without significantly increasing the // complexity of the codebase. config := oauth2CfgFromAuth0Client(*casClient, r.Host) - appLogger(r.Context()).WithFields(logrus.Fields{ - "client_id": config.ClientID, - "token_url": config.Endpoint.TokenURL, - "code": authCode, - }).Debug("auth code exchange") + appLogger(r.Context()).Debug("auth code exchange", + "client_id", config.ClientID, + "token_url", config.Endpoint.TokenURL, + "code", authCode) token, err := config.Exchange(context.WithValue(r.Context(), oauth2.HTTPClient, httpClient), authCode) if err != nil { @@ -266,7 +264,7 @@ func casServiceValidate(w http.ResponseWriter, r *http.Request) { if rErr.Response.StatusCode == 403 { // Rather than decoding the JSON payload, we can assume a 403 means the // auth code (as provided as a CAS service ticket) was invalid. - appLogger(r.Context()).WithError(err).Debug("auth code exchange 403 response") + appLogger(r.Context()).Debug("auth code exchange 403 response", "error", err) outputFailure(r.Context(), w, nil, "INVALID_TICKET", "invalid ticket", useJSON) return } @@ -322,13 +320,13 @@ func casServiceValidate(w http.ResponseWriter, r *http.Request) { } output, err := validationResponse(&success, nil, useJSON) if err != nil { - appLogger(r.Context()).WithError(err).WithField("success", success).Error("error generating validation response") + appLogger(r.Context()).Error("error generating validation response", "error", err, "success", success) w.WriteHeader(http.StatusInternalServerError) http.Error(w, "error generating validation response", http.StatusInternalServerError) return } - appLogger(r.Context()).WithField("body", output).Debug("sending validation response") + appLogger(r.Context()).Debug("sending validation response", "body", output) switch useJSON { case true: @@ -396,27 +394,27 @@ func oauth2Callback(w http.ResponseWriter, r *http.Request) { if errParam == "access_denied" { // Consider this a warning-level error for logging purposes. err := fmt.Errorf("%s: %s", errParam, errDescription) - appLogger(r.Context()).WithError(err).Warning("login aborted") + appLogger(r.Context()).Warn("login aborted", "error", err) http.Error(w, err.Error(), http.StatusBadRequest) return } if errParam != "" { err := fmt.Errorf("%s: %s", errParam, errDescription) - appLogger(r.Context()).WithError(err).Error("login error") + appLogger(r.Context()).Error("login error", "error", err) http.Error(w, err.Error(), http.StatusBadRequest) return } code := params.Get("code") if code == "" { - appLogger(r.Context()).Warning("invalid request") + appLogger(r.Context()).Warn("invalid request") http.Error(w, "invalid request", http.StatusBadRequest) return } state := params.Get("state") if state == "" { - appLogger(r.Context()).Warning("missing state") + appLogger(r.Context()).Warn("missing state") http.Error(w, "missing state", http.StatusBadRequest) return } @@ -425,7 +423,7 @@ func oauth2Callback(w http.ResponseWriter, r *http.Request) { var service string var ok bool if service, ok = session.Values[state].(string); !ok { - appLogger(r.Context()).Warning("session missing or expired") + appLogger(r.Context()).Warn("session missing or expired") http.Error(w, "session missing or expired", http.StatusBadRequest) return } @@ -435,7 +433,7 @@ func oauth2Callback(w http.ResponseWriter, r *http.Request) { serviceURL, err := url.Parse(service) if err != nil { - appLogger(r.Context()).Warning("invalid service URL") + appLogger(r.Context()).Warn("invalid service URL") http.Error(w, "invalid service URL", http.StatusBadRequest) return } @@ -479,26 +477,24 @@ func getLogoutParams(ctx context.Context, returnTo string) *url.Values { returnURL, err := url.Parse(returnTo) if err != nil { // Warn about the error and continue. - appLogger(ctx).WithFields(logrus.Fields{ - "returnTo": returnTo, - logrus.ErrorKey: err, - }).Warn("ignoring invalid returnTo URL") + appLogger(ctx).Warn("ignoring invalid returnTo URL", + "returnTo", returnTo, + "error", err) return nil } casClient, err := getAuth0ClientByService(ctx, returnTo) if err != nil { // Warn about the error and continue. - appLogger(ctx).WithFields(logrus.Fields{ - "returnTo": returnTo, - logrus.ErrorKey: err, - }).Warn("ignoring unexpected error validating logout redirection") + appLogger(ctx).Warn("ignoring unexpected error validating logout redirection", + "returnTo", returnTo, + "error", err) return nil } if casClient == nil { // No cas_service configurations matched the requested logout redirect. - appLogger(ctx).WithField("returnTo", returnTo).Warn("ignoring unauthorized logout redirection") + appLogger(ctx).Warn("ignoring unauthorized logout redirection", "returnTo", returnTo) return nil } @@ -509,7 +505,7 @@ func getLogoutParams(ctx context.Context, returnTo string) *url.Values { for _, allowedLogoutValue := range casClient.AllowedLogoutURLs { allowedLogoutURL, err := url.Parse(allowedLogoutValue) if err != nil { - appLogger(ctx).WithField("allowed_logout_url", allowedLogoutValue).Warn("unable to parse allowed_logout_urls value") + appLogger(ctx).Warn("unable to parse allowed_logout_urls value", "allowed_logout_url", allowedLogoutValue) continue } allowedLogoutURL.RawQuery = "" @@ -522,10 +518,9 @@ func getLogoutParams(ctx context.Context, returnTo string) *url.Values { } } - appLogger(ctx).WithFields(logrus.Fields{ - "returnTo": returnTo, - "client_id": casClient.ClientID, - }).Warn("returnTo not allowed by allowed_logout_urls") + appLogger(ctx).Warn("returnTo not allowed by allowed_logout_urls", + "returnTo", returnTo, + "client_id", casClient.ClientID) return nil } @@ -538,15 +533,15 @@ func getLogoutParams(ctx context.Context, returnTo string) *url.Values { func outputFailure(ctx context.Context, w http.ResponseWriter, err error, code, description string, useJSON bool) { switch { case err != nil: - appLogger(ctx).WithError(err).Error(description) + appLogger(ctx).Error(description, "error", err) default: - appLogger(ctx).Warning(description) + appLogger(ctx).Warn(description) } failure := casAuthenticationFailure{code, description} output, err := validationResponse(nil, &failure, useJSON) if err != nil { - appLogger(ctx).WithError(err).WithField("failure", failure).Error("error generating validation response") + appLogger(ctx).Error("error generating validation response", "error", err, "failure", failure) w.WriteHeader(http.StatusInternalServerError) http.Error(w, "error generating validation response", http.StatusInternalServerError) return diff --git a/docker-compose.yaml b/docker-compose.yaml index e3a33b2..47f0bea 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -53,10 +53,3 @@ services: - DD_ENV depends_on: - jaeger-all-in-one - - # Fluentbit (to stdout) agent for testing Fluent logging - fluentbit: - image: docker.io/fluent/fluent-bit:1.8 - ports: - - "24224:24224" - command: "/fluent-bit/bin/fluent-bit -i forward -o stdout" diff --git a/go.mod b/go.mod index d4bb8a6..00e4db0 100644 --- a/go.mod +++ b/go.mod @@ -3,44 +3,38 @@ module github.com/linuxfoundation/auth0-cas-server-go -go 1.24.2 +go 1.25.0 require ( - github.com/bmatcuk/doublestar/v4 v4.8.1 - github.com/evalphobia/logrus_fluent v0.5.4 + github.com/bmatcuk/doublestar/v4 v4.9.1 github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/sirupsen/logrus v1.9.3 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 - go.opentelemetry.io/otel v1.35.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 - go.opentelemetry.io/otel/sdk v1.35.0 - go.opentelemetry.io/otel/trace v1.35.0 - golang.org/x/oauth2 v0.29.0 - golang.org/x/text v0.24.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 + golang.org/x/oauth2 v0.30.0 + golang.org/x/text v0.28.0 ) require ( - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fluent/fluent-logger-golang v1.9.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect - github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect - github.com/tinylib/msgp v1.2.5 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sys v0.32.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect - google.golang.org/grpc v1.71.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.7 // indirect ) diff --git a/go.sum b/go.sum index dba154c..bff32f6 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,14 @@ -github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= -github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= +github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/evalphobia/logrus_fluent v0.5.4 h1:G4BSBTm7+L+oanWfFtA/A5Y3pvL2OMxviczyZPYO5xc= -github.com/evalphobia/logrus_fluent v0.5.4/go.mod h1:hasyj+CXm3BDP1YhFk/rnTcjlegyqvkokV9A25cQsaA= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluent/fluent-logger-golang v1.9.0 h1:zUdY44CHX2oIUc7VTNZc+4m+ORuO/mldQDA7czhWXEg= -github.com/fluent/fluent-logger-golang v1.9.0/go.mod h1:2/HCT/jTy78yGyeNGQLGQsjF3zzzAuy6Xlk6FCMV5eU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -30,70 +23,55 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= -github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= -google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs= -google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 2c1e67b..d7f4499 100644 --- a/main.go +++ b/main.go @@ -10,13 +10,11 @@ import ( _ "expvar" "flag" "fmt" + "log/slog" "net/http" "os" - "strconv" "time" - "github.com/evalphobia/logrus_fluent" - "github.com/sirupsen/logrus" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" semconv "go.opentelemetry.io/otel/semconv/v1.10.0" "go.opentelemetry.io/otel/trace" @@ -48,55 +46,44 @@ func main() { } flag.Parse() - // Optional debug logging. + // Configure slog logging. + var logLevel slog.Level if os.Getenv("DEBUG") != "" || *debug { - logrus.SetLevel(logrus.DebugLevel) + logLevel = slog.LevelDebug + } else { + logLevel = slog.LevelInfo } _, isECS := os.LookupEnv("ECS_CONTAINER_METADATA_URI_V4") + var handler slog.Handler switch { case isECS: - // Assume output to CloudWatch logs which has native timestamps. Use - // "message" for cleaner integration with log aggregation service. - logrus.SetFormatter(&logrus.JSONFormatter{ - DisableTimestamp: true, - FieldMap: logrus.FieldMap{ - logrus.FieldKeyMsg: "message", + // Assume ECS is logging to CloudWatch logs and use JSON format. + handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: logLevel, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + // The timestamp attribute will be redundant with the CloudWatch logs + // timestamp. + if a.Key == slog.TimeKey { + return slog.Attr{} + } + // Use "message" instead of "msg" for compatibility with external log + // aggregators. + if a.Key == slog.MessageKey { + return slog.String("message", a.Value.String()) + } + return a }, }) case *logJSON: - logrus.SetFormatter(&logrus.JSONFormatter{}) - } - - if fluentHost, useFluent := os.LookupEnv("FLUENT_HOST"); useFluent { - var fluentPort int64 = 24224 - if fluentPortEnv, ok := os.LookupEnv("FLUENT_PORT"); ok { - var err error - fluentPort, err = strconv.ParseInt(fluentPortEnv, 10, 32) - if err != nil { - logrus.WithField("fluent_port", fluentPortEnv).WithError(err).Fatal("unable to parse FLUENT_PORT") - } - } - hook, err := logrus_fluent.NewWithConfig(logrus_fluent.Config{ - Host: fluentHost, - Port: int(fluentPort), - AsyncConnect: true, - }) - if err != nil { - logrus.WithFields(logrus.Fields{ - "host": fluentHost, - "port": fluentPort, - logrus.ErrorKey: err, - }).Fatal("could not set up fluentd logger") - } - hook.SetTag(serviceName + ".log") - - // Convert error struct to string. - hook.AddFilter(logrus.ErrorKey, logrus_fluent.FilterError) - - logrus.AddHook(hook) + handler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel}) + default: + handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: logLevel}) } + logger := slog.New(handler) + slog.SetDefault(logger) + // Instrument Open Telemetry. if !*noTrace { // Start OTLP forwarder and register the global tracing provider. @@ -168,7 +155,8 @@ func main() { } err := server.ListenAndServe() if err != nil { - logrus.WithError(err).Fatal("http listener error") + slog.Error("http listener error", "error", err) + os.Exit(1) } } @@ -186,7 +174,7 @@ func routeTagHandler(inner http.Handler) http.Handler { return http.HandlerFunc(mw) } -// loggingHandler adds a logrus.Entry into the context of the current request. +// loggingHandler adds a slog.Logger into the context of the current request. func loggingHandler(inner http.Handler) http.Handler { mw := func(w http.ResponseWriter, r *http.Request) { ctx := withLogger(r.Context(), requestLogger(r)) @@ -195,28 +183,28 @@ func loggingHandler(inner http.Handler) http.Handler { return http.HandlerFunc(mw) } -func withLogger(ctx context.Context, logger *logrus.Entry) context.Context { +func withLogger(ctx context.Context, logger *slog.Logger) context.Context { return context.WithValue(ctx, logEntryID, logger) } -func appLogger(ctx context.Context) *logrus.Entry { +func appLogger(ctx context.Context) *slog.Logger { if ctx == nil { - return logrus.NewEntry(logrus.StandardLogger()) + return slog.Default() } - if logger, ok := ctx.Value(logEntryID).(*logrus.Entry); ok { + if logger, ok := ctx.Value(logEntryID).(*slog.Logger); ok { return logger } - return logrus.NewEntry(logrus.StandardLogger()) + return slog.Default() } -func requestLogger(r *http.Request) *logrus.Entry { - e := logrus.WithFields(logrus.Fields{ - "method": r.Method, - "url": r.URL.Path, - "query": r.URL.RawQuery, - }) +func requestLogger(r *http.Request) *slog.Logger { + logger := slog.Default().With( + "method", r.Method, + "url", r.URL.Path, + "query", r.URL.RawQuery, + ) var headerIP string if cfg.RemoteIPHeader != "" { @@ -224,29 +212,27 @@ func requestLogger(r *http.Request) *logrus.Entry { } if referer := r.Header.Get("Referer"); referer != "" { - e = e.WithField("referer", referer) + logger = logger.With("referer", referer) } switch headerIP { case "": // Log the client IP. - e = e.WithField("client", r.RemoteAddr) + logger = logger.With("client", r.RemoteAddr) default: // Log the IP recorded in the configured header. - e = e.WithField("client", headerIP) + logger = logger.With("client", headerIP) } // Add trace and span IDs (if any) for log/trace correlation. spanContext := trace.SpanContextFromContext(r.Context()) if traceID := spanContext.TraceID(); traceID.IsValid() { - e = e.WithField("trace_id", traceID.String()) - e = e.WithField("dd.trace_id", convertTraceID(traceID.String())) + logger = logger.With("trace_id", traceID.String(), "dd.trace_id", convertTraceID(traceID.String())) } if spanID := spanContext.SpanID(); spanID.IsValid() { - e = e.WithField("span_id", spanID.String()) - e = e.WithField("dd.span_id", convertTraceID(spanID.String())) + logger = logger.With("span_id", spanID.String(), "dd.span_id", convertTraceID(spanID.String())) } - e = e.WithField("trace_flags", spanContext.TraceFlags().String()) + logger = logger.With("trace_flags", spanContext.TraceFlags().String()) - return e + return logger } diff --git a/tracing.go b/tracing.go index 0846ea5..087a28e 100644 --- a/tracing.go +++ b/tracing.go @@ -7,10 +7,10 @@ package main // spell-checker:disable import ( "context" + "log/slog" "os" "strconv" - "github.com/sirupsen/logrus" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/propagation" @@ -24,7 +24,7 @@ import ( type otelErrorHandler struct{} func (h *otelErrorHandler) Handle(err error) { - logrus.WithError(err).Error("opentelemetry-go error") + slog.Error("opentelemetry-go error", "error", err) } func init() { @@ -38,7 +38,8 @@ func initOTLP() func() { exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithInsecure()) if err != nil { - logrus.WithError(err).Fatal("failed to create exporter") + slog.Error("failed to create exporter", "error", err) + os.Exit(1) } bsp := sdktrace.NewBatchSpanProcessor(exp) @@ -58,7 +59,8 @@ func initOTLP() func() { ), ) if err != nil { - logrus.WithError(err).Fatal("failed to create resource") + slog.Error("failed to create resource", "error", err) + os.Exit(1) } opts = append(opts, sdktrace.WithResource(res)) } @@ -71,10 +73,12 @@ func initOTLP() func() { return func() { if err = tracerProvider.Shutdown(ctx); err != nil { - logrus.WithError(err).Fatal("failed to shutdown provider") + slog.Error("failed to shutdown provider", "error", err) + os.Exit(1) } if err = exp.Shutdown(ctx); err != nil { - logrus.WithError(err).Fatal("failed to stop exporter") + slog.Error("failed to stop exporter", "error", err) + os.Exit(1) } } }