Skip to content

Commit

Permalink
Implement java.util.logging bridge (#849)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrzejressel authored May 27, 2024
1 parent d85b460 commit 178679f
Show file tree
Hide file tree
Showing 9 changed files with 665 additions and 17 deletions.
32 changes: 16 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,36 +85,36 @@ jobs:
uses: actions/checkout@v3.3.0
with:
fetch-depth: '0'
- name: Test
if: ${{ (matrix.java == '17') && (matrix.scala == '3.3.0') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '17') && (matrix.scala == '2.13.10') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '8') && (matrix.scala == '2.13.10') }}
run: 'sbt ++${{ matrix.scala }} coreJS/test slf4jBridge/test coreJVM/test slf4j/test '
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test julBridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '8') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} coreJS/test slf4jBridge/test coreJVM/test slf4j/test '
if: ${{ (matrix.java == '17') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '11') && (matrix.scala == '3.3.0') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '11') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '17') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '11') && (matrix.scala == '2.13.10') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '17') && (matrix.scala == '3.3.0') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test slf4j2Bridge/test coreJS/test coreJVM/test '
if: ${{ (matrix.java == '11') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test jpl/test slf4j2/test julBridge/test slf4j2Bridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '8') && (matrix.scala == '2.12.17') }}
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test julBridge/test coreJS/test coreJVM/test '
- name: Test
if: ${{ (matrix.java == '8') && (matrix.scala == '3.3.0') }}
run: 'sbt ++${{ matrix.scala }} coreJS/test slf4jBridge/test coreJVM/test slf4j/test '
run: 'sbt ++${{ matrix.scala }} slf4j/test slf4jBridge/test julBridge/test coreJS/test coreJVM/test '
- name: Compile additional subprojects
if: ${{ ((startsWith(matrix.scala, '2.12.')) || (startsWith(matrix.scala, '2.13.'))) && (matrix.java == '11') }}
run: sbt ++${{ matrix.scala }} examplesCore/compile examplesJpl/compile examplesSlf4j2Bridge/compile examplesSlf4jLogback/compile examplesSlf4j2Logback/compile examplesSlf4j2Log4j/compile benchmarks/compile
run: sbt ++${{ matrix.scala }} examplesCore/compile examplesJpl/compile examplesSlf4j2Bridge/compile examplesSlf4jLogback/compile examplesSlf4j2Logback/compile examplesSlf4j2Log4j/compile examplesJulBridge/compile benchmarks/compile
ci:
name: ci
runs-on: ubuntu-latest
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@ Other modules:
See [Java Platform/System Logger](docs/jpl.md) section for more details.


* java.util.logging bridge - with this logging bridge, it is possible to use `zio-logging` for JUL loggers (usually third-party non-ZIO libraries), add the one of following lines to your `build.sbt` file:

```scala
// JUL bridge
libraryDependencies += "dev.zio" %% "zio-logging-jul-bridge" % "2.2.2"
```

When to use this module: you are already using JUL logger in some other project, and you like to have same log outputs.
See [java.util.logging bridge](docs/jul-bridge.md) section for more details.



## Example

Let's try an example of ZIO Logging which demonstrates a simple application of ZIO logging.
Expand Down
24 changes: 23 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ inThisBuild(
(coreJS / thisProject).value.id -> (coreJS / crossScalaVersions).value,
(coreJVM / thisProject).value.id -> (coreJVM / crossScalaVersions).value,
(jpl / thisProject).value.id -> (jpl / crossScalaVersions).value,
(julBridge / thisProject).value.id -> (julBridge / crossScalaVersions).value,
(slf4j / thisProject).value.id -> (slf4j / crossScalaVersions).value,
(slf4j2 / thisProject).value.id -> (slf4j2 / crossScalaVersions).value,
(slf4jBridge / thisProject).value.id -> (slf4jBridge / crossScalaVersions).value,
Expand All @@ -37,7 +38,8 @@ inThisBuild(
),
run = Some(
"sbt ++${{ matrix.scala }} examplesCore/compile examplesJpl/compile examplesSlf4j2Bridge/compile " +
"examplesSlf4jLogback/compile examplesSlf4j2Logback/compile examplesSlf4j2Log4j/compile benchmarks/compile"
"examplesSlf4jLogback/compile examplesSlf4j2Logback/compile examplesSlf4j2Log4j/compile " +
"examplesJulBridge/compile benchmarks/compile"
)
)
),
Expand Down Expand Up @@ -79,9 +81,11 @@ lazy val root = project
slf4jBridge,
slf4j2Bridge,
jpl,
julBridge,
benchmarks,
examplesCore,
examplesJpl,
examplesJulBridge,
examplesSlf4j2Bridge,
examplesSlf4jLogback,
examplesSlf4j2Logback,
Expand Down Expand Up @@ -175,6 +179,16 @@ lazy val slf4j2Bridge = project
)
.settings(enableZIO())

