Real-Time Log Analytics for Laravel Microservices Using ELK Stack

When a checkout slows down, minutes matter. Real-time log analytics with ELK gives you a live view of every Laravel service, so you can fix issues before they hurt the bottom line.

R
Rahim
Author
18 Oct 2026 · 11 min read · 1 views
Real-Time Log Analytics for Laravel Microservices Using ELK Stack

Why Real-Time Log Analytics Matters for Laravel Microservices

Software engineer inspecting distributed microservice logs on dual monitors — Epex Knowledge Hub

You're sprinting a checkout feature. You push, CI spins containers, and five minutes later a user complains about a five-second lag. You hop onto host A, tail service-a.log—nothing. Switch to host B, grep "error"—a stack trace that doesn't match the request. By the time you stitch the timeline together, revenue is already slipping.

That's distributed logging in its raw form. Latency spikes and silent timeouts hide because each service logs in isolation. Real-time aggregation flips the script; every event lands in a single searchable store the instant it's emitted. You can follow a request ID across services, spot a sudden wave of 500s, and fire alerts before users notice.

Enter ELK. Elasticsearch indexes each line, Logstash normalises the payload, Kibana visualises it instantly. Filebeat on each Laravel container ships logs over TLS—no custom agents required. The result: one pane of glass, one query, the full request journey from API gateway to DB microservice. Debugging becomes a click, not a hunt.

Architecture Overview & Technical Prerequisites

Elasticsearch and Kibana real-time log analytics telemetry screen — Epex Knowledge Hub

Data flow, simplified:

Laravel Service (Docker) → Filebeat → Logstash → Elasticsearch → Kibana

Each container runs the official Filebeat binary. Filebeat reads storage/logs/laravel.log, ships JSON-encoded events to Logstash on port 5044. Logstash applies a Grok filter, enriches with service name and container ID, then writes to Elasticsearch. Kibana sits on top, serving dashboards and alerts.

What you need:

  • Docker ≥ 20.10, Docker-Compose ≥ 2.0.
  • Elasticsearch 7.17.x (last stable 7 series, works with Laravel 10).
  • Logstash 7.17.x and Kibana 7.17.x – keep versions in lockstep.
  • PHP 8.1+ with ext-json and ext-mbstring.
  • Filebeat 7.17.x – same major version as Elasticsearch.
  • Network: expose 5044 (beats), 9200 (ES API), 5601 (Kibana). Use a dedicated Docker network so containers talk without opening everything to the host.

Give the host at least 4 GB RAM; Elasticsearch will hog most of it. On a laptop, cap the JVM heap at 2 GB (see tuning later).

Step-by-Step Core Setup

First, spin up the ELK stack with Docker-Compose. Create docker-compose.yml in the project root:

version: '3.8'

services:
 elasticsearch:
 image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10
 container_name: es
 environment:
 - discovery.type=single-node
 - ES_JAVA_OPTS=-Xms2g -Xmx2g
 ulimits:
 memlock:
 soft: -1
 hard: -1
 ports:
 - "9200:9200"
 networks:
 - elk

 logstash:
 image: docker.elastic.co/logstash/logstash:7.17.10
 container_name: logstash
 volumes:
 -./logstash/pipeline:/usr/share/logstash/pipeline:ro
 ports:
 - "5044:5044"
 depends_on:
 - elasticsearch
 networks:
 - elk

 kibana:
 image: docker.elastic.co/kibana/kibana:7.17.10
 container_name: kibana
 ports:
 - "5601:5601"
 depends_on:
 - elasticsearch
 networks:
 - elk

networks:
 elk:
 driver: bridge

Next, add a minimal Logstash pipeline. Create logstash/pipeline/laravel.conf:

input {
 beats {
 port => 5044
 }
}

filter {
 json {
 source => "message"
 target => "log"
 }

 mutate {
 add_field => {
 "service" => "%{[host][name]}"
 }
 }
}

