π‘πͺ΅ Real-time log ingestion and streaming service built with PHP 8.3 + ReactPHP + Ratchet
[ Your apps ] ββPOST /api/logsβββΆ [ HTTP server :8081 ]
β
saves to storage
(MariaDB / JSONL)
β
broadcasts via
βΌ
[ WS server :8080 ] βββΆ [ React UI ]
# 1. Install dependencies
composer install
# 2. Configure
cp .env.example .env
# Edit .env β set API_SECRET, choose STORAGE_TYPE, etc.
# 3a. MariaDB (optional)
mysql -u root -p < migrations/001_logs.sql
# 3b. File storage β nothing to do; directories are auto-created.
# 4. Run
php bin/server.phpAll requests to write endpoints must include:
Authorization: Bearer <API_SECRET>
Batch payload (recommended):
{
"app_key": "billing-api",
"app_id": "production",
"batch_id": "550e8400-e29b-41d4-a716-446655440000",
"logs": [
{
"trace_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"level": "error",
"category": "payments",
"message": "Charge failed for invoice #1234",
"context": { "invoice_id": 1234, "code": "card_declined" },
"timestamp": "2025-01-15T14:22:00.000Z"
}
]
}User-Agent header is captured as the application identifier:
User-Agent: BillingService/2.1.0 (PHP 8.3)
Single-entry shorthand:
{
"app_key": "billing-api",
"app_id": "production",
"level": "info",
"category": "startup",
"message": "Service started"
}Response (201):
{
"saved": 1,
"entries": [ { ...LogEntry } ],
"errors": null
}| Param | Type | Description |
|---|---|---|
app_key |
string | Exact match |
app_id |
string | Exact match |
user_agent |
string | Partial match |
level |
string | debug|info|notice|warning|error|critical |
category |
string | Partial match |
trace_id |
string | Exact match |
batch_id |
string | Exact match |
date_from |
string | ISO 8601 timestamp (inclusive) |
date_to |
string | ISO 8601 timestamp (inclusive) |
search |
string | Substring match on message |
limit |
int | Max entries returned (default 100, max 1000) |
offset |
int | Pagination offset (default 0) |
Response (200):
{
"total": 42,
"limit": 100,
"offset": 0,
"entries": [ { ...LogEntry } ]
}Fetch a single entry by internal id or trace_id.
{ "status": "ok", "time": "...", "ws_connections": 2 }Connect from the UI to receive a live stream of every ingested log.
On connect the server sends:
{ "type": "connected", "connections": 1 }Every new log entry is broadcast as:
{
"type": "log",
"data": {
"id": "01HZXYZ...",
"trace_id": "uuid-v4",
"batch_id": "uuid-v4 | null",
"app_key": "billing-api",
"app_id": "production",
"user_agent": "BillingService/2.1.0",
"level": "error",
"category": "payments",
"message": "Charge failed",
"context": { ... },
"timestamp": "2025-01-15T14:22:00.000Z",
"created_at": "2025-01-15T14:22:00.012Z"
}
}Client β server messages:
{ "type": "ping" } β { "type": "pong" }
{ "type": "stats" } β { "type": "stats", "data": { "connections": 2 } }| Field | Type | Notes |
|---|---|---|
id |
string (ULID) | Time-sortable, generated by server |
trace_id |
string (UUID) | Provide your own or let server generate |
batch_id |
string (UUID)? | Groups entries in the same request |
app_key |
string β€100 | Required. Application slug |
app_id |
string β€100 | Required. Instance / environment |
user_agent |
string β€255? | Application name & version |
level |
enum | debug / info / notice / warning / error / critical |
category |
string β€100 | Free-form grouping tag |
message |
string | Human-readable description |
context |
object? | Arbitrary structured data |
timestamp |
ISO 8601 | When the event occurred |
created_at |
ISO 8601 | When the server persisted it |