Skip to content
Draft
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
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@
/src/Resources/public/
/src/Resources/app/administration/node_modules/
/src/Resources/app/administration/.tmp/
# monolog_parser local build only — keep packaged native/lib/*-linux-*.so / *-darwin-*.dylib
/native/monolog_parser/src/*.o
/native/monolog_parser/tests/test_parser
/native/monolog_parser/bench/bench
/native/monolog_parser/libmonolog_parser.so
/native/monolog_parser/libmonolog_parser.dylib
# generic alias from `make install` (optional local convenience)
/native/lib/libmonolog_parser.so
/native/lib/libmonolog_parser.dylib
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Dedicated list/detail UI for Shopware webhooks (including inline search).
| **Queue** | Multi-transport support (Doctrine, Redis, AMQP, fallback); browse without consuming (or fetch+requeue); retry failed messages; purge transport; reset queue. |
| **Scheduled Tasks** | List, run, deactivate; edit interval & next execution; register tasks. |
| **State Machines** | Diagram viewer for any registered state machine. |
| **Log Viewer** | Read `var/log/*.log` in the browser. |
| **Log Viewer** | Read `var/log/*.log` in the browser. Optional SIMD C parser via PHP FFI (`native/monolog_parser`). |
| **Feature Flags** | Inspect and toggle flags from core/plugins. |
| **Elasticsearch** | Status, indices, reindex, alias switch, unused/orphaned cleanup, console. Optional `show_all_indices`. |
| **Fastly** | Purge all / by URL and basic stats when Fastly is configured. |
Expand Down
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
"require-dev": {
"shopware/elasticsearch": "~6.6.0 || ~6.7.0"
},
"suggest": {
"ext-ffi": "Optional: load native/lib/libmonolog_parser.so for SIMD-accelerated Monolog log parsing"
},
"config": {
"allow-plugins": {
"symfony/runtime": true
Expand All @@ -54,6 +57,7 @@
"scripts": {
"format": "docker run --rm -v $(pwd):/ext shopware/shopware-cli:latest extension format /ext",
"check": "docker run --rm -v $(pwd):/ext shopware/shopware-cli:latest extension validate --full /ext",
"phpunit": "../../../vendor/bin/phpunit -c phpunit.xml"
"phpunit": "../../../vendor/bin/phpunit -c phpunit.xml",
"build-native": "make -C native/monolog_parser install"
}
}
24 changes: 24 additions & 0 deletions native/lib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Prebuilt `libmonolog_parser` (Zig cross-compile)

| File | Platform | libc |
| --- | --- | --- |
| `libmonolog_parser-linux-x86_64.so` | Linux amd64 | glibc |
| `libmonolog_parser-linux-arm64.so` | Linux arm64 | glibc |
| `libmonolog_parser-linux-musl-x86_64.so` | Linux amd64 | musl |
| `libmonolog_parser-linux-musl-arm64.so` | Linux arm64 | musl |
| `libmonolog_parser-darwin-x86_64.dylib` | macOS Intel | — |
| `libmonolog_parser-darwin-arm64.dylib` | macOS Apple Silicon | — |

## Rebuild all (one command)

```bash
cd ../monolog_parser
make zig-release # needs zig ≥ 0.13
# or: make zig-release ZIG=/tmp/zig-linux-x86_64-0.13.0/zig
```

Produced here with Zig 0.13 from a single Linux host — no Alpine/macOS runners required.

## Runtime

PHP `NativeLibraryLocator` auto-selects; users need `ext-ffi` only (optional).
Binary file added native/lib/libmonolog_parser-darwin-arm64.dylib
Binary file not shown.
Binary file not shown.
Binary file added native/lib/libmonolog_parser-linux-arm64.so
Binary file not shown.
Binary file added native/lib/libmonolog_parser-linux-musl-arm64.so
Binary file not shown.
Binary file added native/lib/libmonolog_parser-linux-musl-x86_64.so
Binary file not shown.
Binary file added native/lib/libmonolog_parser-linux-x86_64.so
Binary file not shown.
184 changes: 184 additions & 0 deletions native/lib/monolog_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/**
* monolog_parser — SIMD-accelerated Monolog line parser for PHP FFI.
*
* Matches FroshTools LogController LINE_MATCH:
* /\[(?<date>.*)] (?<channel>.*)\.(?<level>DEBUG|INFO|…):(?<message>.*)/m
*
* Two usage modes for PHP:
* 1) Parse a line/buffer you already hold (monolog_parse_line / _buffer)
* 2) Open the log file in C and yield/page parsed entries
* monolog_reader_open_backwards → monolog_reader_next (iterator)
* monolog_file_read_backwards (one-shot page)
*/
#ifndef MONOLOG_PARSER_H
#define MONOLOG_PARSER_H

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#if defined(_WIN32) || defined(__CYGWIN__)
# ifdef MONOLOG_PARSER_BUILD
# define MONOLOG_API __declspec(dllexport)
# else
# define MONOLOG_API __declspec(dllimport)
# endif
#else
# define MONOLOG_API __attribute__((visibility("default")))
#endif