output {
 elasticsearch {
 hosts => ["http://elasticsearch:9200"]
 index => "laravel-%{+YYYY.MM.dd}"
 user => "elastic"
 password => "changeme"
 }
 stdout { codec => rubydebug }
}

Now configure Laravel to emit JSON. In config/logging.php add a new channel:

'elk' => [
 'driver' => 'single',
 'path' => storage_path('logs/laravel.log'),
 'level' => env('LOG_LEVEL', 'debug'),
 'formatter' => Monolog\Formatter\JsonFormatter::class,
],

Make it the default inside containers:

LOG_CHANNEL=elk

Install Filebeat inside each Laravel Docker image. Extend your Dockerfile:

FROM php:8.1-fpm

#... your usual extensions...

# Install Filebeat
RUN curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.10-amd64.deb \
 && dpkg -i filebeat-7.17.10-amd64.deb \
 && rm filebeat-7.17.10-amd64.deb

# Copy Filebeat config
COPY filebeat.yml /etc/filebeat/filebeat.yml

# Ensure the log dir exists
RUN mkdir -p /var/www/html/storage/logs

Filebeat config (filebeat.yml) points to Logstash:

filebeat.inputs:
 - type: log
 enabled: true
 paths:
 - /var/www/html/storage/logs/*.log
 json.message_key: log
 json.keys_under_root: true

output.logstash:
 hosts: ["logstash:5044"]

Fire it up:

docker compose up -d

Open Kibana, create an index pattern laravel-*, and you'll see Laravel JSON events streaming in real time. That's the core loop.

Advanced Configuration & Performance Tuning

Now that the pipeline works, let's tighten it.

Ingest Pipelines for Enrichment

Instead of doing everything in Logstash, push some work to Elasticsearch's ingest node. Create a pipeline that extracts the request ID and adds a timestamp field:

PUT _ingest/pipeline/laravel-pipeline
{
 "description": "Parse Laravel logs",
 "processors": [
 {
 "grok": {
 "field": "message",
 "patterns": ["%{TIMESTAMP_ISO8601:log_timestamp} %{LOGLEVEL:level} %{DATA:msg}"]
 }
 },
 {
 "date": {
 "field": "log_timestamp",
 "formats": ["ISO8601"]
 }
 },
 {
 "rename": {
 "field": "request_id",
 "target_name": "trace.id"
 }
 }
 ]
}

Update Logstash output to reference the pipeline:

output {
 elasticsearch {
 hosts => ["http://elasticsearch:9200"]
 index => "laravel-%{+YYYY.MM.dd}"
 pipeline => "laravel-pipeline"
 }
}

Custom Grok Patterns

If you're using Laravel's default log format, the built-in patterns cover most cases. I often add a pattern for the user_id field my app injects:

POSTGRESQL_LOG %{TIMESTAMP_ISO8601:timestamp} \[%{DATA:level}\] %{GREEDYDATA:msg} user_id=%{INT:user_id}

Save it under logstash/pipeline/patterns/custom and reference it:

grok {
 patterns_dir => ["./patterns"]
 match => { "message" => "%{CUSTOM_LOG}" }
}

Index Lifecycle Management (ILM)

Logs explode fast. Define a policy that rolls over daily and deletes after 30 days:

PUT _ilm/policy/laravel-logs
{
 "policy": {
 "phases": {
 "hot": {
 "actions": {
 "rollover": {
 "max_age": "1d",
 "max_size": "50gb"
 }
 }
 },
 "delete": {
 "min_age": "30d",
 "actions": {
 "delete": {}
 }
 }
 }
 }
}

Attach it to the index template:

PUT _template/laravel
{
 "index_patterns": ["laravel-*"],
 "settings": {
 "number_of_shards": 1,
 "number_of_replicas": 0,
 "index.lifecycle.name": "laravel-logs",
 "index.lifecycle.rollover_alias": "laravel"
 }
}

Batch Size & Pipeline Workers

Filebeat ships events in batches. The default bulk_max_size of 50 works for low traffic, but bump it to 200 if you see "backoff" warnings in Logstash logs. In logstash.yml set:

pipeline.workers: 4
pipeline.batch.size: 125
pipeline.batch.delay: 50

Four workers match a 4-core VM nicely; adjust based on CPU.

JVM Heap Sizing

Elasticsearch runs on the JVM. Never let it stay at the default 1 GB on a production box. Set ES_JAVA_OPTS=-Xms4g -Xmx4g in Docker Compose if you have 8 GB RAM. For dev, keep it at 2 GB to avoid OOM on a laptop.

Kibana Dashboards

Import the community Laravel dashboard from the ELK-Stack-with-Laravel repo. It visualises request latency, error rates per service, and top exception messages. After importing, set the time filter to "Last 5 minutes" and you have a real-time health board.

If you need alerts, use Kibana's rule engine. Create a rule that fires when status >= 500 for a given service exceeds five hits in a minute. Wire the action to a Slack webhook – you'll get a ping before users notice the outage.

Critical Production Pitfalls & Diagnostic Gotchas

Deploying to production flips a switch. Your fancy dev-only logger suddenly behaves like a teenager on caffeine. Here's what actually bites you when you run real-time log analytics for Laravel microservices, plus the commands that surface the trouble fast.

Log format drift – One service still uses the old formatter while the rest have moved on. Elasticsearch starts rejecting docs; the mapping no longer matches. curl -XGET 'http://es-node:9200/_cat/indices?v' | grep _rejected will show a spike. Compare a good _source with a rejected one, then sync Monolog\Formatter\JsonFormatter across config/logging.php. Re-index or spin up a fresh index with the corrected mapping.

Back-pressure on Logstash/Beats – Traffic spikes fill Filebeat's internal queue. You'll see "backoff" warnings. Check the queue size:

journalctl -u filebeat -f | grep "queue full"

Raise queue.mem.events, add another Logstash worker, or tweak bulk_max_size to ship larger batches.

Disk-full Elasticsearch – A single node can run out of space if you never roll over indices. The cluster health goes red; new logs vanish. Verify:

curl -XGET 'http://es-node:9200/_cluster/health?pretty'

If you get "status":"red", free space or add a data node. In production I always enable an ILM policy that rolls over daily and prunes anything older than 30 days.

Time-zone mismatches – Laravel logs in UTC, Kibana often defaults to your local zone. Queries that cross midnight look empty. Check Kibana's Advanced Settings and compare against a sample log:

GET /logs-*/_search
{
 "_source": ["@timestamp"],
 "size": 1,
 "sort": [{ "@timestamp": "desc" }]
}

