Skip to content

Formatters

Formatters

Every formatter implements Cbox\Siem\Contracts\StreamFormatter:

public function format(SiemEvent $event): string;  // one record
public function contentType(): string;             // MIME type for the transport
public function name(): string;                    // stable id: cef, ecs, ...

Formatters are pure and deterministic: the same event always yields the same bytes, with no I/O. A batch is the formatter mapped over many events; framing between records is the transport's job.

JsonFormatter — generic / NDJSON

A neutral, deterministic single-line JSON document (fixed key order, UTF-8 safe, slashes unescaped), so a newline join of records is valid NDJSON. Fields: id, timestamp (RFC-3339 UTC), action, category, outcome, severity, and the optional actor, target, source_ip, message, and context.

contentType() = application/json.

EcsFormatter — Elastic Common Schema

Real ECS field names, not approximations:

ECS field Source
@timestamp occurredAt, RFC-3339 UTC with millisecond precision and Z
ecs.version pinned schema version (EcsFormatter::ECS_VERSION)
event.id / event.action id / action
event.category (array) category->ecsCategory()
event.type (array) derived: allowed/denied for access decisions, change for config/audit, indicator for threat, else info
event.kind "event"
event.outcome outcome (success/failure/unknown)
log.level severity->logLevel()
user.id actor->id
source.ip sourceIp
message message
labels.* context
cbox.* raw domain category, and actor/target type (+ target id)

document() is public, so you can embed the ECS array in another envelope or assert on it directly. contentType() = application/json.

CefFormatter — ArcSight / syslog

CEF:0|Cbox|<product>|<version>|<signatureId>|<name>|<severity>|<extension>
  • Header: vendor (Cbox by default), product and version (constructor args, default SIEM / 0.1.0), signatureIdaction, namemessage (or action), severity ← severity->cef() (0–10).
  • Extension (key=value, space-separated): rt (event time in ms since epoch), cat (raw category), act (action), outcome, externalId (id), src (source IP), suser (actor id), actorType, targetType, targetId, msg (message), and each context entry as cbox_<key>.

Every field is escaped through Cbox\Siem\Support\CefEscaper. This is the security-critical formatter — see Security → escaping. contentType() = text/plain.

SplunkHecFormatter — Splunk HTTP Event Collector

{"time": 1752582896.789, "sourcetype": "cbox:siem", "event": { ... }}
  • time — epoch seconds with a millisecond decimal fraction (a float). This is a common footgun: HEC wants seconds, not milliseconds.
  • sourcetype — constructor arg, default cbox:siem. An optional source is emitted only when configured.
  • event — the structured event (id, action, category, outcome, severity, and optional actor, target, source_ip, message, context).

Records are single-line, so concatenating them (HEC accepts back-to-back JSON objects) yields a valid batch. contentType() = application/json.

GelfFormatter — Graylog (GELF 1.1)

GELF field Source
version "1.1"
host constructor arg (default cbox-siem; the wrapper sets the real host)
short_message message (or action)
timestamp epoch seconds with a millisecond decimal
level severity->syslogLevel() — a numeric 0–7, not a keyword
_action, _category, _outcome, _severity, _event_id, … normalized fields, _-prefixed
_<key> each context entry

The GELF-reserved additional field _id is forbidden: a context key id (which would produce _id) is skipped rather than emitted, as is any context key that would clobber a standard additional field.

Transport note. GELF is often sent over UDP, but this package targets audit/security data where silent datagram loss is unacceptable. The Laravel wrapper delivers GELF over TCP or HTTP only — never UDP. Framing a GELF/TCP stream (null-byte delimited) is the transport's concern, not the formatter's. contentType() = application/json.