Skip to content

Commit afbb233

Browse files
fix: simplify startup log version lookup
Use Go build info instead of the FRANKENPHP_VERSION cgo macro for startup log version detection, and fold the log helper back into frankenphp.go. RED->GREEN: build-info tests failed before the helper existed, then passed after the review cleanup. Local run-ci baseline/final tests pass before diff-coverage on committed HEAD.
1 parent d4f7f9a commit afbb233

3 files changed

Lines changed: 166 additions & 47 deletions

File tree

frankenphp.go

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@ package frankenphp
1818
// #include <php_variables.h>
1919
// #include <zend_llist.h>
2020
// #include <SAPI.h>
21-
// static inline const char *frankenphp_get_build_version() {
22-
// return TOSTRING(FRANKENPHP_VERSION);
23-
// }
2421
import "C"
2522
import (
2623
"bytes"
2724
"context"
25+
"debug/buildinfo"
2826
"errors"
2927
"fmt"
3028
"io"
@@ -33,6 +31,7 @@ import (
3331
"os"
3432
"os/signal"
3533
"runtime"
34+
runtimeDebug "runtime/debug"
3635
"strings"
3736
"sync"
3837
"sync/atomic"
@@ -73,6 +72,10 @@ var (
7372
// atomic: read by in-flight requests while a reload may rewrite it
7473
maxWaitTime atomic.Int64
7574
maxRequestsPerThread int
75+
76+
frankenPHPVersion = sync.OnceValues(func() (string, error) {
77+
return frankenPHPVersionFromExecutable(os.Executable, buildinfo.ReadFile)
78+
})
7679
)
7780

7881
type ErrRejected struct {
@@ -97,6 +100,24 @@ const (
97100
syslogLevelDebug // debug-level messages
98101
)
99102

103+
const (
104+
frankenPHPModulePath = "github.com/dunglas/frankenphp"
105+
unknownModuleVersion = "(devel)"
106+
107+
startupLogMessage = "FrankenPHP started 🐘"
108+
startupLogVersionUnavailableMessage = "FrankenPHP version unavailable"
109+
startupLogAttrVersion = "version"
110+
startupLogAttrPHPVersion = "php_version"
111+
startupLogAttrNumThreads = "num_threads"
112+
startupLogAttrMaxThreads = "max_threads"
113+
startupLogAttrMaxRequests = "max_requests"
114+
startupLogAttrError = "error"
115+
startupLogAttrCapacity = 5
116+
117+
executablePathErrorMessage = "getting executable path: %w"
118+
buildInfoReadErrorMessage = "reading build info: %w"
119+
)
120+
100121
func (l syslogLevel) String() string {
101122
switch l {
102123
case syslogLevelEmerg:
@@ -148,8 +169,46 @@ func Version() PHPVersion {
148169
}
149170
}
150171

151-
func frankenPHPVersion() string {
152-
return C.GoString(C.frankenphp_get_build_version())
172+
func frankenPHPVersionFromExecutable(executablePathFunc func() (string, error), readBuildInfoFunc func(string) (*runtimeDebug.BuildInfo, error)) (string, error) {
173+
executablePath, err := executablePathFunc()
174+
if err != nil {
175+
return "", fmt.Errorf(executablePathErrorMessage, err)
176+
}
177+
178+
info, err := readBuildInfoFunc(executablePath)
179+
if err != nil {
180+
return "", fmt.Errorf(buildInfoReadErrorMessage, err)
181+
}
182+
183+
return frankenPHPVersionFromBuildInfo(info), nil
184+
}
185+
186+
func frankenPHPVersionFromBuildInfo(info *runtimeDebug.BuildInfo) string {
187+
if info == nil {
188+
return ""
189+
}
190+
if info.Main.Path == frankenPHPModulePath && moduleVersionIsKnown(info.Main.Version) {
191+
return info.Main.Version
192+
}
193+
for _, dep := range info.Deps {
194+
if dep.Path == frankenPHPModulePath && moduleVersionIsKnown(dep.Version) {
195+
return dep.Version
196+
}
197+
}
198+
return ""
199+
}
200+
201+
func moduleVersionIsKnown(version string) bool {
202+
return version != "" && version != unknownModuleVersion
203+
}
204+
205+
func startupLogAttrs(frankenPHPVersion string, phpVersion string, numThreads int, maxThreads int, maxRequests int) []slog.Attr {
206+
attrs := make([]slog.Attr, 0, startupLogAttrCapacity)
207+
if frankenPHPVersion != "" {
208+
attrs = append(attrs, slog.String(startupLogAttrVersion, frankenPHPVersion))
209+
}
210+
211+
return append(attrs, slog.String(startupLogAttrPHPVersion, phpVersion), slog.Int(startupLogAttrNumThreads, numThreads), slog.Int(startupLogAttrMaxThreads, maxThreads), slog.Int(startupLogAttrMaxRequests, maxRequests))
153212
}
154213

155214
func Config() PHPConfig {
@@ -349,7 +408,13 @@ func Init(options ...Option) error {
349408
initAutoScaling(mainThread)
350409

351410
if globalLogger.Enabled(globalCtx, slog.LevelInfo) {
352-
globalLogger.LogAttrs(globalCtx, slog.LevelInfo, startupLogMessage, startupLogAttrs(Version().Version, mainThread.numThreads, mainThread.maxThreads, maxRequestsPerThread)...)
411+
frankenPHPBuildVersion := ""
412+
if version, err := frankenPHPVersion(); err != nil {
413+
globalLogger.LogAttrs(globalCtx, slog.LevelDebug, startupLogVersionUnavailableMessage, slog.Any(startupLogAttrError, err))
414+
} else {
415+
frankenPHPBuildVersion = version
416+
}
417+
globalLogger.LogAttrs(globalCtx, slog.LevelInfo, startupLogMessage, startupLogAttrs(frankenPHPBuildVersion, Version().Version, mainThread.numThreads, mainThread.maxThreads, maxRequestsPerThread)...)
353418

354419
if EmbeddedAppPath != "" {
355420
globalLogger.LogAttrs(globalCtx, slog.LevelInfo, "embedded PHP app 📦", slog.String("path", EmbeddedAppPath))

startup_log.go

Lines changed: 0 additions & 27 deletions
This file was deleted.

startup_log_test.go

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,114 @@
11
package frankenphp
22

33
import (
4-
"os"
4+
"errors"
5+
"runtime/debug"
56
"testing"
67
)
78

8-
const startupLogTestExpectedVersionEnv = "FRANKENPHP_EXPECT_VERSION"
9+
const (
10+
startupLogTestDependencyVersion = "v1.12.6"
11+
startupLogTestMainVersion = "v1.12.7"
12+
startupLogTestExecutablePath = "/tmp/frankenphp-test"
13+
startupLogTestPHPVersion = "8.2.31"
14+
startupLogTestNumThreads = 4
15+
startupLogTestMaxThreads = 8
16+
startupLogTestMaxRequests = 0
17+
startupLogTestExecutableError = "executable error"
18+
startupLogTestBuildInfoError = "build info error"
19+
)
920

10-
func TestStartupLogAttrsIncludeFrankenPHPVersion(t *testing.T) {
11-
const (
12-
testPHPVersion = "8.2.31"
13-
testNumThreads = 4
14-
testMaxThreads = 8
15-
testMaxRequests = 0
21+
func TestFrankenPHPVersionFromBuildInfoDependency(t *testing.T) {
22+
info := &debug.BuildInfo{
23+
Deps: []*debug.Module{
24+
{
25+
Path: frankenPHPModulePath,
26+
Version: startupLogTestDependencyVersion,
27+
},
28+
},
29+
}
30+
31+
if got := frankenPHPVersionFromBuildInfo(info); got != startupLogTestDependencyVersion {
32+
t.Fatalf("expected FrankenPHP dependency version %q, got %q", startupLogTestDependencyVersion, got)
33+
}
34+
}
35+
36+
func TestFrankenPHPVersionFromBuildInfoMainModule(t *testing.T) {
37+
info := &debug.BuildInfo{
38+
Main: debug.Module{
39+
Path: frankenPHPModulePath,
40+
Version: startupLogTestMainVersion,
41+
},
42+
}
43+
44+
if got := frankenPHPVersionFromBuildInfo(info); got != startupLogTestMainVersion {
45+
t.Fatalf("expected FrankenPHP main module version %q, got %q", startupLogTestMainVersion, got)
46+
}
47+
}
48+
49+
func TestFrankenPHPVersionFromExecutable(t *testing.T) {
50+
version, err := frankenPHPVersionFromExecutable(
51+
func() (string, error) {
52+
return startupLogTestExecutablePath, nil
53+
},
54+
func(path string) (*debug.BuildInfo, error) {
55+
if path != startupLogTestExecutablePath {
56+
t.Fatalf("expected executable path %q, got %q", startupLogTestExecutablePath, path)
57+
}
58+
return &debug.BuildInfo{
59+
Deps: []*debug.Module{
60+
{
61+
Path: frankenPHPModulePath,
62+
Version: startupLogTestDependencyVersion,
63+
},
64+
},
65+
}, nil
66+
},
67+
)
68+
if err != nil {
69+
t.Fatalf("expected no error, got %v", err)
70+
}
71+
if version != startupLogTestDependencyVersion {
72+
t.Fatalf("expected FrankenPHP version %q, got %q", startupLogTestDependencyVersion, version)
73+
}
74+
}
75+
76+
func TestFrankenPHPVersionFromExecutableErrors(t *testing.T) {
77+
_, err := frankenPHPVersionFromExecutable(
78+
func() (string, error) {
79+
return "", errors.New(startupLogTestExecutableError)
80+
},
81+
func(string) (*debug.BuildInfo, error) {
82+
t.Fatal("expected build info reader not to be called")
83+
return nil, nil
84+
},
1685
)
86+
if err == nil {
87+
t.Fatal("expected executable path error")
88+
}
1789

18-
expectedFrankenPHPVersion := os.Getenv(startupLogTestExpectedVersionEnv)
19-
if expectedFrankenPHPVersion == "" {
20-
expectedFrankenPHPVersion = frankenPHPVersion()
90+
_, err = frankenPHPVersionFromExecutable(
91+
func() (string, error) {
92+
return startupLogTestExecutablePath, nil
93+
},
94+
func(string) (*debug.BuildInfo, error) {
95+
return nil, errors.New(startupLogTestBuildInfoError)
96+
},
97+
)
98+
if err == nil {
99+
t.Fatal("expected build info read error")
21100
}
101+
}
22102

23-
attrs := startupLogAttrs(testPHPVersion, testNumThreads, testMaxThreads, testMaxRequests)
103+
func TestStartupLogAttrsIncludeFrankenPHPVersion(t *testing.T) {
104+
attrs := startupLogAttrs(startupLogTestDependencyVersion, startupLogTestPHPVersion, startupLogTestNumThreads, startupLogTestMaxThreads, startupLogTestMaxRequests)
24105
if len(attrs) == 0 {
25106
t.Fatal("expected startup log attrs")
26107
}
27108
if attrs[0].Key != startupLogAttrVersion {
28109
t.Fatalf("expected first startup log attr key %q, got %q", startupLogAttrVersion, attrs[0].Key)
29110
}
30-
if got := attrs[0].Value.String(); got != expectedFrankenPHPVersion {
31-
t.Fatalf("expected startup log version %q, got %q", expectedFrankenPHPVersion, got)
111+
if got := attrs[0].Value.String(); got != startupLogTestDependencyVersion {
112+
t.Fatalf("expected startup log version %q, got %q", startupLogTestDependencyVersion, got)
32113
}
33114
}

0 commit comments

Comments
 (0)