/** Parsed field offsets relative to the start of the input string. */
typedef struct monolog_fields {
int32_t date_off;
int32_t date_len;
int32_t channel_off;
int32_t channel_len;
int32_t level_off;
int32_t level_len;
int32_t message_off;
int32_t message_len;
/** 1 if the Monolog pattern matched, 0 otherwise. */
int32_t matched;
} monolog_fields_t;

/**
* One line within a multi-line buffer (offsets relative to buffer start).
* line_len excludes a trailing '\\n' / '\\r\\n'.
*/
typedef struct monolog_line_span {
int32_t off;
int32_t len;
} monolog_line_span_t;

/** Library version string (static storage). */
MONOLOG_API const char *monolog_parser_version(void);

/**
* Which SIMD path is active: "sse2", "neon", or "scalar".
*/
MONOLOG_API const char *monolog_parser_simd(void);

/**
* Parse a single Monolog line (need not be NUL-terminated).
* Returns 1 on match, 0 on no match. out must be non-NULL.
*/
MONOLOG_API int monolog_parse_line(const char *line, size_t len, monolog_fields_t *out);

/**
* Find newline byte offsets in [buf, buf+len) using SIMD scan.
* Writes up to max_offsets offsets into offsets[]; returns count found
* (may be > max_offsets if truncated — check return vs max).
* If offsets is NULL, only counts.
*/
MONOLOG_API size_t monolog_find_newlines(const char *buf, size_t len,
size_t *offsets, size_t max_offsets);

/**
* Split buffer into line spans (no trailing CR/LF). Handles \\n and \\r\\n.
* Returns number of lines written (capped by max_lines).
*/
MONOLOG_API size_t monolog_split_lines(const char *buf, size_t len,
monolog_line_span_t *lines, size_t max_lines);

/**
* Parse every line in a buffer. entries[i] fields are relative to buf.
* Unmatched lines still get an entry with matched=0; for those the whole
* line is reported as message (message_off/len set, others 0).
* Returns number of lines processed (capped by max_entries).
*/
MONOLOG_API size_t monolog_parse_buffer(const char *buf, size_t len,
monolog_fields_t *entries, size_t max_entries);

/**
* SIMD memchr — find first occurrence of byte c in [buf, buf+len).
* Returns pointer to the byte, or NULL.
*/
MONOLOG_API const char *monolog_memchr(const char *buf, int c, size_t len);

/**
* Find last occurrence of byte c in [buf, buf+len).
* Returns pointer to the byte, or NULL.
*/
MONOLOG_API const char *monolog_memrchr(const char *buf, int c, size_t len);

/* =====================================================================
* File reader — open in C, yield parsed lines (newest first)
* ===================================================================== */

