(A.I. was used in the coding & coloring process)
Colored using bbcode , as scala is yet unknown to xenforo
Colored using bbcode , as scala is yet unknown to xenforo
Rich (BB code):
// Note you can provide commandline argument to filter
package my.scalafx
//--- SYSTEM LOGISTICS & DRIVER IMPORTS
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import doobie.util.transactor.Transactor
import doobie.syntax.string._
import doobie.syntax.connectionio._
import doobie.free.connection.ConnectionIO
import doobie.util.fragment.Fragment
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.application.Platform
import scalafx.collections.ObservableBuffer
import scalafx.scene.Scene
import scalafx.scene.control.{TableColumn, TableView}
import scalafx.scene.layout.BorderPane
import scalafx.beans.property.StringProperty
import java.util.regex.Pattern
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
//--- RELATIONAL SCHEMA ROW BLUEPRINT
case class LogRow(
host: Option[String],
facility: Option[String],
priority: Option[String],
tag: Option[String],
datetime: Option[String],
program: Option[String],
msg: Option[String]
)
//--- OPTIMIZED UI MODEL FOR FLUID RENDERING
case class UiLogRow(
host: String,
facility: String,
priority: String,
tag: String,
datetime: String,
program: String,
msg: String
)
object ScalaFXHelloWorld extends JFXApp {
// --- UNIX SPECIFICATION STATIC DICTIONARIES ---
private val Facilities = Array(
"kernel", "user", "mail", "daemon", "auth", "syslog", "lpr", "news",
"uucp", "cron", "authpriv", "ftp", "ntp", "audit", "alert", "clock",
"local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"
)
private val Severities = Array(
"emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"
)
// --- PARSING & SCRUBBING UTILITIES ---
private val ProgramExtractorPattern = Pattern.compile("^(?:\\S+)\\s+(?:\\S+)\\s+(?<prog>[a-zA-Z0-9_\\-]+)(?:\\[[0-9]+\\])?\\s+")
private val InputFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
private val OutputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
// --- DRY EXTRACTION & VALUE CONVERTER FUNCTIONS ---
private def getOrBlank(f: Option[String]): String = f.getOrElse("").trim
private def createCell(v: TableColumn.CellDataFeatures[UiLogRow, String], extractor: UiLogRow => String): StringProperty =
new StringProperty(v.value, "", extractor(v.value))
private def formatTimestamp(rawDateTime: String): String = try {
val s = rawDateTime.trim
if (s.isEmpty) "" else ZonedDateTime.parse(s.replaceAll("TT", "T"), InputFormatter).format(OutputFormatter)
} catch { case _: Exception => rawDateTime }
private def decodeHexTag(hexStr: String): String = try {
val h = hexStr.trim.toLowerCase
if (h.isEmpty) "" else {
val n = Integer.parseInt(h, 16)
val fIdx = n >> 3
val sIdx = n & 7
val fText = if (fIdx >= 0 && fIdx < Facilities.length) Facilities(fIdx) else s"unknown($fIdx)"
val sText = if (sIdx >= 0 && sIdx < Severities.length) Severities(sIdx) else s"unknown($sIdx)"
s"$fText.$sText"
}
} catch { case _: NumberFormatException => hexStr }
private def resolveProgramName(rawProg: Option[String], rawMsg: Option[String]): String = {
val p = getOrBlank(rawProg)
val m = getOrBlank(rawMsg)
if (p == "1" || p.isEmpty) {
val mat = ProgramExtractorPattern.matcher(m)
if (mat.find()) mat.group("prog") else p
} else p
}
// --- DATABASE NETWORK DATA LAYER ---
val xa: Transactor[IO] = Transactor.fromDriverManager[IO](
driver = "org.postgresql.Driver",
url = "jdbc:postgresql://localhost:5432/logs",
user = "x",
password = "x",
logHandler = None
)
// Dynamically shifts database processing matching across tables
def fetchLogs(filterTerm: Option[String]): ConnectionIO[List[LogRow]] = {
val baseQuery = fr"SELECT host, facility, priority, tag, datetime, program, msg FROM logs"
val filterFragment = filterTerm match {
case Some(term) if term.trim.nonEmpty =>
val matchPattern = s"%${term.trim}%"
fr"WHERE host ILIKE $matchPattern OR facility ILIKE $matchPattern OR priority ILIKE $matchPattern OR tag ILIKE $matchPattern OR datetime ILIKE $matchPattern OR program ILIKE $matchPattern OR msg ILIKE $matchPattern"
case _ =>
Fragment.empty
}
val orderFragment = fr"ORDER BY datetime DESC"
(baseQuery ++ fr" " ++ filterFragment ++ fr" " ++ orderFragment).query[LogRow].to
-
}
// --- INTERFACE CANVAS STRUCTURAL OBJECTS ---
val tableItems: ObservableBuffer[UiLogRow] = ObservableBuffer[UiLogRow]()
val tableView = new TableView[UiLogRow](tableItems) {
columns ++= Seq(
new TableColumn[UiLogRow, String] { text = "Host"; cellValueFactory = c => createCell(c, _.host) },
new TableColumn[UiLogRow, String] { text = "Facility"; cellValueFactory = c => createCell(c, _.facility) },
new TableColumn[UiLogRow, String] { text = "Priority"; cellValueFactory = c => createCell(c, _.priority) },
new TableColumn[UiLogRow, String] { text = "Tag"; prefWidth = 80; cellValueFactory = c => createCell(c, _.tag) },
new TableColumn[UiLogRow, String] { text = "Datetime"; prefWidth = 200; cellValueFactory = c => createCell(c, _.datetime) },
new TableColumn[UiLogRow, String] { text = "Program"; prefWidth = 100; cellValueFactory = c => createCell(c, _.program) },
new TableColumn[UiLogRow, String] { text = "Message"; prefWidth = 2000; cellValueFactory = c => createCell(c, _.msg) }
)
}
tableView.columnResizePolicy = TableView.UnconstrainedResizePolicy
// --- SCENE & MAIN VIEWPORT WRAPPERS ---
stage = new PrimaryStage {
title = "Database Log Viewer"
width = 1600
height = 800
scene = new Scene { root = new BorderPane { center = tableView } }
}
// --- RUNTIME SCHEDULING INTERFACE MANAGEMENT ---
val databaseTask: IO[Unit] = for {
cliArg <- IO.delay(parameters.raw.headOption)
results <- fetchLogs(cliArg).transact(xa)
processedRows <- IO.delay {
results.map { r =>
val resolvedProg = resolveProgramName(r.program, r.msg)
UiLogRow(
host = getOrBlank(r.host),
facility = getOrBlank(r.facility),
priority = getOrBlank(r.priority),
tag = decodeHexTag(getOrBlank(r.tag)),
datetime = formatTimestamp(getOrBlank(r.datetime)),
program = resolvedProg,
msg = getOrBlank(r.msg).drop(42)
)
}.filterNot { r =>
r.program == "devd" || r.program == "influxd"
}
}
_ <- IO.delay {
Platform.runLater {
tableItems.clear()
tableItems ++= processedRows
}
}
} yield ()
databaseTask.unsafeRunAndForget()
}