lazy val julBridge = project
.in(file("jul-bridge"))
.dependsOn(coreJVM)
.settings(stdSettings("zio-logging-jul-bridge", turnCompilerWarningIntoErrors = false))
.settings(enableZIO(enableTesting = true))
.settings(mimaSettings(failOnProblem = true))
.settings(
Test / fork := true
)

lazy val jpl = project
.in(file("jpl"))
.dependsOn(coreJVM)
Expand Down Expand Up @@ -251,6 +265,14 @@ lazy val examplesJpl = project
publish / skip := true
)

lazy val examplesJulBridge = project
.in(file("examples/jul-bridge"))
.dependsOn(julBridge)
.settings(stdSettings("zio-logging-examples-jul-bridge", turnCompilerWarningIntoErrors = false))
.settings(
publish / skip := true
)

lazy val examplesSlf4j2Bridge = project
.in(file("examples/slf4j2-bridge"))
.dependsOn(slf4j2Bridge)
Expand Down
131 changes: 131 additions & 0 deletions docs/jul-bridge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
id: jul-bridge
title: "java.util.logging bridge"
---

It is possible to use `zio-logging` for included `java.util.logging` Loggers (do not confuse with `java.platform.logging`),
usually third-party non-ZIO libraries (most notable: OpenTelemetry used by ZIO-telemetry). To do so, import the `zio-logging-jul-bridge` module
```scala
libraryDependencies += "dev.zio" %% "zio-logging-jul-bridge" % "@VERSION@"
```

and use one of the `JULBridge` layers when setting up logging

```scala
import zio.logging.jul.bridge.JULBridge

program.provideCustom(JULBridge.init())
```

