Giriş: Log Dosyaları ve Pars Etmenin Önemi
Modern bilgi işlem sistemleri, sunucular, ağ cihazları ve uygulamalar sürekli olarak olay günlükleri (log dosyaları) üretir. Bu loglar, sistemlerin ve uygulamaların çalışma durumları, oluşan hatalar, güvenlik ihlalleri, kullanıcı etkinlikleri ve performans verileri hakkında paha biçilmez bilgiler içerir. Ancak, bu dosyalar genellikle ham ve okunması zor formatlarda bulunur. Milyonlarca satırdan oluşabilen bu devasa veri yığınlarından anlamlı bilgiler çıkarmak için "log pars etme" (log parsing) adı verilen bir sürece ihtiyaç duyarız. Log pars etme, ham log verilerini yapılandırılmış, okunabilir ve analiz edilebilir bir formata dönüştürme işlemidir. Bu süreç, problem tespiti, güvenlik denetimi, performans optimizasyonu ve iş zekası analizi gibi birçok alanda kritik bir rol oynar. Bir betik (script) yazarak bu süreci otomatikleştirmek, zaman kazandırır ve insan hatasını minimize eder.
Log Dosyası Türleri ve Ortak Formatlar
Farklı sistemler ve uygulamalar farklı log formatları kullanır. İşte bazı yaygın log türleri ve onların formatları:
Bu çeşitlilik, log pars betiği yazarken esnek bir yaklaşım benimsememizi gerektirir.
Log Pars Etme Yaklaşımları ve Kullanılabilecek Araçlar/Diller
Logları ayrıştırmak için çeşitli yöntemler ve araçlar mevcuttur:
Bu rehberde, özellikle Python ve Bash kullanarak log pars betikleri yazmaya odaklanacağız, çünkü bunlar çoğu senaryo için yeterli esnekliği ve gücü sunar.
Detaylı Log Pars Betiği Yazım Adımları
Bir log pars betiği geliştirirken izlenmesi gereken temel adımlar şunlardır:
Kapsamlı Python Log Pars Betiği Örneği
Aşağıda, belirtilen adımları birleştiren daha kapsamlı bir Python betiği örneği bulunmaktadır. Bu betik, bir Apache erişim log dosyasını okur, hata durum kodlarına sahip girişleri ayrıştırır ve bunları bir CSV dosyasına kaydeder.
Basit Bash Log Pars Betiği Örneği
Daha küçük ve hızlı görevler için Bash betikleri son derece kullanışlıdır. `grep`, `awk`, `sed` gibi komutlar ile logları filtreleyebilir ve işleyebilirsiniz.
En İyi Uygulamalar ve Performans İpuçları
Bir log pars betiği geliştirirken aşağıdaki en iyi uygulamaları göz önünde bulundurmak önemlidir:
Faydalı Kaynaklar
Sonuç
Log pars betikleri yazmak, sistemlerinizin ve uygulamalarınızın derinlemesine anlaşılması için vazgeçilmez bir beceridir. Bu rehberde ele alınan Python ve Bash örnekleri, çeşitli log formatlarını etkili bir şekilde ayrıştırmak ve değerli bilgileri çıkarmak için size sağlam bir temel sunmaktadır. Doğru araçlar ve yaklaşımlarla, devasa log yığınlarını anlamlı verilere dönüştürebilir, operasyonel verimliliği artırabilir ve güvenlik duruşunuzu güçlendirebilirsiniz. Unutmayın, iyi yazılmış bir log pars betiği, pasif log dosyalarını aktif bir bilgi kaynağına dönüştüren bir köprüdür. Bu alandaki sürekli gelişim ve yeni log formatları göz önüne alındığında, öğrenmeye ve betiklerinizi güncel tutmaya devam etmek önemlidir.
Modern bilgi işlem sistemleri, sunucular, ağ cihazları ve uygulamalar sürekli olarak olay günlükleri (log dosyaları) üretir. Bu loglar, sistemlerin ve uygulamaların çalışma durumları, oluşan hatalar, güvenlik ihlalleri, kullanıcı etkinlikleri ve performans verileri hakkında paha biçilmez bilgiler içerir. Ancak, bu dosyalar genellikle ham ve okunması zor formatlarda bulunur. Milyonlarca satırdan oluşabilen bu devasa veri yığınlarından anlamlı bilgiler çıkarmak için "log pars etme" (log parsing) adı verilen bir sürece ihtiyaç duyarız. Log pars etme, ham log verilerini yapılandırılmış, okunabilir ve analiz edilebilir bir formata dönüştürme işlemidir. Bu süreç, problem tespiti, güvenlik denetimi, performans optimizasyonu ve iş zekası analizi gibi birçok alanda kritik bir rol oynar. Bir betik (script) yazarak bu süreci otomatikleştirmek, zaman kazandırır ve insan hatasını minimize eder.
Log Dosyası Türleri ve Ortak Formatlar
Farklı sistemler ve uygulamalar farklı log formatları kullanır. İşte bazı yaygın log türleri ve onların formatları:
- Web Sunucusu Logları:
- Apache Erişim Logları: Genellikle "Common Log Format (CLF)" veya "Combined Log Format" kullanır. IP adresi, tarih/saat, HTTP metodu, URL, durum kodu, yanıt boyutu gibi bilgileri içerir.
- Nginx Erişim Logları: Apache'ye benzer ancak daha esnek yapılandırma seçenekleri sunar.
- Hata Logları: Sunucu veya uygulamanın karşılaştığı sorunları, uyarıları ve hataları kaydeder.
- Sistem Logları:
- Syslog: Linux/Unix tabanlı sistemlerde yaygın olarak kullanılır. Farklı servislerin ve çekirdeğin olaylarını merkezi bir yerde toplar.
- Auth.log: Kimlik doğrulama ile ilgili olayları (başarılı/başarısız girişler, sudo kullanımları) kaydeder.
- Uygulama Logları: Geliştiricilerin belirli bir uygulamanın davranışını izlemek için oluşturduğu özel formatlardır. Bunlar JSON, XML veya basit metin formatında olabilir.
Bu çeşitlilik, log pars betiği yazarken esnek bir yaklaşım benimsememizi gerektirir.
Log Pars Etme Yaklaşımları ve Kullanılabilecek Araçlar/Diller
Logları ayrıştırmak için çeşitli yöntemler ve araçlar mevcuttur:
- Düzenli İfadeler (Regex): Metin içerisindeki belirli desenleri eşleştirmek ve yakalamak için güçlü bir yöntemdir. Özellikle yapılandırılmamış veya yarı yapılandırılmış loglar için idealdir.
- Programlama Dilleri:
- Python: Metin işleme yetenekleri, zengin kütüphane ekosistemi (re, json, pandas) ve okunabilirliği sayesinde log pars etme için en popüler dillerden biridir.
- Perl: Metin işleme ve regex konusunda güçlü tarihsel bir geçmişi vardır, ancak son zamanlarda Python'ın popülaritesinin gerisinde kalmıştır.
- Bash/Shell Scripting: grep, awk, sed gibi komutlarla hızlı ve basit pars işlemleri için etkilidir. Özellikle küçük ve orta ölçekli görevler için idealdir.
- Go/Java/C#: Büyük ölçekli, yüksek performans gerektiren log işleme sistemleri için tercih edilebilirler.
- Özel Log Yönetim Araçları: Logstash, Splunk, Graylog, ELK Stack (Elasticsearch, Logstash, Kibana) gibi ticari veya açık kaynaklı çözümler, log toplama, ayrıştırma, indeksleme, depolama ve görselleştirme yeteneklerini bir arada sunar.
Bu rehberde, özellikle Python ve Bash kullanarak log pars betikleri yazmaya odaklanacağız, çünkü bunlar çoğu senaryo için yeterli esnekliği ve gücü sunar.
Detaylı Log Pars Betiği Yazım Adımları
Bir log pars betiği geliştirirken izlenmesi gereken temel adımlar şunlardır:
- Adım 1: Log Dosyasını Okuma
Log pars etmenin ilk adımı, işlenecek log dosyasına erişmek ve içeriğini okumaktır. Basit bir metin dosyası ise satır satır okunabilir. Ancak, çok büyük log dosyaları (gigabaytlarca) ile çalışırken belleği verimli kullanmak önemlidir. Bu tür durumlarda, dosyanın tamamını belleğe yüklemek yerine, bir iteratör (döngü) kullanarak satır satır okumak en iyi yaklaşımdır.
Kod:# Python ile dosya okuma örneği def read_log_file(filepath): try: with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: for line in f: yield line.strip() # Her satırı temizleyip döndür except FileNotFoundError: print(f"Hata: Dosya bulunamadı: {filepath}") except Exception as e: print(f"Dosya okunurken bir hata oluştu: {e}") # Kullanım örneği: # for log_entry in read_log_file("sunucu.log"): # print(log_entry)
"Veri girişi her zaman tahmin edilenden daha kirli olacaktır. Bu nedenle, dosya okuma ve ilk temizleme adımları kritik öneme sahiptir." - Bilinmeyen Kaynak - Adım 2: Veri Ayrıştırma (Parsing)
Okunan her log satırını, yapılandırılmış verilere dönüştürme işlemidir. Bu, log formatına bağlı olarak farklı yöntemlerle yapılabilir.
- Regex (Düzenli İfadeler) Kullanımı: En yaygın ve güçlü yöntemlerden biridir. Karmaşık desenleri tanımak ve log satırlarından belirli alanları (IP adresi, tarih, URL, durum kodu vb.) çıkarmak için kullanılır.
Kod:import re # Apache Combined Log Format örneği # 127.0.0.1 - - [10/Oct/2023:10:00:01 +0300] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" APACHE_LOG_PATTERN = re.compile( r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(.*?)\] "(.*?)" (\d{3}) (\d+) "(.*?)" "(.*?)"' ) def parse_apache_log(log_line): match = APACHE_LOG_PATTERN.match(log_line) if match: ip_address, timestamp, request, status_code, bytes_sent, referrer, user_agent = match.groups() return { "ip_address": ip_address, "timestamp": timestamp, "request": request, "status_code": int(status_code), "bytes_sent": int(bytes_sent), "referrer": referrer, "user_agent": user_agent } return None # Kullanım örneği: # log_line = '192.168.1.1 - - [20/Nov/2023:14:30:05 +0200] "GET /api/data HTTP/1.1" 200 567 "-" "PostmanRuntime/7.29.0"' # parsed_data = parse_apache_log(log_line) # if parsed_data: # print(parsed_data)
Regex öğrenmek ve test etmek için harika bir kaynak: https://regex101.com/
- Delimiter (Ayraç) Kullanımı: Log satırlarının virgül, sekme veya başka bir karakterle ayrıldığı durumlarda string.split() gibi yöntemler kullanılabilir. CSV dosyaları bu kategoriye girer.
Kod:# Virgülle ayrılmış log örneği: time,level,message,source def parse_csv_log(log_line): parts = log_line.strip().split(',') if len(parts) == 4: return { "time": parts[0], "level": parts[1], "message": parts[2], "source": parts[3] } return None
- JSON/XML Pars Etme: Eğer loglar doğrudan JSON veya XML formatında yazılmışsa, ilgili kütüphaneler (Python'da `json` modülü veya `xml.etree.ElementTree`) kullanılarak doğrudan nesnelere dönüştürülebilir. Bu, en kolay ayrıştırma yöntemidir çünkü veriler zaten yapılandırılmıştır.
Kod:import json # JSON log örneği: {"timestamp": "2023-11-20T14:30:05Z", "level": "INFO", "message": "User logged in", "user_id": 123} def parse_json_log(log_line): try: return json.loads(log_line) except json.JSONDecodeError: return None # Kullanım örneği: # json_log = '{"timestamp": "2023-11-20T14:30:05Z", "level": "INFO", "message": "User logged in", "user_id": 123}' # parsed_json = parse_json_log(json_log) # if parsed_json: # print(parsed_json)
- Regex (Düzenli İfadeler) Kullanımı: En yaygın ve güçlü yöntemlerden biridir. Karmaşık desenleri tanımak ve log satırlarından belirli alanları (IP adresi, tarih, URL, durum kodu vb.) çıkarmak için kullanılır.
- Adım 3: Veri Filtreleme ve Dönüştürme
Ayrıştırılan veriler üzerinde belirli kriterlere göre filtreleme yapabilir veya daha ileri analizler için formatlarını dönüştürebiliriz. Örneğin, sadece hata loglarını almak, belirli bir IP'den gelen istekleri saymak veya tarih/saat damgalarını daha kullanışlı bir formata dönüştürmek isteyebiliriz.
Kod:from datetime import datetime def filter_and_transform_apache_log(parsed_data): if parsed_data and parsed_data["status_code"] >= 400: # Sadece hata kodları (4xx veya 5xx) # Zaman damgasını datetime nesnesine dönüştürme # Örnek: "10/Oct/2023:10:00:01 +0300" try: # Regex ile yakalanan zaman damgası biraz karmaşık, önce formatını basitleştirelim. # Daha iyi bir yaklaşım, regex'i bu kısmı daha kesin yakalamak için ayarlamaktır. # Şimdilik, sadece tarih ve saati alalım. dt_str = parsed_data["timestamp"].split(' ')[0] # "10/Oct/2023:10:00:01" dt_obj = datetime.strptime(dt_str, "%d/%b/%Y:%H:%M:%S") parsed_data["timestamp_dt"] = dt_obj return parsed_data except ValueError as e: print(f"Tarih dönüştürme hatası: {e} için {parsed_data['timestamp']}") return None return None # Kullanım örneği: # parsed_log = parse_apache_log('192.168.1.5 - - [20/Nov/2023:15:00:10 +0200] "GET /nonexistent HTTP/1.1" 404 345 "-" "curl/7.64.1"') # error_log = filter_and_transform_apache_log(parsed_log) # if error_log: # print(error_log)
- Adım 4: Veriyi Depolama veya Analiz Etme
İşlenen verilerle ne yapacağımız, betiğin amacına bağlıdır. Yaygın seçenekler şunlardır:
- Yeni bir dosyaya yazma: İşlenmiş verileri (örneğin CSV veya JSON formatında) başka bir dosyaya kaydetmek.
- Veritabanına kaydetme: PostgreSQL, MySQL gibi ilişkisel veritabanlarına veya MongoDB, Elasticsearch gibi NoSQL veritabanlarına kaydetmek.
- Doğrudan analiz veya uyarı sistemi: Verileri anında işleyip bir gösterge panosuna göndermek, belirli bir olay durumunda uyarı tetiklemek veya gerçek zamanlı analiz yapmak.
Kod:import csv def save_to_csv(data_list, filename="parsed_logs.csv"): if not data_list: return keys = data_list[0].keys() with open(filename, 'w', newline='', encoding='utf-8') as output_file: dict_writer = csv.DictWriter(output_file, fieldnames=keys) dict_writer.writeheader() dict_writer.writerows(data_list) print(f"{len(data_list)} kayıt {filename} dosyasına yazıldı.") # Toplu işleme örneği: # all_parsed_errors = [] # for line in read_log_file("sunucu.log"): # parsed = parse_apache_log(line) # filtered = filter_and_transform_apache_log(parsed) # if filtered: # all_parsed_errors.append(filtered) # save_to_csv(all_parsed_errors)
Kapsamlı Python Log Pars Betiği Örneği
Aşağıda, belirtilen adımları birleştiren daha kapsamlı bir Python betiği örneği bulunmaktadır. Bu betik, bir Apache erişim log dosyasını okur, hata durum kodlarına sahip girişleri ayrıştırır ve bunları bir CSV dosyasına kaydeder.
Kod:
import re
import csv
from datetime import datetime
# Regex deseni (Apache Combined Log Format için)
APACHE_LOG_PATTERN = re.compile(
r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - - \[(.*?)\s[+\-]\d{4}\] "(.*?)" (\d{3}) (\d+) "(.*?)" "(.*?)"'
)
def read_log_file_generator(filepath):
"""
Belirtilen log dosyasını satır satır okuyan bir jeneratör.
Büyük dosyalar için bellek verimliliği sağlar.
"""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
yield line.strip()
except FileNotFoundError:
print(f"[HATA] Dosya bulunamadı: {filepath}")
return
except Exception as e:
print(f"[HATA] Dosya okunurken bir sorun oluştu: {e}")
return
def parse_apache_log_entry(log_line):
"""
Bir Apache log satırını ayrıştırır ve bir sözlük döndürür.
Eşleşme bulunamazsa None döner.
"""
match = APACHE_LOG_PATTERN.match(log_line)
if match:
try:
ip_address, timestamp_str, request, status_code_str, bytes_sent_str, referrer, user_agent = match.groups()
# Tarih ve saat bilgisini datetime objesine dönüştür
# Örnek: "10/Oct/2023:10:00:01"
dt_obj = datetime.strptime(timestamp_str.split(' ')[0], "%d/%b/%Y:%H:%M:%S")
return {
"ip_address": ip_address,
"timestamp": dt_obj.isoformat(), # ISO formatına dönüştürüyoruz
"request_method": request.split(' ')[0] if request else "",
"request_path": request.split(' ')[1] if len(request.split(' ')) > 1 else "",
"http_version": request.split(' ')[2] if len(request.split(' ')) > 2 else "",
"status_code": int(status_code_str),
"bytes_sent": int(bytes_sent_str),
"referrer": referrer,
"user_agent": user_agent,
"raw_log_line": log_line # Orijinal log satırını da tutmak faydalı olabilir
}
except (ValueError, IndexError) as e:
print(f"[UYARI] Ayrıştırma veya dönüşüm hatası: {e} - Satır: {log_line}")
return None
return None
def main_parser(log_filepath, output_csv_filepath, filter_status_code_gte=None):
"""
Log dosyasını okur, ayrıştırır, filtreler ve CSV'ye kaydeder.
"""
parsed_entries = []
processed_count = 0
error_count = 0
print(f"'{log_filepath}' dosyası işleniyor...")
for line in read_log_file_generator(log_filepath):
processed_count += 1
parsed_data = parse_apache_log_entry(line)
if parsed_data:
if filter_status_code_gte is None or parsed_data["status_code"] >= filter_status_code_gte:
parsed_entries.append(parsed_data)
else:
error_count += 1
if parsed_entries:
# CSV başlıkları için ilk öğenin anahtarlarını kullan
fieldnames = list(parsed_entries[0].keys())
try:
with open(output_csv_filepath, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(parsed_entries)
print(f"Başarıyla ayrıştırılan {len(parsed_entries)} kayıt '{output_csv_filepath}' adresine kaydedildi.")
except IOError as e:
print(f"[HATA] CSV dosyasına yazarken hata oluştu: {e}")
else:
print("Filtreleme kriterlerine uyan veya ayrıştırılabilecek kayıt bulunamadı.")
print(f"Toplam işlenen satır: {processed_count}, Hata veren satır: {error_count}")
print("Log ayrıştırma işlemi tamamlandı.")
# Betiği çalıştırma örneği (bu kısmı gerçek kullanımda yorumdan çıkarılmalıdır)
# if __name__ == "__main__":
# # Örnek bir log dosyası oluşturun veya mevcut bir dosyayı kullanın
# # test_log_content = """
# # 127.0.0.1 - - [20/Nov/2023:10:00:01 +0300] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
# # 192.168.1.5 - - [20/Nov/2023:10:00:02 +0300] "POST /api/users HTTP/1.1" 201 56 "-" "Postman"
# # 10.0.0.10 - - [20/Nov/2023:10:00:03 +0300] "GET /nonexistent HTTP/1.1" 404 345 "-" "curl/7.64.1"
# # 172.16.0.1 - - [20/Nov/2023:10:00:04 +0300] "GET /admin HTTP/1.1" 401 223 "-" "Chrome"
# # invalid log entry here
# # 192.168.1.10 - - [20/Nov/2023:10:00:05 +0300] "GET /error.php HTTP/1.1" 500 1200 "-" "Firefox"
# # """
# # with open("sample_apache.log", "w") as f:
# # f.write(test_log_content.strip())
#
# # main_parser("sample_apache.log", "parsed_errors.csv", filter_status_code_gte=400)
# # main_parser("sample_apache.log", "all_access.csv") # Tüm logları kaydet
Basit Bash Log Pars Betiği Örneği
Daha küçük ve hızlı görevler için Bash betikleri son derece kullanışlıdır. `grep`, `awk`, `sed` gibi komutlar ile logları filtreleyebilir ve işleyebilirsiniz.
Kod:
#!/bin/bash
LOG_FILE="syslog.log"
OUTPUT_FILE="filtered_errors.log"
echo "Hata logları filtreleniyor ve '$OUTPUT_FILE' dosyasına yazılıyor..."
# syslog dosyasındaki "error" veya "fail" kelimesini içeren satırları bul
# ve tarih, işlem ve mesajı ayıkla
grep -E "error|fail" "$LOG_FILE" | awk '{print $1, $2, $3, $5, $6, $7, $8, $9, $10, $11}' > "$OUTPUT_FILE"
echo "İşlem tamamlandı."
echo "Örnek kullanım (Bash):"
echo ' # son 100 satırda "Failed password" içeren logları göster'
echo ' tail -n 100 /var/log/auth.log | grep "Failed password"'
echo ''
echo ' # Belirli bir IP adresinden gelen Apache isteklerini say'
echo ' awk "$1 == \"192.168.1.1\" {count++} END {print count}" /var/log/apache2/access.log'
En İyi Uygulamalar ve Performans İpuçları
Bir log pars betiği geliştirirken aşağıdaki en iyi uygulamaları göz önünde bulundurmak önemlidir:
- Hata Yönetimi: Log dosyaları genellikle bozuk veya beklenmeyen formatta satırlar içerebilir. Betiğinizin bu tür satırları atlaması veya hata kaydetmesi, ancak çalışmaya devam etmesi için uygun `try-except` blokları veya koşullu kontroller kullanın.
- Bellek ve CPU Optimizasyonu: Büyük log dosyalarıyla çalışırken, dosyanın tamamını belleğe yüklemek yerine satır satır işleyen jeneratörler veya akış tabanlı okuma yöntemleri kullanın. Regex desenlerinizi mümkün olduğunca verimli yazmaya çalışın.
- Otomasyon: Log pars betiklerini `cron` (Linux) veya Görev Zamanlayıcı (Windows) gibi araçlarla düzenli olarak çalışacak şekilde planlayın. Bu, sürekli bir izleme ve analiz sağlar.
- Modüler Tasarım: Betiğinizi dosya okuma, ayrıştırma, filtreleme ve depolama gibi ayrı işlevlere bölmek, kodun okunabilirliğini, test edilebilirliğini ve bakımını kolaylaştırır.
- Kayıt Formatı Standardizasyonu: Mümkünse, log üreten sistemlerinizde JSON gibi yapılandırılmış bir log formatı kullanmayı teşvik edin. Bu, ayrıştırma sürecini büyük ölçüde basitleştirir.
- Log Rotasyonu: İşletim sistemleri veya log yönetim araçları tarafından yapılan log rotasyonunu (eski log dosyalarını sıkıştırma veya silme) dikkate alın. Betiğinizin sıkıştırılmış logları (örn. .gz) okuyabilmesi veya rotasyon sonrası yeni log dosyalarını bulabilmesi gerekebilir.
Faydalı Kaynaklar
Sonuç
Log pars betikleri yazmak, sistemlerinizin ve uygulamalarınızın derinlemesine anlaşılması için vazgeçilmez bir beceridir. Bu rehberde ele alınan Python ve Bash örnekleri, çeşitli log formatlarını etkili bir şekilde ayrıştırmak ve değerli bilgileri çıkarmak için size sağlam bir temel sunmaktadır. Doğru araçlar ve yaklaşımlarla, devasa log yığınlarını anlamlı verilere dönüştürebilir, operasyonel verimliliği artırabilir ve güvenlik duruşunuzu güçlendirebilirsiniz. Unutmayın, iyi yazılmış bir log pars betiği, pasif log dosyalarını aktif bir bilgi kaynağına dönüştüren bir köprüdür. Bu alandaki sürekli gelişim ve yeni log formatları göz önüne alındığında, öğrenmeye ve betiklerinizi güncel tutmaya devam etmek önemlidir.