Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion caddy/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/prometheus/client_golang v1.25.0
github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.12.1
go.uber.org/zap/exp v0.3.0
)

require (
Expand Down Expand Up @@ -184,7 +185,6 @@ require (
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/crypto v0.58.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20261009200856-99e4382b128e // indirect
Expand Down
6 changes: 5 additions & 1 deletion caddy/php-cli.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package caddy

import (
"log/slog"
"os"
"path/filepath"
"strings"

"github.com/caddyserver/caddy/v2"
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/dunglas/frankenphp"
"go.uber.org/zap/exp/zapslog"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -35,7 +38,8 @@ func cmdPHPCLI(fs caddycmd.Flags) (int, error) {
}
}

status := frankenphp.ExecuteScriptCLI(args[0], args)
logger := slog.New(zapslog.NewHandler(caddy.Log().Core(), zapslog.WithName("frankenphp")))
status := frankenphp.ExecuteScriptCLI(args[0], args, frankenphp.WithLogger(logger))

os.Exit(status)

Expand Down
26 changes: 24 additions & 2 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,33 @@ package frankenphp

// #include "frankenphp.h"
import "C"
import "unsafe"
import (
"fmt"
"os"
"unsafe"
)

// ExecuteScriptCLI executes the PHP script passed as parameter.
// It returns the exit status code of the script.
func ExecuteScriptCLI(script string, args []string) int {
//
// Only the WithLogger and WithContext options are supported, they configure frankenphp_log().
func ExecuteScriptCLI(script string, args []string, opts ...Option) int {
o := &opt{}
for _, f := range opts {
if err := f(o); err != nil {
fmt.Fprintln(os.Stderr, err)

return 1
}
}

if o.ctx != nil {
globalCtx = o.ctx
}
if o.logger != nil {
globalLogger = o.logger
}

// Ensure extensions are registered before CLI execution
registerExtensions()

Expand Down
24 changes: 23 additions & 1 deletion cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ func TestExecuteCLIHTTPFunctionsUnavailable(t *testing.T) {
{"frankenphp_handle_request", "static function () {}"},
{"headers_send", "103"},
{"mercure_publish", "'https://example.com/topic', 'test'"},
{"frankenphp_log", "'CLI feature detection'"},
} {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
Expand All @@ -266,6 +265,29 @@ var_export(function_exists($function));
}
}

func TestExecuteCLILog(t *testing.T) {
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
}

ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "internal/testcli/testcli", "-r", `
frankenphp_log('hello', FRANKENPHP_LOG_LEVEL_WARN, ['k' => 'v']);
try {
frankenphp_log('invalid context', FRANKENPHP_LOG_LEVEL_ERROR, ['k' => new stdClass()]);
} catch (RuntimeException $e) {
echo 'caught';
}
`)
cmd.WaitDelay = time.Second
output, err := cmd.CombinedOutput()
require.NoError(t, ctx.Err(), "output: %s", output)
require.NoError(t, err, "output: %s", output)
assert.Contains(t, string(output), "WARN hello k=v")
assert.Contains(t, string(output), "caught")
}

func TestExecuteCLINativeHTTPFunctions(t *testing.T) {
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
Expand Down
3 changes: 3 additions & 0 deletions docs/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ When viewing the logs (e.g., via `docker compose logs`), the output will appear
{"level":"warn","ts":1704067200,"logger":"frankenphp","msg":"Memory usage high","current_usage":10485760,"peak_usage":12582912}
```

`frankenphp_log()` is also available in `frankenphp php-cli`, where it writes to the standard error output.
`php-cli` doesn't load the `Caddyfile`, so its `log` directives don't apply there.

## `error_log()`

FrankenPHP also allows logging using the standard `error_log()` function. If the `$message_type` parameter is `4` (SAPI),
Expand Down
21 changes: 15 additions & 6 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1222,16 +1222,25 @@ static zend_module_entry frankenphp_module = {
TOSTRING(FRANKENPHP_VERSION),
STANDARD_MODULE_PROPERTIES};

static const zend_function_entry frankenphp_cli_functions[] = {
ZEND_FE(frankenphp_log, arginfo_frankenphp_log) ZEND_FE_END};

static PHP_MINIT_FUNCTION(frankenphp_cli) {
register_frankenphp_symbols(module_number);

return SUCCESS;
}

/* same phpinfo section in CLI, without the server functions and hooks */
static zend_module_entry frankenphp_cli_module = {
STANDARD_MODULE_HEADER,
"frankenphp-cli",
NULL, /* function table */
NULL, /* initialization */
NULL, /* shutdown */
NULL, /* request initialization */
NULL, /* request shutdown */
PHP_MINFO(frankenphp), /* information */
frankenphp_cli_functions, /* function table */
PHP_MINIT(frankenphp_cli), /* initialization */
NULL, /* shutdown */
NULL, /* request initialization */
NULL, /* request shutdown */
PHP_MINFO(frankenphp), /* information */
TOSTRING(FRANKENPHP_VERSION),
STANDARD_MODULE_PROPERTIES};

Expand Down
Loading