Neden Logging Frameworks Extenability

Logging, her bir yazılım sistemi katmanına dokunan bir endişedir. Üretim Scala uygulamaları, zaman içinde sık sık sık sık sık sık değişiklikler yapar: Bir proje her zaman geliştirme sırasında konsol logunu değiştirmek, giriş stratejisi değişiklikleri yapmak için geçiş yapabilir.

[FONT=0]Fabrika Yöntemi modeli[Dönetici:0) Bu sorunu somut oturum açma uygulamasından giriş arayüzü ayırt ederek ele alır. Bu ayrım, minimum / Closed Prensle bu modeli uygular: Sistem uzatma için açık kalır (yeni loggerlar eklenebilir) ancak değiştirme için kapalıdır (existing client kodu değiştirmeye gerek yoktur). Scala'nın nesne odaklı ve işlevsel doğası, özellikle de bu modeli minimum kazanı ve güçlü tip güvenlik ile uygular.

Fabrika Yönteminin Anlayışı

Fabrika Yöntemi modeli, bir nesne oluşturmak için bir arayüz tanımlayan bir yaratım tasarım modelidir, ancak alt sınıflara anlık karar verme kararı verir. Basit Fabrika deyiminden farklı olarak (bu, koşullu mantıkla tek bir statik yöntem kullanır), gerçek Fabrika Yöntemi modeli miras veya kanal temelli polimorfizme bağlıdır.

Bu model özellikle bir çerçevenin önceden yaratması gereken tam nesneler türü tahmin edemeyeceğinde değerlidir. Kayıt bağlamında, çerçeve bir loggerya ihtiyacı olduğunu bilir, ancak belirli logger türü (konsole, dosya, ağ, veritabanı) konfigürasyon, çevre değişkenleri veya dağıtım bağlamına dayanan olarak belirlenir.

Desenli katılımcılar

  • [[Üyetim:0)Ürün (Logger trait): ), Fabrika yönteminin yarattığı nesneler için arayüzleri tanımlar.
  • [FONT:0)ConcreteProduct (ConsoleLogger, FileLogger, vs.):) Ürün arayüzünü uygulamaktadır.
  • [FONT:0)Creator (LoggerF): Bir Ürün nesnesini geri dönen fabrika yöntemini de içeriyor.
  • [FONT:0)ConcreteCreator (isteğe): ) Belirli beton ürün örneklerini geri getirmek için fabrika yöntemine sahipler.

Logger Trait Hierarchy'ı tasarlayın

Herhangi bir eklenme çerçevesinin temeli iyi bir arayüzdür. Scala'da özellikler bu sözleşmeyi tanımlamak için doğal bir mekanizma sağlar. Minimum giriş arayüzü, çeşitli gerileri desteklemek için yeterince genel olmayan programlama yöntemleri ortaya koymalıdır.

trait Logger {
 def debug(message: => String): Unit
 def info(message: => String): Unit
 def warn(message: => String): Unit
 def error(message: => String, cause: Option[Throwable] = None): Unit
}

İsmin parametrelerini kullanarak ([[DÜT:1) kasıtlı bir tasarım seçimidir: logger mesajın gerçekte ne kadar yayılacağına karar verir.Perkreasyonun tamamen ayarlanma maliyetinden kaçınır.

Add Log Level Filtering

Pratik bir geliştirme doğrudan trait içine log düzey filtreleme yapmak. Bu, sadece uyarılar veya hatalar gerektiğinde çıktıya ulaşmaktan fiilose debug mesajlarının engellenmesini önler.

sealed trait LogLevel
case object Debug extends LogLevel
case object Info extends LogLevel
case object Warn extends LogLevel
case object Error extends LogLevel

trait Logger {
 protected val level: LogLevel

 def debug(message: => String): Unit = log(Debug, message)
 def info(message: => String): Unit = log(Info, message)
 def warn(message: => String): Unit = log(Warn, message)
 def error(message: => String, cause: Option[Throwable] = None): Unit =
 log(Error, message, cause)

 protected def log(level: LogLevel, message: => String, cause: Option[Throwable] = None): Unit
}

Bu tasarım, halka açık olarak yapılandırılmayan bir üretim dosyası loggerları, bir üretim dosyası loggersı, herhangi bir şekilde yapılandırılamazken, kendi eşi üzerinde her somut bir denetim sunar.

Betonu Uygulamayı Etkiliyor

Uyduruk hiyerarşisi ile beton loggerları basit hale gelir. Her bir logger kendi çıkış mekanizmasına ve temel özellikten miras alan seviye bazlı filtrelemeye saygı duyar.

Konsol

class ConsoleLogger(override val level: LogLevel = Debug) extends Logger {
 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 val timestamp = java.time.Instant.now
 println(s"[$timestamp] [$level] $message")
 cause.foreach { t =>
 t.printStackTrace(System.out)
 }
 }
}

KonsolLogger gelişim ve debugging için idealdir. Hemen standart olarak çıktı, bu da gerçek zamanlı olarak log akışını gözlemlemeyi kolaylaştırır. Zamanlar ekleme ve yığın izlerini ekleme, dışsal bir araç gerektirmeden sorun sırasında yardımcı olur.

Dosya Logger

class FileLogger(
 filePath: String,
 override val level: LogLevel = Info,
 append: Boolean = true
) extends Logger {
 import java.io.{BufferedWriter, FileWriter}

 private val writer = new BufferedWriter(new FileWriter(filePath, append))

 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 val timestamp = java.time.Instant.now
 val entry = s"[$timestamp] [$level] $message${cause.fold("")(t => s"\n${t.getStackTrace.mkString("\n")}")}\n"
 writer.write(entry)
 writer.flush()
 }

 def close(): Unit = writer.close()
}

