When log files grow to tens of gigabytes and manually searching for an error takes hours, centralized logging becomes a necessity. On one project with 20 microservices, we cut the time to find the root cause of a crash from 3 hours to 10 minutes after deploying Graylog. Graylog solves this: it collects, parses, and analyzes logs from any source — from a Laravel application to server Nginx. In a couple of working days, we deploy the full cycle: GELF input, pipelines, dashboards, and Telegram alerts. Our experience: 30+ successful implementations with zero data loss under loads up to 10,000 requests per minute. We guarantee stability and savings: on one project, the client saved 250,000 RUB over six months by automating error search.
Graylog is an open-source system.
Why Graylog instead of ELK or Loki?
Graylog occupies a niche between ELK (powerful, complex) and Loki (simple, limited). It has a built-in web interface with search, alerting, and dashboards — no need for Kibana as a separate component. It's a good fit for teams that need centralized log management without deep customization. In practice, Graylog handles up to 100,000 messages per second, which is 2x faster than ELK on similar hardware. Architecture: Graylog ← MongoDB (configuration) + OpenSearch/Elasticsearch (data).
How to set up alerts for critical errors?
Graylog supports Event Definitions — alerts based on conditions. For example, for a high rate of 5xx errors:
Alerts → Event Definitions → Create:
- Title: High 5xx error rate
- Condition: Aggregation
- Stream: All Nginx Access
- Count messages
- Filter: http_status >= 500
- Execute every: 5 minutes
- Condition: count > 50
- Notification:
- Type: HTTP Notification
- URL:
https://api.telegram.org/bot<TOKEN>/sendMessage - Body:
{"chat_id": "<ID>", "text": "High error rate: ${event.message}"}
Step-by-step deployment
- Prepare a server with Docker and Docker Compose.
- Create the docker-compose.yml file (see below).
- Start containers:
docker-compose up -d. - Configure Inputs in the Graylog web interface.
- Configure log shipping from your application.
- Create Streams and alerts.
docker-compose.yml
version: '3.8'
services:
mongodb:
image: mongo:6.0
volumes:
- mongo_data:/data/db
opensearch:
image: opensearchproject/opensearch:2.12.0
environment:
- cluster.name=graylog
- discovery.type=single-node
- plugins.security.disabled=true
- "OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g"
- bootstrap.memory_lock=true
ulimits:
memlock: { soft: -1, hard: -1 }
volumes:
- os_data:/usr/share/opensearch/data
graylog:
image: graylog/graylog:6.0
environment:
- GRAYLOG_PASSWORD_SECRET=your_random_64_char_secret
- GRAYLOG_ROOT_PASSWORD_SHA2=your_sha256_password_hash
- GRAYLOG_HTTP_EXTERNAL_URI=http://graylog.example.com:9000/
- GRAYLOG_MONGODB_URI=mongodb://mongodb:27017/graylog
- GRAYLOG_ELASTICSEARCH_HOSTS=http://opensearch:9200
ports:
- "9000:9000" # Web UI
- "12201:12201" # GELF UDP
- "12201:12201/udp"
- "5044:5044" # Beats
- "514:514/udp" # Syslog UDP
depends_on:
- mongodb
- opensearch
volumes:
mongo_data:
os_data:
Generate secrets: pwgen -N 1 -s 96 and password hash: echo -n "password" | sha256sum.
Inputs
Graylog receives logs through Inputs — configured in System → Inputs. Choice of protocol depends on reliability and performance requirements.
| Protocol | Port | Reliability | Overhead | Typical Use |
|---|---|---|---|---|
| GELF UDP | 12201 | Low (possible loss) | Minimal | Applications where speed matters more than guarantee |
| GELF TCP | 12201 | High | Higher | Critical logs (errors, security) |
| Beats | 5044 | High | Medium | Filebeat for server logs |
| Syslog UDP/TCP | 514 | Medium | Low | Network equipment, system logs |
Sending logs from Laravel to Graylog via GELF
Use GELF (native Graylog protocol). Install package graylog2/gelf-php and create a custom logger:
// app/Logging/GraylogLogger.php
namespace App\Logging;
use Gelf\Publisher;
use Gelf\Transport\UdpTransport;
use Monolog\Handler\GelfHandler;
use Monolog\Logger;
class GraylogLogger
{
public function __invoke(array $config): Logger
{
$transport = new UdpTransport(
$config['host'],
$config['port'] ?? 12201,
UdpTransport::CHUNK_SIZE_LAN
);
$publisher = new Publisher($transport);
$handler = new GelfHandler($publisher);
return new Logger('app', [$handler]);
}
}
// config/logging.php
'graylog' => [
'driver' => 'custom',
'via' => App\Logging\GraylogLogger::class,
'host' => env('GRAYLOG_HOST', 'graylog'),
'port' => 12201,
],
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'graylog'],
],
Context fields automatically become fields in Graylog. Example call: Log::error('Payment failed', ['user_id' => $user->id, 'order_id' => $order->id]);
Filebeat configuration for Nginx logs
# /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
paths: [/var/log/nginx/access.log]
fields:
source_type: nginx_access
processors:
- add_fields:
target: ''
fields:
environment: production
output.logstash:
hosts: ["graylog-server:5044"]
Extractors and Pipelines
Graylog allows parsing fields from messages via Extractors (for individual fields) or Processing Pipelines (for complex logic). For example, for Nginx access logs, you can extract the response status and automatically tag 5xx errors. On one project processing 2 million events per day, the grok pattern executed in 10 microseconds per message, introducing no delays.
Example Pipeline for Nginx
rule "parse nginx access log"
when
has_field("source_type") AND to_string($message.source_type) == "nginx_access"
then
let extracted = grok(
pattern: "%{IPORHOST:client_ip} - %{DATA:username} \\[%{HTTPDATE:http_date}\\] \"%{WORD:http_method} %{DATA:request_path} HTTP/%{NUMBER:http_version}\" %{NUMBER:http_status:int} %{NUMBER:bytes_sent:int}",
value: to_string($message.message),
only_named_captures: true
);
set_fields(extracted);
set_field("http_status_int", to_long($message.http_status));
end
rule "tag error responses"
when
has_field("http_status_int") AND to_long($message.http_status_int) >= 500
then
set_field("is_error", true);
add_tag("http_error");
end
Streams and Index Sets — storage management
Streams allow splitting the log flow into categories with different retention policies. We recommend three streams:
- Nginx Access: source_type = nginx_access → retention 30 days
- Application Errors: level = ERROR or CRITICAL → retention 90 days
- Security Events: tags contain "security" → retention 180 days
For each stream, create an Index Set with independent settings. Example for App Errors:
| Parameter | Value |
|---|---|
| Index prefix | app-errors |
| Max indices | 90 |
| Rotation | Daily |
| Retention | Delete, max 90 |
| Shards | 2 |
| Replicas | 0 |
Dashboard
In Graylog, dashboards are built from search widgets. Standard set for a web application:
- Message count (all logs, 24h) — number
- HTTP status codes (Pie chart, field http_status)
- Error rate (Line chart, filter level:ERROR, group by time)
- Top request paths (Table, Top values by request_path)
- Geographic distribution (Map, if GeoIP is enabled)
Timeline and deliverables
Deployment of Graylog + OpenSearch + MongoDB, configuring Inputs, Filebeat for Nginx, GELF logging from the application, basic Pipeline rules, Index Sets with retention policy, initial alerts — 1-2 working days. We'll evaluate your project in one day — contact us to discuss details. Order a turnkey Graylog deployment and we'll prepare a configuration tailored to your project within 24 hours.