typedef struct monolog_reader monolog_reader_t;

/**
* Open path for reverse iteration (newest log line first).
* Returns NULL on open failure.
*/
MONOLOG_API monolog_reader_t *monolog_reader_open_backwards(const char *path);

/** Close reader and free all buffers. Safe on NULL. */
MONOLOG_API void monolog_reader_close(monolog_reader_t *r);

/** Total line count (SIMD newline scan on open). */
MONOLOG_API uint64_t monolog_reader_total_lines(const monolog_reader_t *r);

/** Last error message, or NULL. */
MONOLOG_API const char *monolog_reader_error(const monolog_reader_t *r);

/**
* Yield next line (newest-first), parse it.
*
* Returns:
* 1 — entry written; *line_out points at NUL-terminated line
* (valid until next next()/close). fields offsets relative to *line_out.
* 0 — EOF
* -1 — error (see monolog_reader_error)
*
* Unmatched lines: fields->matched == 0, message = full line.
*/
MONOLOG_API int monolog_reader_next(monolog_reader_t *r,
monolog_fields_t *out,
const char **line_out);

/**
* Discard the next n lines (newest-first). Returns 1 ok, 0 hit EOF early, -1 error.
*/
MONOLOG_API int monolog_reader_skip(monolog_reader_t *r, uint64_t n);

/* =====================================================================
* One-shot page — preferred for LogController offset/limit
* ===================================================================== */

/** Owned strings; free via monolog_page_free only. */
typedef struct monolog_page_entry {
int32_t matched;
const char *date;
const char *channel;
const char *level;
const char *message;
} monolog_page_entry_t;

typedef struct monolog_page monolog_page_t;

/**
* Read [offset, offset+limit) lines from end of file (offset 0 = newest),
* parse each. On success *out is allocated; free with monolog_page_free.
* Returns 0 on success, -1 on error.
*/
MONOLOG_API int monolog_file_read_backwards(const char *path,
uint64_t offset,
uint64_t limit,
monolog_page_t **out);

MONOLOG_API void monolog_page_free(monolog_page_t *page);

MONOLOG_API uint64_t monolog_page_total_lines(const monolog_page_t *page);
MONOLOG_API uint32_t monolog_page_count(const monolog_page_t *page);
MONOLOG_API const monolog_page_entry_t *monolog_page_entry(const monolog_page_t *page,
uint32_t index);

#ifdef __cplusplus
}
#endif

#endif /* MONOLOG_PARSER_H */
129 changes: 129 additions & 0 deletions native/monolog_parser/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# monolog_parser — SIMD Monolog library
#
# Native (host toolchain):
# make / make test / make package
#
# Cross-compile ALL release targets with Zig (preferred for CI + local):
# make zig-release # needs `zig` on PATH or ZIG=/path/to/zig
# make zig-release ZIG=...
#
# Artifacts → ../lib/
# libmonolog_parser-linux-x86_64.so
# libmonolog_parser-linux-arm64.so
# libmonolog_parser-linux-musl-x86_64.so
# libmonolog_parser-linux-musl-arm64.so
# libmonolog_parser-darwin-x86_64.dylib
# libmonolog_parser-darwin-arm64.dylib

CC ?= cc
CFLAGS ?= -O3 -fPIC -Wall -Wextra -Wpedantic -std=c11
CFLAGS += -Iinclude
ZIG ?= zig

UNAME_S := $(shell uname -s)
UNAME_M := $(shell uname -m)

ARCH_RAW := $(UNAME_M)
ifeq ($(ARCH_RAW),amd64)
ARCH := x86_64
else ifeq ($(ARCH_RAW),x86_64)
ARCH := x86_64
else ifeq ($(ARCH_RAW),aarch64)
ARCH := arm64
else ifeq ($(ARCH_RAW),arm64)
ARCH := arm64
else
ARCH := $(ARCH_RAW)
endif