FileLogger belirli bir yol için yazıyor ve yapılandırılabilir bir günlük seviye eşiği destekliyor.TheETHFLT:5) yöntemi kaynak yönetimi için önemlidir: dosya işlerinin düzgün bir şekilde serbest bırakılması gerekir, özellikle uzun süren uygulamalarda.Bir üretim senaryosuda, muhtemelen bunu bir kaynak yönetimi kütüphanesi ile entegre edersiniz veya Scala'nın bunu yapılandırırsınız.

Network Logger (UDP Örneği)

Fabrika Yöntemi deseninin güçlülerinden biri, yeni logger türleri nadiren mevcut kodu değiştirmek gerekir. UDP'yi merkezi bir koleksiyoncuya giriş giriş giriş girişleri gönderen bir ağ logger: Bu aşırılık gösterir:

class UdpLogger(
 host: String,
 port: Int,
 override val level: LogLevel = Warn
) extends Logger {
 import java.net.{DatagramPacket, DatagramSocket, InetAddress}

 private val socket = new DatagramSocket()
 private val address = InetAddress.getByName(host)

 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 val payload = s"[$level] $message".getBytes("UTF-8")
 val packet = new DatagramPacket(payload, payload.length, address, port)
 socket.send(packet)
 }
}

Bu logger UDP paketlerini uzaktan ev sahibine gönderir. Çünkü müşteri kodu sadece [[ŞUdpLogger'den UdpLogger'e geçiş, fabrikayı yönlendiren yapılandırmadan daha fazlasını gerektirir.

Fabrikayı inşa edin

Fabrika uygun logger seçmek ve anlıklaştırmak için mantığı enkapsulates.In Scala, bir uygulama yöntemi ile bir arkadaş nesnesi deyimomatiktir ve müşteriler için temiz bir sözelz sunar.

Yapın-Driven Factory

object LoggerFactory {
 sealed trait Config
 object Config {
 final case class Console(level: LogLevel = Debug) extends Config
 final case class File(path: String, level: LogLevel = Info, append: Boolean = true) extends Config
 final case class Udp(host: String, port: Int, level: LogLevel = Warn) extends Config
 }

 def apply(config: Config): Logger = config match {
 case Config.Console(level) =>
 new ConsoleLogger(level)
 case Config.File(path, level, append) =>
 new FileLogger(path, level, append)
 case Config.Udp(host, port, level) =>
 new UdpLogger(host, port, level)
 }
}

Bu model, logger yapılandırmalarını temsil etmek için mühürlenmiş vaka sınıflarını kullanır. Kapalı hiyerarşi, derleyici bir model eşleştirmesini derleyici bir zamanda sağlar: yeni bir logger türü eklemek yeni bir vaka sınıfı ve maç ifadesinde yeni bir vaka eklemek gerekir.

Çevre Tabanlı Fabrika

Birçok dağıtımda, giriş yapılandırması, kod düzeyinde yapılandırma yerine çevre değişkenleri tarafından belirlenir. Çevre değişkenlerini okuyan bir fabrika farklı ortamlarda dağıtım basitleştirebilir:

object LoggerFactory {
 def fromEnvironment(): Logger = {
 val loggerType = sys.env.getOrElse("LOGGER_TYPE", "console").toLowerCase
 val level = sys.env.get("LOG_LEVEL").map(parseLevel).getOrElse(Info)

 loggerType match {
 case "console" => new ConsoleLogger(level)
 case "file" =>
 val path = sys.env.getOrElse("LOG_FILE", "application.log")
 new FileLogger(path, level)
 case "udp" =>
 val host = sys.env.getOrElse("LOG_HOST", "localhost")
 val port = sys.env.get("LOG_PORT").map(_.toInt).getOrElse(514)
 new UdpLogger(host, port, level)
 case other =>
 System.err.println(s"Unknown logger type: $other, falling back to console")
 new ConsoleLogger(level)
 }
 }

 private def parseLevel(s: String): LogLevel = s.toLowerCase match {
 case "debug" => Debug
 case "info" => Info
 case "warn" => Warn
 case "error" => Error
 case _ => Info
 }
}

Bu yaklaşım özellikle çevre değişkenlerinin birincil yapılandırma mekanizması olduğu konteynerleşmiş ortamlarda faydalıdır. Fabrika tüm hizmetlerde kayıt yapılandırması için tek bir değişiklik noktası haline gelir.

Logging Framework'ü kullanarak

Müşteri kodu sadece www.D.D. ile etkileşime girer.Bu decoupling, uygulamanın geri kalanının herhangi bir beton logger uygulamasına ilişkin bir derleme zamanı bağımlılığı olmadığı anlamına gelir.

Temel Kullanım Temel Kullanımı

val logger: Logger = LoggerFactory(LoggerFactory.Config.Console(Debug))
logger.debug("Entering method computeResults")
logger.info("Processing completed successfully")
logger.warn("Disk space below threshold")
logger.error("Connection refused", Some(new RuntimeException("timeout")))

Sınıflara Uğramak

Daha büyük uygulamalar için, loggeryı inşaat parametreleri aracılığıyla enjekte etmek, tasarımı temiz ve test edilebilir tutar:

class DataService(logger: Logger, database: Database) {
 def fetchUser(id: String): Option[User] = {
 logger.debug(s"Fetching user with id: $id")
 val result = database.queryUser(id)
 result match {
 case Some(user) =>
 logger.info(s"Found user: ${user.name}")
 Some(user)
 case None =>
 logger.warn(s"User not found: $id")
 None
 }
 }
}

Bu modelde, [[D: 14) Girişin konsola, dosyaya veya ağ üzerinden girilemeyeceği bilgisi yoktur. Fabrika, başvuru giriş noktasında uygun logger yaratır ve hizmet hiyerarşisine bağlanır.

Fabrika Yöntemi ile Test

Bu tasarımın pratik yararlarından biri test edilebilir. Çünkü fabrika yapılandırmaya dayanan loggerlar yaratır, bir test, iddia amaçlı giriş çıkışını yakalayan özel bir logger enjekte edebilir.

class TestLogger extends Logger {
 val messages: scala.collection.mutable.ListBuffer[(LogLevel, String)] =
 scala.collection.mutable.ListBuffer.empty

 override val level: LogLevel = Debug

 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 messages += ((level, message))
 }
}

// In tests:
val testLogger = new TestLogger()
val service = new DataService(testLogger, mockDatabase)
service.fetchUser("42")

assert(testLogger.messages.exists {
 case (Info, msg) if msg.contains("Found user") => true
 case _ => false
})

Bu model, günlük endişeler için alay etme ihtiyacını ortadan kaldırır.TheDANDÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜN

Alternatif Yaklaşımlarla Karşılaştırma

Fabrika Yöntemi modeli, Scala'da örneklemeli giriş elde etmenin tek yolu değildir. Diğer yaklaşımlarla ticaret-offlarını anlamak, Fabrika Yönteminin neden genellikle üretim sistemleri için doğru seçim olduğunu açıklamaya yardımcı olur.

Basit Fabrika Idiom

Birçok Scala projesi basit bir şekilde başlıyor: Bir dize parametreye dayanan bir logger geri dönüyor.Uygulamanın daha basit olmasına rağmen, bu yaklaşım iyi ölçeklenmiyor: her yeni logger türü fabrika işlevini değiştirmek ve merkezi mantık bakım şişesi haline gelebilir.

Bağlanma Çerçevelerine bağlı

Guice veya MacWire gibi modeller otomatik olarak loggerlar yayılabilir. ancak, giriş gibi bir çapraz kesinti endişe için genellikle gereksiz olan ek karmaşıklığı ve runtime eklerini tanıtabilirler.

Fonksiyonel Logger Combinators

Tamamen işlevsel bir yaklaşım, loggerları işlevleri olarak temsil edebilir: “Bu, kediler gibi kütüphanelerde de çalışır, ancak zaten işlevsel etki sistemlerini kullanmadığı projeler için uygunsuz olabilecek bir bağımlılık anlamına gelir.

Fabrika Yöntemi pragmatik bir orta zemine kapılır: basit bir koşullu fabrikadan daha yapılandırılır, ancak tam DI framework veya işlevsel etki sisteminden daha az vaziftir.

Gelişmiş Extensions

Temel fabrika altyapısı yerinde olduğunda, birkaç gelişmiş özellik minimum kod değişiklikleri ile eklenebilir.

Kompozit Logger

Aynı gün birden fazla logger için bir kompozit logger delegesi. Aynı günlük mesajın hem bir dosya hem de izleme paniğe yazılması gerektiği senaryolar için faydalıdır:

class CompositeLogger(loggers: Seq[Logger], override val level: LogLevel = Debug) extends Logger {
 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 loggers.foreach(_.log(level, message, cause))
 }
}

Fabrika, bir dizi yapılandırmayı kabul ederek kompozit loggerlar yaratabilir.Müşteri kodu hala tek bir ESFLT:20 görür.

Async Logger

I/O'yu loggers'ta engellemek, uygulama performansını bozabilir. Bir async logger mevcut bir logger ve delegeler özel bir thread havuzuna yazar:

class AsyncLogger(underlying: Logger, executor: scala.concurrent.ExecutionContext) extends Logger {
 override val level: LogLevel = underlying.level

 override protected def log(
 level: LogLevel,
 message: => String,
 cause: Option[Throwable] = None
 ): Unit = {
 val msg = message // evaluate now, before async boundary
 executor.execute(() => underlying.log(level, msg, cause))
 }
}

Bu sarma cihazı aynı şekilde uygulanır:) trait, böylece müşteri koduna herhangi bir değişiklik olmadan fabrika tarafından şeffaf bir şekilde eklenebilir.

