-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Feature: logging capability #421
Closed
lorandszakacs
wants to merge
15
commits into
typelevel:series/1.x
from
lorandszakacs:feature/logging-capability
Closed
Changes from 12 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
33c5e7f
Add dependency on com.lorandszakacs.enclosure
lorandszakacs ba1c236
Add logging capabilities
lorandszakacs 8c55759
Rename NoopLogging -> NoOpLogging
lorandszakacs b25c559
Implement methods in logging traits in terms for fromName
lorandszakacs 02660d8
Remove variance constraints on LoggingGen*
lorandszakacs 5022e11
Flip Gen <-> Logging to be consistent with CE3 naming
lorandszakacs 45f8bba
Reencode Logging hierarchy starting from GenLogging[F[_], LoggerType]
lorandszakacs 2f3a4f8
Apply scalafmt
lorandszakacs 0ef7a9e
Make Logging back into a type alias, add LoggingF
lorandszakacs 751fe46
Hack build to allow scala-X folders in JS/JVM cross projects
lorandszakacs 0f9e567
Move implicitNotFound test to scala-2 only
lorandszakacs e3b59f0
Improve implicitNotFound messages on Logging and LoggingF
lorandszakacs 895e6bf
Add slf4j.implicits + tweak implicitNotFound messages
lorandszakacs 81422b2
Create NoOpLogging/F/Gen traits + add implicits
lorandszakacs 696f17e
Add missing Slf4jLoggingF trait
lorandszakacs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
core/shared/src/main/scala/org/typelevel/log4cats/loggingCapabilities.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* Copyright 2018 Christopher Davenport | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.typelevel.log4cats | ||
|
||
import com.lorandszakacs.enclosure.Enclosure | ||
|
||
/** | ||
* Logging capability trait, or put crudeley "logger factory". | ||
* | ||
* The recommended way of creating loggers is through this capability trait. | ||
* You instantiate it once in your application (dependent on the specific | ||
* logging backend you use), and pass this around in your application. | ||
* | ||
* This has several advantages: | ||
* - you no longer pass around _very powerful_ `F[_]: Sync` constraints that can do | ||
* almost anything, when you just need logging | ||
* - you are in control of how loggers are created, and you can even add in whatever | ||
* custom functionality you need for your own applications here. e.g. create loggers | ||
* that also send logs to some external providers by giving an implementation to this | ||
* trait. | ||
* | ||
* @tparam G[_] | ||
* the effect type in which the loggers are constructed. | ||
* e.g. make cats.Id if logger creation can be done as a pure computation | ||
*/ | ||
trait GenLogging[G[_], LoggerType] { | ||
def fromName(name: String): G[LoggerType] | ||
|
||
def create(implicit enc: Enclosure): G[LoggerType] = fromName(enc.fullModuleName) | ||
|
||
def fromClass(clazz: Class[_]): G[LoggerType] = | ||
fromName(clazz.getName) //N.B. .getCanonicalName does not exist on scala JS. | ||
} | ||
|
||
object GenLogging { | ||
def apply[G[_], LoggerType](implicit l: GenLogging[G, LoggerType]): GenLogging[G, LoggerType] = | ||
l | ||
} | ||
|
||
object Logging { | ||
def apply[F[_]](implicit l: Logging[F]): Logging[F] = l | ||
} | ||
|
||
object LoggingF { | ||
def apply[F[_]](implicit l: LoggingF[F]): LoggingF[F] = l | ||
} |
123 changes: 123 additions & 0 deletions
123
core/shared/src/main/scala/org/typelevel/log4cats/package.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright 2018 Christopher Davenport | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.typelevel | ||
|
||
package object log4cats { | ||
|
||
/** | ||
* Convenience type for describing Logging where creation of the loggers itself is a pure operation | ||
* | ||
* @tparam F[_] | ||
* the effect type in which logging is done. | ||
*/ | ||
@scala.annotation.implicitNotFound( | ||
""" | ||
Implicit not found for Logging[${F}]. Which is a convenience type alias. | ||
|
||
Keep in mind that: | ||
|
||
a) Logging[${F}] has to be created _explicitely_ once (at least) in your code | ||
|
||
b) for the type Logging, ${F} is _only_ the effect in which logging is done. | ||
``` | ||
Logging[${F}].create.info(message) : ${F}[Unit] | ||
``` | ||
|
||
c) The Logging[${F}] type itself described logging creation as being pure, i.e. in cats.Id | ||
|
||
d) Logging[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. | ||
|
||
The full definition of Logging is: | ||
``` | ||
type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] | ||
``` | ||
|
||
Therefore, your problem might be: | ||
1) you didn't create a Logging[${F}] to begin with, or didn't mark it as implicit | ||
2) you only have a GenLogger[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than cats.Id | ||
3) you do actually have a GenLogger[Id, L], but where L is not SelfAwareStructuredLogger[${F}]. | ||
|
||
If you are unsure how to create a Logging[${F}], then you can to look at the `log4cats-slf4j`, | ||
or `log4cats-noop` modules for concrete implementations. | ||
|
||
Example for slf4j: | ||
``` | ||
import cats.effect.IO | ||
|
||
// we create our Logging[IO] | ||
implicit val logging: Logging[IO] = Slf4jLogging.forSync[IO] | ||
|
||
//we summon our instance, and create our logger | ||
val logger: SelfAwareStructuredLogger[IO] = Logging[IO].create | ||
logger.info("logging in IO!"): IO[Unit] | ||
``` | ||
""" | ||
) | ||
type Logging[F[_]] = GenLogging[cats.Id, SelfAwareStructuredLogger[F]] | ||
|
||
/** | ||
* Convenience type | ||
* | ||
* @tparam F[_] | ||
* the effect type of both logger creation, and logging. | ||
*/ | ||
@scala.annotation.implicitNotFound( | ||
""" | ||
Implicit not found for LoggingF[${F}]. Which is a convenience type alias. | ||
|
||
Keep in mind that: | ||
|
||
a) LoggingF[${F}] has to be created _explicitely_ once (at least) in your code | ||
|
||
b) for the type LoggingF, ${F} is _both_ the effect in which logger creation is done | ||
_and_ the effect in which logging is done | ||
``` | ||
val loggerF: ${F}[SelfAwareStructuredLogger] = Logging[${F}].create | ||
loggerF.flatMap{logger => logger.info("the result of the .info is in F"): ${F}[Unit] }: ${F}[Unit] | ||
``` | ||
|
||
c) LoggingF[${F}] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[${F}]. | ||
|
||
The full definition of Logging is: | ||
``` | ||
type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] | ||
``` | ||
Therefore, your problem might be: | ||
1) you didn't create a LoggingF[${F}] to begin with, or didn't mark it as implicit | ||
2) you only have a GenLoggerF[G[_], SelfAwareStructuredLogger[${F}]] for some G[_] type other than type ${F} | ||
3) you do actually have a GenLogger[${F}, L], but where L is not SelfAwareStructuredLogger[${F}]. | ||
|
||
If you are unsure how to create a LoggingF[${F}], then you can to look at the | ||
`log4cats-slf4j` module for concrete implementations. | ||
|
||
Example for slf4j: | ||
``` | ||
import cats.effect.IO | ||
|
||
// we use IO for both logger creation effect, and logging effect. Otherwise it wouldn't be a LoggingF | ||
implicit val loggingF: LoggingF[IO] = Slf4jGenLogging.forSync[IO, IO] | ||
|
||
//we summon our instance, and create our logger | ||
val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create | ||
|
||
loggerIO.flatMap{logger => logger.info("logging in IO"): IO[Unit]}: IO[Unit] | ||
``` | ||
""" | ||
) | ||
type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] | ||
|
||
} |
56 changes: 56 additions & 0 deletions
56
core/shared/src/test/scala-2/org/typelevel/log4cats/LoggingImplicitNotFoundTests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2018 Christopher Davenport | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.typelevel.log4cats | ||
|
||
// TODO: figure out why the implicitNotFound annotation does not work on Scala 3. | ||
class LoggingImplicitNotFoundTests extends munit.FunSuite { | ||
|
||
test("receive proper implicitNotFound error message on Logging") { | ||
val errors = compileErrors( | ||
"import cats.effect.IO;import org.typelevel.log4cats.Logging;Logging[IO]" | ||
) | ||
assert( | ||
errors.contains( | ||
"you didn't create a Logging[cats.effect.IO] to begin with, or didn't mark it as implicit" | ||
), | ||
clue = s"""|Actual compiler error was: | ||
| | ||
|$errors | ||
| | ||
| | ||
|""".stripMargin | ||
) | ||
} | ||
|
||
test("receive proper implicitNotFound error message on LoggingF") { | ||
val errors = compileErrors( | ||
"import cats.effect.IO;import org.typelevel.log4cats.LoggingF;LoggingF[IO]" | ||
) | ||
assert( | ||
errors.contains( | ||
"you didn't create a LoggingF[cats.effect.IO] to begin with, or didn't mark it as implicit" | ||
), | ||
clue = s"""|Actual compiler error was: | ||
| | ||
|$errors | ||
| | ||
| | ||
|""".stripMargin | ||
) | ||
} | ||
|
||
} |
31 changes: 31 additions & 0 deletions
31
noop/shared/src/main/scala/org/typelevel/log4cats/noop/NoOpLogging.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright 2018 Christopher Davenport | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.typelevel.log4cats.noop | ||
|
||
import cats.Applicative | ||
import org.typelevel.log4cats._ | ||
|
||
trait NoOpLogging[F[_]] extends Logging[F] { | ||
override def fromName(name: String): SelfAwareStructuredLogger[F] | ||
} | ||
|
||
object NoOpLogging { | ||
def apply[F[_]](implicit logging: NoOpLogging[F]): NoOpLogging[F] = logging | ||
def create[F[_]: Applicative] = new NoOpLogging[F] { | ||
override def fromName(name: String): SelfAwareStructuredLogger[F] = NoOpLogger[F] | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
slf4j/src/main/scala/org/typelevel/log4cats/slf4j/slf4jLogging.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
/* | ||
* Copyright 2018 Christopher Davenport | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package org.typelevel.log4cats.slf4j | ||
|
||
import org.slf4j.{Logger => JLogger} | ||
import cats.effect.Sync | ||
import org.typelevel.log4cats._ | ||
import cats.{Applicative, Defer, Id} | ||
|
||
// format: off | ||
trait Slf4jGenLogging[G[_], F[_]] extends GenLogging[G, SelfAwareStructuredLogger[F]] { | ||
protected implicit def syncF: Sync[F] | ||
protected def lift(f: => SelfAwareStructuredLogger[F]): G[SelfAwareStructuredLogger[F]] | ||
|
||
override def fromName(name: String): G[SelfAwareStructuredLogger[F]] = lift(Slf4jLogger.getLoggerFromName[F](name)) | ||
def fromSlf4j(logger: JLogger): G[SelfAwareStructuredLogger[F]] = lift(Slf4jLogger.getLoggerFromSlf4j[F](logger)) | ||
} | ||
// format: off | ||
|
||
object Slf4jGenLogging { | ||
def apply[G[_], F[_]](implicit logging: Slf4jGenLogging[G, F]): Slf4jGenLogging[G, F] = logging | ||
|
||
// format: off | ||
def forSync[G[_], F[_]](implicit F: Sync[F], G: Applicative[G], GD: Defer[G]): Slf4jGenLogging[G, F] = | ||
new Slf4jGenLogging[G, F] { | ||
override protected val syncF: Sync[F] = F | ||
override protected def lift(l: => SelfAwareStructuredLogger[F]): G[SelfAwareStructuredLogger[F]] = GD.defer(G.pure(l)) | ||
} | ||
// format: on | ||
} | ||
|
||
trait Slf4jLogging[F[_]] extends Slf4jGenLogging[Id, F] | ||
|
||
object Slf4jLogging { | ||
def apply[F[_]](implicit logging: Slf4jLogging[F]): Slf4jLogging[F] = logging | ||
|
||
// format: off | ||
def forSync[F[_]](implicit F: Sync[F]): Slf4jLogging[F] = new Slf4jLogging[F] { | ||
override protected def lift(f: => SelfAwareStructuredLogger[F]): Id[SelfAwareStructuredLogger[F]] = f | ||
override protected def syncF: Sync[F] = F | ||
} | ||
// format: on | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can/should we support users doing
import org.typelevel.log4s.Slf4jLogging.implicits._
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer not to 😅
It would carry something like
implicit def genericSlf4jLogging[F[_]: Sync]: Slf4jLogging[F, F]
? And others, provided they don't wind up conflicting with each other under the same import.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, why not? Shouldn't conflict as long as types are different, so it could be both
Logging
andLoggingF
under one object. I've no objections to not having it as an experienced Scala-FP-CE-TF developer but not everybody's like that, and a page's worth of text forimplicitNotFound
to describe how to refactor all your code is probably going to alienate more than help.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good points. But I am still in favor of a page worth of
implicitNotFound
because adopters of Logging as capability are probably experienced Scala-FP-CE-TF devs, since it's an entirely new feature.I can also move the practical examples first, with the alternative of a-la-carte
implicits
import as well, and then move the general points about what might be wrong at the bottom.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that sounds better. Even "experienced Scala-FP-CE-TF devs" prefer a simple entry point (cue: CE2 Timer/ContextShift/Concurrent vs CE3 import unsafe), and for people just trying log4cats out (there's always a lot of people swayed by TF but not yet with enough experience to handle all the issues) that would provide a straightforward experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the PR, rephrased the
implicitNotFound
message so it doesn't seem like we're giving refactoring advice. But now it's somehow even longer 😅