Force dateFormat:tz to UTC. Consistency wins.

Quick remediation checklist

  • Schedule the curl health checks in cron.
  • Standardise Monolog JSON format in config/logging.php.
  • Enable ILM or Curator for index lifecycle.
  • Boost Filebeat queue settings before traffic spikes.
  • Lock Kibana timezone to UTC.

Comparison & Decision Matrix

Solution Cost Setup Complexity Real-Time Guarantees Laravel Integration Scaling
ELK (Elasticsearch + Logstash + Kibana) Open-source, but hosting & storage add up ($/GB) Medium – pipelines, ILM, Beats needed Near-real-time (sub-second to a few seconds) Native Monolog JSON, Filebeat tailing Horizontal via data nodes; shard mgmt required
Loki + Grafana Free core, optional Grafana Cloud fees Low – only promtail config Seconds-level, label-index dependent Community packages exist, less plug-and-play Chunked storage scales; query latency grows with label cardinality
Graylog Free OSS, enterprise adds cost Medium – MongoDB & Elasticsearch underneath Sub-second to a few seconds, pipeline dependent Syslog/beat inputs; need custom Laravel JSON formatter Clustered nodes, but Elasticsearch stays bottleneck
Splunk (cloud or on-prem) License-based, pricey at high ingest rates High – forwarders, indexing policies, provisioning Real-time (sub-second) with heavy indexing overhead HTTP Event Collector works; thin wrapper required Cloud auto-scales; on-prem needs index clustering

