Elasticsearch ile Full Text Search Uygulaması
Logları bir yere toplamak artık yetmiyor. Gigabyte’larca log verisi içinde “şu hata ne zaman ilk çıktı?”, “bu kullanıcı hangi sayfalara baktı?”, “geçen hafta production’da ne oldu?” sorularına saniyeler içinde cevap vermek istiyorsanız, Elasticsearch’ün full text search yeteneklerini doğru kullanmanız gerekiyor. Bu yazıda sıfırdan bir arama altyapısı kurmaktan, gerçek dünya log analizi senaryolarına kadar her şeyi ele alacağım.
Elasticsearch’ü Log Yönetimi İçin Düşünmek
Elasticsearch’e sadece “arama motoru” gözüyle bakmak, onun asıl gücünü görmezden gelmek demek. Özellikle ELK Stack içinde kullandığınızda, yapılandırılmamış log verilerini gerçek zamanlı olarak analiz edebilen bir platforma dönüşüyor.
Tipik bir senaryo şöyle: Nginx access loglarınız, uygulama hata loglarınız ve database slow query loglarınız var. Bunların hepsini tek bir yerde topluyor, birbiriyle ilişkilendiriyor ve “şu IP adresi hem Nginx’te 429 aldı, hem de aynı dakikada uygulama loglarında authentication hatası üretiyor” gibi bağlantıları kurabiliyorsunuz. Bunu klasik grep + awk + sort kombinasyonuyla yapmaya çalışmak yerine Elasticsearch kullanmak, saatlik işi dakikalara indiriyor.
Kurulum ve Temel Yapılandırma
Önce temeli doğru atalım. Elasticsearch’ü production’a almadan önce bazı OS seviyesi ayarlar zorunlu.
# /etc/sysctl.conf içine ekle
vm.max_map_count=262144
fs.file-max=65536
# Anında uygula
sysctl -w vm.max_map_count=262144
# /etc/security/limits.conf
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
elasticsearch soft nproc 4096
elasticsearch hard nproc 4096
Bu ayarları atlamanın bedeli ağır: Elasticsearch yüksek yük altında “max virtual memory areas” hatası fırlatır ve index’leme durur. Production’da bunu yaşadım, gece 2’de pagerduty alarmıyla uyandım, not düşeyim.
Docker ile hızlı başlamak isteyenler için:
docker run -d
--name elasticsearch
-p 9200:9200
-p 9300:9300
-e "discovery.type=single-node"
-e "xpack.security.enabled=true"
-e "ELASTIC_PASSWORD=changeme_bunu_degistir"
-e "ES_JAVA_OPTS=-Xms2g -Xmx2g"
-v elasticsearch-data:/usr/share/elasticsearch/data
docker.elastic.co/elasticsearch/elasticsearch:8.11.0
Heap size için altın kural: Toplam RAM’in yarısını ver, ama asla 32GB’ı geçme. 32GB üzerinde JVM compressed pointers kayboluyor ve performans paradoksal şekilde düşebiliyor.
Index Mapping: Log Verisi İçin Doğru Tasarım
Full text search’ün kalitesi büyük ölçüde mapping tasarımına bağlı. Elasticsearch varsayılan olarak dynamic mapping kullanır, yani yeni bir field geldiğinde otomatik tip çıkarımı yapar. Log verileri için bu tehlikeli olabilir.
# Nginx log index için custom mapping oluştur
curl -X PUT "localhost:9200/nginx-logs-2024"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"index.refresh_interval": "5s",
"index.max_result_window": 50000
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"client_ip": { "type": "ip" },
"request_method": { "type": "keyword" },
"request_uri": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 512 }
}
},
"status_code": { "type": "short" },
"response_size": { "type": "long" },
"response_time": { "type": "float" },
"user_agent": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
},
"log_message": {
"type": "text",
"analyzer": "turkish_log_analyzer"
}
}
}
}'
Dikkat etmeniz gereken nokta: request_uri gibi alanları hem text hem de keyword olarak tanımlıyoruz. Text full text search için, keyword ise exact match ve aggregation için. Bu dual mapping pattern’ı log analizinde çok işe yarıyor.
Custom Analyzer ile Türkçe Log Analizi
Standart analyzer Türkçe için yeterince iyi çalışmıyor. Kendi analyzer’ınızı tanımlamak özellikle uygulama logları ve Türkçe hata mesajları içeren sistemlerde fark yaratıyor.
curl -X PUT "localhost:9200/app-logs"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"settings": {
"analysis": {
"analyzer": {
"turkish_log_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"turkish_stop",
"turkish_stemmer",
"asciifolding"
]
},
"log_path_analyzer": {
"type": "custom",
"tokenizer": "path_hierarchy",
"filter": ["lowercase"]
}
},
"filter": {
"turkish_stop": {
"type": "stop",
"stopwords": "_turkish_"
},
"turkish_stemmer": {
"type": "stemmer",
"language": "turkish"
}
}
}
}
}'
asciifolding filter’ını eklemenizi özellikle öneririm. Bu sayede “şifre” araması “sifre” yazarak da çalışır, Türkçe karakter sorunu olmadan arama yapılabilir.
Logstash ile Veri Besleme
Veriyi doğru parse etmeden göndermek, mapping’i ne kadar iyi tasarlarsanız tasarlayın, sonucu berbat eder. Nginx logları için Logstash konfigürasyonu:
# /etc/logstash/conf.d/nginx-to-es.conf
input {
file {
path => "/var/log/nginx/access.log"
start_position => "beginning"
sincedb_path => "/var/lib/logstash/sincedb_nginx"
tags => ["nginx", "access"]
}
file {
path => "/var/log/nginx/error.log"
start_position => "beginning"
sincedb_path => "/var/lib/logstash/sincedb_nginx_error"
tags => ["nginx", "error"]
}
}
filter {
if "access" in [tags] {
grok {
match => {
"message" => '%{IPORHOST:client_ip} - %{DATA:auth_user} [%{HTTPDATE:log_timestamp}] "%{WORD:request_method} %{DATA:request_uri} HTTP/%{NUMBER:http_version}" %{NUMBER:status_code:int} %{NUMBER:response_size:long} "%{DATA:referer}" "%{DATA:user_agent}" %{NUMBER:response_time:float}'
}
}
date {
match => ["log_timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
remove_field => ["log_timestamp"]
}
geoip {
source => "client_ip"
target => "geoip"
}
useragent {
source => "user_agent"
target => "ua_parsed"
}
}
mutate {
remove_field => ["host", "message", "log"]
}
}
output {
elasticsearch {
hosts => ["localhost:9200"]
user => "elastic"
password => "changeme_bunu_degistir"
index => "nginx-logs-%{+YYYY.MM.dd}"
template_name => "nginx-logs"
template_overwrite => true
}
}
Bu konfigürasyonun güzel tarafı: GeoIP ile her isteğin nereden geldiğini otomatik olarak çözüyor, user agent parsing ile hangi tarayıcı/bot olduğunu anlıyor. Kibana’da dünya haritasında görselleştirdiğinizde müşterilere göstermek için harika bir dashboard çıkıyor ortaya.
Full Text Search Sorguları: Gerçek Dünya Kullanımı
Şimdi asıl işe gelelim. Arama sorgularını REST API üzerinden yazıyorum, bunları Kibana Dev Tools’a da kopyalayabilirsiniz.
Senaryo 1: Son bir saatte 500 hatası üreten tüm endpoint’leri bul.
curl -X GET "localhost:9200/nginx-logs-*/_search"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"query": {
"bool": {
"must": [
{ "term": { "status_code": 500 } }
],
"filter": [
{
"range": {
"@timestamp": {
"gte": "now-1h",
"lte": "now"
}
}
}
]
}
},
"aggs": {
"top_failing_endpoints": {
"terms": {
"field": "request_uri.keyword",
"size": 20,
"order": { "_count": "desc" }
}
},
"errors_over_time": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "5m"
}
}
},
"size": 0
}'
size: 0 kullanmak aggregation odaklı sorgularda önemli. Döküman döndürmeden sadece istatistik almak istiyorsanız gereksiz veri transferinden kurtulursunuz.
Senaryo 2: Hata mesajları içinde belirli bir keyword araması, fuzzy matching ile.
curl -X GET "localhost:9200/app-logs-*/_search"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"query": {
"bool": {
"should": [
{
"match": {
"log_message": {
"query": "database connection timeout",
"operator": "and",
"boost": 2.0
}
}
},
{
"match": {
"log_message": {
"query": "veritabani baglanti hatasi",
"fuzziness": "AUTO",
"boost": 1.5
}
}
},
{
"wildcard": {
"log_message": {
"value": "*connection refused*"
}
}
}
],
"minimum_should_match": 1,
"filter": [
{
"range": {
"@timestamp": { "gte": "now-24h" }
}
},
{
"terms": {
"log_level": ["ERROR", "FATAL", "CRITICAL"]
}
}
]
}
},
"highlight": {
"fields": {
"log_message": {
"pre_tags": ["<em>"],
"post_tags": ["</em>"],
"fragment_size": 150
}
}
},
"sort": [
{ "@timestamp": { "order": "desc" } },
{ "_score": { "order": "desc" } }
]
}'
highlight kullanımı özellikle operasyonel senaryolarda değerli. Kibana’da doğrudan göremesek bile API üzerinden dönen response’da hangi kısımların match ettiğini anlamak için idealdir.
Index Lifecycle Management ile Otomatik Yönetim
Log verileri birikir, disk dolar, performans düşer. ILM politikası olmadan Elasticsearch yönetimi gerçek bir kabuğa dönüşebilir.
# ILM politikası tanımla
curl -X PUT "localhost:9200/_ilm/policy/log-retention-policy"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": {
"max_primary_shard_size": "10gb",
"max_age": "1d"
},
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 },
"set_priority": { "priority": 50 }
}
},
"cold": {
"min_age": "30d",
"actions": {
"set_priority": { "priority": 0 },
"freeze": {}
}
},
"delete": {
"min_age": "90d",
"actions": {
"delete": {}
}
}
}
}
}'
Bu politika şunu yapıyor: Aktif loglar hot phase’de, 7 günden eskiler warm phase’e geçip shard sayısı azaltılıyor ve segment merge ediliyor. 30 günden eskiler cold’a düşüyor, 90 günden eskiler siliniyor. Compliance gereksiniminize göre bu süreleri ayarlayabilirsiniz.
Performans Optimizasyonu: Sorgu Hızlandırma
Büyük log hacimleriyle çalışırken sorgular yavaşlamaya başlar. Birkaç kritik optimizasyon:
# Search template ile sık kullanılan sorguları önbelleğe al
curl -X PUT "localhost:9200/_scripts/error-search-template"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"script": {
"lang": "mustache",
"source": {
"query": {
"bool": {
"must": [
{
"match": {
"log_message": "{{search_term}}"
}
}
],
"filter": [
{
"term": { "log_level": "{{level}}" }
},
{
"range": {
"@timestamp": {
"gte": "{{start_time}}",
"lte": "{{end_time}}"
}
}
}
]
}
},
"size": "{{size}}{{^size}}20{{/size}}"
}
}
}'
# Template kullanımı
curl -X GET "localhost:9200/app-logs-*/_search/template"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"id": "error-search-template",
"params": {
"search_term": "NullPointerException",
"level": "ERROR",
"start_time": "now-6h",
"end_time": "now",
"size": 50
}
}'
Bunun dışında pratik bazı önlemler:
filtercontext’i tercih edin: Scoring hesaplamayan filter context, must’a göre çok daha hızlı çalışır. Timestamp range, exact term eşleşmeleri mutlaka filter içinde yazın.- Wildcard sorgularında dikkatli olun:
errorgibi leading wildcard sorgular index taraması yapar, performansı mahveder. Bunun yerinematchveyaquery_stringkullanın. - Shard sayısını aşırı artırmayın: Her shard bir JVM overhead’i demek. GB başına 20-40 shard mantıklı bir başlangıç noktası.
Monitoring ile Elasticsearch’ün Kendisini İzlemek
Elasticsearch ile log yönetimi yapıyorsunuz ama Elasticsearch’ün kendisi sağlıklı mı? Bunu ihmal eden çok sistem yöneticisi gördüm.
# Cluster health kontrolü - basit ama kritik
curl -X GET "localhost:9200/_cluster/health?pretty"
-u elastic:changeme_bunu_degistir
# Node bazlı istatistikler
curl -X GET "localhost:9200/_nodes/stats/indices,os,jvm?pretty"
-u elastic:changeme_bunu_degistir
# Yavaş sorguları tespit et - slow log threshold ayarı
curl -X PUT "localhost:9200/app-logs-*/_settings"
-H 'Content-Type: application/json'
-u elastic:changeme_bunu_degistir
-d '{
"index.search.slowlog.threshold.query.warn": "5s",
"index.search.slowlog.threshold.query.info": "2s",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.indexing.slowlog.threshold.index.warn": "3s",
"index.indexing.slowlog.threshold.index.info": "1s"
}'
Cluster status “red” olduğunda panik yapmak yerine şunu bilin: Red genellikle unassigned primary shard demek. Shard allocation problemlerini teşhis etmek için:
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"
-u elastic:changeme_bunu_degistir
Bu komut size neden shard atanamdığını açıklıyor: disk dolmuş mu, node yeterli mi, replica sayısı node sayısını mı aşıyor? Production’da bu komutu ezberlemenizi öneririm.
Kibana Dashboard Stratejisi
API’yi doğrudan kullanmak güçlü ama operasyonel izleme için Kibana vazgeçilmez. Log yönetimi için etkili bir dashboard şu bileşenleri içermeli:
- Error Rate Over Time:
status_code >= 400için date histogram, anomalileri anında görürsünüz - Top Error Messages: log_message field üzerinde terms aggregation, en sık karşılaşılan hataları gösterir
- Response Time Percentiles: p50, p95, p99 metrikleri, ortalama değil persentil izleyin
- Geographic Distribution: GeoIP kullanıyorsanız harita üzerinde trafik dağılımı
- Slow Queries Table: response_time > 2s olan istekler, performans sorunlarını proaktif yakalamanızı sağlar
Dashboard’u hazırladıktan sonra Alerting ile entegre edin. Elasticsearch 8.x’te native alerting oldukça güçlü. 5 dakikada 50’den fazla 500 hatası gelirse Slack’e mesaj atsın, bu tek başına gecenizi kurtarabilir.
Güvenlik Konuları
Elasticsearch 8.x ile güvenlik varsayılan olarak açık geliyor, eski versiyonlardan gelenler bunu atlamayın. Production’da minimum yapılması gerekenler:
- TLS zorunlu: Hem HTTP hem de transport layer şifreli olmalı
- Role-based access: Farklı takımlar farklı index’lere erişebilmeli. Geliştirici takımı production log’larına erişebilir ama silme yetkisi olmamalı
- API Key kullanın: Logstash ve uygulama entegrasyonları için kullanıcı adı/şifre yerine API key tercih edin, rotation kolaylaşır
- Field-level security: Kişisel veri içeren fieldlar için masking veya kısıtlama tanımlayın, KVKK uyumu için önemli
Sonuç
Elasticsearch full text search yeteneklerini log yönetimiyle doğru birleştirmek ciddi bir operasyonel avantaj sağlıyor. Yüzlerce gigabyte log içinde saniyeler içinde anlamlı bir cevap almak, bir sorunu saatler yerine dakikalarda tespit etmek mümkün hale geliyor.
Başlangıç için yapmanız gereken sıralama şöyle olabilir: Önce OS seviyesi parametreler ve JVM heap, sonra doğru mapping tasarımı, ardından Logstash pipeline kurulumu ve son olarak ILM politikasıyla otomatik yaşam döngüsü yönetimi. Bu dört adımı sağlam attıktan sonra arama sorgularını ve dashboard’ları iteratif olarak geliştirebilirsiniz.
En önemli uyarıyı da şununla bitireyim: Elasticsearch kapasite planlaması yapmadan büyük log hacmiyle başlamayın. Disk, RAM ve CPU hesabını önceden yapın. Index başına shard sayısını, replica politikasını ve veri saklama süresini belirleyin. Bunları sonradan değiştirmek hem zor hem de operasyonel risk yaratıyor.
