Gatekeeper inqaba-security/gatekeeper
v1.x

Log, Null & Custom Sinks

The log sink#

Writes events to a Laravel log channel — useful in development, as a local audit trail, and as the built-in fallback destination when other sinks exhaust their retries.

SIEM_SINKS=log
SIEM_LOG_CHANNEL=security    # optional; defaults to your default channel
Key Env Default
sinks.log.channel SIEM_LOG_CHANNEL default channel
sinks.log.level info

Pair it with a dedicated channel in config/logging.php for a tidy on-box audit file:

'security' => [
    'driver' => 'daily',
    'path' => storage_path('logs/security.log'),
    'days' => 30,
],

The null sink#

Discards everything. Useful as a kill switch in tests or while wiring things up:

SIEM_SINKS=null

Writing a custom sink#

Implement the Sink contract — one method that receives the ECS document and throws on failure (so the queue can retry):

namespace App\Siem;

use Inqaba\Gatekeeper\Sinks\Sink;

class KafkaSink implements Sink
{
    public function __construct(private KafkaProducer $producer) {}

    public function send(array $event): void
    {
        $this->producer->produce('security-events', json_encode($event));
    }

    public function name(): string
    {
        return 'kafka';
    }
}

Register it in a service provider:

use Inqaba\Gatekeeper\Facades\Siem;

public function boot(): void
{
    Siem::extend('kafka', fn ($app) => new KafkaSink($app->make(KafkaProducer::class)));
}

Activate it like any built-in sink:

SIEM_SINKS=sentinel,kafka

Your sink automatically participates in the whole pipeline: queue shipping with retries, GeoIP enrichment, redaction, the global severity floor, and — if you add a minimum_severity key under sinks.kafka in the config — per-sink severity filtering.

Contract notes#

  • send() receives the final ECS document as an array (post-redaction, post-enrichment). Ship it as-is where possible so downstream consumers get consistent fields.
  • Throw on failure. Exceptions trigger the retry/backoff cycle; after the final attempt the event is written to the local log with the full payload. Swallowing errors silently loses events.
  • Sinks are resolved once and cached per worker process — keep constructors cheap and connection setup lazy if your transport is heavy.

Ideas#

  • Kafka / Kinesis / PubSub — stream into a data lake or detection pipeline
  • Elasticsearch / OpenSearch — the events are already ECS; index them directly
  • PagerDuty / Opsgenie — a minimum_severity: critical incident sink
  • Database table — a quick local security dashboard without SIEM round-trips