`JULBridge` layers:
* `JULBridge.init(configPath: NonEmptyChunk[String] = logFilterConfigPath)` - setup with `LogFilter` from [filter configuration](log-filter.md#configuration), default configuration path: `logger.filter`, default `LogLevel` is `INFO`
* `JULBridge.init(filter: LogFilter[Any])` - setup with given `LogFilter`
* `JULBridge.initialize` - setup without filtering

Need for log filtering in JUL bridge: filtering in JUL is made on higher level than `jul-bridge` (on `Logger` level and not `Handler` level - which `JULBridge` is). Due to that the whole
filtering in JUL is disabled and is implemented in JULBridge. This may cause degraded performance and much more logs when using other Handlers.

<br/>

JUL logger name is stored in log annotation with key `logger_name` (`zio.logging.loggerNameAnnotationKey`), following log format

```scala
import zio.logging.jul.bridge.JULBridge
import zio.logging.LoggerNameExtractor

val loggerName = LoggerNameExtractor.loggerNameAnnotationOrTrace
val loggerNameFormat = loggerName.toLogFormat()
```
may be used to get logger name from log annotation or ZIO Trace.

This logger name extractor is used by default in log filter, which applying log filtering by defined logger name and level:

```scala
val logFilterConfig = LogFilter.LogLevelByNameConfig(
LogLevel.Info,
"zio.logging.jul " -> LogLevel.Debug,
"JUL-LOGGER" -> LogLevel.Warning
)

val logFilter: LogFilter[String] = logFilterConfig.toFilter
```
<br/>


JUL bridge with custom logger can be setup:

```scala
import zio.logging.jul.bridge.JULBridge
import zio.logging.consoleJsonLogger

val logger = Runtime.removeDefaultLoggers >>> consoleJsonLogger() >+> JULBridge.init()
```

<br/>

## Examples

You can find the source code [here](https://github.com/zio/zio-logging/tree/master/examples)

### JUL bridge with JSON console logger

[//]: # (TODO: make snippet type-checked using mdoc)


```scala
package zio.logging.example

import zio.logging.jul.bridge.JULBridge
import zio.logging.{ConsoleLoggerConfig, LogAnnotation, LogFilter, LogFormat, LoggerNameExtractor, consoleJsonLogger}
import zio.{ExitCode, LogLevel, Runtime, Scope, ZIO, ZIOAppArgs, ZIOAppDefault, ZLayer}

import java.util.UUID

object JULBridgeExampleApp extends ZIOAppDefault {

private val julLogger = java.util.logging.Logger.getLogger("JUL-LOGGER")

private val logFilterConfig = LogFilter.LogLevelByNameConfig(
LogLevel.Info,
"zio.logging.slf4j" -> LogLevel.Debug,
"SLF4J-LOGGER" -> LogLevel.Warning
)

private val logFormat = LogFormat.label(
"name",
LoggerNameExtractor.loggerNameAnnotationOrTrace.toLogFormat()
) + LogFormat.logAnnotation(LogAnnotation.UserId) + LogFormat.logAnnotation(
LogAnnotation.TraceId
) + LogFormat.default

private val loggerConfig = ConsoleLoggerConfig(logFormat, logFilterConfig)

override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
Runtime.removeDefaultLoggers >>> consoleJsonLogger(loggerConfig) >+> JULBridge.init(loggerConfig.toFilter)

private val uuids = List.fill(2)(UUID.randomUUID())

override def run: ZIO[Scope, Any, ExitCode] =
for {
_ <- ZIO.logInfo("Start")
_ <- ZIO.foreachPar(uuids) { u =>
ZIO.succeed(julLogger.info("Test INFO!")) *> ZIO.succeed(
julLogger.warning("Test WARNING!")
) @@ LogAnnotation.UserId(
u.toString
)
} @@ LogAnnotation.TraceId(UUID.randomUUID())
_ <- ZIO.logDebug("Done")
} yield ExitCode.success

}
```

Expected console output:
```
{"name":"zio.logging.example.JULbridgeExampleApp","timestamp":"2024-05-26T13:50:20.6832831+02:0","level":"INFO","thread":"zio-fiber-1143120685","message":"Start"}
{"name":"JUL-LOGGER","trace_id":"08e9e10a-d3c5-4f90-8627-2ae4ddee1522","timestamp":"2024-05-26T13:50:20.7112909+02:0","level":"INFO","thread":"zio-fiber-1683803358","message":"Test INFO!"}
{"name":"JUL-LOGGER","trace_id":"08e9e10a-d3c5-4f90-8627-2ae4ddee1522","timestamp":"2024-05-26T13:50:20.7112909+02:0","level":"INFO","thread":"zio-fiber-71852457","message":"Test INFO!"}
{"name":"JUL-LOGGER","user_id":"85f762cc-e62c-4576-9f14-6a3ad0918d99","trace_id":"08e9e10a-d3c5-4f90-8627-2ae4ddee1522","timestamp":"2024-05-26T13:50:20.7142882+02:0","level":"WARN","thread":"zio-fiber-1911711828","message":"Test WARNING!"}
{"name":"JUL-LOGGER","user_id":"47850c02-bb60-4b6a-9c0f-0aa095066d10","trace_id":"08e9e10a-d3c5-4f90-8627-2ae4ddee1522","timestamp":"2024-05-26T13:50:20.7142882+02:0","level":"WARN","thread":"zio-fiber-1801412106","message":"Test WARNING!"}
```
1 change: 1 addition & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const sidebars = {
'file-logger',
'reconfigurable-logger',
'jpl',
'jul-bridge',
'slf4j2',
'slf4j1',
'slf4j2-bridge',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2019-2024 John A. De Goes and the ZIO Contributors
*
* 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
*
* http://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 zio.logging.example

import zio.logging.jul.bridge.JULBridge
import zio.logging.{ ConsoleLoggerConfig, LogAnnotation, LogFilter, LogFormat, LoggerNameExtractor, consoleJsonLogger }
import zio.{ ExitCode, LogLevel, Runtime, Scope, ZIO, ZIOAppArgs, ZIOAppDefault, ZLayer }

import java.util.UUID

object JULBridgeExampleApp extends ZIOAppDefault {

private val julLogger = java.util.logging.Logger.getLogger("JUL-LOGGER")

private val logFilterConfig = LogFilter.LogLevelByNameConfig(
LogLevel.Info,
"zio.logging.slf4j" -> LogLevel.Debug,
"SLF4J-LOGGER" -> LogLevel.Warning
)

private val logFormat = LogFormat.label(
"name",
LoggerNameExtractor.loggerNameAnnotationOrTrace.toLogFormat()
) + LogFormat.logAnnotation(LogAnnotation.UserId) + LogFormat.logAnnotation(
LogAnnotation.TraceId
) + LogFormat.default

private val loggerConfig = ConsoleLoggerConfig(logFormat, logFilterConfig)

override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
Runtime.removeDefaultLoggers >>> consoleJsonLogger(loggerConfig) >+> JULBridge.init(loggerConfig.toFilter)

private val uuids = List.fill(2)(UUID.randomUUID())

override def run: ZIO[Scope, Any, ExitCode] =
for {
_ <- ZIO.logInfo("Start")
_ <- ZIO.foreachPar(uuids) { u =>
ZIO.succeed(julLogger.info("Test INFO!")) *> ZIO.succeed(
julLogger.warning("Test WARNING!")
) @@ LogAnnotation.UserId(
u.toString
)
} @@ LogAnnotation.TraceId(UUID.randomUUID())
_ <- ZIO.logDebug("Done")
} yield ExitCode.success

}
Loading

0 comments on commit 178679f

Please sign in to comment.