If you already own an ELK cluster, stick with it. Need a lighter footprint and already run Prometheus? Loki feels natural. Graylog shines when you crave a SIEM-like UI without the Elasticsearch price tag. Splunk is overkill unless compliance forces you.

FAQ – High-Intent Technical Questions

How do I ship logs from queued jobs that run on separate workers?

Queued jobs inherit the same logger instance, but they execute in a different process pool. The trick: inject the request ID into the job payload and re-apply it in handle. Example:

class ProcessOrder implements ShouldQueue
{
 public $tries = 3;

 public function __construct(public string $requestId) {}

 public function handle
 {
 Log::withContext(['request_id' => $this->requestId]);
 Log::info('Processing order', ['order_id' => $this->orderId]);
 }
}

Tell Filebeat to watch storage/logs/laravel-queue.log. You'll see the same request_id flowing through Elasticsearch.

Can I filter logs by request ID in Kibana without writing a full query?

Yes. Save a search that uses request_id as a quick filter. In Discover, type request_id:"{{your-id}}", click "Add to filter", then pin the search to a dashboard. Paste a new ID and you instantly get a live view across services.

What's the best way to secure Elasticsearch in a production microservice environment?

Don't expose it directly. Put NGINX or Traefik in front, enable TLS, require client certs. Turn on xpack.security.enabled: true (basic security works even in OSS). Create a role that only allows indices:write for Logstash/Filebeat and a read-only role for Kibana users. Lock the network so only Beats and Logstash IPs can hit the ES port.

How should I handle log rotation without losing entries?

Laravel's single channel writes to storage/logs/laravel.log. If you rotate with logrotate, use copytruncate so the file descriptor stays valid for Filebeat. Example /etc/logrotate.d/laravel:

/var/www/app/storage/logs/*.log {
 daily
 rotate 7
 compress
 missingok
 copytruncate
 notifempty
}

Copytruncate prevents the "file not found" moment that would otherwise drop a batch of lines.

Is it safe to push logs directly from Laravel to Logstash over TCP?

It works, but you lose the resilience Beats give you. Beats buffer locally, handle back-pressure, retry automatically. Going straight TCP means you must code your own retry logic and accept dropped logs during network hiccups. For most teams the extra hop through Filebeat is worth the reliability.

Operational Verification Checklist & Next Steps

  1. Hit each microservice's /health endpoint; look for a log_pipeline flag and a 200 status.
  2. Run GET /_cat/indices?v – all indices should be green or yellow, never red.
  3. Validate Filebeat on every host: systemctl status filebeat and verify "Harvesting" lines appear.
  4. Confirm your ILM policy has rolled over at least once; query _ilm_explain on a recent index.
  5. Set a Kibana alert for logstash.errors > 0 in the last five minutes.
  6. Open the "Live Requests" dashboard; make sure request IDs appear across all services.

Next phase? Add distributed tracing (OpenTelemetry) to correlate logs with spans, and hook the alert webhook into your incident-response Slack channel. That closes the loop: logs, metrics, traces, alerts.

Need a hand tailoring the stack to your Laravel microservices? Our observability service can design, implement, and hand off a production-grade pipeline. Drop us a line and we'll brew a solution that fits your traffic patterns.

R
Author
Rahim

Software engineer and technology contributor at Epex Logics, specializing in modern web development, performance optimization, and scalable software systems.

Technical Consultation

Ready to build or scale your digital presence?

Whether you need a high-performance web platform, e-commerce re-platforming, or custom application engineering, our team delivers production-ready solutions.

Epex Logics

Typically replies instantly

07:18 PM ✓✓

Start WhatsApp Chat

🔒 Your chat is private & secure