diff --git a/.env.example b/.env.example index 05a899e45..7020e0afc 100644 --- a/.env.example +++ b/.env.example @@ -1,33 +1,55 @@ ENV=development +# port the servers listens on PORT=8080 +# replace me in a production environment +AUTHENTICATION_JWT_SIGNING_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAs+6r50m7qqLHHy7CvfmJPnAi+t/tubi7DPSM2jvA1etT1jEX\nrwbFbmooOu9LTgmjmxOq01p+XwkW1f7iPZViKrf7dEDEuqmpqYG9jPX4G/7xFcci\nGn1iSOiNx9awIKSYZa1wodlMCRM081DGqFNDMf1PScWIyM40nIwaGqLZht4HcOAq\nLbKDa15bxubBqZ9o/YnE1KmyBfq1tTnk0KzAb12Axt0xN4qB2zktsV/LLds+szMk\n/gRHjann1+fCZvxw1JzzRPtgeHLLYzn4ks3mwzy67RO3q/663KPZCsuYhlNCsMqp\n/HAbrF5PaihqzCZqLTDoIXXciCFFgwtLwm951wIDAQABAoIBAQCpb60tJX+1VYeQ\n06XK43rb8xjdiZUA+PYbYwZoUzBpwSq3Xo9g4E12hjzQEpqlJ+qKk+CfGm457AM3\nDMfbGhrRA2Oku4EGDdKYrnXikZVMN6yqx1RUAZJV+bfZYU+Fzbk8tjCEGG3DdfS8\n02nfBFkYb+MEIyGFhriAWmYSgxu4JTN0XRTyPqBytoSLqVCFbv0/yV2oJQDaXW08\nWAA8JtWhzqxACbFnPYe0hYUnrCA71t0v1P/N5uB4kKxI0tulGtW84noSyWA2LSdn\nJlKQW5WsyeMulGBMnIpj/OQJtQErupoITsh1TNi+6ffGgmuMCT1za70DHXVq9Ihu\nkpKBe0wRAoGBAOSarLfNvsS2lTH/8zPyhWBddCS5CfQAeUFLD5xWhQ7/6SenYzYY\n+oiiH2uL7d8grkobX5QLVvJ5ZXziYWoKgJe3SlrvRuNJZCAxvuynUCahhCT+chwW\nGz7ihXh3bGD0gtO6iogGBfrAkvRQnorkdSmVEZd1PsJV/lXp8LKgxJ91AoGBAMl+\ny/6NbzVHt9oQsrVrG/sCAOlqlfTt5KW6pI1WC4LoKBaGe+hy4emZ0G/M2feAJEPR\n92QrPRkVF5bVCjalJj42/7gQIl6r+DQ4+08gLB1MSpWua2M3UtEi/2gsMcQff/wg\n6kmNZObW5Jcnqpp6u72zQTQwF4H29XucV/Yw93abAoGADGvfIKmcSQIGv03CADuY\nRbEuQ2SOhuSTshmLApqs5jC/kXkF6gWXb18nx+c1iJ80+S/dlKS9F7XC7vM6CdIC\nRLwf3SsNNgJh32H0ltVMhJzYGk59EsuctWEHkZEjoW0HwstrBZMWNhbKpV3QD4n0\nV8sSxqEHRPX5ON/aRUp5BJUCgYEAlsymr2P6js2V80X7+Xqn/juJoyd6A0znioEd\nFgoHo3lMR09u/JC+Mq5DKOkPWAQ3H+rMU9NobpUyilf2xN7kuDtBNugcUO4zXCIp\nMxbI7URjrZJUHHUTLiIbNEOfG0DX8EJSFaoUkg7SFa5CKEsipt65Ne2oKkRBhLmF\nu2L6UXECgYBH1bpi0R6j7lIADtZtIJII/TezQbp+VK2R9qoNgkTnHoDjkRVR7v3m\n75wReMvTy1h0Qx/ROtStZz8d5uQuhdeJvbQPQR8KGFUFZDmVWxU+y15WI2H39FMA\nMireKxzCfGGtTsZnhDqYl9NuRPcAGYt5jvoERXlz7b69rkqQUrfy+Q==\n-----END RSA PRIVATE KEY-----" + +# Env variables for the postgresql database PG_HOSTNAME=localhost PG_PORT=5432 PG_USER=postgres PG_PASSWORD=marble -AUTHENTICATION_JWT_SIGNING_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAs+6r50m7qqLHHy7CvfmJPnAi+t/tubi7DPSM2jvA1etT1jEX\nrwbFbmooOu9LTgmjmxOq01p+XwkW1f7iPZViKrf7dEDEuqmpqYG9jPX4G/7xFcci\nGn1iSOiNx9awIKSYZa1wodlMCRM081DGqFNDMf1PScWIyM40nIwaGqLZht4HcOAq\nLbKDa15bxubBqZ9o/YnE1KmyBfq1tTnk0KzAb12Axt0xN4qB2zktsV/LLds+szMk\n/gRHjann1+fCZvxw1JzzRPtgeHLLYzn4ks3mwzy67RO3q/663KPZCsuYhlNCsMqp\n/HAbrF5PaihqzCZqLTDoIXXciCFFgwtLwm951wIDAQABAoIBAQCpb60tJX+1VYeQ\n06XK43rb8xjdiZUA+PYbYwZoUzBpwSq3Xo9g4E12hjzQEpqlJ+qKk+CfGm457AM3\nDMfbGhrRA2Oku4EGDdKYrnXikZVMN6yqx1RUAZJV+bfZYU+Fzbk8tjCEGG3DdfS8\n02nfBFkYb+MEIyGFhriAWmYSgxu4JTN0XRTyPqBytoSLqVCFbv0/yV2oJQDaXW08\nWAA8JtWhzqxACbFnPYe0hYUnrCA71t0v1P/N5uB4kKxI0tulGtW84noSyWA2LSdn\nJlKQW5WsyeMulGBMnIpj/OQJtQErupoITsh1TNi+6ffGgmuMCT1za70DHXVq9Ihu\nkpKBe0wRAoGBAOSarLfNvsS2lTH/8zPyhWBddCS5CfQAeUFLD5xWhQ7/6SenYzYY\n+oiiH2uL7d8grkobX5QLVvJ5ZXziYWoKgJe3SlrvRuNJZCAxvuynUCahhCT+chwW\nGz7ihXh3bGD0gtO6iogGBfrAkvRQnorkdSmVEZd1PsJV/lXp8LKgxJ91AoGBAMl+\ny/6NbzVHt9oQsrVrG/sCAOlqlfTt5KW6pI1WC4LoKBaGe+hy4emZ0G/M2feAJEPR\n92QrPRkVF5bVCjalJj42/7gQIl6r+DQ4+08gLB1MSpWua2M3UtEi/2gsMcQff/wg\n6kmNZObW5Jcnqpp6u72zQTQwF4H29XucV/Yw93abAoGADGvfIKmcSQIGv03CADuY\nRbEuQ2SOhuSTshmLApqs5jC/kXkF6gWXb18nx+c1iJ80+S/dlKS9F7XC7vM6CdIC\nRLwf3SsNNgJh32H0ltVMhJzYGk59EsuctWEHkZEjoW0HwstrBZMWNhbKpV3QD4n0\nV8sSxqEHRPX5ON/aRUp5BJUCgYEAlsymr2P6js2V80X7+Xqn/juJoyd6A0znioEd\nFgoHo3lMR09u/JC+Mq5DKOkPWAQ3H+rMU9NobpUyilf2xN7kuDtBNugcUO4zXCIp\nMxbI7URjrZJUHHUTLiIbNEOfG0DX8EJSFaoUkg7SFa5CKEsipt65Ne2oKkRBhLmF\nu2L6UXECgYBH1bpi0R6j7lIADtZtIJII/TezQbp+VK2R9qoNgkTnHoDjkRVR7v3m\n75wReMvTy1h0Qx/ROtStZz8d5uQuhdeJvbQPQR8KGFUFZDmVWxU+y15WI2H39FMA\nMireKxzCfGGtTsZnhDqYl9NuRPcAGYt5jvoERXlz7b69rkqQUrfy+Q==\n-----END RSA PRIVATE KEY-----" -GCS_INGESTION_BUCKET="data-ingestion-bucket" -GCS_CASE_MANAGER_BUCKET="case-manager-bucket" -# The frontend sign +# Used by the firebase sdk to validate the id tokens FIREBASE_AUTH_EMULATOR_HOST="localhost:9099" -# When using firebase emulator, JWT Token are issued for this audience -GOOGLE_CLOUD_PROJECT="fake-project-id" +# Used: +# - by the firebase sdk to validate the id tokens +# - by the opentelemetry tracing agent to send traces to the collector +GOOGLE_CLOUD_PROJECT="tokyo-country-381508" -MARBLE_ADMIN_EMAIL=admin@checkmarble.com +# Enable to activate GCP tracing +# If activated, you MUST have GCP application default credentials set up - see https://cloud.google.com/trace/docs/setup/go-ot +# Will send error logs (but the app will still run) if an unexisting project is specified in GOOGLE_CLOUD_PROJECT or +# if the runner does not have the correct permissions to use tracing on the project +ENABLE_GCP_TRACING=false -# Required if FAKE_AWS_S3 is false -# AWS_REGION=eu-west-3 -# AWS_ACCESS_KEY= -# AWS_SECRET_KEY= +# 'liveness' (only log liveness requests) || 'all' (log all requests) || any other value for no request logs +REQUEST_LOGGING_LEVEL=all +# 'json' || 'text' +LOGGING_FORMAT=text -FAKE_AWS_S3=true +# configure the document storage backend with optional fake backends +GCS_INGESTION_BUCKET="data-ingestion-bucket" +GCS_CASE_MANAGER_BUCKET="case-manager-bucket" FAKE_GCS=true +# Configure the AWS S3 backend for sending decision files +FAKE_AWS_S3=true +# The 3 below are required if FAKE_AWS_S3 is false +AWS_REGION=eu-west-3 +AWS_ACCESS_KEY= +AWS_SECRET_KEY= + +# Othe dependency configurations SEGMENT_WRITE_KEY=UgkImFmHmBZAWh5fxIKBY3QtvlcBrhqQ SENTRY_DSN= +# Metabase configuration METABASE_SITE_URL="https://your_subdomain.metabaseapp.com" METABASE_JWT_SIGNING_KEY="dummy" -METABASE_GLOBAL_DASHBOARD_ID=123 \ No newline at end of file +METABASE_GLOBAL_DASHBOARD_ID=123 + +# Used to create a first default admin user if no user exists +MARBLE_ADMIN_EMAIL=admin@checkmarble.com \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 6901f4065..e9004ffc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,11 +7,11 @@ RUN go get RUN CGO_ENABLED=0 go build -o /go/bin/app -ldflags="-X 'main.version=`git rev-parse --short HEAD`'" -FROM gcr.io/distroless/static +FROM alpine:3.19 COPY --from=build /go/bin/app / -ENV PORT=8080 +ENV PORT=${PORT:-8080} EXPOSE $PORT ENTRYPOINT ["/app"] \ No newline at end of file diff --git a/api/api.go b/api/api.go index 7e6a95532..d375a595f 100644 --- a/api/api.go +++ b/api/api.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "log/slog" "net/http" "time" @@ -26,14 +25,13 @@ func New( usecases usecases.Usecases, auth *Authentication, tokenHandler *TokenHandler, - logger *slog.Logger, ) *http.Server { s := &API{ router: router, usecases: usecases, } - s.routes(auth, tokenHandler, logger) + s.routes(auth, tokenHandler) return &http.Server{ Addr: fmt.Sprintf("0.0.0.0:%s", port), diff --git a/api/middleware/logging.go b/api/middleware/logging.go index 37a86ec3b..b4894deaf 100644 --- a/api/middleware/logging.go +++ b/api/middleware/logging.go @@ -26,7 +26,7 @@ func WithIgnorePath(s []string) LoggerOption { } } -func NewLogging(logger *slog.Logger, options ...LoggerOption) gin.HandlerFunc { +func NewLogging(logger *slog.Logger, level string, options ...LoggerOption) gin.HandlerFunc { l := &config{ logger: logger, defaultLevel: slog.LevelInfo, @@ -44,6 +44,17 @@ func NewLogging(logger *slog.Logger, options ...LoggerOption) gin.HandlerFunc { } return func(c *gin.Context) { + switch level { + case "liveness": + if c.Request.URL.Path != "/liveness" { + return + } + case "all": + // continue + default: + return + } + if _, ok := ignore[c.Request.URL.Path]; ok { return } diff --git a/api/routes.go b/api/routes.go index 36b1c2265..437c395ff 100644 --- a/api/routes.go +++ b/api/routes.go @@ -1,11 +1,9 @@ package api import ( - "log/slog" "net/http" "time" - "github.com/checkmarble/marble-backend/api/middleware" limits "github.com/gin-contrib/size" "github.com/gin-contrib/timeout" "github.com/gin-gonic/gin" @@ -25,8 +23,8 @@ func timeoutMiddleware(duration time.Duration) gin.HandlerFunc { ) } -func (api *API) routes(auth *Authentication, tokenHandler *TokenHandler, logger *slog.Logger) { - api.router.GET("/liveness", middleware.NewLogging(logger), HandleLivenessProbe) +func (api *API) routes(auth *Authentication, tokenHandler *TokenHandler) { + api.router.GET("/liveness", HandleLivenessProbe) api.router.POST("/crash", HandleCrash) api.router.POST("/token", tokenHandler.GenerateToken) diff --git a/infra/parse_signing_key.go b/infra/parse_signing_key.go index 84128a92f..6d7518fd0 100644 --- a/infra/parse_signing_key.go +++ b/infra/parse_signing_key.go @@ -5,9 +5,12 @@ import ( "crypto/x509" "encoding/pem" "log" + "strings" ) func MustParseSigningKey(privateKeyString string) *rsa.PrivateKey { + // when a multi-line env variable is passed to the docker container by docker-compose, it escapes the newlines + privateKeyString = strings.Replace(privateKeyString, "\\n", "\n", -1) block, _ := pem.Decode([]byte(privateKeyString)) if block == nil || block.Type != "RSA PRIVATE KEY" { log.Fatalf("failed to decode PEM block containing RSA private key") diff --git a/main.go b/main.go index 3996ce14a..07b71a37f 100644 --- a/main.go +++ b/main.go @@ -39,13 +39,14 @@ type dependencies struct { func initDependencies(conf AppConfiguration, signingKey *rsa.PrivateKey) (dependencies, error) { tracer, err := tracing.Init(tracing.Configuration{ - Enabled: conf.env != "development", ApplicationName: "marble-backend", + Enabled: conf.enableGcpTracing, ProjectID: conf.gcpProject, }) if err != nil { return dependencies{}, fmt.Errorf("tracing.Init error: %w", err) } + database, err := postgres.New(postgres.Configuration{ Host: conf.pgConfig.Hostname, Port: conf.pgConfig.Port, @@ -67,9 +68,9 @@ func initDependencies(conf AppConfiguration, signingKey *rsa.PrivateKey) (depend return dependencies{ Authentication: api.NewAuthentication(tokenValidator), - TokenHandler: api.NewTokenHandler(tokenGenerator), - SegmentClient: segmentClient, OpenTelemetryTracer: tracer, + SegmentClient: segmentClient, + TokenHandler: api.NewTokenHandler(tokenGenerator), }, nil } @@ -109,7 +110,7 @@ func runServer(ctx context.Context, appConfig AppConfiguration) { } router := initRouter(ctx, appConfig, deps) - server := api.New(router, appConfig.port, uc, deps.Authentication, deps.TokenHandler, logger) + server := api.New(router, appConfig.port, uc, deps.Authentication, deps.TokenHandler) notify, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() @@ -133,20 +134,26 @@ func runServer(ctx context.Context, appConfig AppConfiguration) { } type AppConfiguration struct { - env string - port string - gcpProject string - pgConfig utils.PGConfig - config models.GlobalConfiguration - sentryDsn string - metabase models.MetabaseConfiguration + env string + port string + gcpProject string + enableGcpTracing bool + requestLoggingLevel string + loggingFormat string + pgConfig utils.PGConfig + config models.GlobalConfiguration + sentryDsn string + metabase models.MetabaseConfiguration } func main() { appConfig := AppConfiguration{ - env: utils.GetEnv("ENV", "development"), - port: utils.GetRequiredEnv[string]("PORT"), - gcpProject: os.Getenv("GOOGLE_CLOUD_PROJECT"), + env: utils.GetEnv("ENV", "development"), + port: utils.GetRequiredEnv[string]("PORT"), + gcpProject: os.Getenv("GOOGLE_CLOUD_PROJECT"), + enableGcpTracing: utils.GetEnv("ENABLE_GCP_TRACING", false), + requestLoggingLevel: utils.GetEnv("REQUEST_LOGGING_LEVEL", "all"), + loggingFormat: utils.GetEnv("LOGGING_FORMAT", "text"), pgConfig: utils.PGConfig{ Database: "marble", DbConnectWithSocket: utils.GetEnv("PG_CONNECT_WITH_SOCKET", false), @@ -178,7 +185,7 @@ func main() { // Setup dependencies //////////////////////////////////////////////////////////// - logger := utils.NewLogger(appConfig.env) + logger := utils.NewLogger(appConfig.loggingFormat) appContext := utils.StoreLoggerInContext(context.Background(), logger) shouldRunMigrations := flag.Bool("migrations", false, "Run migrations") diff --git a/repositories/firebase/client.go b/repositories/firebase/client.go index 83f90f4f6..404444da5 100644 --- a/repositories/firebase/client.go +++ b/repositories/firebase/client.go @@ -11,7 +11,6 @@ import ( type tokenCookieVerifier interface { VerifyIDToken(ctx context.Context, idToken string) (*auth.Token, error) - VerifySessionCookie(ctx context.Context, sessionCookie string) (*auth.Token, error) } type Client struct { @@ -20,9 +19,6 @@ type Client struct { func (c *Client) verifyTokenOrCookie(ctx context.Context, firebaseToken string) (*auth.Token, error) { token, err := c.verifier.VerifyIDToken(ctx, firebaseToken) - if err != nil { - token, err = c.verifier.VerifySessionCookie(ctx, firebaseToken) - } if err != nil { return nil, err } diff --git a/repositories/firebase/client_test.go b/repositories/firebase/client_test.go index 2e3c85f08..2263dfe71 100644 --- a/repositories/firebase/client_test.go +++ b/repositories/firebase/client_test.go @@ -20,11 +20,6 @@ func (m *mockTokenCookieVerifier) VerifyIDToken(ctx context.Context, idToken str return args.Get(0).(*auth.Token), args.Error(1) } -func (m *mockTokenCookieVerifier) VerifySessionCookie(ctx context.Context, sessionCookie string) (*auth.Token, error) { - args := m.Called(ctx, sessionCookie) - return args.Get(0).(*auth.Token), args.Error(1) -} - func TestClient_VerifyFirebaseToken(t *testing.T) { token := auth.Token{ Subject: "token_subject", @@ -56,27 +51,6 @@ func TestClient_VerifyFirebaseToken(t *testing.T) { mockVerifier := new(mockTokenCookieVerifier) mockVerifier.On("VerifyIDToken", mock.Anything, "token"). Return(&auth.Token{}, assert.AnError) - mockVerifier.On("VerifySessionCookie", mock.Anything, "token"). - Return(&token, nil) - - c := Client{ - verifier: mockVerifier, - } - - identity, err := c.VerifyFirebaseToken(context.Background(), "token") - assert.NoError(t, err) - assert.Equal(t, models.FirebaseIdentity{ - Email: "user@email.com", - }, identity) - mockVerifier.AssertExpectations(t) - }) - - t.Run("VerifySessionCookie error", func(t *testing.T) { - mockVerifier := new(mockTokenCookieVerifier) - mockVerifier.On("VerifyIDToken", mock.Anything, "token"). - Return(&auth.Token{}, assert.AnError) - mockVerifier.On("VerifySessionCookie", mock.Anything, "token"). - Return(&auth.Token{}, assert.AnError) c := Client{ verifier: mockVerifier, diff --git a/router.go b/router.go index bbc344d00..91563aed1 100644 --- a/router.go +++ b/router.go @@ -72,10 +72,7 @@ func initRouter(ctx context.Context, conf AppConfiguration, deps dependencies) * r.Use(gin.Recovery()) r.Use(sentrygin.New(sentrygin.Options{Repanic: true})) r.Use(cors.New(corsOption(conf.env))) - if conf.env == "development" { - // GCP already logs those elements - r.Use(middleware.NewLogging(logger)) - } + r.Use(middleware.NewLogging(logger, conf.requestLoggingLevel)) r.Use(otelgin.Middleware("marble-backend")) r.Use(utils.StoreLoggerInContextMiddleware(logger)) r.Use(utils.StoreSegmentClientInContextMiddleware(deps.SegmentClient)) diff --git a/usecases/data_model_usecase_test.go b/usecases/data_model_usecase_test.go index 58832a116..c84020a82 100644 --- a/usecases/data_model_usecase_test.go +++ b/usecases/data_model_usecase_test.go @@ -190,7 +190,7 @@ func (suite *DatamodelUsecaseTestSuite) SetupTest() { suite.repositoryError = errors.New("some repository error") suite.securityError = errors.New("some security error") - suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("test")) + suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("text")) } func (suite *DatamodelUsecaseTestSuite) makeUsecase() *DataModelUseCase { diff --git a/usecases/indexes/aggregate_query_test.go b/usecases/indexes/aggregate_query_test.go index b1efbb0eb..679441b5f 100644 --- a/usecases/indexes/aggregate_query_test.go +++ b/usecases/indexes/aggregate_query_test.go @@ -15,7 +15,7 @@ import ( func makeTestContext() context.Context { ctx := context.Background() - return utils.StoreLoggerInContext(ctx, utils.NewLogger("test")) + return utils.StoreLoggerInContext(ctx, utils.NewLogger("text")) } func TestAggregationNodeToQueryFamily(t *testing.T) { diff --git a/usecases/indexes/index_editor_test.go b/usecases/indexes/index_editor_test.go index 31df8ac5e..a70e16071 100644 --- a/usecases/indexes/index_editor_test.go +++ b/usecases/indexes/index_editor_test.go @@ -133,7 +133,7 @@ func (suite *ClientDbIndexEditorTestSuite) SetupTest() { suite.repositoryError = errors.New("some repository error") suite.securityError = errors.New("some security error") - suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("test")) + suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("text")) } func (suite *ClientDbIndexEditorTestSuite) makeUsecase() ClientDbIndexEditor { diff --git a/usecases/scenario_publication_usecase_test.go b/usecases/scenario_publication_usecase_test.go index 3cd7e4603..e6db3329c 100644 --- a/usecases/scenario_publication_usecase_test.go +++ b/usecases/scenario_publication_usecase_test.go @@ -138,7 +138,7 @@ func (suite *ScenarioPublicationUsecaseTestSuite) SetupTest() { suite.repositoryError = errors.New("some repository error") suite.securityError = errors.New("some security error") - suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("test")) + suite.ctx = utils.StoreLoggerInContext(context.Background(), utils.NewLogger("text")) } func (suite *ScenarioPublicationUsecaseTestSuite) makeUsecase() *ScenarioPublicationUsecase { diff --git a/usecases/scenarios/scenario_validation_test.go b/usecases/scenarios/scenario_validation_test.go index ed54fbad3..fe419eb5e 100644 --- a/usecases/scenarios/scenario_validation_test.go +++ b/usecases/scenarios/scenario_validation_test.go @@ -16,7 +16,7 @@ import ( ) func TestValidateScenarioIterationImpl_Validate(t *testing.T) { - ctx := utils.StoreLoggerInContext(context.Background(), utils.NewLogger("test")) + ctx := utils.StoreLoggerInContext(context.Background(), utils.NewLogger("text")) scenario := models.Scenario{ Id: uuid.New().String(), OrganizationId: uuid.New().String(), diff --git a/utils/context_logging.go b/utils/context_logging.go index d9bca463c..9d356c783 100644 --- a/utils/context_logging.go +++ b/utils/context_logging.go @@ -7,21 +7,26 @@ import ( "net/http" "os" "regexp" + "slices" "github.com/gin-gonic/gin" ) -func NewLogger(env string) *slog.Logger { +func NewLogger(format string) *slog.Logger { var logger *slog.Logger + if !slices.Contains([]string{"text", "json"}, format) { + fmt.Printf("invalid log format '%s', falling back to 'text'\n", format) + format = "text" + } - isDevEnv := env == "development" - if isDevEnv { + switch format { + case "text": logHandler := LocalDevHandlerOptions{ SlogOpts: slog.HandlerOptions{Level: slog.LevelDebug}, UseColor: true, }.NewLocalDevHandler(os.Stdout) logger = slog.New(logHandler) - } else { + case "json": slogOption := slog.HandlerOptions{ReplaceAttr: GCPLoggerAttributeReplacer} jsonHandler := slog.NewJSONHandler(os.Stdout, &slogOption) logger = slog.New(jsonHandler)