/
Log4sConfig.scala
170 lines (142 loc) · 5.9 KB
/
Log4sConfig.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package org.log4s
package log4sjs
import scala.annotation.tailrec
import scala.collection.{ breakOut, mutable, immutable }
import scala.math.Ordered._
import scala.scalajs.js
import js.annotation._
case class AppenderSetting(appenders: immutable.Seq[Log4sAppender.Type], additive: Boolean)
object Log4sConfig { thisConfig =>
private[this] lazy val standardAppender = new Log4sConsoleAppender
private[this] lazy val defaultAppenderSetting = AppenderSetting(Nil, true)
private[this] case class ConcreteLoggerState(threshold: LogLevel, appenders: Iterable[Log4sAppender]) {
def withChild(ls: LoggerState): ConcreteLoggerState = {
val newThreshold = ls.threshold.getOrElse(threshold)
val newAppenders = {
val AppenderSetting(childAppenders, childAdditive) = ls.appenders.getOrElse(defaultAppenderSetting)
if (childAdditive) {
appenders ++ childAppenders
} else {
childAppenders
}
}
ConcreteLoggerState(newThreshold, newAppenders)
}
def isEnabled(ll: LogLevel): Boolean = {
ll >= threshold
}
}
private[this] case class LoggerState(threshold: Option[LogLevel] = None, appenders: Option[AppenderSetting] = None)
private[this] lazy val emptyLoggerState = LoggerState()
private[this] class Node(
val children: mutable.Map[String, Node] = mutable.Map.empty,
var state: LoggerState = emptyLoggerState)
private[this] object LoggerState {
private[this] val defaultRootState = ConcreteLoggerState(Trace, Seq(standardAppender))
private[this] val root = new Node()
def apply(parts: Seq[String]): ConcreteLoggerState = {
@inline
@tailrec
def helper(tree: collection.Map[String, Node], state: ConcreteLoggerState, path: Seq[String]): ConcreteLoggerState = {
if (path.isEmpty) {
state
} else {
tree.get(path.head) match {
case None => state
case Some(n) => helper(n.children, state.withChild(n.state), path.tail)
}
}
}
helper(root.children, defaultRootState.withChild(root.state), parts)
}
@tailrec
protected[this] def getNode(parts: Seq[String], node: Node = root): Option[Node] = {
/* This method can be written in one line as a left fold that does a `return None` in a
* `getOrElse`. However the early return is unusual and it may not perform very well to
* throw an exception in the most common case, so the tail-recursive form is fine. */
if (parts.isEmpty) {
Some(node)
} else {
node.children.get(parts.head) match {
case None => None
case Some(n2) => getNode(parts.tail, n2)
}
}
}
def get(parts: Seq[String]): LoggerState = {
getNode(parts)
.map(_.state)
.getOrElse(emptyLoggerState)
}
def update(parts: Seq[String], state: LoggerState): Unit = {
val pathNode =
parts.foldLeft(root) { (node, part) =>
node.children.getOrElseUpdate(part, new Node())
}
/* TODO: Merge settings rather than pure overwrite? */
pathNode.state = state
}
}
def logger(name: String, threshold: Option[Option[LogLevel]] = None, appenders: Option[Option[AppenderSetting]] = None): Unit = {
val parts = CategoryParser(name)
val currentState = LoggerState.get(parts)
var updatedState = currentState
for (t <- threshold) {
updatedState = updatedState.copy(threshold = t)
}
for (a <- appenders) {
updatedState = updatedState.copy(appenders = a)
}
LoggerState.update(parts, updatedState)
}
@JSExportTopLevel("Config.setLoggerThreshold")
def setLoggerThreshold(name: String, threshold: LogLevel): Unit = {
logger(name, threshold = Some(Option(threshold)))
}
@JSExportTopLevel("Config.setLoggerThreshold")
def setLoggerThreshold(name: String, threshold: String): Unit = {
setLoggerThreshold(name, LogLevel.forName(threshold))
}
@JSExportTopLevel("Config.setCategoryAppenders")
def setCategoryAppendersDynamic(name: String, additive: Boolean, appenders: js.Array[Log4sAppender.DynamicType]): Unit = {
setCategoryAppenders(name, additive, appenders)
}
def setCategoryAppenders[A: Log4sAppender.Provider](name: String, additive: Boolean, appenders: Seq[A]): Unit = {
val appenderSeq: immutable.Seq[Log4sAppender] = appenders.map(Log4sAppender.from(_))(breakOut)
logger(name, appenders = Some(Some(AppenderSetting(appenderSeq, additive))))
}
/** Add an appender for a given category */
@JSExportTopLevel("Config.addCategoryAppender")
def addCategoryAppenderDynamic(name: String, appender: Log4sAppender.DynamicType): Unit = {
addCategoryAppender(name, appender)
}
def addCategoryAppender[A: Log4sAppender.Provider](name: String, appender: A): Unit = {
val parts = CategoryParser(name)
val currentState = LoggerState.get(parts)
val currentAppenderSetting = currentState.appenders.getOrElse(AppenderSetting(Nil, true))
val newAppenderSetting = currentAppenderSetting.copy(appenders = currentAppenderSetting.appenders :+ Log4sAppender.from(appender))
val updatedState = currentState.copy(appenders = Some(newAppenderSetting))
LoggerState(parts) = updatedState
}
private[log4sjs] final def doLog(e: LoggedEvent): Unit = {
val state = LoggerState(e.loggerPath)
if (state.isEnabled(e.level)) {
for (appender <- state.appenders) {
appender.append(e)
}
}
}
private[log4sjs] final def isPathEnabled(path: Seq[String], ll: LogLevel): Boolean = {
LoggerState(path).isEnabled(ll)
}
private[log4sjs] final def isNameEnabled(name: String, ll: LogLevel): Boolean =
isPathEnabled(CategoryParser(name), ll)
private[this] def levelNumber(ll: LogLevel): Byte = ll match {
case Trace => 1
case Debug => 2
case Info => 3
case Warn => 4
case Error => 5
}
private[this] implicit val logLevelOrdering: Ordering[LogLevel] = Ordering by levelNumber
}