├── .gitignore
├── .scalafmt.conf
├── LICENSE
├── README.MD
├── build.sbt
├── gpg.sh
├── project
├── build.properties
└── plugins.sbt
└── src
├── main
└── scala
│ └── com
│ └── github
│ └── mlangc
│ └── slf4zio
│ └── api
│ ├── LogMessage.scala
│ ├── LogSpec.scala
│ ├── LoggingSupport.scala
│ ├── MDZIO.scala
│ └── package.scala
└── test
├── resources
└── logback-test.xml
└── scala
└── com
└── github
└── mlangc
└── slf4zio
├── LogbackInitializationTimeout.scala
├── LogbackTestAppender.scala
├── LogbackTestUtils.scala
├── LoggingStrategiesBenchmark.scala
├── ReadmeExamplesTest.scala
└── api
├── LoggingServiceTest.scala
├── LoggingSupportTest.scala
├── MDZIOTest.scala
└── RawPerfLogTest.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | .sbtopts
3 | project/.sbt
4 | .idea
5 | .bloop/
6 | .vscode/
7 | metals.sbt
8 | .metals/
9 | .bsp/
10 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = 3.5.9
2 | maxColumn = 120
3 | runner.dialect = scala213
4 | align.preset = none
5 |
6 | rewrite.rules = [Imports]
7 | rewrite.imports.expand = true
8 | rewrite.imports.sort = original
9 |
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # SLF4ZIO
2 | *Integrates SLF4J with ZIO in a simple manner.*
3 |
4 | [](https://oss.sonatype.org/content/repositories/releases/com/github/mlangc/slf4zio_2.13)
5 | [](https://oss.sonatype.org/content/repositories/snapshots/com/github/mlangc/slf4zio_2.13/)
6 |
7 | ## When to Use
8 | If your code is based on [ZIO](https://zio.dev/) and you want to log with [SLF4J](https://www.slf4j.org/) without additional abstractions getting
9 | in your way.
10 |
11 | ## How to Use
12 | The library supports three different coding styles, that you can mix and match according to your needs. They are listed here in the order of
13 | my personal preference:
14 |
15 | ### 1. Using the LoggingSupport Convenience Trait
16 |
17 | ````scala
18 | import com.github.mlangc.slf4zio.api._
19 | import zio._
20 |
21 | object SomeObject extends LoggingSupport {
22 | def doStuff: Task[Unit] = {
23 | for {
24 | _ <- logger.warnIO("What the heck")
25 | _ <- ZIO.ifZIO(Random.nextBoolean)(
26 | logger.infoIO("Uff, that was close"),
27 | logger.errorIO("Game over", new IllegalStateException("This is the end"))
28 | )
29 |
30 | _ <- ZIO.attempt {
31 | // logger is just a plain SLF4J logger; you can therefore use it from
32 | // effectful code directly:
33 | logger.trace("Wink wink nudge nudge")
34 | }
35 |
36 | _ <- ZIO
37 | .sleep(8.millis)
38 | .as(23)
39 | .perfLog(
40 | // See below for more examples with `LogSpec`
41 | LogSpec
42 | .onSucceed[Int]((d, i) => debug"Finally done with $i after ${d.render}")
43 | .withThreshold(5.millis)
44 | )
45 | } yield ()
46 | }
47 | }
48 | ````
49 |
50 | #### Side Note
51 | Note that the `logger` field in the `LoggingSupport` trait is lazy. Since the implicit class
52 | that implements the various `**IO` methods, like `debugIO`, `infoIO` and so forth, wraps the
53 | logger using a [by-name parameter](https://docs.scala-lang.org/tour/by-name-parameters.html),
54 | logger initialization won't happen before `unsafeRun`, if you don't access the logger directly
55 | from non effectful code. To ensure referential transparency for creating an object of a class that
56 | inherits the `LoggingSupport` trait even with outright broken or strange logger implementations,
57 | you have wrap the creation of the object in an effect of its own. It might make more sense to use
58 | another logger implementation though. For practical purposes, I would consider obtaining a
59 | logger to be a pure operation as soon as the logging framework has finished its initialization,
60 | and not care too much about this subtlety.
61 |
62 | ### 2. Creating Loggers as Needed
63 |
64 | ```scala
65 | import com.github.mlangc.slf4zio.api._
66 | import zio._
67 |
68 | val effect: Task[Unit] = {
69 | // ...
70 | class SomeClass
71 | // ...
72 | for {
73 | logger <- makeLogger[SomeClass]
74 | _ <- logger.debugIO("Debug me tender")
75 | // ...
76 | _ <- ZIO.attempt {
77 | // Note that makeLogger just returns a plain SLF4J logger; you can therefore use it from
78 | // effectful code directly:
79 | logger.info("Don't be shy")
80 | // ...
81 | logger.warn("Please take me home")
82 | // ...
83 | }
84 | // ...
85 | _ <- logger.perfLogZIO(ZIO.sleep(10.millis))(
86 | // See below for more examples with `LogSpec`
87 | LogSpec.onSucceed(d => info"Feeling relaxed after sleeping ${d.render}")
88 | )
89 | } yield ()
90 | }
91 | ```
92 |
93 | ### 3. Using the Logging Service
94 |
95 | ```scala
96 | import com.github.mlangc.slf4zio.api._
97 | import zio._
98 |
99 | val effect: RIO[Logging, Unit] =
100 | for {
101 | _ <- logging.warnIO("Surprise, surprise")
102 | plainLogger <- logging.logger
103 | _ <- ZIO.attempt {
104 | plainLogger.debug("Shhh...")
105 | plainLogger.warn("The devil always comes in disguise")
106 | }
107 | _ <- logging.traceIO("...")
108 | getNumber = ZIO.succeed(42)
109 | // See below for more examples with `LogSpec`
110 | _ <- getNumber.perfLogZ(LogSpec.onSucceed(d => debug"Got number after ${d.render}"))
111 | } yield ()
112 | ```
113 |
114 | ### Using Markers
115 |
116 | ```scala
117 | import com.github.mlangc.slf4zio.api._
118 | import zio._
119 |
120 | val effect: RIO[Logging, Unit] =
121 | for {
122 | marker <- getMarker("[MARKER]")
123 | _ <- logging.infoIO(marker, "Here we are")
124 | logger <- logging.logger
125 | _ <- logger.debugIO(marker, "Wat?")
126 | _ <- ZIO.attempt {
127 | logger.warn(marker, "Don't worry")
128 | }
129 | } yield ()
130 | ```
131 |
132 | ### Performance Logging
133 | Apart from providing ZIO aware wrappers for [SLF4J](https://www.slf4j.org/), the library might also
134 | help you with performance related logging. The examples from above are meant to give you the overall
135 | idea. Here is another snippet, that is meant to illustrate how to build complex `LogSpec`s from simple
136 | ones, utilizing the underlying monoidial structure:
137 |
138 | ```scala
139 | import com.github.mlangc.slf4zio.api._
140 | import zio._
141 |
142 | // Simple specs can be combined using the `++` to obtain more complex specs
143 | val logSpec1: LogSpec[Throwable, Int] =
144 | LogSpec.onSucceed[Int]((d, a) => info"Succeeded after ${d.render} with $a") ++
145 | LogSpec.onError[Throwable]((d, th) => error"Failed after ${d.render} with $th") ++
146 | LogSpec.onTermination((d, c) => error"Fatal failure after ${d.render}: ${c.prettyPrint}")
147 |
148 | // A threshold can be applied to a LogSpec. Nothing will be logged, unless the threshold is exceeded.
149 | val logSpec2: LogSpec[Any, Any] =
150 | LogSpec
151 | .onSucceed(d => warn"Operation took ${d.render}")
152 | .withThreshold(1.milli)
153 |
154 | // Will behave like logSpec1 and eventually log a warning as specified in logSpec2
155 | val logSpec3: LogSpec[Throwable, Int] = logSpec1 ++ logSpec2
156 |
157 | val effect: ZIO[Logging, Nothing, Unit] = for {
158 | _ <- ZIO.sleep(5.micros).perfLogZ(LogSpec.onSucceed(d => debug"Done after ${d.render}"))
159 | _ <- ZIO.sleep(1.milli).as(42).perfLogZ(logSpec1)
160 | _ <- ZIO.sleep(2.milli).perfLogZ(logSpec2)
161 | _ <- ZIO.sleep(3.milli).as(23).perfLogZ(logSpec3)
162 | } yield ()
163 | ```
164 |
165 | ### Using MDC convenience APIs
166 | SLF4ZIO also ships with a set of convenience APIs for `org.slf4j.MDC`. Note however, that traditional
167 | MDC implementations are based on thread local data, which doesn't work at all with ZIO, where a
168 | single `zio.Fiber` might run on different threads during its lifetime, and a single thread might
169 | accommodate multiple fibers. **If you want to use MDC logging in your ZIO based application, it is
170 | critical to use a fiber aware MDC implementation, as provided for example by
171 | [zio-interop-log4j2](https://github.com/mlangc/zio-interop-log4j2). `MDZIO` is just a collection of
172 | convenience APIs for interacting with `org.slf4j.MDC` that doesn't add any functionality of its own.**
173 |
174 | ## Alternatives
175 | If you want to track logging effects using the [ZIO Environment](http://degoes.net/articles/zio-environment) exclusively, consider using
176 | [zio-logging](https://github.com/zio/zio-logging). If you are into Tagless Final,
177 | take a look at [log4cats](https://github.com/ChristopherDavenport/log4cats).
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := "slf4zio"
2 |
3 | organization := "com.github.mlangc"
4 |
5 | scalaVersion := "2.13.8"
6 |
7 | crossScalaVersions := Seq("2.12.16", "2.11.12", "2.13.8")
8 |
9 | ThisBuild / dynverSonatypeSnapshots := true
10 |
11 | // See https://tpolecat.github.io/2017/04/25/scalac-flags.html
12 | val scala212Opts = Seq(
13 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
14 | "-encoding",
15 | "utf-8", // Specify character encoding used by source files.
16 | "-explaintypes", // Explain type errors in more detail.
17 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
18 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
19 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
20 | "-language:higherKinds", // Allow higher-kinded types
21 | "-language:implicitConversions", // Allow definition of implicit functions called views
22 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
23 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
24 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
25 | "-Xfuture", // Turn on future language features.
26 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
27 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
28 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
29 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
30 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
31 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
32 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
33 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
34 | "-Xlint:option-implicit", // Option.apply used implicit view.
35 | "-Xlint:package-object-classes", // Class or object defined in package object.
36 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
37 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
38 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
39 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
40 | "-Xlint:unsound-match", // Pattern match may not be typesafe.
41 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
42 | "-Ypartial-unification", // Enable partial unification in type constructor inference
43 | "-Ywarn-dead-code", // Warn when dead code is identified.
44 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
45 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures.
46 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
47 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
48 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
49 | "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
50 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
51 | "-Ywarn-unused:locals", // Warn if a local definition is unused.
52 | "-Ywarn-unused:params", // Warn if a value parameter is unused.
53 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
54 | "-Ywarn-unused:privates", // Warn if a private member is unused.
55 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
56 | )
57 |
58 | // See https://nathankleyn.com/2019/05/13/recommended-scalac-flags-for-2-13/
59 | val scala213Opts = Seq(
60 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
61 | "-explaintypes", // Explain type errors in more detail.
62 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
63 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
64 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
65 | "-language:higherKinds", // Allow higher-kinded types
66 | "-language:implicitConversions", // Allow definition of implicit functions called views
67 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
68 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
69 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
70 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
71 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
72 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
73 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
74 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
75 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
76 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
77 | "-Xlint:option-implicit", // Option.apply used implicit view.
78 | "-Xlint:package-object-classes", // Class or object defined in package object.
79 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
80 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
81 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
82 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
83 | "-Ywarn-dead-code", // Warn when dead code is identified.
84 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
85 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
86 | "-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
87 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
88 | "-Ywarn-unused:locals", // Warn if a local definition is unused.
89 | "-Ywarn-unused:params", // Warn if a value parameter is unused.
90 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
91 | "-Ywarn-unused:privates", // Warn if a private member is unused.
92 | "-Ywarn-value-discard", // Warn when non-Unit expression results are unused.
93 | "-Ybackend-parallelism",
94 | "8", // Enable paralellisation — change to desired number!
95 | "-Ycache-plugin-class-loader:last-modified", // Enables caching of classloaders for compiler plugins
96 | "-Ycache-macro-class-loader:last-modified" // and macro definitions. This can lead to performance improvements.
97 | )
98 |
99 | val scala211Opts = Seq(
100 | "-deprecation",
101 | "-encoding",
102 | "UTF-8",
103 | "-feature",
104 | "-language:existentials",
105 | "-language:higherKinds",
106 | "-language:implicitConversions",
107 | "-unchecked",
108 | "-Xfatal-warnings",
109 | "-Xlint",
110 | "-Yno-adapted-args",
111 | "-Ywarn-dead-code",
112 | "-Ywarn-numeric-widen",
113 | "-Ywarn-value-discard",
114 | "-Xfuture",
115 | "-Ywarn-unused-import"
116 | )
117 |
118 | scalacOptions ++= {
119 | if (scalaVersion.value.startsWith("2.12.")) scala212Opts
120 | else if (scalaVersion.value.startsWith("2.11.")) scala211Opts
121 | else scala213Opts
122 | }
123 |
124 | Compile / console / scalacOptions --= Seq("-Ywarn-unused:imports", "-Xfatal-warnings")
125 |
126 | val silencerVersion = "1.7.9"
127 | libraryDependencies ++= Seq(
128 | compilerPlugin("com.github.ghik" % "silencer-plugin" % silencerVersion cross CrossVersion.full),
129 | "com.github.ghik" % "silencer-lib" % silencerVersion % Provided cross CrossVersion.full
130 | )
131 |
132 | libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.30"
133 |
134 | resolvers +=
135 | "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
136 |
137 | val zioVersion = "2.0.0"
138 | libraryDependencies += "dev.zio" %% "zio" % zioVersion
139 | libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.4.0" % Test
140 | libraryDependencies += "com.storm-enroute" %% "scalameter" % "0.19" % Test
141 |
142 | libraryDependencies ++= Seq(
143 | "dev.zio" %% "zio-test" % zioVersion % "test",
144 | "dev.zio" %% "zio-test-sbt" % zioVersion % "test"
145 | )
146 |
147 | testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
148 |
149 | publishMavenStyle := true
150 | publishTo := sonatypePublishToBundle.value
151 |
152 | // License of your choice
153 | licenses := Seq("APL2" -> url("http://www.apache.org/licenses/LICENSE-2.0.txt"))
154 |
155 | // Where is the source code hosted
156 | import xerial.sbt.Sonatype._
157 | sonatypeProjectHosting := Some(GitHubHosting("mlangc", "slf4zio", "m.langer798@gmail.com"))
158 |
159 | developers := List(
160 | Developer(
161 | id = "mlangc",
162 | name = "Matthias Langer",
163 | email = "m.langer798@gmail.com",
164 | url = url("https://mlangc.wordpress.com/")
165 | )
166 | )
167 |
168 | Global / PgpKeys.gpgCommand := (baseDirectory.value / "gpg.sh").getAbsolutePath
169 |
--------------------------------------------------------------------------------
/gpg.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 |
3 | gpg --pinentry-mode loopback $@
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version = 1.7.1
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.13")
2 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
3 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
4 | addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1")
5 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/mlangc/slf4zio/api/LogMessage.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import org.slf4j.event.Level
4 |
5 | case class LogMessage(text: String, level: Level, suppressed: Boolean = false)
6 |
7 | object LogMessage {
8 | val Suppressed = LogMessage("", Level.TRACE, true)
9 |
10 | def trace(text: String) = LogMessage(text, Level.TRACE)
11 | def debug(text: String) = LogMessage(text, Level.DEBUG)
12 | def info(text: String) = LogMessage(text, Level.INFO)
13 | def warn(text: String) = LogMessage(text, Level.WARN)
14 | def error(text: String) = LogMessage(text, Level.ERROR)
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/mlangc/slf4zio/api/LogSpec.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import zio.duration2DurationOps
4 | import zio.Cause
5 | import zio.Duration
6 |
7 | case class LogSpec[-E, -A](
8 | onError: List[(Duration, E) => LogMessage] = Nil,
9 | onSucceed: List[(Duration, A) => LogMessage] = Nil,
10 | onTermination: List[(Duration, Cause[Nothing]) => LogMessage] = Nil
11 | ) {
12 |
13 | def combine[E2 <: E, A2 <: A](other: LogSpec[E2, A2]): LogSpec[E2, A2] =
14 | LogSpec(
15 | onError = onError ::: other.onError,
16 | onSucceed = onSucceed ::: other.onSucceed,
17 | onTermination = onTermination ::: other.onTermination
18 | )
19 |
20 | def ++[E2 <: E, A2 <: A](other: LogSpec[E2, A2]) = combine(other)
21 |
22 | def withThreshold(threshold: Duration): LogSpec[E, A] = {
23 | LogSpec(
24 | onError.map(f => (d: Duration, e: E) => if (d < threshold) LogMessage.Suppressed else f(d, e)),
25 | onSucceed.map(f => (d: Duration, e: A) => if (d < threshold) LogMessage.Suppressed else f(d, e)),
26 | onTermination.map(f => (d: Duration, c: Cause[Nothing]) => if (d < threshold) LogMessage.Suppressed else f(d, c))
27 | )
28 | }
29 |
30 | def isNoOp: Boolean =
31 | this == LogSpec.NoOp
32 | }
33 |
34 | object LogSpec {
35 | val NoOp: LogSpec[Any, Any] = LogSpec(Nil, Nil, Nil)
36 |
37 | def onSucceed(msg: Duration => LogMessage): LogSpec[Any, Any] =
38 | onSucceed((d, _) => msg(d))
39 |
40 | def onSucceed[A](msg: (Duration, A) => LogMessage): LogSpec[Any, A] =
41 | LogSpec(onSucceed = List(msg))
42 |
43 | def onError[E](msg: (Duration, E) => LogMessage): LogSpec[E, Any] =
44 | LogSpec(onError = List(msg))
45 |
46 | def onError(msg: Duration => LogMessage): LogSpec[Any, Any] =
47 | onError((d, _) => msg(d))
48 |
49 | def onTermination(msg: (Duration, Cause[Nothing]) => LogMessage): LogSpec[Any, Any] =
50 | LogSpec(onTermination = List(msg))
51 |
52 | def onTermination(msg: Duration => LogMessage): LogSpec[Any, Any] =
53 | onTermination((d, _) => msg(d))
54 |
55 | def onTerminationOrError[E](msg: (Duration, Cause[E]) => LogMessage): LogSpec[E, Any] =
56 | onError[E]((d, e) => msg(d, Cause.fail(e))) ++ onTermination(msg)
57 |
58 | def onTerminationOrError(msg: Duration => LogMessage): LogSpec[Any, Any] =
59 | onTerminationOrError((d, _) => msg(d))
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/mlangc/slf4zio/api/LoggingSupport.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import org.slf4j.Logger
4 | import zio.ZIO
5 |
6 | trait LoggingSupport { outer =>
7 | @transient
8 | protected final lazy val logger: Logger = getLogger(getClass)
9 |
10 | protected implicit final class ZioLoggerOps[R, E, A](zio: ZIO[R, E, A]) {
11 | def perfLog[E1 >: E](spec: LogSpec[E1, A]): ZIO[R, E, A] =
12 | ZIO.environmentWithZIO[R] { r =>
13 | val io = zio.provideEnvironment(r)
14 | io.perfLogZ(spec)
15 | .provideLayer(Logging.forLogger(logger))
16 | }
17 | }
18 |
19 | protected final def perfLog[A](thunk: => A)(spec: LogSpec[Throwable, A]): A =
20 | logger.perfLog(thunk)(spec)
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/mlangc/slf4zio/api/MDZIO.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.slf4j.MDC
5 | import scala.collection.JavaConverters._
6 | import zio.UIO
7 | import zio.ZIO
8 |
9 | /** Convenience APIs for interacting with the MDC context.
10 | *
11 | * ==Important==
12 | * Make sure that you use a fiber aware MDC implementation, as provided for example by zio-interop-log4j2. Using the convenience wrappers found
14 | * here won't make the underlying MDC context implementation aware of ZIO fibers.
15 | */
16 | @silent("JavaConverters")
17 | abstract class MDZIO {
18 | final def put(key: String, value: String): UIO[Unit] =
19 | ZIO.succeed(MDC.put(key, value))
20 |
21 | final def get(key: String): UIO[Option[String]] =
22 | ZIO.succeed(Option(MDC.get(key)))
23 |
24 | final def remove(key: String): UIO[Unit] =
25 | ZIO.succeed(MDC.remove(key))
26 |
27 | final def clear(): UIO[Unit] =
28 | ZIO.succeed(MDC.clear())
29 |
30 | final def putAll(pairs: (String, String)*): UIO[Unit] =
31 | putAll(pairs)
32 |
33 | final def putAll(pairs: Iterable[(String, String)]): UIO[Unit] =
34 | ZIO.foreachDiscard(pairs)((put _).tupled)
35 |
36 | final def removeAll(keys: Iterable[String]): UIO[Unit] =
37 | ZIO.foreachDiscard(keys)(remove)
38 |
39 | /** Puts the given key value pairs in the context, executes the given action, and restores the original context.
40 | */
41 | final def doWith[R, E, A](pairs: Iterable[(String, String)])(zio: ZIO[R, E, A]): ZIO[R, E, A] =
42 | for {
43 | state1 <- ZIO.succeed(pairs.toMap)
44 | state0 <- getAll(state1.keys)
45 | newKeys = state1.keySet.diff(state0.keySet)
46 | a <- (putAll(state1) *> zio).ensuring(removeAll(newKeys) *> putAll(state0))
47 | } yield a
48 |
49 | final def doWith[R, E, A](pairs: (String, String)*)(zio: ZIO[R, E, A]): ZIO[R, E, A] =
50 | doWith(pairs)(zio)
51 |
52 | final def getContextMap: UIO[Option[Map[String, String]]] =
53 | ZIO.succeed(Option(MDC.getCopyOfContextMap).map(_.asScala.toMap))
54 |
55 | final def setContextMap(map: Map[String, String]): UIO[Unit] =
56 | ZIO.succeed(MDC.setContextMap(map.asJava))
57 |
58 | private def getAll(keys: Iterable[String]): UIO[Map[String, String]] =
59 | ZIO.foldLeft(keys)(Map.empty[String, String]) { (acc, key) =>
60 | get(key).map(_.fold(acc)(v => acc + (key -> v)))
61 | }
62 | }
63 |
64 | /** See also [[Logging.Service.mdzio]] if you want all logging related calls go through the service.
65 | */
66 | object MDZIO extends MDZIO
67 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/mlangc/slf4zio/api/package.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import org.slf4j.event.Level
4 | import org.slf4j.Logger
5 | import org.slf4j.LoggerFactory
6 | import org.slf4j.Marker
7 | import org.slf4j.MarkerFactory
8 | import scala.reflect.ClassTag
9 | import scala.util.Failure
10 | import scala.util.Success
11 | import scala.util.Try
12 | import zio.Cause
13 | import zio.Clock
14 | import zio.Duration
15 | import zio.UIO
16 | import zio.ULayer
17 | import zio.URIO
18 | import zio.ZIO
19 | import zio.ZLayer
20 |
21 | package object api {
22 | implicit final class Slf4jLoggerOps(logger: => Logger) {
23 | def traceIO(msg: => String): UIO[Unit] = ZIO.succeed {
24 | if (logger.isTraceEnabled())
25 | logger.trace(msg)
26 | }
27 |
28 | def debugIO(msg: => String): UIO[Unit] = ZIO.succeed {
29 | if (logger.isDebugEnabled)
30 | logger.debug(msg)
31 | }
32 |
33 | def infoIO(msg: => String): UIO[Unit] = ZIO.succeed {
34 | if (logger.isInfoEnabled())
35 | logger.info(msg)
36 | }
37 |
38 | def warnIO(msg: => String): UIO[Unit] = ZIO.succeed {
39 | if (logger.isWarnEnabled())
40 | logger.warn(msg)
41 | }
42 |
43 | def errorIO(msg: => String): UIO[Unit] = ZIO.succeed {
44 | if (logger.isErrorEnabled())
45 | logger.error(msg)
46 | }
47 |
48 | def traceIO(msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
49 | if (logger.isTraceEnabled())
50 | logger.trace(msg, th)
51 | }
52 |
53 | def debugIO(msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
54 | if (logger.isDebugEnabled)
55 | logger.debug(msg, th)
56 | }
57 |
58 | def infoIO(msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
59 | if (logger.isInfoEnabled)
60 | logger.info(msg, th)
61 | }
62 |
63 | def warnIO(msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
64 | if (logger.isWarnEnabled)
65 | logger.warn(msg, th)
66 | }
67 |
68 | def errorIO(msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
69 | if (logger.isErrorEnabled)
70 | logger.error(msg, th)
71 | }
72 |
73 | def traceIO(marker: Marker, msg: => String): UIO[Unit] = ZIO.succeed {
74 | if (logger.isTraceEnabled(marker))
75 | logger.trace(marker, msg)
76 | }
77 |
78 | def debugIO(marker: Marker, msg: => String): UIO[Unit] = ZIO.succeed {
79 | if (logger.isDebugEnabled(marker))
80 | logger.debug(marker, msg)
81 | }
82 |
83 | def infoIO(marker: Marker, msg: => String): UIO[Unit] = ZIO.succeed {
84 | if (logger.isInfoEnabled(marker))
85 | logger.info(marker, msg)
86 | }
87 |
88 | def warnIO(marker: Marker, msg: => String): UIO[Unit] = ZIO.succeed {
89 | if (logger.isWarnEnabled(marker))
90 | logger.warn(marker, msg)
91 | }
92 |
93 | def errorIO(marker: Marker, msg: => String): UIO[Unit] = ZIO.succeed {
94 | if (logger.isErrorEnabled(marker))
95 | logger.error(marker, msg)
96 | }
97 |
98 | def traceIO(marker: Marker, msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
99 | if (logger.isTraceEnabled(marker))
100 | logger.trace(marker, msg, th)
101 | }
102 |
103 | def debugIO(marker: Marker, msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
104 | if (logger.isDebugEnabled)
105 | logger.debug(marker, msg, th)
106 | }
107 |
108 | def infoIO(marker: Marker, msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
109 | if (logger.isInfoEnabled)
110 | logger.info(marker, msg, th)
111 | }
112 |
113 | def warnIO(marker: Marker, msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
114 | if (logger.isWarnEnabled)
115 | logger.warn(marker, msg, th)
116 | }
117 |
118 | def errorIO(marker: Marker, msg: => String, th: Throwable): UIO[Unit] = ZIO.succeed {
119 | if (logger.isErrorEnabled)
120 | logger.error(marker, msg, th)
121 | }
122 |
123 | def logIO(msg: LogMessage): UIO[Unit] =
124 | ZIO.succeed(log(msg))
125 |
126 | def log(msg: LogMessage): Unit =
127 | if (msg.suppressed) ()
128 | else
129 | msg.level match {
130 | case Level.ERROR => logger.error(msg.text)
131 | case Level.WARN => logger.warn(msg.text)
132 | case Level.INFO => logger.info(msg.text)
133 | case Level.DEBUG => logger.debug(msg.text)
134 | case Level.TRACE => logger.trace(msg.text)
135 | }
136 |
137 | def perfLogZIO[R, E, A, E2 >: E, A2 >: A](zio: ZIO[R, E, A])(spec: LogSpec[E2, A2]): ZIO[R, E, A] =
138 | if (spec.isNoOp) zio
139 | else
140 | Clock.nanoTime.flatMap { t0 =>
141 | def handleError(cause: Cause[E]): ZIO[Any, E, Nothing] =
142 | if (spec.onError.isEmpty && spec.onTermination.isEmpty) ZIO.failCause(cause)
143 | else
144 | Clock.nanoTime.flatMap { t1 =>
145 | val d = Duration.fromNanos(t1 - t0)
146 |
147 | val msgs = cause.failureOrCause match {
148 | case Right(failure) => spec.onTermination.map(_(d, failure))
149 | case Left(e) => spec.onError.map(_(d, e))
150 | }
151 |
152 | ZIO.foreach(msgs)(m => logger.logIO(m)) *> ZIO.failCause(cause)
153 | }
154 |
155 | def handleSuccess(a: A): ZIO[Any, Nothing, A] =
156 | if (spec.onSucceed.isEmpty) ZIO.succeed(a)
157 | else {
158 | for {
159 | t1 <- Clock.nanoTime
160 | d = Duration.fromNanos(t1 - t0)
161 | msgs = spec.onSucceed.map(_(d, a))
162 | _ <- ZIO.foreach(msgs)(m => logger.logIO(m))
163 | } yield a
164 | }
165 |
166 | zio.foldCauseZIO(handleError, handleSuccess)
167 | }
168 |
169 | def perfLogIO[R, E, A](zio: ZIO[R, E, A])(spec: LogSpec[E, A]): ZIO[R, E, A] =
170 | ZIO.environmentWithZIO[R] { r =>
171 | val io = zio.provideEnvironment(r)
172 | perfLogZIO(io)(spec).provideLayer(ZLayer.succeed(Clock.ClockLive))
173 | }
174 |
175 | def perfLog[A](thunk: => A)(spec: LogSpec[Throwable, A]): A =
176 | if (spec.isNoOp) thunk
177 | else {
178 | val t0 = System.nanoTime()
179 | val res = Try(thunk)
180 |
181 | def onError(th: Throwable): Nothing =
182 | if (spec.onError.isEmpty) throw th
183 | else {
184 | val t1 = System.nanoTime()
185 | val d = Duration.fromNanos(t1 - t0)
186 | val msgs = spec.onError.map(_(d, th))
187 | msgs.foreach(logger.log)
188 | throw th
189 | }
190 |
191 | def onSuccess(a: A): A =
192 | if (spec.onSucceed.isEmpty) a
193 | else {
194 | val t1 = System.nanoTime()
195 | val d = Duration.fromNanos(t1 - t0)
196 | val msgs = spec.onSucceed.map(_(d, a))
197 | msgs.foreach(logger.log)
198 | a
199 | }
200 |
201 | res match {
202 | case Failure(e) => onError(e)
203 | case Success(a) => onSuccess(a)
204 | }
205 | }
206 | }
207 |
208 | object Logging {
209 | trait Service[-R] {
210 | final def traceIO(msg: => String): URIO[R, Unit] =
211 | withUnderlying(_.traceIO(msg))
212 |
213 | final def debugIO(msg: => String): URIO[R, Unit] =
214 | withUnderlying(_.debugIO(msg))
215 |
216 | final def infoIO(msg: => String): URIO[R, Unit] =
217 | withUnderlying(_.infoIO(msg))
218 |
219 | final def warnIO(msg: => String): URIO[R, Unit] =
220 | withUnderlying(_.warnIO(msg))
221 |
222 | final def errorIO(msg: => String): URIO[R, Unit] =
223 | withUnderlying(_.errorIO(msg))
224 |
225 | final def traceIO(msg: => String, th: Throwable): URIO[R, Unit] =
226 | withUnderlying(_.traceIO(msg, th))
227 |
228 | final def debugIO(msg: => String, th: Throwable): URIO[R, Unit] =
229 | withUnderlying(_.debugIO(msg, th))
230 |
231 | final def infoIO(msg: => String, th: Throwable): URIO[R, Unit] =
232 | withUnderlying(_.infoIO(msg, th))
233 |
234 | final def warnIO(msg: => String, th: Throwable): URIO[R, Unit] =
235 | withUnderlying(_.warnIO(msg, th))
236 |
237 | final def errorIO(msg: => String, th: Throwable): URIO[R, Unit] =
238 | withUnderlying(_.errorIO(msg, th))
239 |
240 | final def traceIO(marker: Marker, msg: => String): URIO[R, Unit] =
241 | withUnderlying(_.traceIO(marker, msg))
242 |
243 | final def debugIO(marker: Marker, msg: => String): URIO[R, Unit] =
244 | withUnderlying(_.debugIO(marker, msg))
245 |
246 | final def infoIO(marker: Marker, msg: => String): URIO[R, Unit] =
247 | withUnderlying(_.infoIO(marker, msg))
248 |
249 | final def warnIO(marker: Marker, msg: => String): URIO[R, Unit] =
250 | withUnderlying(_.warnIO(marker, msg))
251 |
252 | final def errorIO(marker: Marker, msg: => String): URIO[R, Unit] =
253 | withUnderlying(_.errorIO(marker, msg))
254 |
255 | final def traceIO(marker: Marker, msg: => String, th: Throwable): URIO[R, Unit] =
256 | withUnderlying(_.traceIO(marker, msg, th))
257 |
258 | final def debugIO(marker: Marker, msg: => String, th: Throwable): URIO[R, Unit] =
259 | withUnderlying(_.debugIO(marker, msg, th))
260 |
261 | final def infoIO(marker: Marker, msg: => String, th: Throwable): URIO[R, Unit] =
262 | withUnderlying(_.infoIO(marker, msg, th))
263 |
264 | final def warnIO(marker: Marker, msg: => String, th: Throwable): URIO[R, Unit] =
265 | withUnderlying(_.warnIO(marker, msg, th))
266 |
267 | final def errorIO(marker: Marker, msg: => String, th: Throwable): URIO[R, Unit] =
268 | withUnderlying(_.errorIO(marker, msg, th))
269 |
270 | final def logIO(msg: => LogMessage): URIO[R, Unit] =
271 | withUnderlying(_.logIO(msg))
272 |
273 | final def mdzio: MDZIO = MDZIO
274 |
275 | def logger: URIO[R, Logger]
276 |
277 | private def withUnderlying(op: Logger => UIO[Unit]): URIO[R, Unit] =
278 | logger.flatMap(op)
279 | }
280 |
281 | def forClass(clazz: Class[_]): ULayer[Logging] = ZLayer.succeed {
282 | new Service[Any] {
283 | @transient
284 | private lazy val theLogger = getLogger(clazz)
285 |
286 | def logger: UIO[Logger] = ZIO.succeed(theLogger)
287 | }
288 | }
289 |
290 | def forLogger(getLogger: => Logger): ULayer[Logging] = ZLayer.succeed {
291 | new Service[Any] {
292 | def logger: UIO[Logger] = ZIO.succeed(getLogger)
293 | }
294 | }
295 |
296 | def global: ULayer[Logging] = forClass(Logging.getClass)
297 |
298 | val any: ZLayer[Logging, Nothing, Logging] =
299 | ZLayer.environment[Logging]
300 | }
301 |
302 | type Logging = Logging.Service[Any]
303 |
304 | val logging: Logging.Service[Logging] = new Logging.Service[Logging] {
305 | def logger: URIO[Logging, Logger] =
306 | ZIO.environmentWithZIO[Logging](_.get[Logging.Service[Any]].logger)
307 | }
308 |
309 | def getLogger[T](implicit classTag: ClassTag[T]): Logger =
310 | getLogger(classTag.runtimeClass)
311 |
312 | def getLogger(clazz: Class[_]): Logger =
313 | LoggerFactory.getLogger(clazz)
314 |
315 | def getLogger(name: String): Logger =
316 | LoggerFactory.getLogger(name)
317 |
318 | def getMarker(name: String): UIO[Marker] =
319 | ZIO.succeed(MarkerFactory.getMarker(name))
320 |
321 | def makeLogger(name: String): UIO[Logger] =
322 | ZIO.succeed(getLogger(name))
323 |
324 | def makeLogger[T](implicit classTag: ClassTag[T]): UIO[Logger] =
325 | ZIO.succeed(getLogger[T])
326 |
327 | def makeLogger[T](clazz: Class[_]): UIO[Logger] =
328 | ZIO.succeed(getLogger(clazz))
329 |
330 | implicit final class LogMessageInterpolator(val stringContext: StringContext) extends AnyVal {
331 | def trace(args: Any*): LogMessage = {
332 | LogMessage.trace(stringContext.s(args: _*))
333 | }
334 |
335 | def debug(args: Any*): LogMessage = {
336 | LogMessage.debug(stringContext.s(args: _*))
337 | }
338 |
339 | def info(args: Any*): LogMessage = {
340 | LogMessage.info(stringContext.s(args: _*))
341 | }
342 |
343 | def warn(args: Any*): LogMessage = {
344 | LogMessage.warn(stringContext.s(args: _*))
345 | }
346 |
347 | def error(args: Any*): LogMessage = {
348 | LogMessage.error(stringContext.s(args: _*))
349 | }
350 | }
351 |
352 | implicit final class ZioLoggingOps[R, E, A](val zio: ZIO[R, E, A]) {
353 | def perfLogZ[E2 >: E, A2 >: A](spec: LogSpec[E2, A2]): ZIO[R with Logging, E, A] =
354 | ZIO
355 | .environmentWithZIO[Logging](_.get[Logging.Service[Any]].logger)
356 | .flatMap(_.perfLogZIO(zio)(spec))
357 | }
358 | }
359 |
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %d{yyyy-MM-dd HH:mm:ss} %-5level %marker %logger{0} - %msg%n
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/LogbackInitializationTimeout.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import zio.duration2DurationOps
4 | import zio.Duration
5 |
6 | case class LogbackInitializationTimeout(elapsed: Duration)
7 | extends RuntimeException(s"Logback initialization timed out after ${elapsed.render}")
8 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/LogbackTestAppender.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent
4 | import ch.qos.logback.core.AppenderBase
5 | import java.util.concurrent.atomic.AtomicReference
6 | import java.util.function.UnaryOperator
7 | import zio.UIO
8 | import zio.ZIO
9 |
10 | class LogbackTestAppender extends AppenderBase[ILoggingEvent] {
11 | def append(eventObject: ILoggingEvent): Unit = {
12 | LogbackTestAppender.eventsRef.updateAndGet(new UnaryOperator[List[ILoggingEvent]] {
13 | def apply(evts: List[ILoggingEvent]): List[ILoggingEvent] = eventObject :: evts
14 | })
15 |
16 | ()
17 | }
18 | }
19 |
20 | object LogbackTestAppender {
21 | private val eventsRef = new AtomicReference(List.empty[ILoggingEvent])
22 |
23 | def events: UIO[List[ILoggingEvent]] =
24 | ZIO.succeed(eventsRef.get())
25 |
26 | def eventsFor(clazz: Class[_]): UIO[List[ILoggingEvent]] =
27 | events.map(_.filter(_.getLoggerName == clazz.getCanonicalName))
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/LogbackTestUtils.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import ch.qos.logback.classic.LoggerContext
4 | import org.slf4j.LoggerFactory
5 | import zio.durationInt
6 | import zio.Duration
7 | import zio.IO
8 | import zio.Schedule
9 | import zio.UIO
10 | import zio.ZIO
11 |
12 | object LogbackTestUtils {
13 | def waitForLogbackInitialization: IO[LogbackInitializationTimeout, Unit] = {
14 | val schedule: Schedule[Any, Boolean, (Boolean, Duration)] =
15 | (Schedule.recurUntil[Boolean](identity) <* (Schedule.spaced(1.milli) <* Schedule.recurs(500))) && Schedule.elapsed
16 |
17 | logbackInitialized.repeat(schedule).flatMap {
18 | case (true, _) => ZIO.unit
19 | case (false, elapsed) => ZIO.fail(LogbackInitializationTimeout(elapsed))
20 | }
21 | }
22 |
23 | def logbackInitialized: UIO[Boolean] = ZIO.succeed {
24 | LoggerFactory.getILoggerFactory.isInstanceOf[LoggerContext]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/LoggingStrategiesBenchmark.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import org.scalameter.picklers.noPickler._
4 | import org.scalameter.Bench
5 | import org.scalameter.Gen
6 | import org.slf4j.event.Level
7 | import org.slf4j.Logger
8 | import org.slf4j.LoggerFactory
9 | import zio.Runtime
10 | import zio.UIO
11 | import zio.Unsafe
12 | import zio.ZIO
13 |
14 | object LoggingStrategiesBenchmark extends Bench.LocalTime {
15 | private implicit class LoggerOpsAnyVal(val logger: Logger) extends AnyVal {
16 | def debugAnyValIO(msg: => String): UIO[Unit] = ZIO.succeed {
17 | if (logger.isDebugEnabled)
18 | logger.debug(msg)
19 | }
20 | }
21 |
22 | private implicit class LoggerOpsLazy(logger: => Logger) {
23 | def debugLazyIO(msg: => String): UIO[Unit] = ZIO.succeed {
24 | if (logger.isDebugEnabled)
25 | logger.debug(msg)
26 | }
27 | }
28 |
29 | private val nLogs = Gen.enumeration("nlogs")(1, 100)
30 |
31 | private lazy val debugLogger = getLogger(Level.DEBUG)
32 | private lazy val infoLogger = getLogger(Level.INFO)
33 |
34 | private def logStr = "--benchmark me--"
35 |
36 | performance of "debugLogger" in {
37 | measure method "debug" in {
38 | using(nLogs) in nTimes(debugLogger.debug(logStr))
39 | }
40 |
41 | measure method "debugAnyValIO" in {
42 | using(nLogs) in runNtimesIO(debugLogger.debugAnyValIO(logStr))
43 | }
44 |
45 | measure method "debugLazyIO" in {
46 | using(nLogs) in runNtimesIO(debugLogger.debugLazyIO(logStr))
47 | }
48 | }
49 |
50 | performance of "infoLogger" in {
51 | measure method "debug" in {
52 | using(nLogs) in nTimes(infoLogger.debug(logStr))
53 | }
54 |
55 | measure method "debugAnyValIO" in {
56 | using(nLogs) in runNtimesIO(infoLogger.debugAnyValIO(logStr))
57 | }
58 |
59 | measure method "debugLazyIO" in {
60 | using(nLogs) in runNtimesIO(infoLogger.debugLazyIO(logStr))
61 | }
62 | }
63 |
64 | private def nTimes(block: => Unit)(n: Int): Unit = {
65 | 1.to(n).foreach(_ => block)
66 | }
67 |
68 | private def getLogger(level: Level): Logger =
69 | LoggerFactory.getLogger(getClass.getCanonicalName + "." + level.toString.toLowerCase)
70 |
71 | private def runNtimesIO(io: UIO[Unit])(n: Int): Unit =
72 | Unsafe.unsafe(implicit u => Runtime.default.unsafe.run(nTimesIO(n)(io)).getOrThrowFiberFailure())
73 |
74 | private def nTimesIO[R, E](n: Int)(io: ZIO[R, E, Unit]): ZIO[R, E, Unit] = n match {
75 | case 1 => io
76 | case n if n > 1 => io *> nTimesIO(n - 1)(io)
77 | case _ => ZIO.unit
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/ReadmeExamplesTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio
2 |
3 | import com.github.mlangc.slf4zio.api.Logging
4 | import zio.test._
5 | import zio.test.Assertion._
6 |
7 | object ReadmeExamplesTest extends ZIOSpecDefault {
8 | def spec = suite("ReadmeExamplesTest")(
9 | test("creating loggers as needed") {
10 | import com.github.mlangc.slf4zio.api._
11 | import zio._
12 |
13 | val effect: Task[Unit] = {
14 | // ...
15 | class SomeClass
16 | // ...
17 | for {
18 | logger <- makeLogger[SomeClass]
19 | _ <- logger.debugIO("Debug me tender")
20 | // ...
21 | _ <- ZIO.attempt {
22 | // Note that makeLogger just returns a plain SLF4J logger; you can therefore use it from
23 | // effectful code directly:
24 | logger.info("Don't be shy")
25 | // ...
26 | logger.warn("Please take me home")
27 | // ...
28 | }
29 | // ...
30 | _ <- logger.perfLogZIO(ZIO.sleep(10.millis))(
31 | // See below for more examples with `LogSpec`
32 | LogSpec.onSucceed(d => info"Feeling relaxed after sleeping ${d.render}")
33 | )
34 | } yield ()
35 | }
36 |
37 | assertZIO(effect)(isUnit)
38 | },
39 | test("Using the convenience trait") {
40 | import com.github.mlangc.slf4zio.api._
41 | import zio._
42 |
43 | object SomeObject extends LoggingSupport {
44 | def doStuff: Task[Unit] = {
45 | for {
46 | _ <- logger.warnIO("What the heck")
47 | _ <- ZIO.ifZIO(Random.nextBoolean)(
48 | logger.infoIO("Uff, that was close"),
49 | logger.errorIO("Game over", new IllegalStateException("This is the end"))
50 | )
51 |
52 | _ <- ZIO.attempt {
53 | // logger is just a plain SLF4J logger; you can therefore use it from
54 | // effectful code directly:
55 | logger.trace("Wink wink nudge nudge")
56 | }
57 |
58 | _ <- ZIO
59 | .sleep(8.millis)
60 | .as(23)
61 | .perfLog(
62 | // See below for more examples with `LogSpec`
63 | LogSpec
64 | .onSucceed[Int]((d, i) => debug"Finally done with $i after ${d.render}")
65 | .withThreshold(5.millis)
66 | )
67 | } yield ()
68 | }
69 | }
70 |
71 | assertZIO(SomeObject.doStuff.ignore)(isUnit)
72 | },
73 | test("Using the service") {
74 | import com.github.mlangc.slf4zio.api._
75 | import zio._
76 |
77 | val effect: RIO[Logging, Unit] =
78 | for {
79 | _ <- logging.warnIO("Surprise, surprise")
80 | plainLogger <- logging.logger
81 | _ <- ZIO.attempt {
82 | plainLogger.debug("Shhh...")
83 | plainLogger.warn("The devil always comes in disguise")
84 | }
85 | _ <- logging.traceIO("...")
86 | getNumber = ZIO.succeed(42)
87 | // See below for more examples with `LogSpec`
88 | _ <- getNumber.perfLogZ(LogSpec.onSucceed(d => debug"Got number after ${d.render}"))
89 | } yield ()
90 |
91 | assertZIO(effect)(isUnit)
92 | },
93 | test("Performance Logging - Using the Logging Service") {
94 | import com.github.mlangc.slf4zio.api._
95 | import zio._
96 |
97 | // Simple specs can be combined using the `++` to obtain more complex specs
98 | val logSpec1: LogSpec[Throwable, Int] =
99 | LogSpec.onSucceed[Int]((d, a) => info"Succeeded after ${d.render} with $a") ++
100 | LogSpec.onError[Throwable]((d, th) => error"Failed after ${d.render} with $th") ++
101 | LogSpec.onTermination((d, c) => error"Fatal failure after ${d.render}: ${c.prettyPrint}")
102 |
103 | // A threshold can be applied to a LogSpec. Nothing will be logged, unless the threshold is exceeded.
104 | val logSpec2: LogSpec[Any, Any] =
105 | LogSpec
106 | .onSucceed(d => warn"Operation took ${d.render}")
107 | .withThreshold(1.milli)
108 |
109 | // Will behave like logSpec1 and eventually log a warning as specified in logSpec2
110 | val logSpec3: LogSpec[Throwable, Int] = logSpec1 ++ logSpec2
111 |
112 | val effect: ZIO[Logging, Nothing, Unit] = for {
113 | _ <- ZIO.sleep(5.micros).perfLogZ(LogSpec.onSucceed(d => debug"Done after ${d.render}"))
114 | _ <- ZIO.sleep(1.milli).as(42).perfLogZ(logSpec1)
115 | _ <- ZIO.sleep(2.milli).perfLogZ(logSpec2)
116 | _ <- ZIO.sleep(3.milli).as(23).perfLogZ(logSpec3)
117 | } yield ()
118 |
119 | assertZIO(effect)(isUnit)
120 | },
121 | test("Working with Markers") {
122 | import com.github.mlangc.slf4zio.api._
123 | import zio._
124 |
125 | val effect: RIO[Logging, Unit] =
126 | for {
127 | marker <- getMarker("[MARKER]")
128 | _ <- logging.infoIO(marker, "Here we are")
129 | logger <- logging.logger
130 | _ <- logger.debugIO(marker, "Wat?")
131 | _ <- ZIO.attempt {
132 | logger.warn(marker, "Don't worry")
133 | }
134 | } yield ()
135 |
136 | assertZIO(effect)(isUnit)
137 | }
138 | ).provideCustomLayer(Logging.forClass(getClass)) @@ TestAspect.withLiveClock
139 | }
140 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/api/LoggingServiceTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import ch.qos.logback.classic.Level
4 | import com.github.mlangc.slf4zio.LogbackTestAppender
5 | import com.github.mlangc.slf4zio.LogbackTestUtils
6 | import zio._
7 | import zio.test._
8 | import zio.test.Assertion._
9 | import zio.ZIO
10 |
11 | object LoggingServiceTest extends ZIOSpecDefault {
12 | def spec = suite("LoggingService")(
13 | test("Logging with thresholds") {
14 | val spec = {
15 | LogSpec.onError(_ => warn"ERROR") ++
16 | LogSpec.onSucceed(_ => info"OK") ++
17 | LogSpec.onTermination(_ => error"UPS")
18 | }.withThreshold(1.second)
19 |
20 | def consume(d: Duration) =
21 | TestClock.adjust(d)
22 |
23 | def consumeAndFail(d: Duration) =
24 | consume(d) *> ZIO.fail(new RuntimeException("Test"))
25 |
26 | def consumeAndDie(d: Duration) =
27 | consume(d) *> ZIO.dieMessage("Test")
28 |
29 | (for {
30 | _ <- consume(1.milli).perfLogZ(spec)
31 | _ <- consume(1.second).perfLogZ(spec)
32 | _ <- consumeAndFail(999.millis + 999.micros + 999.nanos).perfLogZ(spec).ignore
33 | _ <- consumeAndFail(1.second + 1.nano).perfLogZ(spec).ignore
34 | _ <- consumeAndDie(Duration.Zero).perfLogZ(spec).catchAllCause(_ => ZIO.unit)
35 | _ <- consumeAndDie(1.second).perfLogZ(spec).catchAllCause(_ => ZIO.unit)
36 | evts <- LogbackTestAppender.events.map(evts =>
37 | evts.filter(evt => evt.getLoggerName == getClass.getCanonicalName)
38 | )
39 | warnEvts = evts.filter(_.getLevel == Level.WARN)
40 | infoEvts = evts.filter(_.getLevel == Level.INFO)
41 | errEvts = evts.filter(_.getLevel == Level.ERROR)
42 | } yield {
43 | assert(warnEvts)(hasSize(equalTo(1))) &&
44 | assert(infoEvts)(hasSize(equalTo(1))) &&
45 | assert(errEvts)(hasSize(equalTo(1)))
46 | })
47 | }
48 | ).provideCustomLayer((Logging.forClass(getClass))) @@
49 | TestAspect.before(live(LogbackTestUtils.waitForLogbackInitialization.orDie)) @@
50 | TestAspect.timeout(15.seconds)
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/api/LoggingSupportTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import ch.qos.logback.classic.spi.ILoggingEvent
4 | import ch.qos.logback.classic.Level
5 | import com.github.mlangc.slf4zio.LogbackInitializationTimeout
6 | import com.github.mlangc.slf4zio.LogbackTestAppender
7 | import com.github.mlangc.slf4zio.LogbackTestUtils
8 | import zio.duration2DurationOps
9 | import zio.test._
10 | import zio.test.Assertion
11 | import zio.test.Assertion.equalTo
12 | import zio.test.Assertion.hasSize
13 | import zio.test.ZIOSpecDefault
14 | import zio.IO
15 | import zio.ZIO
16 |
17 | object LoggingSupportTest extends ZIOSpecDefault with LoggingSupport {
18 | def spec = suite("LoggingSupport")(
19 | test("Make sure something is logged") {
20 | for {
21 | _ <- logger.infoIO("Test")
22 | evts <- getLogEvents
23 | } yield assert(evts)(Assertion.isNonEmpty)
24 | },
25 | suite("Performance logging")(
26 | suite("ZIO syntax")(
27 | test("Success only") {
28 | for {
29 | _ <- ZIO.unit.perfLog(LogSpec.onSucceed(d => debug"Simple ${d.render}"))
30 | _ <- ZIO.succeed(42).perfLog(LogSpec.onSucceed((d, a) => debug"Simple (${d.render}, $a)"))
31 | evts <- getLogEvents(_.getMessage.startsWith("Simple"))
32 | } yield assert(evts.size)(equalTo(2))
33 | },
34 | test("Success and failures") {
35 | val spec: LogSpec[Throwable, Any] = LogSpec.onSucceed(d => info"Success after ${d.render}") ++
36 | LogSpec.onError[Throwable]((d, e) => warn"Error $e after ${d.render}") ++
37 | LogSpec.onTermination((d, c) => error"Fatal error ${c.prettyPrint} after ${d.render}")
38 |
39 | for {
40 | _ <- ZIO.unit.perfLog(spec)
41 | _ <- ZIO.fail(new RuntimeException).perfLog(spec).ignore
42 | _ <- ZIO.die(new RuntimeException).perfLog(spec).catchAllCause(_ => ZIO.unit)
43 | succEvents <- getLogEvents(evt => evt.getLevel == Level.INFO && evt.getMessage.startsWith("Success"))
44 | errEvents <- getLogEvents(evt => evt.getLevel == Level.WARN && evt.getMessage.startsWith("Error"))
45 | termEvents <- getLogEvents(evt => evt.getLevel == Level.ERROR && evt.getMessage.startsWith("Fatal"))
46 | } yield {
47 | assert(succEvents)(hasSize(equalTo(1))) &&
48 | assert(errEvents)(hasSize(equalTo(1))) &&
49 | assert(termEvents)(hasSize(equalTo(1)))
50 | }
51 | }
52 | )
53 | )
54 | ) @@ TestAspect.before(live(LogbackTestUtils.waitForLogbackInitialization).orDie)
55 |
56 | private def getLogEvents(p: ILoggingEvent => Boolean): IO[LogbackInitializationTimeout, List[ILoggingEvent]] =
57 | LogbackTestAppender.events.map { evts =>
58 | evts.filter(evt => p(evt) && evt.getLoggerName.contains(getClass.getSimpleName))
59 | }
60 |
61 | private def getLogEvents: IO[LogbackInitializationTimeout, List[ILoggingEvent]] =
62 | getLogEvents(_ => true)
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/api/MDZIOTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import zio.test._
4 | import zio.test.Assertion.equalTo
5 | import zio.test.Assertion.isNone
6 | import zio.test.ZIOSpecDefault
7 | import zio.ZIO
8 |
9 | object MDZIOTest extends ZIOSpecDefault {
10 | def spec = suite("MDZIO")(
11 | test("put, get & clear works properly") {
12 | val (x, y) = ("x", "y")
13 | val (a, b) = ("a", "b")
14 |
15 | for {
16 | _ <- MDZIO.putAll(x -> a, y -> b)
17 | v1 <- MDZIO.get(x)
18 | v2 <- MDZIO.get(y)
19 | _ <- MDZIO.clear()
20 | v3 <- MDZIO.get(x)
21 | v4 <- MDZIO.get(y)
22 | } yield assert((v1, v2, v3, v4))(equalTo((Some(a), Some(b), None, None)))
23 | },
24 | test("doWith") {
25 | val (d, e, f, g) = ("d", "e", "f", "g")
26 | val (k, l, m, n) = ("k", "l", "m", "n")
27 | val m0 = "m0"
28 | val km0 = List("k", "m0")
29 | val defg = List(d, e, f, g)
30 | val klmn = List(k, l, m, n)
31 |
32 | for {
33 | _ <- MDZIO.putAll(d -> k, e -> m0)
34 | v1 <- MDZIO.doWith(e -> l, f -> m, g -> n)(ZIO.foreach(defg)(MDZIO.get))
35 | v2 <- ZIO.foreach(defg)(MDZIO.get)
36 | } yield assert(v1.flatten)(equalTo(klmn)) && assert(v2.flatten)(equalTo(km0))
37 | },
38 | test("setContextMap") {
39 | val (r, s) = ("r", "s")
40 |
41 | for {
42 | _ <- MDZIO.put(r, s)
43 | _ <- MDZIO.setContextMap(Map.empty)
44 | v <- MDZIO.get(r)
45 | } yield assert(v)(isNone)
46 | }
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/scala/com/github/mlangc/slf4zio/api/RawPerfLogTest.scala:
--------------------------------------------------------------------------------
1 | package com.github.mlangc.slf4zio.api
2 |
3 | import com.github.mlangc.slf4zio.LogbackTestAppender
4 | import com.github.mlangc.slf4zio.LogbackTestUtils
5 | import zio.duration2DurationOps
6 | import zio.durationInt
7 | import zio.test._
8 | import zio.ZIO
9 |
10 | object RawPerfLogTest extends ZIOSpecDefault {
11 | def spec = suite("Raw Performance Log")(
12 | test("Simple Examples") {
13 | val spec1 =
14 | LogSpec.onSucceed[String]((d, s) => info"Good: (${d.render}, $s)") ++
15 | LogSpec.onError[Throwable]((d, e) => error"Not good: (${d.render}, $e)") ++
16 | LogSpec.onTermination((d, c) => error"Unexpected: (${d.render}, $c}")
17 |
18 | val spec2 =
19 | spec1.withThreshold(1.hour)
20 |
21 | val ok = "!!OK!!"
22 | val error = "!!ERROR!!"
23 |
24 | for {
25 | logger <- makeLogger(getClass)
26 | _ <- ZIO.attempt(logger.perfLog(ok)(spec1))
27 | _ <- ZIO.attempt(logger.perfLog[String](throw new RuntimeException(error))(spec1)).ignore
28 | _ <- ZIO.attempt(logger.perfLog(ok)(spec2))
29 | _ <- ZIO.attempt(logger.perfLog[String](throw new RuntimeException(error))(spec2)).ignore
30 | evts <- LogbackTestAppender.eventsFor(this.getClass)
31 | } yield assert(evts)(Assertion.hasSize(Assertion.equalTo(2)))
32 | }
33 | ) @@ TestAspect.before(LogbackTestUtils.waitForLogbackInitialization.orDie) @@ TestAspect.withLiveClock
34 | }
35 |
--------------------------------------------------------------------------------