2

我正在尝试按需向我的 Swing 应用程序的用户显示日志内容。显示日志事件列表,可能允许他们查看有关日志消息的详细信息,理想情况下类似于 eclipse“错误日志”视图显示的内容。

是否有任何预制的日志显示组件/日志附加器?

我意识到我可以轻松地将字符串值附加到 Textarea,但如果可能的话,我想要更多“免费”的花哨(搜索、按事件过滤、按记录器过滤、导出等)

我目前正在使用带有 logback 的 SLF4J,但如果存在这样的组件,我在切换到另一个日志框架时没有任何问题。

4

2 回答 2

1

我对此进行了抨击。虽然这对我来说是一个教学练习,并且非常接近完全可用,但仍然缺少一些必需品。也许有人可以帮助进行最后的润色。

背景

这是一个更大的工作应用程序的一部分,所以虽然我只发布与这个问题相关的代码,但有一些应用程序的工件。代码在 Scala 中,但降级到 Java 应该很简单。我不包括大多数import指令;希望很清楚我指的是什么类。我相信只有两个具有冲突简单类名的类是 scala.swing.Componentjava.awt.Component

概述

该代码存在于以下文件中:

  • Main.scala具有创建日志窗口的代码。
  • LogFrame是显示日志条目的摆动窗口
  • LogModel存储记录数据
  • AbstractBaseTableModelLogModel我的应用程序中另一个类的超类,此处未显示。
  • TableAppender.scala是将 Swing 连接到 Logback 的原因。
  • 格式化时间戳的单行方法在我的包对象中,因为它在我的应用程序的其他地方使用。
  • logback.xml有一行激活我的新附加程序

编码

Main.scala显示日志输出的框架中可见:

object Main extends swing.SwingApplication {
  override def startup(args: Array[String]) {
    val logFrame = LogFrame
    if (logFrame.size == new Dimension(0,0)) logFrame.pack()
    logFrame.visible = true
  }
}

LogFrame例对象定义在LogFrame.scala

object LogFrame extends Frame {
  title = "Log"
  iconImage = new ImageIcon("log.png").getImage
  preferredSize = new Dimension(1200,370)

  object LogTable extends Table {
    model = LogModel
    Map(0 -> 50, 1 -> 32, 4 -> 400) foreach { m =>
      peer.getColumnModel getColumn m._1 setPreferredWidth m._2
    }

    override def rendererComponent(
      isSelected: Boolean, focused: Boolean, row: Int, column: Int
    ) = {
      val v = model.getValueAt(
        peer.convertRowIndexToModel(row), 
        peer.convertColumnIndexToModel(column)
      ).toString
      TableCellRenderer.componentFor(this, isSelected, focused, v, row, column)
    }

    import ch.qos.logback.classic.Level.{ERROR,WARN}
    object TableCellRenderer extends AbstractRenderer[String, TextArea](new TextArea {
      lineWrap = true; wordWrap = true
    }) {
      val brown = new java.awt.Color(143,112,0)
      def configure(t: Table, sel: Boolean, foc: Boolean, s: String, row: Int, col: Int) = {
        component.text = s
        model.getValueAt(
          LogTable.this.peer.convertRowIndexToModel(row),
          LogModel.columnNames.indexOf("Level")
        ) match {
          case ERROR => component.foreground = java.awt.Color.RED
          case WARN => component.foreground = brown
          case _ =>
        }
      }
    }
  }

  contents = new BoxPanel(Vertical) {
    contents += new ScrollPane { viewportView = LogTable }
  }

  def logEvent(event: ILoggingEvent) {
    event +=: LogModel
  }

}

LogModel例在LogModel.scala.

object LogModel extends AbstractBaseTableModel {

  final val columnNames = Array("Time","Level","Thread","Logger","Message")

  val data = ListBuffer[Array[AnyRef]]()

  def +=:(event: ILoggingEvent) {
    Array[AnyRef](
      formatTimeStamp(event.getTimeStamp),
      event.getLevel,
      event.getThreadName,
      event.getLoggerName.replaceFirst(".*\\.",""),
      event.getFormattedMessage
    ) +=: data
    fireTableChanged( new TableModelEvent(this) )
  }
}

我的包对象中的这两行定义了 formatTimeStamp()方法:

final val isoFormatter = org.joda.time.format.ISODateTimeFormat.dateTimeNoMillis
def formatTimeStamp(millis: Long)      = isoFormatter.print(millis)

LogModel扩展的原因AbstractBaseTableModel是因为我的应用程序有一些其他的表模型是一次性更新的,而不是像日志模型那样一次更新一行。因此 AbstractBaseTableModel有一个datatype 的成员SeqLike,并且子类可以根据需要使用不可变List或可变 ListBuffer的。

import scala.collection.SeqLike

abstract class AbstractBaseTableModel extends AbstractTableModel {
  val columnNames: Array[String]

  val data: SeqLike[Array[AnyRef],_]

  def getRowCount: Int = data.size
  def getColumnCount: Int = columnNames.size
  override def getColumnName(column: Int) = columnNames(column)
  override def getValueAt(row: Int, column: Int): AnyRef = data(row)(column)
  override def isCellEditable(row: Int, col: Int) = false

  override def getColumnClass(columnIndex: Int): Class[_] =
    getValueAt(0, columnIndex).getClass

}

TableAppender.scala文件很小:

class TableAppender extends AppenderBase[ILoggingEvent] {
  def append(event: ILoggingEvent) {
    LogFrame.logEvent(event)
  }
}

最后,对我的logback.xml文件的更改甚至更小:

<appender name="swingTable" class="mypackage.TableAppender"/>

问题

唯一让我无法决定这比老式日志文件更好的问题是:

1)因为我已经覆盖Table.rendererComponent()JTable.setDefaultRenderer()不能像往常一样工作。

2)我还没有弄清楚如何让包裹线在表格中可见。我所知道的是我必须增加行的高度。

如果这些问题得到解决,接下来就是根据日志级别和记录器名称添加实时过滤。事实上,我至少暂时将其搁置一旁,但如果有人有任何建议,我很想听听他们的意见。

参考

以下资源对我编写上述代码很有用,或者可能对解决剩余的问题有用:

于 2014-01-20T12:24:49.330 回答
0

我会看看SwingX。在这个页面上有一个很好的演示。以 JXTable 为例。

于 2013-04-10T18:21:23.157 回答