ifndef LIBC
ifneq ($(wildcard /lib/ld-musl-*.so.1),)
LIBC := musl
else
LIBC := gnu
endif
endif

ifeq ($(UNAME_S),Darwin)
OS := darwin
LIBEXT := dylib
LIBC := n/a
LDFLAGS += -dynamiclib -install_name @rpath/libmonolog_parser.$(LIBEXT)
CFLAGS += -mmacosx-version-min=11.0
TEST_RPATH := -Wl,-rpath,@loader_path/..
LIB_PACKAGED := libmonolog_parser-darwin-$(ARCH).$(LIBEXT)
else
OS := linux
LIBEXT := so
LDFLAGS += -shared
TEST_RPATH := -Wl,-rpath,'$$ORIGIN/..'
ifeq ($(LIBC),musl)
LIB_PACKAGED := libmonolog_parser-linux-musl-$(ARCH).$(LIBEXT)
else
LIB_PACKAGED := libmonolog_parser-linux-$(ARCH).$(LIBEXT)
endif
endif

ifeq ($(ARCH),arm64)
ifneq ($(UNAME_S),Darwin)
CFLAGS += -march=armv8-a+simd
endif
endif

SRC := src/simd_scan.c src/monolog_parser.c src/monolog_reader.c
OBJ := $(SRC:.c=.o)

LIB_LOCAL := libmonolog_parser.$(LIBEXT)
INSTALL_DIR ?= ../lib

.PHONY: all clean test bench install package shared info zig-release zig-one

all: shared

info:
@echo "os=$(OS) arch=$(ARCH) libc=$(LIBC) ext=$(LIBEXT)"
@echo "local=$(LIB_LOCAL) packaged=$(LIB_PACKAGED)"
@echo "zig=$(ZIG)"

shared: $(LIB_LOCAL)

$(LIB_LOCAL): $(OBJ)
$(CC) $(LDFLAGS) -o $@ $^
@echo "built $@"

src/%.o: src/%.c include/monolog_parser.h
$(CC) $(CFLAGS) -DMONOLOG_PARSER_BUILD -c -o $@ $<

tests/test_parser: tests/test_parser.c $(LIB_LOCAL)
$(CC) $(CFLAGS) -o $@ tests/test_parser.c -L. -lmonolog_parser $(TEST_RPATH)

bench/bench: bench/bench.c $(LIB_LOCAL)
$(CC) $(CFLAGS) -o $@ bench/bench.c -L. -lmonolog_parser $(TEST_RPATH)

test: tests/test_parser
LD_LIBRARY_PATH="$(CURDIR):$$LD_LIBRARY_PATH" ./tests/test_parser

bench: bench/bench
LD_LIBRARY_PATH="$(CURDIR):$$LD_LIBRARY_PATH" ./bench/bench 100000

package: $(LIB_LOCAL)
mkdir -p $(INSTALL_DIR)
cp -f $(LIB_LOCAL) $(INSTALL_DIR)/$(LIB_PACKAGED)
cp -f include/monolog_parser.h $(INSTALL_DIR)/
@echo "packaged $(INSTALL_DIR)/$(LIB_PACKAGED) [libc=$(LIBC)]"

install: package
cp -f $(LIB_LOCAL) $(INSTALL_DIR)/$(LIB_LOCAL)

# ---- Zig cross-compile (all platforms from one machine) ------------------

zig-release:
ZIG="$(ZIG)" OUT="$(abspath $(INSTALL_DIR))" sh ./build-zig.sh

# make zig-one TARGET=linux-musl-arm64
zig-one:
@test -n "$(TARGET)" || { echo "usage: make zig-one TARGET=linux-musl-arm64"; exit 1; }
ZIG="$(ZIG)" OUT="$(abspath $(INSTALL_DIR))" sh ./build-zig.sh $(TARGET)

clean:
rm -f $(OBJ) $(LIB_LOCAL) tests/test_parser bench/bench
Loading
Loading