En İyi Uygulamalar ve Ortak Pitfalls

Fabrika Yöntemi modeli ile eski bir giriş çerçevesi inşa etmek basit, ancak birkaç uygulama üretim sistemlerindeki sonucu geliştirir.

Yapın için Hazırlanmış Tip Hierarchies for Build

Kapalı özellikleri veya logger yapılandırması için sınıfları kullanmak, fabrikadaki desen maçının yorucu olmasını sağlar. Bu, üretimde sürprizleri azaltır.

Kaynakları Yönetin

Kaynakları tutan Loggers (file handles, network soketleri, iplik havuzları) temizlenme mekanizması sağlamalıdır. loggers uzatma işlemi yapmak için [[FONTT:23) ve kullanım için [[FONTD|SQUARES/FONT=) veya Scala'nın [[GÖRÜŞÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜŞÜNÜ

Premature Optimizasyonu

Birçok giriş çerçevesi, topluca yazı ile veya kilitli olmayan veri yapıları kullanarak optimize edilir. Basit uygulamalarla başlayın ve sadece profillemeden sonra, bu oturum açmanın bir şişenck olduğunu ortaya çıkar. Fabrika soyutlama daha hızlı bir şekilde yavaş bir logger takas etmek için kolay hale getirir.

Trait Minimal'ı tut

Uygun yöntemleri ekleyebilmek için uyarıyı bırakın.En az arayüz uygulamak ve test etmek daha kolaydır. Domain-specific formatting or filtreleme mantığı genişletilebilir loggers.

Deeper Learning için Dış Kaynaklar

  • [FONT:0]Scala Design Desenler[Dönem:2) Ivan Nikolov[DÜye Olmayanlar: 3 ) - Fabrika Yöntemi modelini diğer yapısal ve yaratım şekilleri ile kapsayan kapsamlı bir kılavuz.
  • [FONT:0] Kontrol konteynerlerinin ve Bağımlılığı Enjeksiyonu ) Martin Fowler) tarafından belirlenen ilişki, her yaklaşımın uygun olduğu zaman açıklığa kavuşturularak açıklığa kavuşturulur.
  • [FONT:0]En İyi Uygulamalar[Dönemli[D:2) Loggly) tarafından tartışılmıştır - Burada tartışılan örnekleme çerçevelerini tartışır.

Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç Sonuç

Scala'daki Fabrika Yöntemi modeli, mevcut mantığı yeniden yazmadan performans optimizasyonlarını yeniden yazmanın temiz, abartılı bir temel sağlar.In Fabrika Yöntemi modeli, uygulama kodu, yeni logger türleri, değişim giriş yerlerini eklemek ve performans optimizasyonlarını yeniden yazmanız mümkün değildir.

Tek bir konsol logger ile küçük projelerden desen ölçekleri, aynı anda birden fazla kanal üzerinden giriş yapan büyük dağıtılmış sistemlere kadar uzanır. Scala'nın mühürlü hiyerarşileri ve isim parametreleri ile birlikte, Fabrika Yöntemi modeli hem esnekliği hem de güvenliği eşit bir şekilde sağlar.