diff --git a/caddy/caddy.go b/caddy/caddy.go index 501af3ed..940f1b3c 100644 --- a/caddy/caddy.go +++ b/caddy/caddy.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "net/http" "path/filepath" "strconv" @@ -23,7 +24,6 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver" "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite" "github.com/dunglas/frankenphp" - "go.uber.org/zap" ) const defaultDocumentRoot = "public" @@ -70,7 +70,7 @@ type FrankenPHPApp struct { MaxWaitTime time.Duration `json:"max_wait_time,omitempty"` metrics frankenphp.Metrics - logger *zap.Logger + logger *slog.Logger } // CaddyModule returns the Caddy module information. @@ -83,7 +83,7 @@ func (f FrankenPHPApp) CaddyModule() caddy.ModuleInfo { // Provision sets up the module. func (f *FrankenPHPApp) Provision(ctx caddy.Context) error { - f.logger = ctx.Logger() + f.logger = ctx.Slogger() if httpApp, err := ctx.AppIfConfigured("http"); err == nil { if httpApp.(*caddyhttp.App).Metrics != nil { @@ -345,7 +345,7 @@ type FrankenPHPModule struct { resolvedDocumentRoot string preparedEnv frankenphp.PreparedEnv preparedEnvNeedsReplacement bool - logger *zap.Logger + logger *slog.Logger } // CaddyModule returns the Caddy module information. @@ -358,7 +358,7 @@ func (FrankenPHPModule) CaddyModule() caddy.ModuleInfo { // Provision sets up the module. func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { - f.logger = ctx.Logger() + f.logger = ctx.Slogger() if f.Root == "" { if frankenphp.EmbeddedAppPath == "" { diff --git a/caddy/go.mod b/caddy/go.mod index 3fe42599..c902443c 100644 --- a/caddy/go.mod +++ b/caddy/go.mod @@ -18,7 +18,6 @@ require ( github.com/prometheus/client_golang v1.22.0 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 - go.uber.org/zap v1.27.0 ) require github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 // indirect @@ -186,6 +185,7 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/mock v0.5.1 // indirect go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto/x509roots/fallback v0.0.0-20250418111936-9c1aa6af88df // indirect diff --git a/caddy/php-server.go b/caddy/php-server.go index e2e4eabd..711b9ec3 100644 --- a/caddy/php-server.go +++ b/caddy/php-server.go @@ -3,6 +3,7 @@ package caddy import ( "encoding/json" "log" + "log/slog" "net/http" "os" "path/filepath" @@ -11,7 +12,6 @@ import ( "time" mercureModule "github.com/dunglas/mercure/caddy" - "go.uber.org/zap/zapcore" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -334,7 +334,7 @@ func cmdPHPServer(fs caddycmd.Flags) (int, error) { cfg.Logging = &caddy.Logging{ Logs: map[string]*caddy.CustomLog{ "default": { - BaseLog: caddy.BaseLog{Level: zapcore.DebugLevel.CapitalString()}, + BaseLog: caddy.BaseLog{Level: slog.LevelDebug.String()}, }, }, } diff --git a/context.go b/context.go index cdbeab15..bbfa33b5 100644 --- a/context.go +++ b/context.go @@ -2,12 +2,11 @@ package frankenphp import ( "context" + "log/slog" "net/http" "os" "strings" "time" - - "go.uber.org/zap" ) // frankenPHPContext provides contextual information about the Request to handle. @@ -15,7 +14,7 @@ type frankenPHPContext struct { documentRoot string splitPath []string env PreparedEnv - logger *zap.Logger + logger *slog.Logger request *http.Request originalRequest *http.Request diff --git a/frankenphp.go b/frankenphp.go index 017a0556..258d7608 100644 --- a/frankenphp.go +++ b/frankenphp.go @@ -33,6 +33,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "os" "os/signal" @@ -43,9 +44,6 @@ import ( "syscall" "time" "unsafe" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" // debug on Linux //_ "github.com/ianlancetaylor/cgosymbolizer" ) @@ -66,7 +64,7 @@ var ( isRunning bool loggerMu sync.RWMutex - logger *zap.Logger + logger *slog.Logger metrics Metrics = nullMetrics{} @@ -234,10 +232,9 @@ func Init(options ...Option) error { } if opt.logger == nil { - l, err := zap.NewDevelopment() - if err != nil { - return err - } + // set a default logger + // to disable logging, set the logger to slog.New(slog.NewTextHandler(io.Discard, nil)) + l := slog.New(slog.NewTextHandler(os.Stdout, nil)) loggerMu.Lock() logger = l @@ -294,13 +291,9 @@ func Init(options ...Option) error { initAutoScaling(mainThread) - if c := logger.Check(zapcore.InfoLevel, "FrankenPHP started 🐘"); c != nil { - c.Write(zap.String("php_version", Version().Version), zap.Int("num_threads", mainThread.numThreads), zap.Int("max_threads", mainThread.maxThreads)) - } + logger.LogAttrs(nil, slog.LevelInfo, "FrankenPHP started 🐘", slog.String("php_version", Version().Version), slog.Int("num_threads", mainThread.numThreads), slog.Int("max_threads", mainThread.maxThreads)) if EmbeddedAppPath != "" { - if c := logger.Check(zapcore.InfoLevel, "embedded PHP app 📦"); c != nil { - c.Write(zap.String("path", EmbeddedAppPath)) - } + logger.LogAttrs(nil, slog.LevelInfo, "embedded PHP app 📦", slog.String("path", EmbeddedAppPath)) } return nil @@ -435,9 +428,7 @@ func go_ub_write(threadIndex C.uintptr_t, cBuf *C.char, length C.int) (C.size_t, i, e := writer.Write(unsafe.Slice((*byte)(unsafe.Pointer(cBuf)), length)) if e != nil { - if c := fc.logger.Check(zapcore.ErrorLevel, "write error"); c != nil { - c.Write(zap.Error(e)) - } + fc.logger.LogAttrs(nil, slog.LevelError, "write error", slog.Any("error", e)) } if fc.responseWriter == nil { @@ -456,9 +447,7 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t) if fc.responseWriter == nil { // worker mode, not handling a request - if c := logger.Check(zapcore.DebugLevel, "apache_request_headers() called in non-HTTP context"); c != nil { - c.Write(zap.String("worker", fc.scriptFilename)) - } + logger.LogAttrs(nil, slog.LevelDebug, "apache_request_headers() called in non-HTTP context", slog.String("worker", fc.scriptFilename)) return nil, 0 } @@ -489,9 +478,7 @@ func go_apache_request_headers(threadIndex C.uintptr_t) (*C.go_string, C.size_t) func addHeader(fc *frankenPHPContext, cString *C.char, length C.int) { parts := strings.SplitN(C.GoStringN(cString, length), ": ", 2) if len(parts) != 2 { - if c := fc.logger.Check(zapcore.DebugLevel, "invalid header"); c != nil { - c.Write(zap.String("header", parts[0])) - } + fc.logger.LogAttrs(nil, slog.LevelDebug, "invalid header", slog.String("header", parts[0])) return } @@ -545,9 +532,7 @@ func go_sapi_flush(threadIndex C.uintptr_t) bool { } if err := http.NewResponseController(fc.responseWriter).Flush(); err != nil { - if c := fc.logger.Check(zapcore.ErrorLevel, "the current responseWriter is not a flusher"); c != nil { - c.Write(zap.Error(err)) - } + logger.LogAttrs(nil, slog.LevelError, "the current responseWriter is not a flusher", slog.Any("error", err)) } return false @@ -600,24 +585,15 @@ func go_log(message *C.char, level C.int) { switch le { case emerg, alert, crit, err: - if c := logger.Check(zapcore.ErrorLevel, m); c != nil { - c.Write(zap.Stringer("syslog_level", syslogLevel(level))) - } + logger.LogAttrs(nil, slog.LevelError, m, slog.String("syslog_level", syslogLevel(level).String())) case warning: - if c := logger.Check(zapcore.WarnLevel, m); c != nil { - c.Write(zap.Stringer("syslog_level", syslogLevel(level))) - } - + logger.LogAttrs(nil, slog.LevelWarn, m, slog.String("syslog_level", syslogLevel(level).String())) case debug: - if c := logger.Check(zapcore.DebugLevel, m); c != nil { - c.Write(zap.Stringer("syslog_level", syslogLevel(level))) - } + logger.LogAttrs(nil, slog.LevelDebug, m, slog.String("syslog_level", syslogLevel(level).String())) default: - if c := logger.Check(zapcore.InfoLevel, m); c != nil { - c.Write(zap.Stringer("syslog_level", syslogLevel(level))) - } + logger.LogAttrs(nil, slog.LevelInfo, m, slog.String("syslog_level", syslogLevel(level).String())) } } diff --git a/frankenphp_test.go b/frankenphp_test.go index efd226d8..cef6a9ba 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "log" + "log/slog" "mime/multipart" "net/http" "net/http/cookiejar" @@ -30,7 +31,7 @@ import ( "github.com/dunglas/frankenphp/internal/fastabs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest/observer" @@ -43,7 +44,7 @@ type testOptions struct { env map[string]string nbParallelRequests int realServer bool - logger *zap.Logger + logger *slog.Logger initOpts []frankenphp.Option phpIni map[string]string } @@ -60,7 +61,7 @@ func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), * testDataDir := cwd + "/testdata/" if opts.logger == nil { - opts.logger = zaptest.NewLogger(t) + opts.logger = slog.New(zapslog.NewHandler(zaptest.NewLogger(t).Core())) } initOpts := []frankenphp.Option{frankenphp.WithLogger(opts.logger)} @@ -445,7 +446,7 @@ func TestLog_worker(t *testing.T) { } func testLog(t *testing.T, opts *testOptions) { logger, logs := observer.New(zapcore.InfoLevel) - opts.logger = zap.New(logger) + opts.logger = slog.New(zapslog.NewHandler(logger)) runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/log.php?i=%d", i), nil) @@ -465,7 +466,7 @@ func testConnectionAbort(t *testing.T, opts *testOptions) { testFinish := func(finish string) { t.Run(fmt.Sprintf("finish=%s", finish), func(t *testing.T) { logger, logs := observer.New(zapcore.InfoLevel) - opts.logger = zap.New(logger) + opts.logger = slog.New(zapslog.NewHandler(logger)) runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/connectionStatusLog.php?i=%d&finish=%s", i, finish), nil) @@ -819,7 +820,7 @@ func ExampleExecuteScriptCLI() { } func BenchmarkHelloWorld(b *testing.B) { - if err := frankenphp.Init(frankenphp.WithLogger(zap.NewNop())); err != nil { + if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil { panic(err) } defer frankenphp.Shutdown() @@ -847,7 +848,7 @@ func BenchmarkHelloWorld(b *testing.B) { } func BenchmarkEcho(b *testing.B) { - if err := frankenphp.Init(frankenphp.WithLogger(zap.NewNop())); err != nil { + if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil { panic(err) } defer frankenphp.Shutdown() @@ -914,7 +915,7 @@ func BenchmarkEcho(b *testing.B) { } func BenchmarkServerSuperGlobal(b *testing.B) { - if err := frankenphp.Init(frankenphp.WithLogger(zap.NewNop())); err != nil { + if err := frankenphp.Init(frankenphp.WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil)))); err != nil { panic(err) } defer frankenphp.Shutdown() diff --git a/go.mod b/go.mod index 7444f61e..41d3c554 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/prometheus/client_golang v1.22.0 github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.27.0 + go.uber.org/zap/exp v0.3.0 golang.org/x/net v0.39.0 ) diff --git a/go.sum b/go.sum index 7921078d..94592a77 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ= 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/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= diff --git a/internal/testserver/main.go b/internal/testserver/main.go index e1bd2e9e..d1c7caee 100644 --- a/internal/testserver/main.go +++ b/internal/testserver/main.go @@ -1,19 +1,15 @@ package main import ( + "log/slog" "net/http" "os" "github.com/dunglas/frankenphp" - "go.uber.org/zap" ) func main() { - logger, err := zap.NewDevelopment() - if err != nil { - panic(err) - } - + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) if err := frankenphp.Init(frankenphp.WithLogger(logger)); err != nil { panic(err) } @@ -35,5 +31,6 @@ func main() { port = "8080" } - logger.Fatal("server error", zap.Error(http.ListenAndServe(":"+port, nil))) + logger.LogAttrs(nil, slog.LevelError, "server error", slog.Any("error", http.ListenAndServe(":"+port, nil))) + os.Exit(1) } diff --git a/internal/watcher/watch_pattern.go b/internal/watcher/watch_pattern.go index 12bbe1f8..58a4d609 100644 --- a/internal/watcher/watch_pattern.go +++ b/internal/watcher/watch_pattern.go @@ -3,11 +3,11 @@ package watcher import ( - "github.com/dunglas/frankenphp/internal/fastabs" + "log/slog" "path/filepath" "strings" - "go.uber.org/zap" + "github.com/dunglas/frankenphp/internal/fastabs" ) type watchPattern struct { @@ -82,7 +82,7 @@ func isValidEventType(eventType int) bool { // 0:dir,1:file,2:hard_link,3:sym_link,4:watcher,5:other, func isValidPathType(pathType int, fileName string) bool { if pathType == 4 { - logger.Debug("special edant/watcher event", zap.String("fileName", fileName)) + logger.LogAttrs(nil, slog.LevelDebug, "special edant/watcher event", slog.String("fileName", fileName)) } return pathType <= 2 } @@ -165,7 +165,7 @@ func matchPattern(pattern string, fileName string) bool { } patternMatches, err := filepath.Match(pattern, fileName) if err != nil { - logger.Error("failed to match filename", zap.String("file", fileName), zap.Error(err)) + logger.LogAttrs(nil, slog.LevelError, "failed to match filename", slog.String("file", fileName), slog.Any("error", err)) return false } diff --git a/internal/watcher/watcher-skip.go b/internal/watcher/watcher-skip.go index fbfdc090..9dd24112 100644 --- a/internal/watcher/watcher-skip.go +++ b/internal/watcher/watcher-skip.go @@ -2,10 +2,10 @@ package watcher -import "go.uber.org/zap" +import "log/slog" -func InitWatcher(filePatterns []string, callback func(), zapLogger *zap.Logger) error { - zapLogger.Error("watcher support is not enabled") +func InitWatcher(filePatterns []string, callback func(), logger *slog.Logger) error { + logger.Error("watcher support is not enabled") return nil } diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index c420f32f..f716cd9c 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -9,14 +9,13 @@ package watcher import "C" import ( "errors" + "log/slog" "runtime/cgo" "strings" "sync" "sync/atomic" "time" "unsafe" - - "go.uber.org/zap" ) type watcher struct { @@ -45,10 +44,10 @@ var ( // after stopping the watcher we will wait for eventual reloads to finish reloadWaitGroup sync.WaitGroup // we are passing the logger from the main package to the watcher - logger *zap.Logger + logger *slog.Logger ) -func InitWatcher(filePatterns []string, callback func(), zapLogger *zap.Logger) error { +func InitWatcher(filePatterns []string, callback func(), slogger *slog.Logger) error { if len(filePatterns) == 0 { return nil } @@ -56,7 +55,7 @@ func InitWatcher(filePatterns []string, callback func(), zapLogger *zap.Logger) return ErrAlreadyStarted } watcherIsActive.Store(true) - logger = zapLogger + logger = slogger activeWatcher = &watcher{callback: callback} err := activeWatcher.startWatching(filePatterns) if err != nil { @@ -83,10 +82,10 @@ func retryWatching(watchPattern *watchPattern) { failureMu.Lock() defer failureMu.Unlock() if watchPattern.failureCount >= maxFailureCount { - logger.Warn("gave up watching", zap.String("dir", watchPattern.dir)) + logger.LogAttrs(nil, slog.LevelWarn, "giving up watching", slog.String("dir", watchPattern.dir)) return } - logger.Info("watcher was closed prematurely, retrying...", zap.String("dir", watchPattern.dir)) + logger.LogAttrs(nil, slog.LevelInfo, "watcher was closed prematurely, retrying...", slog.String("dir", watchPattern.dir)) watchPattern.failureCount++ session, err := startSession(watchPattern) @@ -138,10 +137,10 @@ func startSession(w *watchPattern) (C.uintptr_t, error) { defer C.free(unsafe.Pointer(cDir)) watchSession := C.start_new_watcher(cDir, C.uintptr_t(handle)) if watchSession != 0 { - logger.Debug("watching", zap.String("dir", w.dir), zap.Strings("patterns", w.patterns)) + logger.LogAttrs(nil, slog.LevelDebug, "watching", slog.String("dir", w.dir), slog.Any("patterns", w.patterns)) return watchSession, nil } - logger.Error("couldn't start watching", zap.String("dir", w.dir)) + logger.LogAttrs(nil, slog.LevelError, "couldn't start watching", slog.String("dir", w.dir)) return watchSession, ErrUnableToStartWatching } @@ -191,7 +190,7 @@ func listenForFileEvents(triggerWatcher chan string, stopWatcher chan struct{}) timer.Reset(debounceDuration) case <-timer.C: timer.Stop() - logger.Info("filesystem change detected", zap.String("file", lastChangedFile)) + logger.LogAttrs(nil, slog.LevelInfo, "filesystem change detected", slog.String("file", lastChangedFile)) scheduleReload() } } diff --git a/options.go b/options.go index c5d24885..57c98b20 100644 --- a/options.go +++ b/options.go @@ -1,9 +1,8 @@ package frankenphp import ( + "log/slog" "time" - - "go.uber.org/zap" ) // Option instances allow to configure FrankenPHP. @@ -16,7 +15,7 @@ type opt struct { numThreads int maxThreads int workers []workerOpt - logger *zap.Logger + logger *slog.Logger metrics Metrics phpIni map[string]string maxWaitTime time.Duration @@ -65,7 +64,7 @@ func WithWorkers(name string, fileName string, num int, env map[string]string, w } // WithLogger configures the global logger to use. -func WithLogger(l *zap.Logger) Option { +func WithLogger(l *slog.Logger) Option { return func(o *opt) error { o.logger = l diff --git a/phpmainthread.go b/phpmainthread.go index f3e18363..d8613e16 100644 --- a/phpmainthread.go +++ b/phpmainthread.go @@ -8,13 +8,12 @@ package frankenphp // #include "frankenphp.h" import "C" import ( + "log/slog" "strings" "sync" "github.com/dunglas/frankenphp/internal/memory" "github.com/dunglas/frankenphp/internal/phpheaders" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) // represents the main PHP thread @@ -171,9 +170,7 @@ func (mainThread *phpMainThread) setAutomaticMaxThreads() { maxAllowedThreads := totalSysMemory / uint64(perThreadMemoryLimit) mainThread.maxThreads = int(maxAllowedThreads) - if c := logger.Check(zapcore.DebugLevel, "Automatic thread limit"); c != nil { - c.Write(zap.Int("perThreadMemoryLimitMB", int(perThreadMemoryLimit/1024/1024)), zap.Int("maxThreads", mainThread.maxThreads)) - } + logger.LogAttrs(nil, slog.LevelDebug, "Automatic thread limit", slog.Int("perThreadMemoryLimitMB", int(perThreadMemoryLimit/1024/1024)), slog.Int("maxThreads", mainThread.maxThreads)) } //export go_frankenphp_shutdown_main_thread diff --git a/phpmainthread_test.go b/phpmainthread_test.go index 7639c825..62159a93 100644 --- a/phpmainthread_test.go +++ b/phpmainthread_test.go @@ -2,6 +2,7 @@ package frankenphp import ( "io" + "log/slog" "math/rand/v2" "net/http/httptest" "path/filepath" @@ -13,13 +14,12 @@ import ( "github.com/dunglas/frankenphp/internal/phpheaders" "github.com/stretchr/testify/assert" - "go.uber.org/zap" ) var testDataPath, _ = filepath.Abs("./testdata") func TestStartAndStopTheMainThreadWithOneInactiveThread(t *testing.T) { - logger = zap.NewNop() // the logger needs to not be nil + logger = slog.New(slog.NewTextHandler(io.Discard, nil)) _, err := initPHPThreads(1, 1, nil) // boot 1 thread assert.NoError(t, err) @@ -32,7 +32,7 @@ func TestStartAndStopTheMainThreadWithOneInactiveThread(t *testing.T) { } func TestTransitionRegularThreadToWorkerThread(t *testing.T) { - logger = zap.NewNop() + logger = slog.New(slog.NewTextHandler(io.Discard, nil)) _, err := initPHPThreads(1, 1, nil) assert.NoError(t, err) @@ -56,7 +56,7 @@ func TestTransitionRegularThreadToWorkerThread(t *testing.T) { } func TestTransitionAThreadBetween2DifferentWorkers(t *testing.T) { - logger = zap.NewNop() + logger = slog.New(slog.NewTextHandler(io.Discard, nil)) _, err := initPHPThreads(1, 1, nil) assert.NoError(t, err) firstWorker := getDummyWorker("transition-worker-1.php") @@ -96,7 +96,7 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) { WithNumThreads(numThreads), WithWorkers(worker1Name, worker1Path, 1, map[string]string{"ENV1": "foo"}, []string{}), WithWorkers(worker2Name, worker2Path, 1, map[string]string{"ENV1": "foo"}, []string{}), - WithLogger(zap.NewNop()), + WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))), )) // try all possible permutations of transition, transition every ms @@ -156,7 +156,7 @@ func TestAllCommonHeadersAreCorrect(t *testing.T) { } } func TestFinishBootingAWorkerScript(t *testing.T) { - logger = zap.NewNop() + logger = slog.New(slog.NewTextHandler(io.Discard, nil)) _, err := initPHPThreads(1, 1, nil) assert.NoError(t, err) diff --git a/phpthread.go b/phpthread.go index 933acbd6..d28fb153 100644 --- a/phpthread.go +++ b/phpthread.go @@ -4,11 +4,10 @@ package frankenphp // #include "frankenphp.h" import "C" import ( + "log/slog" "runtime" "sync" "unsafe" - - "go.uber.org/zap" ) // representation of the actual underlying PHP thread @@ -44,8 +43,8 @@ func newPHPThread(threadIndex int) *phpThread { func (thread *phpThread) boot() { // thread must be in reserved state to boot if !thread.state.compareAndSwap(stateReserved, stateBooting) && !thread.state.compareAndSwap(stateBootRequested, stateBooting) { - logger.Panic("thread is not in reserved state: " + thread.state.name()) - return + logger.Error("thread is not in reserved state: " + thread.state.name()) + panic("thread is not in reserved state: " + thread.state.name()) } // boot threads as inactive @@ -56,7 +55,8 @@ func (thread *phpThread) boot() { // start the actual posix thread - TODO: try this with go threads instead if !C.frankenphp_new_php_thread(C.uintptr_t(thread.threadIndex)) { - logger.Panic("unable to create thread", zap.Int("threadIndex", thread.threadIndex)) + logger.LogAttrs(nil, slog.LevelError, "unable to create thread", slog.Int("threadIndex", thread.threadIndex)) + panic("unable to create thread") } thread.state.waitFor(stateInactive) diff --git a/request_options.go b/request_options.go index 4e238773..ba1b1d54 100644 --- a/request_options.go +++ b/request_options.go @@ -1,13 +1,13 @@ package frankenphp import ( - "github.com/dunglas/frankenphp/internal/fastabs" + "log/slog" "net/http" "path/filepath" "sync" "sync/atomic" - "go.uber.org/zap" + "github.com/dunglas/frankenphp/internal/fastabs" ) // RequestOption instances allow to configure a FrankenPHP Request. @@ -116,7 +116,7 @@ func WithOriginalRequest(r *http.Request) RequestOption { } // WithRequestLogger sets the logger associated with the current request -func WithRequestLogger(logger *zap.Logger) RequestOption { +func WithRequestLogger(logger *slog.Logger) RequestOption { return func(o *frankenPHPContext) error { o.logger = logger diff --git a/scaling.go b/scaling.go index a5ceb673..b9b8370e 100644 --- a/scaling.go +++ b/scaling.go @@ -5,12 +5,11 @@ package frankenphp import "C" import ( "errors" + "log/slog" "sync" "time" "github.com/dunglas/frankenphp/internal/cpu" - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) const ( @@ -54,9 +53,7 @@ func initAutoScaling(mainThread *phpMainThread) { func drainAutoScaling() { scalingMu.Lock() - if c := logger.Check(zapcore.DebugLevel, "shutting down autoscaling"); c != nil { - c.Write(zap.Int("autoScaledThreads", len(autoScaledThreads))) - } + logger.LogAttrs(nil, slog.LevelDebug, "shutting down autoscaling", slog.Int("autoScaledThreads", len(autoScaledThreads))) scalingMu.Unlock() } @@ -96,9 +93,7 @@ func scaleWorkerThread(worker *worker) { thread, err := addWorkerThread(worker) if err != nil { - if c := logger.Check(zapcore.WarnLevel, "could not increase max_threads, consider raising this limit"); c != nil { - c.Write(zap.String("worker", worker.name), zap.Error(err)) - } + logger.LogAttrs(nil, slog.LevelWarn, "could not increase max_threads, consider raising this limit", slog.String("worker", worker.name), slog.Any("error", err)) return } @@ -121,9 +116,7 @@ func scaleRegularThread() { thread, err := addRegularThread() if err != nil { - if c := logger.Check(zapcore.WarnLevel, "could not increase max_threads, consider raising this limit"); c != nil { - c.Write(zap.Error(err)) - } + logger.LogAttrs(nil, slog.LevelWarn, "could not increase max_threads, consider raising this limit", slog.Any("error", err)) return } @@ -203,9 +196,7 @@ func deactivateThreads() { // convert threads to inactive if they have been idle for too long if thread.state.is(stateReady) && waitTime > maxThreadIdleTime.Milliseconds() { - if c := logger.Check(zapcore.DebugLevel, "auto-converting thread to inactive"); c != nil { - c.Write(zap.Int("threadIndex", thread.threadIndex)) - } + logger.LogAttrs(nil, slog.LevelDebug, "auto-converting thread to inactive", slog.Int("threadIndex", thread.threadIndex)) convertToInactiveThread(thread) stoppedThreadCount++ autoScaledThreads = append(autoScaledThreads[:i], autoScaledThreads[i+1:]...) @@ -216,14 +207,12 @@ func deactivateThreads() { // TODO: Completely stopping threads is more memory efficient // Some PECL extensions like #1296 will prevent threads from fully stopping (they leak memory) // Reactivate this if there is a better solution or workaround - //if thread.state.is(stateInactive) && waitTime > maxThreadIdleTime.Milliseconds() { - // if c := logger.Check(zapcore.DebugLevel, "auto-stopping thread"); c != nil { - // c.Write(zap.Int("threadIndex", thread.threadIndex)) - // } - // thread.shutdown() - // stoppedThreadCount++ - // autoScaledThreads = append(autoScaledThreads[:i], autoScaledThreads[i+1:]...) - // continue - //} + // if thread.state.is(stateInactive) && waitTime > maxThreadIdleTime.Milliseconds() { + // logger.LogAttrs(nil, slog.LevelDebug, "auto-stopping thread", slog.Int("threadIndex", thread.threadIndex)) + // thread.shutdown() + // stoppedThreadCount++ + // autoScaledThreads = append(autoScaledThreads[:i], autoScaledThreads[i+1:]...) + // continue + // } } } diff --git a/scaling_test.go b/scaling_test.go index 8429dd8b..85adc586 100644 --- a/scaling_test.go +++ b/scaling_test.go @@ -1,18 +1,19 @@ package frankenphp import ( + "io" + "log/slog" "testing" "time" "github.com/stretchr/testify/assert" - "go.uber.org/zap" ) func TestScaleARegularThreadUpAndDown(t *testing.T) { assert.NoError(t, Init( WithNumThreads(1), WithMaxThreads(2), - WithLogger(zap.NewNop()), + WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))), )) autoScaledThread := phpThreads[1] @@ -37,7 +38,7 @@ func TestScaleAWorkerThreadUpAndDown(t *testing.T) { WithNumThreads(2), WithMaxThreads(3), WithWorkers(workerName, workerPath, 1, map[string]string{}, []string{}), - WithLogger(zap.NewNop()), + WithLogger(slog.New(slog.NewTextHandler(io.Discard, nil))), )) autoScaledThread := phpThreads[2] diff --git a/threadworker.go b/threadworker.go index c9df8874..3b0c47cb 100644 --- a/threadworker.go +++ b/threadworker.go @@ -3,11 +3,9 @@ package frankenphp // #include "frankenphp.h" import "C" import ( + "log/slog" "path/filepath" "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" ) // representation of a thread assigned to a worker script @@ -95,9 +93,7 @@ func setupWorkerScript(handler *workerThread, worker *worker) { handler.dummyContext = fc handler.isBootingScript = true clearSandboxedEnv(handler.thread) - if c := logger.Check(zapcore.DebugLevel, "starting"); c != nil { - c.Write(zap.String("worker", worker.name), zap.Int("thread", handler.thread.threadIndex)) - } + logger.LogAttrs(nil, slog.LevelDebug, "starting", slog.String("worker", worker.name), slog.Int("thread", handler.thread.threadIndex)) } func tearDownWorkerScript(handler *workerThread, exitStatus int) { @@ -116,9 +112,7 @@ func tearDownWorkerScript(handler *workerThread, exitStatus int) { // TODO: make the max restart configurable metrics.StopWorker(worker.name, StopReasonRestart) handler.backoff.recordSuccess() - if c := logger.Check(zapcore.DebugLevel, "restarting"); c != nil { - c.Write(zap.String("worker", worker.name)) - } + logger.LogAttrs(nil, slog.LevelDebug, "restarting", slog.String("worker", worker.name)) return } @@ -130,14 +124,15 @@ func tearDownWorkerScript(handler *workerThread, exitStatus int) { return } - logger.Error("worker script has not reached frankenphp_handle_request", zap.String("worker", worker.name)) + logger.LogAttrs(nil, slog.LevelError, "worker script has not reached frankenphp_handle_request", slog.String("worker", worker.name)) // panic after exponential backoff if the worker has never reached frankenphp_handle_request if handler.backoff.recordFailure() { if !watcherIsEnabled && !handler.state.is(stateReady) { - logger.Panic("too many consecutive worker failures", zap.String("worker", worker.name), zap.Int("failures", handler.backoff.failureCount)) + logger.LogAttrs(nil, slog.LevelError, "too many consecutive worker failures", slog.String("worker", worker.name), slog.Int("failures", handler.backoff.failureCount)) + panic("too many consecutive worker failures") } - logger.Warn("many consecutive worker failures", zap.String("worker", worker.name), zap.Int("failures", handler.backoff.failureCount)) + logger.LogAttrs(nil, slog.LevelWarn, "many consecutive worker failures", slog.String("worker", worker.name), slog.Int("failures", handler.backoff.failureCount)) } } @@ -146,9 +141,7 @@ func (handler *workerThread) waitForWorkerRequest() bool { // unpin any memory left over from previous requests handler.thread.Unpin() - if c := logger.Check(zapcore.DebugLevel, "waiting for request"); c != nil { - c.Write(zap.String("worker", handler.worker.name)) - } + logger.LogAttrs(nil, slog.LevelDebug, "waiting for request", slog.String("worker", handler.worker.name)) // Clear the first dummy request created to initialize the worker if handler.isBootingScript { @@ -171,9 +164,7 @@ func (handler *workerThread) waitForWorkerRequest() bool { var fc *frankenPHPContext select { case <-handler.thread.drainChan: - if c := logger.Check(zapcore.DebugLevel, "shutting down"); c != nil { - c.Write(zap.String("worker", handler.worker.name)) - } + logger.LogAttrs(nil, slog.LevelDebug, "shutting down", slog.String("worker", handler.worker.name)) // flush the opcache when restarting due to watcher or admin api // note: this is done right before frankenphp_handle_request() returns 'false' @@ -189,15 +180,11 @@ func (handler *workerThread) waitForWorkerRequest() bool { handler.workerContext = fc handler.state.markAsWaiting(false) - if c := logger.Check(zapcore.DebugLevel, "request handling started"); c != nil { - c.Write(zap.String("worker", handler.worker.name), zap.String("url", fc.request.RequestURI)) - } + logger.LogAttrs(nil, slog.LevelDebug, "request handling started", slog.String("worker", handler.worker.name), slog.String("url", fc.request.RequestURI)) if err := updateServerContext(handler.thread, fc, true); err != nil { // Unexpected error or invalid request - if c := logger.Check(zapcore.DebugLevel, "unexpected error"); c != nil { - c.Write(zap.String("worker", handler.worker.name), zap.String("url", fc.request.RequestURI), zap.Error(err)) - } + logger.LogAttrs(nil, slog.LevelDebug, "unexpected error", slog.String("worker", handler.worker.name), slog.String("url", fc.request.RequestURI), slog.Any("error", err)) fc.rejectBadRequest(err.Error()) handler.workerContext = nil @@ -225,9 +212,7 @@ func go_frankenphp_finish_worker_request(threadIndex C.uintptr_t) { fc.closeContext() thread.handler.(*workerThread).workerContext = nil - if c := fc.logger.Check(zapcore.DebugLevel, "request handling finished"); c != nil { - c.Write(zap.String("worker", fc.scriptFilename), zap.String("url", fc.request.RequestURI)) - } + fc.logger.LogAttrs(nil, slog.LevelDebug, "request handling finished", slog.String("worker", fc.scriptFilename), slog.String("url", fc.request.RequestURI)) } // when frankenphp_finish_request() is directly called from PHP @@ -237,7 +222,5 @@ func go_frankenphp_finish_php_request(threadIndex C.uintptr_t) { fc := phpThreads[threadIndex].getRequestContext() fc.closeContext() - if c := fc.logger.Check(zapcore.DebugLevel, "request handling finished"); c != nil { - c.Write(zap.String("url", fc.request.RequestURI)) - } + fc.logger.LogAttrs(nil, slog.LevelDebug, "request handling finished", slog.String("url", fc.request.RequestURI)) } diff --git a/worker_test.go b/worker_test.go index f3b9a9a7..05969542 100644 --- a/worker_test.go +++ b/worker_test.go @@ -2,9 +2,9 @@ package frankenphp_test import ( "fmt" - "github.com/stretchr/testify/require" "io" "log" + "log/slog" "net/http" "net/http/httptest" "net/url" @@ -13,9 +13,11 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/dunglas/frankenphp" "github.com/stretchr/testify/assert" - "go.uber.org/zap" + "go.uber.org/zap/exp/zapslog" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" ) @@ -96,7 +98,7 @@ func TestWorkerEnv(t *testing.T) { func TestWorkerGetOpt(t *testing.T) { obs, logs := observer.New(zapcore.InfoLevel) - logger := zap.New(obs) + logger := slog.New(zapslog.NewHandler(obs)) runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) { req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/worker-getopt.php?i=%d", i), nil)