diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edf63482..6c7bfabd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,12 @@ jobs: matrix: os: [ubuntu-22.04] scala: [2.12, 2.13, 3] - java: [temurin@11, temurin@17] - project: [rootJS, rootJVM] + java: + - temurin@11 + - temurin@17 + - temurin@21 + - temurin@25 + project: [log-effectJS, log-effectNative, log-effectJVM] runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: @@ -68,6 +72,32 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update + - name: Setup Java (temurin@21) + id: setup-java-temurin-21 + if: matrix.java == 'temurin@21' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@21' && steps.setup-java-temurin-21.outputs.cache-hit == 'false' + run: sbt +update + + - name: Setup Java (temurin@25) + id: setup-java-temurin-25 + if: matrix.java == 'temurin@25' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@25' && steps.setup-java-temurin-25.outputs.cache-hit == 'false' + run: sbt +update + - name: Check that workflows are up to date run: sbt githubWorkflowCheck @@ -76,9 +106,13 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck - name: scalaJSLink - if: matrix.project == 'rootJS' + if: matrix.project == 'log-effectJS' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult + - name: nativeLink + if: matrix.project == 'log-effectNative' + run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/nativeLink + - name: Test run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test @@ -92,11 +126,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: mkdir -p zio/.js/target interop/.jvm/target interop/.js/target core/.js/target zio/.jvm/target core/.jvm/target fs2/.js/target fs2/.jvm/target project/target + run: mkdir -p zio/js/target fs2/native/target fs2/jvm/target core/native/target interop/.js/target core/js/target zio/jvm/target fs2/js/target core/jvm/target zio/native/target interop/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: tar cf targets.tar zio/.js/target interop/.jvm/target interop/.js/target core/.js/target zio/.jvm/target core/.jvm/target fs2/.js/target fs2/.jvm/target project/target + run: tar cf targets.tar zio/js/target fs2/native/target fs2/jvm/target core/native/target interop/.js/target core/js/target zio/jvm/target fs2/js/target core/jvm/target zio/native/target interop/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') @@ -149,62 +183,118 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update - - name: Download target directories (2.12, rootJS) + - name: Setup Java (temurin@21) + id: setup-java-temurin-21 + if: matrix.java == 'temurin@21' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@21' && steps.setup-java-temurin-21.outputs.cache-hit == 'false' + run: sbt +update + + - name: Setup Java (temurin@25) + id: setup-java-temurin-25 + if: matrix.java == 'temurin@25' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@25' && steps.setup-java-temurin-25.outputs.cache-hit == 'false' + run: sbt +update + + - name: Download target directories (2.12, log-effectJS) + uses: actions/download-artifact@v6 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-log-effectJS + + - name: Inflate target directories (2.12, log-effectJS) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (2.12, log-effectNative) + uses: actions/download-artifact@v6 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-log-effectNative + + - name: Inflate target directories (2.12, log-effectNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (2.12, log-effectJVM) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-log-effectJVM - - name: Inflate target directories (2.12, rootJS) + - name: Inflate target directories (2.12, log-effectJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12, rootJVM) + - name: Download target directories (2.13, log-effectJS) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-log-effectJS - - name: Inflate target directories (2.12, rootJVM) + - name: Inflate target directories (2.13, log-effectJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootJS) + - name: Download target directories (2.13, log-effectNative) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-log-effectNative - - name: Inflate target directories (2.13, rootJS) + - name: Inflate target directories (2.13, log-effectNative) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13, rootJVM) + - name: Download target directories (2.13, log-effectJVM) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13-log-effectJVM - - name: Inflate target directories (2.13, rootJVM) + - name: Inflate target directories (2.13, log-effectJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJS) + - name: Download target directories (3, log-effectJS) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-3-log-effectJS - - name: Inflate target directories (3, rootJS) + - name: Inflate target directories (3, log-effectJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3, rootJVM) + - name: Download target directories (3, log-effectNative) uses: actions/download-artifact@v6 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3-log-effectNative - - name: Inflate target directories (3, rootJVM) + - name: Inflate target directories (3, log-effectNative) + run: | + tar xf targets.tar + rm targets.tar + + - name: Download target directories (3, log-effectJVM) + uses: actions/download-artifact@v6 + with: + name: target-${{ matrix.os }}-${{ matrix.java }}-3-log-effectJVM + + - name: Inflate target directories (3, log-effectJVM) run: | tar xf targets.tar rm targets.tar @@ -276,8 +366,34 @@ jobs: if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false' run: sbt +update + - name: Setup Java (temurin@21) + id: setup-java-temurin-21 + if: matrix.java == 'temurin@21' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@21' && steps.setup-java-temurin-21.outputs.cache-hit == 'false' + run: sbt +update + + - name: Setup Java (temurin@25) + id: setup-java-temurin-25 + if: matrix.java == 'temurin@25' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@25' && steps.setup-java-temurin-25.outputs.cache-hit == 'false' + run: sbt +update + - name: Submit Dependencies uses: scalacenter/sbt-dependency-submission@v2 with: - modules-ignore: rootjs_2.12 rootjs_2.13 rootjs_3 rootjvm_2.12 rootjvm_2.13 rootjvm_3 rootnative_2.12 rootnative_2.13 rootnative_3 + modules-ignore: log-effectjs_2.12 log-effectjs_2.13 log-effectjs_3 log-effectnative_2.12 log-effectnative_2.13 log-effectnative_3 log-effectjvm_2.12 log-effectjvm_2.13 log-effectjvm_3 configs-ignore: test scala-tool scala-doc-tool test-internal diff --git a/.sbtopts b/.sbtopts new file mode 100644 index 00000000..224cb189 --- /dev/null +++ b/.sbtopts @@ -0,0 +1,3 @@ +-J-Xms2g +-J-Xmx4g +-J-XX:MaxMetaspaceSize=512m diff --git a/.scalafmt.conf b/.scalafmt.conf index 89b53c27..7b4ee5a8 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,6 @@ version=3.10.7 -runner.dialect = scala213 +runner.dialect = scala213source3 align.preset = more maxColumn = 100 diff --git a/LICENSE b/LICENSE index 21e634d9..7ae3c783 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 laserdisc-io +Copyright (c) 2018-2026 LaserDisc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 1ee0c32f..e269e4fa 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ [![log-effect-interop Scala version support](https://index.scala-lang.org/laserdisc-io/log-effect/log-effect-interop/latest-by-scala-version.svg?platform=jvm&color=009933)](https://index.scala-lang.org/laserdisc-io/log-effect/log-effect-interop) ## Start -Log Effect is available for Scala **_2.12_**, **_2.13_** and **_3_**. Helper constructors are provided for **Cats Effect**'s `Sync` `F[_]`, for **Fs2**'s `Stream` and for **ZIO**'s `Task`. +Log Effect is available for Scala **_2.12_**, **_2.13_** and **_3_** on JVM, JavaScript and Native. Helper constructors are provided for **Cats Effect**'s `Sync` `F[_]`, for **Fs2**'s `Stream` and for **ZIO**'s `Task`. Add ```scala libraryDependencies += "io.laserdisc" %% "log-effect-fs2" % @@ -32,7 +32,7 @@ will be enough. For the latest versions available please refer to the badges bel ## Backends Currently Log Effect supports the following backends -- Log4s +- Log4s (on JVM and JS) - Java Logging (Jul) - Scribe - Console diff --git a/build.sbt b/build.sbt index a2a6c2c8..05764e82 100644 --- a/build.sbt +++ b/build.sbt @@ -3,15 +3,18 @@ val scala_213 = "2.13.18" val scala_3 = "3.3.7" val V = new { - val cats = "2.13.0" - val catsEffect = "3.6.3" - val fs2 = "3.12.2" - val log4cats = "2.7.1" - val log4s = "1.10.0" - val scalaCheck = "1.19.0" - val scalaTest = "3.2.19" - val scribe = "3.15.2" - val zio = "2.1.24" + val cats = "2.13.0" + val catsEffect = "3.7.0-RC1" + val fs2 = "3.13.0-M8" + val log4cats = "2.7.1" + val log4s = "1.10.0" + val scalaCheck = "1.19.0" + val scalaJavaTime = "2.6.0" + val scalaJSJUL = "1.0.0" + val scalaNativeJUL = "1.0.0" + val scalaTest = "3.2.19" + val scribe = "3.15.2" + val zio = "2.1.24" } val D = new { @@ -23,12 +26,18 @@ val D = new { lazy val log4s = Def.setting("org.log4s" %%% "log4s" % V.log4s) lazy val `log4s-testing` = Def.setting("org.log4s" %%% "log4s-testing" % V.log4s) lazy val scalacheck = Def.setting("org.scalacheck" %%% "scalacheck" % V.scalaCheck) - lazy val scalatest = Def.setting("org.scalatest" %%% "scalatest" % V.scalaTest) - lazy val scribe = Def.setting("com.outr" %%% "scribe" % V.scribe) - lazy val zio = Def.setting("dev.zio" %%% "zio" % V.zio) + lazy val `scala-java-time` = + Def.setting("io.github.cquiroz" %%% "scala-java-time" % V.scalaJavaTime) + lazy val `scalajs-java-logging` = + Def.setting("org.scala-js" %%% "scalajs-java-logging" % V.scalaJSJUL) + lazy val `scala-native-java-logging` = + Def.setting("org.scala-native" %%% "scala-native-java-logging" % V.scalaNativeJUL) + lazy val scalatest = Def.setting("org.scalatest" %%% "scalatest" % V.scalaTest) + lazy val scribe = Def.setting("com.outr" %%% "scribe" % V.scribe) + lazy val zio = Def.setting("dev.zio" %%% "zio" % V.zio) } -ThisBuild / tlBaseVersion := "0.19" +ThisBuild / tlBaseVersion := "0.20" ThisBuild / tlCiReleaseBranches := Seq("master") ThisBuild / tlVersionIntroduced := Map("3" -> "0.16.3") ThisBuild / organization := "io.laserdisc" @@ -40,18 +49,19 @@ ThisBuild / crossScalaVersions := Seq(scala_212, scala_213, scala_3) ThisBuild / scalaVersion := scala_213 ThisBuild / githubWorkflowJavaVersions := Seq( JavaSpec.temurin("11"), - JavaSpec.temurin("17") + JavaSpec.temurin("17"), + JavaSpec.temurin("21"), + JavaSpec.temurin("25") ) ThisBuild / githubWorkflowBuildMatrixExclusions := Seq() ThisBuild / Test / parallelExecution := false -ThisBuild / libraryDependencies ++= Seq( - D.scalacheck.value % Test, - D.scalatest.value % Test -) - lazy val commonSettings = Seq( - headerEndYear := Some(2025), + headerEndYear := Some(2026), + libraryDependencies ++= Seq( + D.scalacheck.value % Test, + D.scalatest.value % Test + ), scalacOptions ++= { CrossVersion.partialVersion(scalaVersion.value) match { case Some((2, major)) if major >= 13 => Seq("-Wconf:cat=unused-nowarn:s") @@ -60,26 +70,31 @@ lazy val commonSettings = Seq( } ) -lazy val root = tlCrossRootProject +lazy val `log-effect` = tlCrossRootProject .aggregate(core, fs2, zio, interop) - .settings(commonSettings) .settings( addCommandAlias("fmt", "scalafmt; Test/scalafmt; scalafmtSbt"), addCommandAlias("checkFormat", "scalafmtCheck; Test/scalafmtCheck; scalafmtSbtCheck"), addCommandAlias("check", "checkFormat; clean; test") ) -lazy val core = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) +lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("core")) .settings(commonSettings) .settings( name := "log-effect-core", - libraryDependencies ++= Seq(D.log4s.value, D.scribe.value) + libraryDependencies += D.scribe.value + ) + .jsSettings( + libraryDependencies += D.log4s.value + ) + .jvmSettings( + libraryDependencies += D.log4s.value ) -lazy val fs2 = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) +lazy val fs2 = crossProject(JVMPlatform, JSPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("fs2")) .dependsOn(core) .settings(commonSettings) @@ -89,36 +104,61 @@ lazy val fs2 = crossProject(JVMPlatform, JSPlatform) D.`cats-core`.value, D.`cats-effect`.value, D.`fs2-core`.value, - D.log4s.value, D.scribe.value ) ) + .jsSettings( + libraryDependencies += D.log4s.value + ) + .jvmSettings( + libraryDependencies += D.log4s.value + ) -lazy val zio = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) +lazy val zio = crossProject(JVMPlatform, JSPlatform, NativePlatform) + .withoutSuffixFor(JVMPlatform) .in(file("zio")) .dependsOn(core) .settings(commonSettings) .settings( name := "log-effect-zio", libraryDependencies ++= Seq( - D.log4s.value, - D.`log4s-testing`.value % Test, D.scribe.value, D.zio.value ) ) - -lazy val interop = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Pure) - .in(file("interop")) - .dependsOn(core, fs2) - .settings(commonSettings) - .settings( - name := "log-effect-interop", + .jsSettings( + libraryDependencies ++= Seq( + D.log4s.value, + D.`log4s-testing`.value % Test, + D.`scala-java-time`.value % Test, + (D.`scalajs-java-logging`.value % Test).cross(CrossVersion.for3Use2_13) + ) + ) + .jvmSettings( libraryDependencies ++= Seq( - D.`cats-effect`.value % Test, - D.`log4cats-core`.value, - D.`log4cats-testing`.value % Test + D.log4s.value, + D.`log4s-testing`.value % Test ) ) + .nativeSettings( + libraryDependencies ++= Seq( + D.`scala-java-time`.value % Test, + D.`scala-native-java-logging`.value % Test + ) + ) + +lazy val interop = + crossProject(JVMPlatform, JSPlatform /*, NativePlatform // log4cats is on 0.4 atm */ ) + .crossType(CrossType.Pure) + .withoutSuffixFor(JVMPlatform) + .in(file("interop")) + .dependsOn(core, fs2) + .settings(commonSettings) + .settings( + name := "log-effect-interop", + libraryDependencies ++= Seq( + D.`cats-effect`.value % Test, + D.`log4cats-core`.value, + D.`log4cats-testing`.value % Test + ) + ) diff --git a/core/js-jvm/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala b/core/js-jvm/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala new file mode 100644 index 00000000..75bf5fa8 --- /dev/null +++ b/core/js-jvm/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect + +import org.{log4s => l4s} + +trait LogWriterConstructorPlatformSpecific { + import internal._ + import internal.syntax._ + + implicit def log4sConstructor[G[_]: Functor, F[_]]( + implicit F: EffectSuspension[F] + ): LogWriterConstructor[l4s.Logger, G, F] = + new LogWriterConstructor[l4s.Logger, G, F] { + val construction: G[l4s.Logger] => G[LogWriter[F]] = + _ map { l4sLogger => + new LogWriter[F] { + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = { + val beLevel = level match { + case LogLevels.Trace => l4s.Trace + case LogLevels.Debug => l4s.Debug + case LogLevels.Info => l4s.Info + case LogLevels.Error => l4s.Error + case LogLevels.Warn => l4s.Warn + } + + F.suspend( + a match { + case Failure(msg, th) => l4sLogger(beLevel)(th)(msg) + case _ => l4sLogger(beLevel)(a.show) + } + ) + } + } + } + } +} diff --git a/core/native/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala b/core/native/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala new file mode 100644 index 00000000..09824f0d --- /dev/null +++ b/core/native/src/main/scala/log/effect/LogWriterConstructorPlatformSpecific.scala @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect + +trait LogWriterConstructorPlatformSpecific {} diff --git a/core/src/main/scala/log/effect/Failure.scala b/core/shared/src/main/scala/log/effect/Failure.scala similarity index 97% rename from core/src/main/scala/log/effect/Failure.scala rename to core/shared/src/main/scala/log/effect/Failure.scala index c1eda764..7891968d 100644 --- a/core/src/main/scala/log/effect/Failure.scala +++ b/core/shared/src/main/scala/log/effect/Failure.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/LogLevel.scala b/core/shared/src/main/scala/log/effect/LogLevel.scala similarity index 98% rename from core/src/main/scala/log/effect/LogLevel.scala rename to core/shared/src/main/scala/log/effect/LogLevel.scala index 3b85c269..608dac33 100644 --- a/core/src/main/scala/log/effect/LogLevel.scala +++ b/core/shared/src/main/scala/log/effect/LogLevel.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/LogWriter.scala b/core/shared/src/main/scala/log/effect/LogWriter.scala similarity index 99% rename from core/src/main/scala/log/effect/LogWriter.scala rename to core/shared/src/main/scala/log/effect/LogWriter.scala index d908813a..4cd10cf5 100644 --- a/core/src/main/scala/log/effect/LogWriter.scala +++ b/core/shared/src/main/scala/log/effect/LogWriter.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/LogWriterConstructor.scala b/core/shared/src/main/scala/log/effect/LogWriterConstructor.scala similarity index 81% rename from core/src/main/scala/log/effect/LogWriterConstructor.scala rename to core/shared/src/main/scala/log/effect/LogWriterConstructor.scala index 9dd8fd46..13bd5899 100644 --- a/core/src/main/scala/log/effect/LogWriterConstructor.scala +++ b/core/shared/src/main/scala/log/effect/LogWriterConstructor.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -23,43 +23,15 @@ package log.effect import log.effect.internal._ import log.effect.internal.syntax._ -import org.{log4s => l4s} import scribe.message.LoggableMessage._ import java.util.{logging => jul} -sealed trait LogWriterConstructor[R, G[_], F[_]] { +trait LogWriterConstructor[R, G[_], F[_]] { def construction: G[R] => G[LogWriter[F]] } -object LogWriterConstructor { - implicit def log4sConstructor[G[_]: Functor, F[_]]( - implicit F: EffectSuspension[F] - ): LogWriterConstructor[l4s.Logger, G, F] = - new LogWriterConstructor[l4s.Logger, G, F] { - val construction: G[l4s.Logger] => G[LogWriter[F]] = - _ map { l4sLogger => - new LogWriter[F] { - def write[A: Show](level: LogLevel, a: =>A): F[Unit] = { - val beLevel = level match { - case LogLevels.Trace => l4s.Trace - case LogLevels.Debug => l4s.Debug - case LogLevels.Info => l4s.Info - case LogLevels.Error => l4s.Error - case LogLevels.Warn => l4s.Warn - } - - F.suspend( - a match { - case Failure(msg, th) => l4sLogger(beLevel)(th)(msg) - case _ => l4sLogger(beLevel)(a.show) - } - ) - } - } - } - } - +object LogWriterConstructor extends LogWriterConstructorPlatformSpecific { implicit def julConstructor[G[_]: Functor, F[_]]( implicit F: EffectSuspension[F] ): LogWriterConstructor[jul.Logger, G, F] = diff --git a/core/src/main/scala/log/effect/internal/EffectSuspension.scala b/core/shared/src/main/scala/log/effect/internal/EffectSuspension.scala similarity index 97% rename from core/src/main/scala/log/effect/internal/EffectSuspension.scala rename to core/shared/src/main/scala/log/effect/internal/EffectSuspension.scala index 23596c40..9e3b2c7a 100644 --- a/core/src/main/scala/log/effect/internal/EffectSuspension.scala +++ b/core/shared/src/main/scala/log/effect/internal/EffectSuspension.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/Functor.scala b/core/shared/src/main/scala/log/effect/internal/Functor.scala similarity index 97% rename from core/src/main/scala/log/effect/internal/Functor.scala rename to core/shared/src/main/scala/log/effect/internal/Functor.scala index ba8346da..3ecf3a73 100644 --- a/core/src/main/scala/log/effect/internal/Functor.scala +++ b/core/shared/src/main/scala/log/effect/internal/Functor.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/Show.scala b/core/shared/src/main/scala/log/effect/internal/Show.scala similarity index 97% rename from core/src/main/scala/log/effect/internal/Show.scala rename to core/shared/src/main/scala/log/effect/internal/Show.scala index a77e6dfa..2e6e2c3a 100644 --- a/core/src/main/scala/log/effect/internal/Show.scala +++ b/core/shared/src/main/scala/log/effect/internal/Show.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/package.scala b/core/shared/src/main/scala/log/effect/internal/package.scala similarity index 96% rename from core/src/main/scala/log/effect/internal/package.scala rename to core/shared/src/main/scala/log/effect/internal/package.scala index c1df3b1f..6753d780 100644 --- a/core/src/main/scala/log/effect/internal/package.scala +++ b/core/shared/src/main/scala/log/effect/internal/package.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala b/core/shared/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala similarity index 97% rename from core/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala rename to core/shared/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala index cc3001b5..c6844194 100644 --- a/core/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala +++ b/core/shared/src/main/scala/log/effect/internal/syntax/FunctorSyntax.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala b/core/shared/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala similarity index 97% rename from core/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala rename to core/shared/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala index 2d8b13d5..80a5f16e 100644 --- a/core/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala +++ b/core/shared/src/main/scala/log/effect/internal/syntax/ShowSyntax.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/main/scala/log/effect/internal/syntax/package.scala b/core/shared/src/main/scala/log/effect/internal/syntax/package.scala similarity index 96% rename from core/src/main/scala/log/effect/internal/syntax/package.scala rename to core/shared/src/main/scala/log/effect/internal/syntax/package.scala index e331fadc..a7b7d189 100644 --- a/core/src/main/scala/log/effect/internal/syntax/package.scala +++ b/core/shared/src/main/scala/log/effect/internal/syntax/package.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/core/src/test/scala/LogWriterSyntaxResolutionTest.scala b/core/shared/src/test/scala/LogWriterSyntaxResolutionTest.scala similarity index 98% rename from core/src/test/scala/LogWriterSyntaxResolutionTest.scala rename to core/shared/src/test/scala/LogWriterSyntaxResolutionTest.scala index 2f662795..a32d793b 100644 --- a/core/src/test/scala/LogWriterSyntaxResolutionTest.scala +++ b/core/shared/src/test/scala/LogWriterSyntaxResolutionTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/js-jvm/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala b/fs2/js-jvm/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala new file mode 100644 index 00000000..809f9261 --- /dev/null +++ b/fs2/js-jvm/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package fs2 + +import _root_.fs2.Stream +import cats.effect.Sync +import org.{log4s => l4s} +import log.effect.fs2.SyncLogWriter._ + +trait Fs2LogWriterPlatformSpecific { + def log4sLogStream[F[_]: Sync](l: l4s.Logger): Stream[F, LogWriter[F]] = + Stream emit log4sLog(l) + + def log4sLogStream[F[_]: Sync](c: Class[_]): Stream[F, LogWriter[F]] = + Stream eval log4sLog(c) + + def log4sLogStream[F[_]: Sync](n: String): Stream[F, LogWriter[F]] = + Stream eval log4sLog(n) +} diff --git a/fs2/js-jvm/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala b/fs2/js-jvm/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala new file mode 100644 index 00000000..329a9f70 --- /dev/null +++ b/fs2/js-jvm/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package fs2 + +import cats.effect.Sync +import log.effect.LogWriter +import org.{log4s => l4s} + +trait SyncLogWriterPlatformSpecific { + import instances._ + + def log4sLog[F[_]: Sync](l: l4s.Logger): LogWriter[F] = + LogWriter.pureOf[F](l) + + def log4sLog[F[_]](c: Class[_])(implicit F: Sync[F]): F[LogWriter[F]] = + LogWriter.of(F.delay(l4s.getLogger(c))) + + def log4sLog[F[_]](n: String)(implicit F: Sync[F]): F[LogWriter[F]] = + LogWriter.of(F.delay(l4s.getLogger(n))) +} diff --git a/fs2/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala b/fs2/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala new file mode 100644 index 00000000..6dbd5160 --- /dev/null +++ b/fs2/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import cats.syntax.functor._ +import log.effect.internal +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpecLike + +import scala.annotation.nowarn + +final class LogWriterResolutionLog4sTest extends AnyWordSpecLike with Matchers { + "the construction" should { + "correctly infer a valid log4s constructor for an F[_] given an implicit evidence of Sync[F]" in { + import cats.effect.Sync + import log.effect.fs2.SyncLogWriter.log4sLog + import log.effect.internal.{EffectSuspension, Functor} + import log.effect.{LogWriter, LogWriterConstructor} + import org.log4s + + def c[F[_]]: F[org.log4s.Logger] => F[LogWriter[F]] = { + implicit def F: EffectSuspension[F] = ??? + implicit def FF: Functor[F] = ??? + implicitly[LogWriterConstructor[log4s.Logger, F, F]].construction + } + + def cPure[F[_]]: org.log4s.Logger => LogWriter[F] = { + implicit def F: EffectSuspension[F] = ??? + implicitly[LogWriterConstructor[log4s.Logger, internal.Id, F]].construction + } + + @nowarn def l1[F[_]](implicit F: Sync[F]): F[LogWriter[F]] = + c(F.delay(org.log4s.getLogger("test"))) + + @nowarn def pureL1[F[_]]: LogWriter[F] = + cPure[F](org.log4s.getLogger("test")) + + @nowarn def l2[F[_]](implicit F: Sync[F]): F[LogWriter[F]] = + F.delay(org.log4s.getLogger("test")) map log4sLog[F] + } + + "not infer a valid log4s constructor for an F[_] if there is no implicit evidence of EffectSuspension[F]" in { + """ + |import log.effect.{LogWriter, LogWriterConstructor} + |import log.effect.internal.Functor + |import org.log4s + | + |def c[F[_]]: F[org.log4s.Logger] => F[LogWriter[F]] = { + | implicit def FF: Functor[F] = ??? + | implicitly[LogWriterConstructor[F[log4s.Logger], F, F]].construction + |} + """.stripMargin shouldNot compile + } + + "correctly infer a valid log4s constructor for IO" in { + import cats.effect.IO + import log.effect.fs2.SyncLogWriter.log4sLog + import log.effect.internal.{EffectSuspension, Functor} + import log.effect.{LogWriter, LogWriterConstructor} + import org.log4s + + def c: IO[org.log4s.Logger] => IO[LogWriter[IO]] = { + implicit def F: EffectSuspension[IO] = ??? + implicit def FF: Functor[IO] = ??? + implicitly[LogWriterConstructor[log4s.Logger, IO, IO]].construction + } + + def cPure: org.log4s.Logger => LogWriter[IO] = { + implicit def F: EffectSuspension[IO] = ??? + implicitly[LogWriterConstructor[log4s.Logger, internal.Id, IO]].construction + } + + @nowarn def l1: IO[LogWriter[IO]] = + c(IO.delay(org.log4s.getLogger("test"))) + + @nowarn def pureL1: LogWriter[IO] = + cPure(org.log4s.getLogger("test")) + + @nowarn def l2: IO[LogWriter[IO]] = + IO.delay(org.log4s.getLogger("test")) map log4sLog[IO] + } + } +} diff --git a/fs2/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala b/fs2/js-jvm/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala similarity index 99% rename from fs2/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala rename to fs2/js-jvm/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala index 642ff3ce..32a4311a 100644 --- a/fs2/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala +++ b/fs2/js-jvm/src/test/scala/ReadmeConstructionCodeSnippetsTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/native/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala b/fs2/native/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala new file mode 100644 index 00000000..c05dd763 --- /dev/null +++ b/fs2/native/src/main/scala/log/effect/fs2/Fs2LogWriterPlatformSpecific.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package fs2 + +trait Fs2LogWriterPlatformSpecific {} diff --git a/fs2/native/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala b/fs2/native/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala new file mode 100644 index 00000000..9bf73c89 --- /dev/null +++ b/fs2/native/src/main/scala/log/effect/fs2/SyncLogWriterPlatformSpecific.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package fs2 + +trait SyncLogWriterPlatformSpecific {} diff --git a/fs2/src/main/scala/log/effect/fs2/Fs2LogWriter.scala b/fs2/shared/src/main/scala/log/effect/fs2/Fs2LogWriter.scala similarity index 84% rename from fs2/src/main/scala/log/effect/fs2/Fs2LogWriter.scala rename to fs2/shared/src/main/scala/log/effect/fs2/Fs2LogWriter.scala index 7fcdce69..e0a58afe 100644 --- a/fs2/src/main/scala/log/effect/fs2/Fs2LogWriter.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/Fs2LogWriter.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -28,18 +28,8 @@ import _root_.fs2.Stream import cats.Applicative import cats.effect.Sync import log.effect.fs2.SyncLogWriter._ -import org.{log4s => l4s} - -object Fs2LogWriter { - def log4sLogStream[F[_]: Sync](l: l4s.Logger): Stream[F, LogWriter[F]] = - Stream emit log4sLog(l) - - def log4sLogStream[F[_]: Sync](c: Class[_]): Stream[F, LogWriter[F]] = - Stream eval log4sLog(c) - - def log4sLogStream[F[_]: Sync](n: String): Stream[F, LogWriter[F]] = - Stream eval log4sLog(n) +object Fs2LogWriter extends Fs2LogWriterPlatformSpecific { def julLogStream[F[_]: Sync](l: jul.Logger): Stream[F, LogWriter[F]] = Stream emit julLog(l) diff --git a/fs2/src/main/scala/log/effect/fs2/LogSelector.scala b/fs2/shared/src/main/scala/log/effect/fs2/LogSelector.scala similarity index 98% rename from fs2/src/main/scala/log/effect/fs2/LogSelector.scala rename to fs2/shared/src/main/scala/log/effect/fs2/LogSelector.scala index 3cce5a86..196679f0 100644 --- a/fs2/src/main/scala/log/effect/fs2/LogSelector.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/LogSelector.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala b/fs2/shared/src/main/scala/log/effect/fs2/SyncLogWriter.scala similarity index 61% rename from fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala rename to fs2/shared/src/main/scala/log/effect/fs2/SyncLogWriter.scala index 7e232006..a231f372 100644 --- a/fs2/src/main/scala/log/effect/fs2/SyncLogWriter.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/SyncLogWriter.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -26,21 +26,11 @@ import java.util.{logging => jul} import cats.Applicative import cats.effect.Sync -import log.effect.internal.{EffectSuspension, Id, Show} -import org.{log4s => l4s} +import log.effect.internal.Id -object SyncLogWriter { +object SyncLogWriter extends SyncLogWriterPlatformSpecific { import instances._ - def log4sLog[F[_]: Sync](l: l4s.Logger): LogWriter[F] = - LogWriter.pureOf[F](l) - - def log4sLog[F[_]](c: Class[_])(implicit F: Sync[F]): F[LogWriter[F]] = - LogWriter.of(F.delay(l4s.getLogger(c))) - - def log4sLog[F[_]](n: String)(implicit F: Sync[F]): F[LogWriter[F]] = - LogWriter.of(F.delay(l4s.getLogger(n))) - def julLog[F[_]: Sync](l: jul.Logger): LogWriter[F] = LogWriter.pureOf[F](l) @@ -66,26 +56,4 @@ object SyncLogWriter { def noOpLog[F[_]: Applicative]: LogWriter[F] = LogWriter.of[Id](()).liftF - - private[this] object instances { - private[fs2] implicit final def syncInstances[F[_]](implicit F: Sync[F]): EffectSuspension[F] = - new EffectSuspension[F] { - def suspend[A](a: =>A): F[A] = F delay a - } - - private[fs2] implicit final def functorInstances[F[_]]( - implicit F: cats.Functor[F] - ): internal.Functor[F] = - new internal.Functor[F] { - def fmap[A, B](f: A => B): F[A] => F[B] = F lift f - } - - implicit final class NoOpLogF(private val _underlying: LogWriter[Id]) extends AnyVal { - def liftF[F[_]: Applicative]: LogWriter[F] = - new LogWriter[F] { - private[this] val unit = Applicative[F].unit - def write[A: Show](level: LogLevel, a: =>A): F[Unit] = unit - } - } - } } diff --git a/fs2/shared/src/main/scala/log/effect/fs2/instances.scala b/fs2/shared/src/main/scala/log/effect/fs2/instances.scala new file mode 100644 index 00000000..e7fb8485 --- /dev/null +++ b/fs2/shared/src/main/scala/log/effect/fs2/instances.scala @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package fs2 + +import cats.Applicative +import cats.effect.Sync +import log.effect.internal.{EffectSuspension, Id, Show} + +private[fs2] object instances { + private[fs2] implicit final def syncInstances[F[_]](implicit F: Sync[F]): EffectSuspension[F] = + new EffectSuspension[F] { + def suspend[A](a: =>A): F[A] = F delay a + } + + private[fs2] implicit final def functorInstances[F[_]]( + implicit F: cats.Functor[F] + ): internal.Functor[F] = + new internal.Functor[F] { + def fmap[A, B](f: A => B): F[A] => F[B] = F lift f + } + + implicit final class NoOpLogF(private val _underlying: LogWriter[Id]) extends AnyVal { + def liftF[F[_]: Applicative]: LogWriter[F] = + new LogWriter[F] { + private[this] val unit = Applicative[F].unit + def write[A: Show](level: LogLevel, a: =>A): F[Unit] = unit + } + } +} diff --git a/fs2/src/main/scala/log/effect/fs2/interop/show.scala b/fs2/shared/src/main/scala/log/effect/fs2/interop/show.scala similarity index 97% rename from fs2/src/main/scala/log/effect/fs2/interop/show.scala rename to fs2/shared/src/main/scala/log/effect/fs2/interop/show.scala index adb2324d..fbdc0196 100644 --- a/fs2/src/main/scala/log/effect/fs2/interop/show.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/interop/show.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala b/fs2/shared/src/main/scala/log/effect/fs2/mtl/readerT.scala similarity index 97% rename from fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala rename to fs2/shared/src/main/scala/log/effect/fs2/mtl/readerT.scala index e7602fca..184685c8 100644 --- a/fs2/src/main/scala/log/effect/fs2/mtl/readerT.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/mtl/readerT.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala b/fs2/shared/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala similarity index 99% rename from fs2/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala rename to fs2/shared/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala index be887dbf..af3dd1ea 100644 --- a/fs2/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/syntax/Fs2LogEffectSyntax.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/main/scala/log/effect/fs2/syntax/package.scala b/fs2/shared/src/main/scala/log/effect/fs2/syntax/package.scala similarity index 96% rename from fs2/src/main/scala/log/effect/fs2/syntax/package.scala rename to fs2/shared/src/main/scala/log/effect/fs2/syntax/package.scala index 21bcd254..72425d6b 100644 --- a/fs2/src/main/scala/log/effect/fs2/syntax/package.scala +++ b/fs2/shared/src/main/scala/log/effect/fs2/syntax/package.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/test/scala/LogSelectorTest.scala b/fs2/shared/src/test/scala/LogSelectorTest.scala similarity index 96% rename from fs2/src/test/scala/LogSelectorTest.scala rename to fs2/shared/src/test/scala/LogSelectorTest.scala index 74de0eea..1fde00c4 100644 --- a/fs2/src/test/scala/LogSelectorTest.scala +++ b/fs2/shared/src/test/scala/LogSelectorTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -21,7 +21,7 @@ import cats.effect.{IO, Resource, Sync} import log.effect.LogWriter -import log.effect.fs2.{LogSelector, TestLogCapture} +import log.effect.fs2.LogSelector import log.effect.fs2.SyncLogWriter.consoleLog import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike diff --git a/fs2/src/test/scala/LogWriterResolutionTest.scala b/fs2/shared/src/test/scala/LogWriterResolutionTest.scala similarity index 75% rename from fs2/src/test/scala/LogWriterResolutionTest.scala rename to fs2/shared/src/test/scala/LogWriterResolutionTest.scala index 7c51e7d7..f3b40e3e 100644 --- a/fs2/src/test/scala/LogWriterResolutionTest.scala +++ b/fs2/shared/src/test/scala/LogWriterResolutionTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -29,34 +29,6 @@ import scala.annotation.nowarn final class LogWriterResolutionTest extends AnyWordSpecLike with Matchers { "the construction" should { - "correctly infer a valid log4s constructor for an F[_] given an implicit evidence of Sync[F]" in { - import cats.effect.Sync - import log.effect.fs2.SyncLogWriter.log4sLog - import log.effect.internal.{EffectSuspension, Functor} - import log.effect.{LogWriter, LogWriterConstructor} - import org.log4s - - def c[F[_]]: F[org.log4s.Logger] => F[LogWriter[F]] = { - implicit def F: EffectSuspension[F] = ??? - implicit def FF: Functor[F] = ??? - implicitly[LogWriterConstructor[log4s.Logger, F, F]].construction - } - - def cPure[F[_]]: org.log4s.Logger => LogWriter[F] = { - implicit def F: EffectSuspension[F] = ??? - implicitly[LogWriterConstructor[log4s.Logger, internal.Id, F]].construction - } - - @nowarn def l1[F[_]](implicit F: Sync[F]): F[LogWriter[F]] = - c(F.delay(org.log4s.getLogger("test"))) - - @nowarn def pureL1[F[_]]: LogWriter[F] = - cPure[F](org.log4s.getLogger("test")) - - @nowarn def l2[F[_]](implicit F: Sync[F]): F[LogWriter[F]] = - F.delay(org.log4s.getLogger("test")) map log4sLog[F] - } - "correctly infer a valid jul constructor for an F[_] given an implicit evidence of Sync[F]" in { import java.util.{logging => jul} @@ -86,19 +58,6 @@ final class LogWriterResolutionTest extends AnyWordSpecLike with Matchers { F.delay(java.util.logging.Logger.getGlobal) map julLog[F] } - "not infer a valid log4s constructor for an F[_] if there is no implicit evidence of EffectSuspension[F]" in { - """ - |import log.effect.{LogWriter, LogWriterConstructor} - |import log.effect.internal.Functor - |import org.log4s - | - |def c[F[_]]: F[org.log4s.Logger] => F[LogWriter[F]] = { - | implicit def FF: Functor[F] = ??? - | implicitly[LogWriterConstructor[F[log4s.Logger], F, F]].construction - |} - """.stripMargin shouldNot compile - } - "correctly infer a valid console constructor for an F[_] given an implicit evidence of Sync[F]" in { import cats.effect.Sync import log.effect.fs2.SyncLogWriter.{consoleLog, consoleLogUpToLevel} @@ -136,34 +95,6 @@ final class LogWriterResolutionTest extends AnyWordSpecLike with Matchers { @nowarn def l2[F[_]: Applicative]: LogWriter[F] = noOpLog[F] } - "correctly infer a valid log4s constructor for IO" in { - import cats.effect.IO - import log.effect.fs2.SyncLogWriter.log4sLog - import log.effect.internal.{EffectSuspension, Functor} - import log.effect.{LogWriter, LogWriterConstructor} - import org.log4s - - def c: IO[org.log4s.Logger] => IO[LogWriter[IO]] = { - implicit def F: EffectSuspension[IO] = ??? - implicit def FF: Functor[IO] = ??? - implicitly[LogWriterConstructor[log4s.Logger, IO, IO]].construction - } - - def cPure: org.log4s.Logger => LogWriter[IO] = { - implicit def F: EffectSuspension[IO] = ??? - implicitly[LogWriterConstructor[log4s.Logger, internal.Id, IO]].construction - } - - @nowarn def l1: IO[LogWriter[IO]] = - c(IO.delay(org.log4s.getLogger("test"))) - - @nowarn def pureL1: LogWriter[IO] = - cPure(org.log4s.getLogger("test")) - - @nowarn def l2: IO[LogWriter[IO]] = - IO.delay(org.log4s.getLogger("test")) map log4sLog[IO] - } - "correctly infer a valid jul constructor for IO" in { import java.util.{logging => jul} @@ -285,7 +216,7 @@ final class LogWriterResolutionTest extends AnyWordSpecLike with Matchers { F.info("A message") @nowarn def anotherLogger[F[_]: Sync, Env](env: Env): F[Unit] = - log4sLog[F]("A log") >>= { implicit log => aLogger[F, Env].run(env) } + scribeLog[F]("A log") >>= { implicit log => aLogger[F, Env].run(env) } } } } diff --git a/fs2/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala b/fs2/shared/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala similarity index 99% rename from fs2/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala rename to fs2/shared/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala index f524d298..65f9bd43 100644 --- a/fs2/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala +++ b/fs2/shared/src/test/scala/ReadmeLogSubmissionCodeSnippetsTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/fs2/src/test/scala/log/effect/fs2/TestLogCapture.scala b/fs2/shared/src/test/scala/TestLogCapture.scala similarity index 95% rename from fs2/src/test/scala/log/effect/fs2/TestLogCapture.scala rename to fs2/shared/src/test/scala/TestLogCapture.scala index 5dc6602a..2a070939 100644 --- a/fs2/src/test/scala/log/effect/fs2/TestLogCapture.scala +++ b/fs2/shared/src/test/scala/TestLogCapture.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,9 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package log.effect -package fs2 - import java.io.{ByteArrayOutputStream, PrintStream} import cats.effect.IO diff --git a/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala b/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala index d8473b6f..4a8e39d4 100644 --- a/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala +++ b/interop/src/main/scala/log/effect/interop/Log4catsInterop.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/interop/src/main/scala/log/effect/interop/package.scala b/interop/src/main/scala/log/effect/interop/package.scala index fcec63e7..b5876df5 100644 --- a/interop/src/main/scala/log/effect/interop/package.scala +++ b/interop/src/main/scala/log/effect/interop/package.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/interop/src/test/scala/InteropLogSelectorTest.scala b/interop/src/test/scala/InteropLogSelectorTest.scala index 2c42ab55..73434d03 100644 --- a/interop/src/test/scala/InteropLogSelectorTest.scala +++ b/interop/src/test/scala/InteropLogSelectorTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/interop/src/test/scala/InteropTest.scala b/interop/src/test/scala/InteropTest.scala index cefdb6f7..23a3d6c3 100644 --- a/interop/src/test/scala/InteropTest.scala +++ b/interop/src/test/scala/InteropTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/project/plugins.sbt b/project/plugins.sbt index ebd21ce0..cef0d50b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,5 @@ +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.20.2") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.10") addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.8.5") diff --git a/zio/js-jvm/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala b/zio/js-jvm/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala new file mode 100644 index 00000000..61dcee6e --- /dev/null +++ b/zio/js-jvm/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package zio + +import _root_.zio.{LogLevel => _, _} +import log.effect.LogWriter +import org.{log4s => l4s} + +trait ZioLogWriterPlatformSpecific { + import instances._ + + final type ZLog4sLogger = l4s.Logger + + val log4sFromLogger: URIO[ZLog4sLogger, ZLogWriter] = + ZIO.serviceWith(log4sLogger => LogWriter.pureOf[Task](log4sLogger)) + + val log4sFromName: RIO[String, ZLogWriter] = + ZIO.serviceWithZIO(name => LogWriter.of[Task](ZIO.attempt(l4s.getLogger(name)))) + + val log4sFromClass: RIO[Class[_], ZLogWriter] = + ZIO.serviceWithZIO((clazz: Class[_]) => LogWriter.of[Task](ZIO.attempt(l4s.getLogger(clazz)))) + + val log4sLayerFromName: RLayer[ZLogName, ZLogWriter] = + ZLayer(ZIO.serviceWithZIO { name => + log4sFromName.provideEnvironment(ZEnvironment(name.x)) + }) + + val log4sLayerFromLogger: RLayer[ZLog4sLogger, ZLogWriter] = + ZLayer(ZIO.serviceWithZIO { l => + log4sFromLogger.provideEnvironment(ZEnvironment(l)) + }) + +} diff --git a/zio/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala b/zio/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala new file mode 100644 index 00000000..4fe79b32 --- /dev/null +++ b/zio/js-jvm/src/test/scala/LogWriterResolutionLog4sTest.scala @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import log.effect.internal +import org.scalatest.wordspec.AnyWordSpecLike +import org.scalatest.matchers.should.Matchers + +import scala.annotation.nowarn + +final class LogWriterResolutionLog4sTest extends AnyWordSpecLike with Matchers { + "the construction" should { + "correctly infer a valid log4s constructor for ZIO" in { + import _root_.zio.{Task, ZEnvironment, ZIO} + import log.effect.internal.{EffectSuspension, Functor} + import log.effect.zio.ZioLogWriter.log4sFromLogger + import log.effect.{LogWriter, LogWriterConstructor} + import org.{log4s => l4s} + + def c: Task[l4s.Logger] => Task[LogWriter[Task]] = { + implicit def F: EffectSuspension[Task] = ??? + implicit def FF: Functor[Task] = ??? + implicitly[LogWriterConstructor[l4s.Logger, Task, Task]].construction + } + + def cPure: l4s.Logger => LogWriter[Task] = { + implicit def F: EffectSuspension[Task] = ??? + implicitly[LogWriterConstructor[l4s.Logger, internal.Id, Task]].construction + } + + @nowarn def l1: Task[LogWriter[Task]] = + c(ZIO.attempt(l4s.getLogger("test"))) + + @nowarn def pureL1: LogWriter[Task] = + cPure(l4s.getLogger("test")) + + @nowarn def l2: Task[LogWriter[Task]] = + log4sFromLogger.provideEnvironment(ZEnvironment(l4s.getLogger("test"))) + } + } +} diff --git a/zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala b/zio/js-jvm/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala similarity index 96% rename from zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala rename to zio/js-jvm/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala index ffa344fc..d1258f15 100644 --- a/zio/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala +++ b/zio/js-jvm/src/test/scala/ReadmeLayerConstructionCodeSnippetsTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -28,7 +28,7 @@ import scala.annotation.nowarn "Zio Layer construction snippets should compile" in { import java.util.{logging => jul} - import log.effect.zio._, log.effect.zio.ZioLogWriter._ + import log.effect.zio._, log.effect.zio.instances._, log.effect.zio.ZioLogWriter._ import org.{log4s => l4s} import zio._ diff --git a/zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala b/zio/js-jvm/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala similarity index 97% rename from zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala rename to zio/js-jvm/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala index 36ad7dcc..c281cc4d 100644 --- a/zio/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala +++ b/zio/js-jvm/src/test/scala/ReadmeRioConstructionCodeSnippetsTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -28,7 +28,7 @@ import scala.annotation.nowarn "Zio RIO construction snippets should compile" in { import java.util.{logging => jul} - import log.effect.zio.ZioLogWriter._ + import log.effect.zio.instances._, log.effect.zio.ZioLogWriter._ import log.effect.{LogLevels, LogWriter} import org.{log4s => l4s} import zio._ diff --git a/zio/src/test/resources/logback-test.xml b/zio/jvm/src/test/resources/logback-test.xml similarity index 100% rename from zio/src/test/resources/logback-test.xml rename to zio/jvm/src/test/resources/logback-test.xml diff --git a/zio/src/test/scala/Log4sLogWriterTest.scala b/zio/jvm/src/test/scala/Log4sLogWriterTest.scala similarity index 97% rename from zio/src/test/scala/Log4sLogWriterTest.scala rename to zio/jvm/src/test/scala/Log4sLogWriterTest.scala index dc16143e..1edafbb1 100644 --- a/zio/src/test/scala/Log4sLogWriterTest.scala +++ b/zio/jvm/src/test/scala/Log4sLogWriterTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,7 +19,6 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import log.effect.zio.TestLogCapture import log.effect.zio.ZioLogWriter.log4sFromLogger import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpecLike diff --git a/zio/src/test/scala/log/effect/zio/TestLogCapture.scala b/zio/jvm/src/test/scala/TestLogCapture.scala similarity index 95% rename from zio/src/test/scala/log/effect/zio/TestLogCapture.scala rename to zio/jvm/src/test/scala/TestLogCapture.scala index c1336cd6..38455699 100644 --- a/zio/src/test/scala/log/effect/zio/TestLogCapture.scala +++ b/zio/jvm/src/test/scala/TestLogCapture.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,14 +19,10 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package log.effect -package zio - import _root_.zio.{Runtime, Task, Unsafe, ZEnvironment, ZIO} import org.log4s.{LoggedEvent, Logger, TestAppender, getLogger} trait TestLogCapture { - protected final def capturedLog4sOutOf( logWrite: ZIO[Logger, Throwable, Unit] ): Option[LoggedEvent] = { @@ -36,11 +32,9 @@ trait TestLogCapture { logWrite.provideEnvironment(ZEnvironment(logger)) } } - Unsafe.unsafe { implicit unsafe => Runtime.default.unsafe.run(loggingAction).getOrThrowFiberFailure() } - TestAppender.dequeue } } diff --git a/zio/native/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala b/zio/native/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala new file mode 100644 index 00000000..564f0ea1 --- /dev/null +++ b/zio/native/src/main/scala/log/effect/zio/ZioLogWriterPlatformSpecific.scala @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package zio + +trait ZioLogWriterPlatformSpecific {} diff --git a/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala b/zio/shared/src/main/scala/log/effect/zio/ZioLogWriter.scala similarity index 57% rename from zio/src/main/scala/log/effect/zio/ZioLogWriter.scala rename to zio/shared/src/main/scala/log/effect/zio/ZioLogWriter.scala index fc1f6b81..f6e72eb2 100644 --- a/zio/src/main/scala/log/effect/zio/ZioLogWriter.scala +++ b/zio/shared/src/main/scala/log/effect/zio/ZioLogWriter.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -23,32 +23,13 @@ package log.effect package zio import _root_.zio.{LogLevel => _, _} -import log.effect.internal.{EffectSuspension, Id, Show} -import org.{log4s => l4s} +import log.effect.internal.Id import java.util.{logging => jul} -object ZioLogWriter { +object ZioLogWriter extends ZioLogWriterPlatformSpecific { import instances._ - final case class LogName(x: String) extends AnyVal - - final type ZLogName = LogName - final type ZLogClass[A] = Class[A] - final type ZLog4sLogger = l4s.Logger - final type ZJulLogger = jul.Logger - final type ZScribeLogger = scribe.Logger - final type ZLogWriter = LogWriter[Task] - - val log4sFromLogger: URIO[ZLog4sLogger, ZLogWriter] = - ZIO.serviceWith(log4sLogger => LogWriter.pureOf[Task](log4sLogger)) - - val log4sFromName: RIO[String, ZLogWriter] = - ZIO.serviceWithZIO(name => LogWriter.of[Task](ZIO.attempt(l4s.getLogger(name)))) - - val log4sFromClass: RIO[Class[_], ZLogWriter] = - ZIO.serviceWithZIO((clazz: Class[_]) => LogWriter.of[Task](ZIO.attempt(l4s.getLogger(clazz)))) - val julFromLogger: URIO[ZJulLogger, ZLogWriter] = ZIO.serviceWith(julLogger => LogWriter.pureOf[Task](julLogger)) @@ -76,16 +57,6 @@ object ZioLogWriter { val noOpLog: ZLogWriter = LogWriter.of[Id](()).liftT - val log4sLayerFromName: RLayer[ZLogName, ZLogWriter] = - ZLayer(ZIO.serviceWithZIO { name => - log4sFromName.provideEnvironment(ZEnvironment(name.x)) - }) - - val log4sLayerFromLogger: RLayer[ZLog4sLogger, ZLogWriter] = - ZLayer(ZIO.serviceWithZIO { l => - log4sFromLogger.provideEnvironment(ZEnvironment(l)) - }) - val julLayerFromLogger: RLayer[ZJulLogger, ZLogWriter] = ZLayer(ZIO.serviceWithZIO { l => julFromLogger.provideEnvironment(ZEnvironment(l)) @@ -109,31 +80,4 @@ object ZioLogWriter { val noOpLogLayer: ULayer[ZLogWriter] = ZLayer.succeed(noOpLog) - - private[this] object instances { - private[zio] implicit final val taskEffectSuspension: EffectSuspension[Task] = - new EffectSuspension[Task] { - def suspend[A](a: =>A): Task[A] = ZIO.attempt(a) - } - - private[zio] implicit final val uioEffectSuspension: EffectSuspension[UIO] = - new EffectSuspension[UIO] { - def suspend[A](a: =>A): UIO[A] = ZIO.succeed(a) - } - - private[zio] implicit final def functorInstances[ - R, - E - ]: log.effect.internal.Functor[ZIO[R, E, *]] = - new log.effect.internal.Functor[ZIO[R, E, *]] { - def fmap[A, B](f: A => B): ZIO[R, E, A] => ZIO[R, E, B] = _ map f - } - - implicit final class NoOpLogT(private val _underlying: LogWriter[Id]) extends AnyVal { - def liftT: ZLogWriter = - new LogWriter[Task] { - override def write[A: Show](level: LogLevel, a: =>A): Task[Unit] = ZIO.unit - } - } - } } diff --git a/zio/shared/src/main/scala/log/effect/zio/instances.scala b/zio/shared/src/main/scala/log/effect/zio/instances.scala new file mode 100644 index 00000000..ef73e686 --- /dev/null +++ b/zio/shared/src/main/scala/log/effect/zio/instances.scala @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2026 LaserDisc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package log.effect +package zio + +import _root_.zio.{LogLevel => _, _} +import log.effect.internal.{EffectSuspension, Id, Show} + +import java.util.{logging => jul} + +object instances { + final case class LogName(x: String) extends AnyVal + + final type ZLogName = LogName + final type ZLogClass[A] = Class[A] + final type ZJulLogger = jul.Logger + final type ZScribeLogger = scribe.Logger + final type ZLogWriter = LogWriter[Task] + + private[zio] implicit final val taskEffectSuspension: EffectSuspension[Task] = + new EffectSuspension[Task] { + def suspend[A](a: =>A): Task[A] = ZIO.attempt(a) + } + + private[zio] implicit final val uioEffectSuspension: EffectSuspension[UIO] = + new EffectSuspension[UIO] { + def suspend[A](a: =>A): UIO[A] = ZIO.succeed(a) + } + + private[zio] implicit final def functorInstances[ + R, + E + ]: log.effect.internal.Functor[ZIO[R, E, *]] = + new log.effect.internal.Functor[ZIO[R, E, *]] { + def fmap[A, B](f: A => B): ZIO[R, E, A] => ZIO[R, E, B] = _ map f + } + + implicit final class NoOpLogT(private val _underlying: LogWriter[Id]) extends AnyVal { + def liftT: ZLogWriter = + new LogWriter[Task] { + override def write[A: Show](level: LogLevel, a: =>A): Task[Unit] = ZIO.unit + } + } +} diff --git a/zio/src/test/scala/ConsoleLogWriterTest.scala b/zio/shared/src/test/scala/ConsoleLogWriterTest.scala similarity index 99% rename from zio/src/test/scala/ConsoleLogWriterTest.scala rename to zio/shared/src/test/scala/ConsoleLogWriterTest.scala index 276ce103..2aea70fd 100644 --- a/zio/src/test/scala/ConsoleLogWriterTest.scala +++ b/zio/shared/src/test/scala/ConsoleLogWriterTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in diff --git a/zio/src/test/scala/LogWriterResolutionTest.scala b/zio/shared/src/test/scala/LogWriterResolutionTest.scala similarity index 78% rename from zio/src/test/scala/LogWriterResolutionTest.scala rename to zio/shared/src/test/scala/LogWriterResolutionTest.scala index 16661ef5..112dfbf1 100644 --- a/zio/src/test/scala/LogWriterResolutionTest.scala +++ b/zio/shared/src/test/scala/LogWriterResolutionTest.scala @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2025 LaserDisc + * Copyright (c) 2018-2026 LaserDisc * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -27,34 +27,6 @@ import scala.annotation.nowarn final class LogWriterResolutionTest extends AnyWordSpecLike with Matchers { "the construction" should { - "correctly infer a valid log4s constructor for ZIO" in { - import _root_.zio.{Task, ZEnvironment, ZIO} - import log.effect.internal.{EffectSuspension, Functor} - import log.effect.zio.ZioLogWriter.log4sFromLogger - import log.effect.{LogWriter, LogWriterConstructor} - import org.{log4s => l4s} - - def c: Task[l4s.Logger] => Task[LogWriter[Task]] = { - implicit def F: EffectSuspension[Task] = ??? - implicit def FF: Functor[Task] = ??? - implicitly[LogWriterConstructor[l4s.Logger, Task, Task]].construction - } - - def cPure: l4s.Logger => LogWriter[Task] = { - implicit def F: EffectSuspension[Task] = ??? - implicitly[LogWriterConstructor[l4s.Logger, internal.Id, Task]].construction - } - - @nowarn def l1: Task[LogWriter[Task]] = - c(ZIO.attempt(l4s.getLogger("test"))) - - @nowarn def pureL1: LogWriter[Task] = - cPure(l4s.getLogger("test")) - - @nowarn def l2: Task[LogWriter[Task]] = - log4sFromLogger.provideEnvironment(ZEnvironment(l4s.getLogger("test"))) - } - "correctly infer a valid jul constructor for IO" in { import java.util.{logging => jul}