Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement java.util.logging bridge #849

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Other modules:
When to use this module: you are already using Java Platform/System Logger in some other project, and you like to have same log outputs.
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"
```

## Example

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

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() >+> Slf4jBridge.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!"}
```
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
Loading