-
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
Changes from all commits
33c5e7f
ba1c236
8c55759
b25c559
02660d8
5022e11
45f8bba
2f3a4f8
0ef7a9e
751fe46
0f9e567
e3b59f0
895e6bf
81422b2
696f17e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
/* | ||
* 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. | ||
|
||
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. | ||
|
||
Quickest fix might be to import the implicits: | ||
|
||
``` | ||
// assumes dependency on log4cats-slf4j module | ||
import org.typelevel.log4cats._ | ||
import org.typelevel.log4cats.slf4j.implicits._ | ||
val logger: SelfAwareStructuredLogger[IO] = Logging[IO].create | ||
// or: | ||
def anyFSyncLogger[F[_]: Sync]: SelfAwareStructuredLogger[F] = Logging[F].create | ||
``` | ||
|
||
Alternatively, a mutually exclusive solution, is to explicitely create your own Logging[F] instances | ||
and pass them around implicitely: | ||
|
||
``` | ||
import cats.effect.IO | ||
import org.typelevel.log4cats._ | ||
import org.typelevel.log4cats.slf4j._ | ||
|
||
// 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] | ||
|
||
def useLogging[F[_]: Logging] = Logging[F].create.info("yay! effect polymorphic code") | ||
|
||
useLogging[IO] | ||
``` | ||
|
||
If neither of those things work, keep in mind that: | ||
a) for the type Logging, ${F} is _only_ the effect in which logging is done. | ||
``` | ||
Logging[${F}].create.info(message) : ${F}[Unit] | ||
``` | ||
|
||
b) The Logging[${F}] type itself described logging creation as being pure, i.e. in cats.Id | ||
|
||
c) 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 `import org.typelevel.log4cats.slf4j.implicits._` AND created your own instance. Do one or the either. | ||
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}]. | ||
""" | ||
) | ||
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. | ||
|
||
If you are unsure how to create a LoggingF[${F}], then you can to look at the | ||
`log4cats-slf4j` module for concrete implementations. | ||
|
||
Quickest fix might be to import the implicits: | ||
|
||
``` | ||
// assumes dependency on log4cats-slf4j module | ||
import org.typelevel.log4cats._ | ||
import org.typelevel.log4cats.slf4j.implicits._ | ||
import cats.effect.{Sync, IO} | ||
|
||
//we summon our instance, and create our logger | ||
val loggerIO: IO[SelfAwareStructuredLogger[IO]] = LoggingF[IO].create | ||
|
||
def anyFSyncLogger[F[_]: Sync]: F[SelfAwareStructuredLogger[F]] = LoggingF[F].create | ||
``` | ||
|
||
Alternatively, a mutually exclusive solution, is to explicitely create your own LoggingF[F] instances | ||
and pass them around implicitely: | ||
|
||
``` | ||
// assumes dependency on log4cats-slf4j module | ||
import org.typelevel.log4cats._ | ||
import org.typelevel.log4cats.slf4j._ | ||
import cats.effect.{Sync, 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] | ||
|
||
def useLogging[F[_]: LoggingF] = LoggingF[F].create.flatMap{logger => logger.info("yay! effect polymorphic code")} | ||
|
||
useLogging[IO] | ||
``` | ||
|
||
If neither of those things work, keep in mind that: | ||
a) 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] | ||
``` | ||
|
||
b) 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 `import org.typelevel.log4cats.slf4j.implicits._` AND created your own instance. Do one or the either. | ||
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}]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can/should we support users doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I would prefer not to 😅 It would carry something like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 I can also move the practical examples first, with the alternative of a-la-carte There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Updated the PR, rephrased the |
||
""" | ||
) | ||
type LoggingF[F[_]] = GenLogging[F, SelfAwareStructuredLogger[F]] | ||
|
||
} |
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( | ||
"Logging[cats.effect.IO] is defined as creating _only_ loggers of type SelfAwareStructuredLogger[cats.effect.IO]" | ||
), | ||
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( | ||
"val loggerF: cats.effect.IO[SelfAwareStructuredLogger] = Logging[cats.effect.IO].create" | ||
), | ||
clue = s"""|Actual compiler error was: | ||
| | ||
|$errors | ||
| | ||
| | ||
|""".stripMargin | ||
) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/* | ||
* 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 org.typelevel.log4cats.noop.internal.NoOpLoggingInstances | ||
|
||
object implicits extends NoOpLoggingInstances |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* 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.internal | ||
|
||
import cats.Applicative | ||
import org.typelevel.log4cats.noop._ | ||
|
||
trait NoOpLoggingInstances { | ||
implicit def log4catsSummonNoOpGenLogging[G[_]: Applicative, F[_]: Applicative]: NoOpLoggingGen[ | ||
G, | ||
F | ||
] = NoOpLoggingGen[G, F] | ||
} |
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.
Just passing by here... Is this wall of text really necessary? I imagine that in most cases this warning would be caused by missing imports. I feel like the rest of this should be moved to documentation, with the warning itself stripped to something like this:
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.
That's probably a better approach 👍 will move the message once I put docs updates in the PR.