-
Notifications
You must be signed in to change notification settings - Fork 580
/
Logger.scala
398 lines (343 loc) · 13.5 KB
/
Logger.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
* Copyright 2010 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.twitter.logging
import java.util.concurrent.ConcurrentHashMap
import java.util.{logging => javalog}
import scala.annotation.varargs
import scala.collection.Map
import scala.jdk.CollectionConverters._
class LoggingException(reason: String) extends Exception(reason)
/**
* Scala wrapper for logging.
*/
class Logger protected (val name: String, private val wrapped: javalog.Logger) {
// wrapped methods:
def addHandler(handler: javalog.Handler): Unit = wrapped.addHandler(handler)
def getFilter(): javalog.Filter = wrapped.getFilter()
def getHandlers(): Array[javalog.Handler] = wrapped.getHandlers()
def getLevel(): javalog.Level = wrapped.getLevel()
def getParent(): javalog.Logger = wrapped.getParent()
def getUseParentHandlers(): Boolean = wrapped.getUseParentHandlers()
def isLoggable(level: javalog.Level): Boolean = wrapped.isLoggable(level)
def log(record: javalog.LogRecord): Unit = wrapped.log(record)
def removeHandler(handler: javalog.Handler): Unit = wrapped.removeHandler(handler)
def setFilter(filter: javalog.Filter): Unit = wrapped.setFilter(filter)
def setLevel(level: javalog.Level): Unit = wrapped.setLevel(level)
def setUseParentHandlers(use: Boolean): Unit = wrapped.setUseParentHandlers(use)
override def toString: String = {
"<%s name='%s' level=%s handlers=%s use_parent=%s>".format(
getClass.getName,
name,
getLevel(),
getHandlers().toList.mkString("[", ", ", "]"),
if (getUseParentHandlers()) "true" else "false"
)
}
/**
* Log a message, with sprintf formatting, at the desired level.
*/
@varargs
final def log(level: Level, message: String, items: Any*): Unit =
log(level, null: Throwable, message, items: _*)
/**
* Log a message, with sprintf formatting, at the desired level, and
* attach an exception and stack trace. The message is lazily formatted if
* formatting is required.
*/
@varargs
final def log(level: Level, thrown: Throwable, message: String, items: Any*): Unit = {
val myLevel = getLevel
if ((myLevel eq null) || (level.intValue >= myLevel.intValue)) {
val record =
if (items.size > 0) new LazyLogRecordUnformatted(level, message, items: _*)
else new LogRecord(level, message)
record.setLoggerName(wrapped.getName)
if (thrown ne null) {
record.setThrown(thrown)
}
wrapped.log(record)
}
}
final def apply(level: Level, message: String, items: Any*): Unit = log(level, message, items: _*)
final def apply(level: Level, thrown: Throwable, message: String, items: Any*): Unit =
log(level, thrown, message, items: _*)
// convenience methods:
@varargs
def fatal(msg: String, items: Any*): Unit = log(Level.FATAL, msg, items: _*)
@varargs
def fatal(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.FATAL, thrown, msg, items: _*)
@varargs
def critical(msg: String, items: Any*): Unit = log(Level.CRITICAL, msg, items: _*)
@varargs
def critical(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.CRITICAL, thrown, msg, items: _*)
@varargs
def error(msg: String, items: Any*): Unit = log(Level.ERROR, msg, items: _*)
@varargs
def error(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.ERROR, thrown, msg, items: _*)
@varargs
def warning(msg: String, items: Any*): Unit = log(Level.WARNING, msg, items: _*)
@varargs
def warning(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.WARNING, thrown, msg, items: _*)
@varargs
def info(msg: String, items: Any*): Unit = log(Level.INFO, msg, items: _*)
@varargs
def info(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.INFO, thrown, msg, items: _*)
@varargs
def debug(msg: String, items: Any*): Unit = log(Level.DEBUG, msg, items: _*)
@varargs
def debug(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.DEBUG, thrown, msg, items: _*)
@varargs
def trace(msg: String, items: Any*): Unit = log(Level.TRACE, msg, items: _*)
@varargs
def trace(thrown: Throwable, msg: String, items: Any*): Unit =
log(Level.TRACE, thrown, msg, items: _*)
def debugLazy(msg: => AnyRef): Unit = logLazy(Level.DEBUG, null, msg)
def traceLazy(msg: => AnyRef): Unit = logLazy(Level.TRACE, null, msg)
/**
* Log a message, with lazy (call-by-name) computation of the message,
* at the desired level.
*/
def logLazy(level: Level, message: => AnyRef): Unit = logLazy(level, null: Throwable, message)
/**
* Log a message, with lazy (call-by-name) computation of the message,
* and attach an exception and stack trace.
*/
def logLazy(level: Level, thrown: Throwable, message: => AnyRef): Unit = {
val myLevel = getLevel
if ((myLevel eq null) || (level.intValue >= myLevel.intValue)) {
val record = new LazyLogRecord(level, message)
record.setLoggerName(wrapped.getName)
if (thrown ne null) {
record.setThrown(thrown)
}
wrapped.log(record)
}
}
// convenience methods:
def ifFatal(message: => AnyRef): Unit = logLazy(Level.FATAL, message)
def ifFatal(thrown: Throwable, message: => AnyRef): Unit = logLazy(Level.FATAL, thrown, message)
def ifCritical(message: => AnyRef): Unit = logLazy(Level.CRITICAL, message)
def ifCritical(thrown: Throwable, message: => AnyRef): Unit =
logLazy(Level.CRITICAL, thrown, message)
def ifError(message: => AnyRef): Unit = logLazy(Level.ERROR, message)
def ifError(thrown: Throwable, message: => AnyRef): Unit = logLazy(Level.ERROR, thrown, message)
def ifWarning(message: => AnyRef): Unit = logLazy(Level.WARNING, message)
def ifWarning(thrown: Throwable, message: => AnyRef): Unit =
logLazy(Level.WARNING, thrown, message)
def ifInfo(message: => AnyRef): Unit = logLazy(Level.INFO, message)
def ifInfo(thrown: Throwable, message: => AnyRef): Unit = logLazy(Level.INFO, thrown, message)
def ifDebug(message: => AnyRef): Unit = logLazy(Level.DEBUG, message)
def ifDebug(thrown: Throwable, message: => AnyRef): Unit = logLazy(Level.DEBUG, thrown, message)
def ifTrace(message: => AnyRef): Unit = logLazy(Level.TRACE, message)
def ifTrace(thrown: Throwable, message: => AnyRef): Unit = logLazy(Level.TRACE, thrown, message)
/**
* Lazily logs a throwable. If the throwable contains a log level (via [[HasLogLevel]]), it
* will log at the stored level, otherwise it will log at `defaultLevel`.
*/
def throwable(thrown: Throwable, message: => AnyRef, defaultLevel: Level): Unit =
thrown match {
case HasLogLevel(level) => logLazy(level, thrown, message)
case _ => logLazy(defaultLevel, thrown, message)
}
/**
* Lazily logs a throwable. If the throwable contains a log level (via [[HasLogLevel]]), it
* will log at the stored level, otherwise it will log at `Level.ERROR`.
*/
def throwable(thrown: Throwable, message: => AnyRef): Unit =
throwable(thrown, message, Level.ERROR)
/**
* Remove all existing log handlers.
*/
def clearHandlers(): Unit = {
// some custom Logger implementations may return null from getHandlers
val handlers = getHandlers()
if (handlers ne null) {
for (handler <- handlers) {
try {
handler.close()
} catch { case _: Throwable => () }
removeHandler(handler)
}
}
}
}
object NullLogger
extends Logger(
"null", {
val jLog = javalog.Logger.getLogger("null")
jLog.setLevel(Level.OFF)
jLog
}
)
object Logger extends Iterable[Logger] {
private[this] val levelNamesMap: Map[String, Level] =
Level.AllLevels.map { level =>
level.name -> level
}.toMap
private[this] val levelsMap: Map[Int, Level] =
Level.AllLevels.map { level =>
level.value -> level
}.toMap
// A cache of scala Logger objects by name.
// Using a low concurrencyLevel (2), with the assumption that we aren't ever creating too
// many loggers at the same time.
private[this] val loggersCache = new ConcurrentHashMap[String, Logger](128, 0.75f, 2)
// A cache of LoggerFactory functions passed into Logger.configure.
@volatile private[this] var loggerFactoryCache = List[() => Logger]()
private[logging] val root: Logger = get("")
// ----- convenience methods:
/** OFF is used to turn off logging entirely. */
def OFF: Level.OFF.type = Level.OFF
/** Describes an event which will cause the application to exit immediately, in failure. */
def FATAL: Level.FATAL.type = Level.FATAL
/** Describes an event which will cause the application to fail to work correctly, but
* keep attempt to continue. The application may be unusable.
*/
def CRITICAL: Level.CRITICAL.type = Level.CRITICAL
/** Describes a user-visible error that may be transient or not affect other users. */
def ERROR: Level.ERROR.type = Level.ERROR
/** Describes a problem which is probably not user-visible but is notable and/or may be
* an early indication of a future error.
*/
def WARNING: Level.WARNING.type = Level.WARNING
/** Describes information about the normal, functioning state of the application. */
def INFO: Level.INFO.type = Level.INFO
/** Describes information useful for general debugging, but probably not normal,
* day-to-day use.
*/
def DEBUG: Level.DEBUG.type = Level.DEBUG
/** Describes information useful for intense debugging. */
def TRACE: Level.TRACE.type = Level.TRACE
/** ALL is used to log everything. */
def ALL: Level.ALL.type = Level.ALL
/**
* Return a map of log level values to the corresponding Level objects.
*/
def levels: Map[Int, Level] = levelsMap
/**
* Return a map of log level names to the corresponding Level objects.
*/
def levelNames: Map[String, Level] = levelNamesMap
/**
* Reset logging to an initial state, where all logging is set at
* INFO level and goes to the console (stderr). Any existing log
* handlers are removed.
*/
def reset(): Unit = {
clearHandlers()
loggersCache.clear()
root.addHandler(new ConsoleHandler(new Formatter(), None))
}
/**
* Remove all existing log handlers from all existing loggers.
*/
def clearHandlers(): Unit = {
foreach { logger =>
logger.clearHandlers()
logger.setLevel(null)
}
}
/**
* Execute a block with a given set of handlers, reverting back to the original
* handlers upon completion.
*/
def withLoggers(loggerFactories: List[() => Logger])(f: => Unit): Unit =
withLazyLoggers(loggerFactories.map(_()))(f)
/**
* Execute a block with a given set of handlers, reverting back to the original
* handlers upon completion.
*/
def withLazyLoggers(loggers: => List[Logger])(f: => Unit): Unit = {
// Hold onto a local copy of loggerFactoryCache in case Logger.configure is called within f.
val localLoggerFactoryCache = loggerFactoryCache
clearHandlers()
loggers
f
reset()
loggerFactoryCache = localLoggerFactoryCache
loggerFactoryCache.foreach { _() }
}
/**
* Return a logger for the given package name. If one doesn't already
* exist, a new logger will be created and returned.
*/
def get(name: String): Logger = {
loggersCache.get(name) match {
case logger: Logger =>
logger
case null =>
val logger = new Logger(name, javalog.Logger.getLogger(name))
val oldLogger = loggersCache.putIfAbsent(name, logger)
if (oldLogger != null) {
oldLogger
} else {
logger
}
}
}
/** An alias for `get(name)` */
def apply(name: String): Logger = get(name)
private def get(depth: Int): Logger = {
val st = new Throwable().getStackTrace
if (st.length >= depth) {
getForClassName(st(depth).getClassName)
} else {
// Seems like there is no stack trace in throwable, possibly the JVM is running with `-XX:-StackTraceInThrowable` option
getForClassName("stackTraceIsAbsent")
}
}
/**
* Return a logger for the class name of the class/object that called
* this method. Normally you would use this in a "private val"
* declaration on the class/object. The class name is determined
* by sniffing around on the stack.
*/
def get(): Logger = get(2)
/** An alias for `get()` */
def apply(): Logger = get(2)
private def getForClassName(className: String) = {
if (className.endsWith("$")) {
get(className.substring(0, className.length - 1))
} else {
get(className)
}
}
/**
* Return a logger for the package of the class given.
*/
def get(cls: Class[_]): Logger = getForClassName(cls.getName)
/** An alias for `get(class)` */
def apply(cls: Class[_]): Logger = get(cls)
/**
* Iterate the Logger objects that have been created.
*/
def iterator: Iterator[Logger] = loggersCache.values.iterator.asScala
/**
* Reset all the loggers and register new loggers
* @note Only one logger is permitted per namespace
*/
def configure(loggerFactories: List[() => Logger]): Unit = {
loggerFactoryCache = loggerFactories
clearHandlers()
loggerFactories.foreach { _() }
}
}