Winston ile Node.js Uygulamalarını Ubuntu 16.04 Üzerinde Günlüğe Kaydetme Rehberi
Giriş
Modern yazılım geliştirmenin temel taşlarından biri, uygulamaların davranışlarını izlemek, hataları ayıklamak ve performans sorunlarını tespit etmek için kapsamlı bir günlük (logging) sistemine sahip olmaktır. Node.js uygulamaları, özellikle sunucu tarafında çalıştıklarında, üretim ortamında oluşan sorunları hızlıca tespit edebilmek ve çözebilmek için güçlü bir günlükleme mekanizmasına ihtiyaç duyarlar. Geleneksel console.log() kullanımı geliştirme aşamasında yeterli olsa da, üretim ortamında bu yöntem yetersiz kalır. Günlük seviyeleri, farklı çıktı hedefleri (konsol, dosya, veritabanı, uzak sunucu) ve özelleştirilebilir formatlar gibi özellikler sunan profesyonel bir günlükleme kütüphanesi kullanmak zorunluluktur.
Bu teknik makalede, Node.js ekosisteminin en popüler ve güçlü günlükleme kütüphanelerinden biri olan Winston’ı, Ubuntu 16.04 işletim sistemi üzerinde Node.js uygulamalarınızı günlüğe kaydetmek için nasıl kullanacağınızı ayrıntılı olarak ele alacağız. Winston’ın temel kurulumundan gelişmiş konfigürasyonlarına, farklı günlük taşıyıcılarının (transports) kullanımından hata yönetimine ve Ubuntu’nun yerel araçlarıyla (örneğin logrotate) entegrasyonuna kadar her adımı pratik örneklerle açıklayacağız. Amacımız, Node.js uygulamalarınız için sağlam, yönetilebilir ve etkili bir günlükleme altyapısı oluşturmanıza yardımcı olmaktır.
Neden Winston?
Piyasada birçok Node.js günlükleme kütüphanesi bulunmasına rağmen, Winston birçok geliştirici tarafından tercih edilmektedir. Bunun başlıca nedenleri şunlardır:
* Modüler Mimari (Transports): Winston, günlük mesajlarını farklı hedeflere (konsol, dosya, HTTP, veritabanı vb.) gönderebilen “taşıyıcılar” (transports) adı verilen modüler bir yapıya sahiptir. Bu, aynı günlük mesajını farklı formatlarda birden fazla yere göndermenize olanak tanır.
* Esnek Günlük Seviyeleri: error, warn, info, http, verbose, debug, silly gibi standart günlük seviyelerini destekler ve isteğe bağlı olarak özel seviyeler tanımlamanıza izin verir. Bu seviyeler sayesinde, belirli önem derecesindeki mesajları filtreleyebilir ve yalnızca ilgili günlükleri görebilirsiniz.
* Geniş Formatlama Seçenekleri: Winston, günlük mesajlarının çıktısını istediğiniz gibi biçimlendirmenizi sağlayan zengin formatlama seçenekleri sunar. JSON, metin, zaman damgası, renkli çıktı ve tamamen özel formatlar oluşturabilirsiniz.
* Hata Yönetimi: Uygulamanızdaki yakalanmamış istisnaları (uncaught exceptions) ve işlenmemiş reddedilmeleri (unhandled rejections) otomatik olarak günlüğe kaydetme yeteneği, üretim ortamındaki kritik hataları tespit etmek için hayati öneme sahiptir.
* Genişletilebilirlik: Kendi özel taşıyıcılarınızı veya formatlarınızı kolayca yazarak Winston’ı ihtiyaçlarınıza göre uyarlayabilirsiniz.
* Topluluk Desteği: Aktif bir topluluğa ve iyi belgelere sahip olması, karşılaşılan sorunlarda hızlı çözümler bulmayı kolaylaştırır.
Bu özellikler, Winston’ı Node.js uygulamaları için güçlü ve esnek bir günlükleme çözümü haline getirmektedir.
Ön Koşullar
Bu rehberi takip edebilmek için aşağıdaki ön koşulların sağlanmış olması gerekmektedir:
* Ubuntu 16.04 İşletim Sistemi: Bir Ubuntu 16.04 sunucusu veya sanal makinesi. (Temel Node.js ve Winston prensipleri diğer Linux dağıtımlarında da geçerli olsa da, bazı sistem ayarları Ubuntu 16.04’e özel olabilir.)
* Node.js ve npm: Ubuntu 16.04 üzerinde Node.js ve npm (Node Package Manager) kurulu olmalıdır.
* Temel Node.js Bilgisi: Node.js modül sistemi, callback’ler, promise’ler ve genel JavaScript sözdizimi hakkında temel bilgi.
* Temel Linux Komut Satırı Bilgisi: Dosya sistemi navigasyonu, paket yükleme ve temel sistem komutları hakkında bilgi.
Ubuntu 16.04 Ortamının Hazırlanması
İlk olarak, Node.js ve npm’in sisteminizde kurulu olduğundan emin olalım. Eğer kurulu değilse, Node..js’yi kurmanın birkaç yolu vardır. nvm (Node Version Manager) kullanmak, farklı Node.js sürümleri arasında geçiş yapma esnekliği sunduğu için önerilir.
1. Sistemi Güncelleme:
sudo apt update
sudo apt upgrade
2. Node.js ve npm Kurulumu (nvm ile Önerilen):
nvm kurulumu için aşağıdaki komutu kullanabilirsiniz:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
Kurulumdan sonra terminali kapatıp açın veya aşağıdaki komutu çalıştırın:
source ~/.bashrc
Ardından, Node.js’nin son LTS (Uzun Süreli Destek) sürümünü kurun:
nvm install --lts
nvm use --lts
nvm alias default 'lts/*'
Kurulumu doğrulayın:
node -v
npm -v
Eğer apt ile kurmayı tercih ediyorsanız (daha az esneklik sunar):
sudo apt install curl
curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt install nodejs
3. Örnek Node.js Projesi Oluşturma:
Günlükleme işlemlerini test etmek için basit bir Node.js projesi oluşturalım:
mkdir my-winston-app
cd my-winston-app
npm init -y
touch app.js
Artık my-winston-app dizininde bir package.json dosyası ve app.js dosyanız olmalı.
Winston’ı Yükleme
Projenize Winston’ı eklemek oldukça basittir. npm kullanarak yükleyebilirsiniz:
npm install winston
Winston’ın 3.x sürümü şu anda kararlı ve yaygın olarak kullanılmaktadır. Eğer belirli bir sürümü hedeflemek isterseniz, winston@3 veya winston@next kullanabilirsiniz, ancak npm install winston genellikle en güncel kararlı sürümü yükleyecektir.
Temel Winston Kullanımı: Konsola Günlük Kaydetme
Winston’ı kullanmaya başlamak için öncelikle bir logger (günlükleyici) örneği oluşturmanız gerekir. Bu örnek, günlük seviyelerini ve taşıyıcıları (transports) yapılandıracağınız yerdir.
app.js dosyanızı açın ve aşağıdaki kodu ekleyin:
// app.js
const winston = require('winston');
// Logger örneği oluşturma
const logger = winston.createLogger({
level: 'info', // Varsayılan günlük seviyesi
format: winston.format.json(), // Günlük formatı
transports: [
new winston.transports.Console(), // Konsola çıktı veren taşıyıcı
],
});
// Farklı günlük seviyelerinde mesajlar gönderme
logger.error('Bu bir hata mesajıdır.');
logger.warn('Bu bir uyarı mesajıdır.');
logger.info('Bu bir bilgi mesajıdır.');
logger.http('Bu bir HTTP isteği günlüğüdür.');
logger.verbose('Bu daha detaylı bir mesajdır.');
logger.debug('Bu bir hata ayıklama mesajıdır.');
logger.silly('Bu en düşük seviyeli bir mesajdır.');
console.log('Uygulama çalışıyor...');
Uygulamayı çalıştırın:
node app.js
Konsol çıktısı şuna benzer olacaktır (JSON formatında):
{"level":"error","message":"Bu bir hata mesajıdır."}
{"level":"warn","message":"Bu bir uyarı mesajıdır."}
{"level":"info","message":"Bu bir bilgi mesajıdır."}
{"level":"http","message":"Bu bir HTTP isteği günlüğüdür."}
{"level":"verbose","message":"Bu daha detaylı bir mesajdır."}
{"level":"debug","message":"Bu bir hata ayıklama mesajıdır."}
{"level":"silly","message":"Bu en düşük seviyeli bir mesajdır."}
Uygulama çalışıyor...
Açıklama:
* winston.createLogger(): Yeni bir logger örneği oluşturur.
* level: 'info': Bu logger için varsayılan günlük seviyesini info olarak ayarlar. Bu, yalnızca info seviyesi ve üzerindeki (warn, error) mesajların işleneceği anlamına gelir. Yukarıdaki örnekte debug ve silly mesajlarının da çıktığını görebilirsiniz, çünkü Console taşıyıcısının varsayılan seviyesi silly‘dir. Taşıyıcının seviyesi logger’ın genel seviyesinden daha düşük veya eşitse, o taşıyıcı tüm bu seviyelerdeki mesajları işler.
* format: winston.format.json(): Günlük mesajlarının JSON formatında olmasını sağlar.
* transports: [new winston.transports.Console()]: Günlük mesajlarını konsola yazan bir taşıyıcı ekler.
Günlük Taşıyıcılarını (Transports) Yapılandırma
Winston’ın gücü, farklı taşıyıcılar aracılığıyla günlükleri çeşitli hedeflere yönlendirebilmesidir. En yaygın kullanılan taşıyıcıları inceleyelim.
Konsol Taşıyıcısı (Console Transport)
Konsol taşıyıcısı, geliştirme sırasında ve bazen üretimde temel izleme için kullanışlıdır. Çıktıyı daha okunabilir hale getirmek için özelleştirebiliriz.
const winston = require('winston');
const logger = winston.createLogger({
level: 'debug', // Tüm seviyelerdeki mesajları görelim
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(), // Çıktıyı renklendir
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), // Zaman damgası ekle
winston.format.printf(info => ${info.timestamp} ${info.level}: ${info.message}) // Özel format
),
level: 'silly' // Konsol taşıyıcısı için en düşük seviye
}),
],
});
logger.error('Bu bir hata mesajıdır.');
logger.warn('Bu bir uyarı mesajıdır.');
logger.info('Bu bir bilgi mesajıdır.');
logger.debug('Bu bir hata ayıklama mesajıdır.');
Çıktı, renkli ve özel formatta olacaktır:
2023-10-27 10:30:00 error: Bu bir hata mesajıdır.
2023-10-27 10:30:00 warn: Bu bir uyarı mesajıdır.
2023-10-27 10:30:00 info: Bu bir bilgi mesajıdır.
2023-10-27 10:30:00 debug: Bu bir hata ayıklama mesajıdır.
Açıklama:
* winston.format.combine(): Birden fazla formatlama işlevini bir araya getirmenizi sağlar.
* winston.format.colorize(): Günlük seviyelerini renklendirir.
* winston.format.timestamp(): Günlüğe bir zaman damgası ekler.
* winston.format.printf(): Günlük mesajının nasıl görüneceğini tamamen özelleştirmek için bir fonksiyon tanımlamanıza olanak tanır. info nesnesi level, message, timestamp gibi özellikler içerir.
Dosya Taşıyıcısı (File Transport)
Üretim ortamında günlükleri dosyalara yazmak kritik öneme sahiptir. Winston, bu amaçla File taşıyıcısını sunar. Genellikle hata günlükleri için ayrı bir dosya ve genel uygulama günlükleri için başka bir dosya tutulur.
const winston = require('winston');
const path = require('path');
// Günlük dosyalarının kaydedileceği dizin
const logDir = 'logs';
// Dizinin mevcut olduğundan emin olun, yoksa oluşturun
// (Bu işlemi uygulamanızın başlangıcında veya bir kurulum betiğinde yapmanız önerilir)
const fs = require('fs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }), // Hata nesneleri için stack trace ekle
winston.format.splat(), // String interpolation için
winston.format.json() // JSON formatında çıktı
),
transports: [
// Konsol taşıyıcısı (isteğe bağlı)
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(info => ${info.timestamp} ${info.level}: ${info.message})
),
level: 'debug' // Konsol için debug seviyesini göster
}),
// Tüm bilgi seviyesi ve üzerindeki günlükleri 'combined.log' dosyasına kaydet
new winston.transports.File({
filename: path.join(logDir, 'combined.log'),
level: 'info',
}),
// Sadece hata seviyesindeki günlükleri 'error.log' dosyasına kaydet
new winston.transports.File({
filename: path.join(logDir, 'error.log'),
level: 'error',
}),
],
exceptionHandlers: [ // Yakalanmamış istisnaları kaydet
new winston.transports.File({ filename: path.join(logDir, 'exceptions.log') })
],
rejectionHandlers: [ // İşlenmemiş promise reddedilmelerini kaydet
new winston.transports.File({ filename: path.join(logDir, 'rejections.log') })
]
});
logger.info('Uygulama başlatıldı.');
logger.warn('Disk alanı azalıyor olabilir.');
logger.error('Veritabanı bağlantısı başarısız oldu!', new Error('Connection refused.'));
logger.debug('Bu mesaj sadece konsolda görünür, dosyalarda değil (çünkü dosya seviyesi info).');
// Yakalanmamış bir istisna tetikleme
// setTimeout(() => {
// throw new Error('Bu yakalanmamış bir istisnadır!');
// }, 100);
// İşlenmemiş bir promise reddedilmesi tetikleme
// Promise.reject(new Error('Bu işlenmemiş bir reddedilmedir!'));
Bu kodu çalıştırdığınızda, my-winston-app dizini altında logs adında bir dizin oluşacak ve içinde combined.log, error.log ve exceptions.log (eğer bir istisna oluşursa) dosyalarını göreceksiniz.
Önemli Not: Dosya İzinleri
Ubuntu sunucularında, Node.js uygulamanızın günlük dosyalarını yazabilmesi için ilgili dizinlere (örneğin logs) yazma iznine sahip olması gerekir. Genellikle Node.js uygulamanız www-data gibi bir kullanıcı altında çalışır. Bu durumda, logs dizininin bu kullanıcı tarafından yazılabilir olduğundan emin olmalısınız:
sudo chown -R www-data:www-data my-winston-app/logs
sudo chmod -R 755 my-winston-app/logs
Veya daha basit bir test için:
sudo chmod -R 777 my-winston-app/logs # Sadece test amaçlı, üretimde dikkatli olun!
Günlük Rotasyonu (Log Rotation)
Uygulama çalıştıkça günlük dosyaları hızla büyüyebilir ve disk alanını tüketebilir. Bu durumu önlemek için günlük rotasyonu (log rotation) kullanmak önemlidir. Winston’ın kendisi doğrudan günlük rotasyonu sağlamaz, ancak winston-daily-rotate-file gibi bir eklenti veya sistem düzeyinde logrotate aracı kullanılabilir.
winston-daily-rotate-file ile Rotasyon:
Bu eklenti, günlük dosyalarını tarihe göre otomatik olarak döndürmenizi sağlar.
1. Yükleme:
npm install winston-daily-rotate-file
2. Kullanım:
app.js dosyanızda winston.transports.File yerine winston.transports.DailyRotateFile kullanın:
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const path = require('path');
const fs = require('fs');
const logDir = 'logs';
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
// Ortak günlük dosyası için taşıyıcı
const transportCombined = new DailyRotateFile({
filename: path.join(logDir, 'application-%DATE%.log'),
datePattern: 'YYYY-MM-DD-HH', // Her saat başı yeni dosya
zippedArchive: true, // Eski günlükleri sıkıştır
maxSize: '20m', // Bir dosya maksimum 20MB olabilir
maxFiles: '14d', // Son 14 günlük dosyaları sakla
level: 'info',
});
// Hata günlük dosyası için taşıyıcı
const transportError = new DailyRotateFile({
filename: path.join(logDir, 'error-%DATE%.log'),
datePattern: 'YYYY-MM-DD', // Her gün yeni dosya
zippedArchive: true,
maxSize: '10m',
maxFiles: '7d',
level: 'error',
});
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(info => ${info.timestamp} ${info.level}: ${info.message})
),
level: 'debug'
}),
transportCombined,
transportError,
],
exceptionHandlers: [
new DailyRotateFile({
filename: path.join(logDir, 'exceptions-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '5m',
maxFiles: '30d',
})
],
rejectionHandlers: [
new DailyRotateFile({
filename: path.join(logDir, 'rejections-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '5m',
maxFiles: '30d',
})
]
});
logger.info('Uygulama başlatıldı.');
logger.error('Kritik bir hata oluştu!');
Bu yapılandırma, günlük dosyalarınızı otomatik olarak tarihe göre döndürecek, belirli bir boyuta ulaştığında yeni bir dosya oluşturacak ve eski dosyaları sıkıştırarak saklayacaktır.
HTTP Taşıyıcısı (HTTP Transport)
Günlükleri merkezi bir günlük toplama hizmetine (örneğin ELK Stack, Splunk, Graylog) göndermek için HTTP taşıyıcısını kullanabilirsiniz.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.Http({
host: 'localhost', // Merkezi günlük sunucusunun adresi
port: 3000, // Merkezi günlük sunucusunun portu
path: '/logs', // Günlüklerin gönderileceği yol
// auth: { username: 'user', password: 'password' } // Kimlik doğrulama gerekiyorsa
}),
],
});
logger.info('Uygulama başlatıldı, HTTP ile günlük gönderiliyor.');
Bu, günlükleri belirtilen HTTP endpoint’ine POST isteği olarak gönderecektir. Merkezi günlük toplama sisteminiz bu istekleri alıp işleyecektir.
Günlük Seviyeleri ve Filtreleme
Winston, varsayılan olarak aşağıdaki günlük seviyelerini destekler (artan önem sırasına göre):
* silly
* debug
* verbose
* http
* info
* warn
* error
createLogger içindeki level seçeneği, logger’ın genel olarak hangi seviyeden itibaren günlükleri işleyeceğini belirler. Bir taşıyıcı için belirtilen level ise, o taşıyıcının hangi seviyeden itibaren günlükleri işleyeceğini belirler. Bir taşıyıcının seviyesi, logger’ın genel seviyesinden daha düşük veya eşitse, o taşıyıcı bu seviyedeki ve daha yüksek seviyelerdeki tüm mesajları alacaktır.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info', // Logger'ın genel seviyesi info
format: winston.format.simple(),
transports: [
new winston.transports.Console({
level: 'debug' // Konsol taşıyıcısı debug seviyesinden itibaren gösterir
}),
new winston.transports.File({
filename: 'combined.log',
level: 'warn' // Dosya taşıyıcısı warn seviyesinden itibaren gösterir
})
]
});
logger.debug('Bu bir debug mesajıdır.'); // Sadece konsolda görünür
logger.info('Bu bir info mesajıdır.'); // Konsolda ve dosyada görünür
logger.warn('Bu bir uyarı mesajıdır.'); // Konsolda ve dosyada görünür
logger.error('Bu bir hata mesajıdır.'); // Konsolda ve dosyada görünür
Yukarıdaki örnekte:
* debug mesajı sadece konsolda görünür çünkü konsolun seviyesi debug iken, dosya taşıyıcısının seviyesi warn‘dır.
* info, warn, error mesajları hem konsolda hem de dosyada görünür, çünkü her iki taşıyıcının da seviyeleri bu mesajları kapsar.
Günlük Formatlama
Winston’ın winston.format modülü, günlük mesajlarını biçimlendirmek için güçlü araçlar sunar. Daha önce json(), timestamp(), colorize(), printf() gibi formatları gördük. Diğer bazı kullanışlı formatlar:
* simple(): Basit bir metin çıktısı sağlar (varsayılan).
* json(): JSON formatında çıktı verir.
* prettyPrint(): JSON çıktısını okunabilir hale getirir.
* label({ label: 'my-app' }): Her günlüğe özel bir etiket ekler.
* metadata(): Günlük mesajına eklenen meta verileri (örneğin, Express’ten req objesi) otomatik olarak günlüğe dahil eder.
* errors({ stack: true }): Hata nesnelerinin stack trace’ini günlüğe ekler.
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.label({ label: 'my-web-app' }),
winston.format.timestamp(),
winston.format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] }), // Mesaj dışındaki tüm meta verileri ekle
winston.format.printf(info => {
const { timestamp, label, level, message, metadata } = info;
// Meta verileri string'e dönüştürerek ekliyoruz
const meta = Object.keys(metadata).length ? JSON.stringify(metadata) : '';
return ${timestamp} [${label}] ${level}: ${message} ${meta};
})
),
transports: [
new winston.transports.Console()
]
});
logger.info('Kullanıcı giriş yaptı.', { userId: 123, ipAddress: '192.168.1.1' });
logger.error('Veritabanı hatası.', new Error('DB connection failed!'), { transactionId: 'abc' });
Bu örnekte, metadata formatlayıcısı ve printf ile daha gelişmiş bir çıktı formatı oluşturulmuştur.
Winston ile Hata Yönetimi
Node.js uygulamalarında yakalanmamış istisnalar (uncaught exceptions) ve işlenmemiş promise reddedilmeleri (unhandled rejections) uygulamanın çökmesine neden olabilir. Winston, bu durumları özel taşıyıcılar kullanarak günlüğe kaydetme yeteneği sunar.
Yukarıdaki dosya taşıyıcısı örneğinde exceptionHandlers ve rejectionHandlers kullanımlarını zaten göstermiştik. Bu taşıyıcılar, uygulamanızda beklenmedik bir hata meydana geldiğinde otomatik olarak devreye girer ve hatayı belirtilen dosyalara kaydeder.
const winston = require('winston');
const path = require('path');
const fs = require('fs');
const logDir = 'logs';
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: path.join(logDir, 'combined.log') }),
],
exceptionHandlers: [
new winston.transports.File({ filename: path.join(logDir, 'exceptions.log') })
],
rejectionHandlers: [
new winston.transports.File({ filename: path.join(logDir, 'rejections.log') })
]
});
logger.info('Uygulama başlatıldı.');
// Yakalanmamış bir istisna tetikleyelim
// setTimeout(() => {
// throw new Error('Bu yakalanmamış bir istisnadır!');
// }, 100);
// İşlenmemiş bir promise reddedilmesi tetikleyelim
// Promise.reject(new Error('Bu işlenmemiş bir reddedilmedir!'));
Bu satırları yorumdan çıkarıp uygulamayı çalıştırdığınızda, hata mesajlarının exceptions.log veya rejections.log dosyalarına yazıldığını göreceksiniz. Uygulamanız bu hatalardan sonra varsayılan olarak kapanacaktır, ancak hatanın kaydını tutmuş olacaksınız.
Winston’ı Node.js Uygulamanıza Entegre Etme (Express Örneği)
Gerçek dünya Node.js uygulamalarında, logger’ı merkezi bir modül olarak tanımlamak ve uygulamanızın farklı yerlerinde bu logger’ı kullanmak iyi bir pratiktir. Express.js tabanlı bir uygulama için bir örnek oluşturalım.
1. Gerekli Paketleri Yükleyin:
npm install express
2. logger.js Oluşturma:
my-winston-app dizini içinde logger.js adında yeni bir dosya oluşturun:
// logger.js
const winston = require('winston');
const DailyRotateFile = require('winston-daily-rotate-file');
const path = require('path');
const fs = require('fs');
const logDir = path.join(__dirname, 'logs'); // Uygulama kök dizinindeki logs klasörü
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const format = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json()
);
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', // Ortama göre seviye ayarı
format: format,
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(info => ${info.timestamp} ${info.level}: ${info.message})
),
level: 'debug' // Konsol için her zaman debug seviyesini göster
}),
new DailyRotateFile({
filename: path.join(logDir, 'application-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
level: 'info',
}),
new DailyRotateFile({
filename: path.join(logDir, 'error-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '10m',
maxFiles: '7d',
level: 'error',
}),
],
exceptionHandlers: [
new DailyRotateFile({
filename: path.join(logDir, 'exceptions-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '5m',
maxFiles: '30d',
})
],
rejectionHandlers: [
new DailyRotateFile({
filename: path.join(logDir, 'rejections-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '5m',
maxFiles: '30d',
})
]
});
module.exports = logger;
3. app.js Güncelleme (Express ile Kullanım):
app.js dosyanızı Express uygulaması ve Winston logger’ı ile güncelleyin:
// app.js
const express = require('express');
const logger = require('./logger'); // Logger modülümüzü import et
const app = express();
const port = 3000;
// Her isteği loglayan bir middleware
app.use((req, res, next) => {
logger.http(${req.method} ${req.url} - ${req.ip});
next();
});
app.get('/', (req, res) => {
logger.info('Ana sayfaya istek geldi.');
res.send('Merhaba Winston!');
});
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
logger.debug(Kullanıcı bilgisi isteniyor: ${userId});
if (userId === '1') {
res.send(Kullanıcı ID: ${userId});
} else {
logger.warn(Kullanıcı bulunamadı: ${userId});
res.status(404).send('Kullanıcı bulunamadı.');
}
});
app.get('/error-test', (req, res, next) => {
try {
throw new Error('Bu bir test hatasıdır!');
} catch (err) {
logger.error('Bir hata oluştu:', err);
next(err); // Hata işleyici middleware'e gönder
}
});
// Hata işleyici middleware
app.use((err, req, res, next) => {
logger.error(Uygulama hatası: ${err.message}, { stack: err.stack, url: req.originalUrl });
res.status(500).send('Sunucu hatası!');
});
app.listen(port, () => {
logger.info(Uygulama http://localhost:${port} adresinde çalışıyor.);
});
// Yakalanmamış istisna ve reddedilme durumlarını test etmek için
// setTimeout(() => {
// throw new Error('Uygulama dışında yakalanmamış hata!');
// }, 5000);
// Promise.reject(new Error('Uygulama dışında işlenmemiş reddedilme!'));
Bu yapılandırma ile Express uygulamanızdaki tüm günlükleme işlemlerini merkezi logger.js modülü üzerinden yapabilir ve farklı seviyelerdeki mesajları uygun taşıyıcılara yönlendirebilirsiniz.
Gelişmiş Konular
Ubuntu’da logrotate ile Günlük Rotasyonu
winston-daily-rotate-file eklentisi Node.js tarafında günlük rotasyonu sağlasa da, Linux sistemlerinin yerleşik logrotate aracı daha güçlü ve merkezi bir günlük yönetimi çözümü sunar. Özellikle çok sayıda uygulamanız varsa veya farklı sistem günlüklerini de yönetiyorsanız logrotate idealdir.
logrotate için bir yapılandırma dosyası oluşturmanız gerekir:
1. Yapılandırma Dosyası Oluşturma:
sudo nano /etc/logrotate.d/my-winston-app
2. Yapılandırma İçeriği:
Aşağıdaki içeriği dosyaya yapıştırın. Bu örnek, my-winston-app/logs dizinindeki tüm .log dosyalarını her gün döndürecektir.
/path/to/my-winston-app/logs/*.log {
daily # Her gün döndür
missingok # Günlük dosyası yoksa hata verme
rotate 7 # Son 7 günlük dosyayı sakla
compress # Eski günlükleri sıkıştır
delaycompress # Sıkıştırmayı bir sonraki rotasyona ertele
notifempty # Boşsa döndürme
create 0640 www-data www-data # Yeni günlük dosyalarını belirli izinlerle ve sahiple oluştur
# postrotate # Rotasyondan sonra çalışacak komutlar (isteğe bağlı)
# /usr/bin/pkill -HUP node # Uygulamaya SIGHUP sinyali göndererek günlük dosyasını yeniden açmasını söyle
# endscript
}
Önemli: /path/to/my-winston-app/logs/*.log kısmını uygulamanızın gerçek günlük dizini yolu ile değiştirin.
create satırındaki www-data www-data kısmı, uygulamanızın hangi kullanıcı ve grup altında çalıştığına bağlı olarak değişebilir.
3. Test Etme:
Yapılandırmanızı test etmek için kuru çalıştırma yapabilirsiniz:
sudo logrotate -d /etc/logrotate.d/my-winston-app
Gerçek bir rotasyon tetiklemek için (manuel olarak):
sudo logrotate -f /etc/logrotate.d/my-winston-app
logrotate genellikle cron aracılığıyla günde bir kez çalışacak şekilde ayarlanmıştır.
Merkezi Günlükleme Sistemleri
Büyük ölçekli uygulamalar ve mikroservis mimarilerinde, günlükleri merkezi bir yerde toplamak ve analiz etmek esastır. Winston, HTTP taşıyıcısı veya özel taşıyıcılar aracılığıyla Elasticsearch, Logstash, Kibana (ELK Stack), Graylog, Splunk gibi sistemlere kolayca entegre edilebilir. Bu sistemler, günlükleri aramanıza, filtrelemenize, görselleştirmenize ve uyarılar oluşturmanıza olanak tanır.
Performans Değerlendirmeleri
Günlükleme, I/O yoğun bir işlem olabilir ve uygulamanızın performansını etkileyebilir.
* Asenkron Günlükleme: Winston’ın dosya taşıyıcıları varsayılan olarak asenkrondur, bu da uygulamanızın günlük yazma işlemini beklemeden devam etmesini sağlar.
* Bufferlama: Yüksek hacimli günlüklerde, günlükleri bir araya getirip toplu olarak yazmak (bufferlama) I/O yükünü azaltabilir. winston-daily-rotate-file gibi taşıyıcılar bu tür optimizasyonları sunar.
* Gereksiz Günlükleme Yapmaktan Kaçının: Üretim ortamında debug veya silly gibi çok düşük seviyeli günlükleri dosyalara veya uzak sunuculara yazmaktan kaçının. Yalnızca ihtiyaç duyduğunuz bilgileri günlüğe kaydedin.
En İyi Uygulamalar
* Anlamlı Mesajlar: Günlük mesajlarınızın açık, özlü ve bağlamı anlaşılır olmasını sağlayın. Sadece “Hata oluştu” yerine “Kullanıcı X için sipariş işlenirken veritabanı bağlantısı hatası oluştu” gibi detaylı mesajlar yazın.
* Uygun Günlük Seviyeleri: Her mesaj için doğru günlük seviyesini kullanın. Örneğin, uygulamanın çökmesine neden olan durumlar için error, beklenmedik ama uygulamanın çalışmasını engellemeyen durumlar için warn, normal operasyonel olaylar için info kullanın.
* Hassas Verilerden Kaçının: Günlüklere parola, kredi kartı numarası, kişisel kimlik bilgileri gibi hassas verileri yazmaktan kesinlikle kaçının. Eğer bu tür verileri günlüğe kaydetmeniz gerekiyorsa, mutlaka maskeleme veya şifreleme kullanın.
* Günlük Dosyası Yönetimi: Günlük dosyalarının diskte yer kaplamasını önlemek için günlük rotasyonunu (Winston eklentileri veya logrotate ile) etkinleştirin. Eski günlük dosyalarını belirli bir süre sonra silin veya arşivleyin.
* Standartlaştırılmış Formatlar: Özellikle birden fazla uygulamanız varsa, tüm uygulamalarınızda günlük formatını standartlaştırın (örneğin JSON). Bu, merkezi günlük toplama ve analizini kolaylaştırır.
* Merkezi Günlükleme: Üretim ortamında, günlükleri merkezi bir sisteme (ELK, Graylog vb.) göndermeyi düşünün. Bu, dağıtık sistemlerde sorunları teşhis etmeyi ve genel sistem sağlığını izlemeyi büyük ölçüde kolaylaştırır.
* Bağlam Ekleyin: Günlük mesajlarına ilgili bağlamı (kullanıcı ID’si, istek ID’si, işlem ID’si vb.) eklemek, sorunları daha hızlı izlemenize yardımcı olur. Winston’ın meta veri özellikleri bunun için idealdir.
Sonuç
Winston, Node.js uygulamalarınız için güçlü, esnek ve özelleştirilebilir bir günlükleme çözümü sunar. Bu rehberde, Ubuntu 16.04 üzerinde Winston’ı kurmaktan, farklı taşıyıcıları yapılandırmaya, günlük seviyelerini ve formatları yönetmeye, hata işleme ve Express.js uygulamalarına entegrasyona kadar birçok konuyu ele aldık. Ayrıca, günlük rotasyonu gibi önemli operasyonel konulara ve en iyi uygulamalara da değindik.
Sağlam bir günlükleme stratejisi, uygulamanızın üretim ortamındaki kararlılığını ve izlenebilirliğini artırır. Winston’ı doğru bir şekilde uygulayarak, Node.js uygulamalarınızın her zaman gözünüzün önünde olmasını sağlayabilir ve potansiyel sorunları proaktif bir şekilde çözebilirsiniz. Uygulamanızın karmaşıklığı arttıkça ve ölçeklendikçe, iyi yapılandırılmış bir günlükleme sisteminin değeri paha biçilmez olacaktır.
