Formatting Log Records
A LogFormat represents a DSL to describe the format of text log messages.
import zio.logging.console
import zio.logging.LogFormat._
val myLogFormat = timestamp.fixed(32) |-| level |-| label("message", quoted(line))
val myConsoleLogger = console(myLogFormat)
LogFormat.filter returns a new log format that produces the same result, if LogFilter is satisfied.
import zio.logging.LogFormat
import zio.logging.LogFilter
LogFormat.label("cause", LogFormat.cause).filter(LogFilter.causeNonEmpty)
Log Format Configuration​
String representation of LogFormat:
| format | description |
|---|---|
| %timestamp{date-format} %timestamp | Timestamp, where date format is java.time.format.DateTimeFormatter,example: %timestamp{yyyy-MM-dd'T'HH:mm:ssZ} |
| %fiberId | Fiber Id |
| %fixed{size}{format} | Fixed size for format, example: %fixed{7}{%level} |
| %label{name}{format} | Labeled format, example: %label{abcSpan}{%span{abc}} |
| %color{LogColor}{format} | Colored format, log color values: RED, BLUE, YELLOW, CYAN, GREEN, MAGENTA, WHITE, example: %color{CYAN}{%timestamp} |
| %level | Log level |
| %name | Logger name (from logger name annotation or Trace) |
| %line | Log/trace line (from Trace) |
| %message | Log message |
| %cause | Cause |
| %kvs | Key-values - all annotations |
| %kv{key} | Key-value - annotation with given key |
| %spans | All log spans |
| %span{span} | Log spans with key |
| %highlight{format} | Highlight given format with colors based on log Level |
| %% | % character |
| %{ | { character |
| %} | } character |
examples:
%timestamp %level [%fiberId] %name:%line %message %cause
%highlight{%timestamp{yyyy-MM-dd'T'HH:mm:ssZ} %fixed{7}{%level} [%fiberId] %name:%line %message %cause}
%label{timestamp}{%fixed{32}{%timestamp}} %label{level}{%level} %label{thread}{%fiberId} %label{message}{%message} %label{cause}{%cause}
LogFormat and LogAppender​
A LogFormat represents a DSL to describe the format of text log messages.
A LogAppender is a low-level interface designed to be the bridge between, ZIO Logging and logging backends, such as
Logback.
This interface is slightly higher-level than a string builder, because it allows for structured logging,
and preserves all ZIO-specific information about runtime failures.
LogFormat may be created by following function:
object LogFormat {
def make(format: (LogAppender, Trace, FiberId, LogLevel, () => String, Cause[Any], FiberRefs, List[LogSpan], Map[String, String]) => Any): LogFormat
}
format function arguments can be split to two sections:
- LogAppender
- all others - all log inputs provided by ZIO core logging:
- Trace - current trace (
zio.Trace) - FiberId - fiber id (
zio.FiberId) - LogLevel - log level (
zio.LogLevel) - () => String - log message
- Cause[Any] - cause (
zio.Cause) - FiberRefs - fiber refs (
zio.FiberRefs), collection ofzio.FiberRef- ZIO's equivalent of Java's ThreadLocal - List[LogSpan] - log spans (
zio.LogSpan) - Map[String, String] - ZIO core log annotations values, where key is annotation key/name, and value is annotation value
- Trace - current trace (
essential LogAppender functions, which are used in predefined log formats:
def appendCause(cause: Cause[Any])- appends azio.Causeto the log, some logging backends may have special support for logging failuresdef appendNumeric[A](numeric: A)- appends a numeric value to the logdef appendText(text: String)- appends unstructured text to the logdef appendKeyValue(key: String, value: String)- appends a key/value string pair to the log
then it depends on the specific logging backend how these functions are implemented with respect to the backend output, for example:
- slf4j v1 logging backend - key/value is appended to slf4j MDC context, Cause is transformed to Throwable and placed to slf4j throwable section, all other text and numeric parts are added to slf4j log message
- console logging backend - in general all values are added to log line,
Cause.prettyPrintis used to log cause details
example of some predefined log formats implementations:
def annotation(name: String): LogFormat =
LogFormat.make { (builder, _, _, _, _, _, _, _, annotations) =>
annotations.get(name).foreach { value =>
builder.appendKeyValue(name, value)
}
}
val cause: LogFormat =
LogFormat.make { (builder, _, _, _, _, cause, _, _, _) =>
if (!cause.isEmpty) {
builder.appendCause(cause)
}
}
def text(value: => String): LogFormat =
LogFormat.make { (builder, _, _, _, _, _, _, _, _) =>
builder.appendText(value)
}