Skip to content

Commit

Permalink
Add documentation for Log and LogOf (#317)
Browse files Browse the repository at this point in the history
* Add documentation for `Log` and `LogOf`.
* Add documentation for MDC-like style of the logging.
* Allow to bypass the recommended practice.
* Update core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala
Co-authored-by: Max Lanin <max.m.lanin@gmail.com>
* Update core/src/main/scala/com/evolutiongaming/catshelper/Log.scala
Co-authored-by: Max Lanin <max.m.lanin@gmail.com>
  • Loading branch information
rtar authored Jan 15, 2025
1 parent 01ee360 commit 62a557d
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
11 changes: 11 additions & 0 deletions core/src/main/scala/com/evolutiongaming/catshelper/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ import org.slf4j.{Logger, MDC}
import scala.annotation.tailrec
import scala.collection.immutable.SortedMap

/** Named/prefixed logger instance.
*
* Use [[LogOf]] to create the new instances of the class.
*
* Avoid passing `Log` instances implicitly. Passing implicit instances around could "leak" a logger
* into an unrelated code, and create a confusion with wrongly attributed log messages.
*
* @see [[LogOf]] for usage examples.
* @see [[https://slf4j.org/api/org/slf4j/Logger.html Logger]] for a typical
* underlying implementation.
*/
trait Log[F[_]] {

@inline def trace(msg: => String): F[Unit] = trace(msg, mdc = Log.Mdc.empty)
Expand Down
104 changes: 104 additions & 0 deletions core/src/main/scala/com/evolutiongaming/catshelper/LogOf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,110 @@ import org.slf4j.{ILoggerFactory, LoggerFactory}

import scala.reflect.ClassTag

/** Factory of [[Log]] instances.
*
* There are, currently, two ways of using the class and it is recommended to
* select one, describe it in project coding guidelines, and stick with it
* to avoid a confusion for the newcomers. Both ways are described below.
*
* =Traditional approach=
*
* The intented usage is to have a single instance of `LogOf` in the
* application and use it to create `Log` instances for each class.
*
* The following could be written somewhere in the application initialization
* code such as [[cats.effect.IOApp]] instance:
* {{{
* implicit val logOf = LogOf.slf4j[F]
* }}}
*
* Then a typical example would be:
* {{{
* object UserService {
*
* def of[F[_]: LogOf]: F[UserService[F]] =
* for {
* log <- LogOf[F].forClass[UserService]
* service = new UserService[F](log)
* } yield service
*
* }
*
* class UserService[F[_]: Monad](log: Log[F]) {
*
* def create(user: User): F[Unit] =
* for {
* _ <- log.info(s"Creating user...")
* _ <- ...
* } yield ()
*
* }
* }}}
*
* The expected output, depending on the logging configuration, would be:
* {{{
* [2025-01-13T12:24:23.0Z] INFO UserService - Creating user...
* }}}
*
* The main advantage of such approach is a minimal boilerplate when passing
* `LogOf` around, and an ability to reuse `LogOf` and `Log` instances,
* decreasing the performance overhead.
*
* =MDC-style approach=
*
* The alternative way to use `LogOf` is to create a child instance each time
* the additional information should be passed to the underlying call, making
* it a poor's man MDC. The additonial information is then to be passed using
* `withField` method on `LogOf` itself.
*
* If `LogOf` is used in such a way, it should not be passed implicitly, to
* avoid accidential mix up of the `LogOf` instances.
*
* For sake of uniformity, if this approach is used, the recommended practice
* is to never pass `Log` instance anywhere at all, creating a new one each
* time it is required. Saying that, it is acceptable to reuse them for
* performance or brevity reasons.
*
* A typical example of such usage could be following:
* {{{
* class HandleService[F[_]: Monad] {
*
* def create(logOf: LogOf[F]): F[String] =
* for {
* log <- logOf.forClass[HandleService]
* _ <- log.info(s"Creating handle...")
* _ <- ...
* } yield ()
*
* }
*
* class UserService[F[_]: Monad](handleService: HandleService[F]) {
*
* def create(user: User, logOf: LogOf[F]): F[Unit] =
* for {
* log <- logOf.forClass[UserService]
* _ <- log.info(s"Creating user...")
* handle <- handleService.create(logOf.withField("user", user.id))
* _ <- ...
* } yield ()
*
* }
*
* }}}
*
* The expected output, depending on the logging configuration, would be:
* {{{
* [2025-01-13T12:24:23.0Z] INFO UserService - Creating user...
* [2025-01-13T12:24:23.0Z] INFO HandleService user=n32fkj43asxa45ak - Creating handle...
* }}}
*
* This approach introduces a slight overhead, because it requires creating
* both `LogOf` and `Log` instances on each call, but allows passing an
* additional context to the called methods.
*
* @see [[https://slf4j.org/api/org/slf4j/LoggerFactory.html LoggerFactory]]
* for a typical underlying implementation.
*/
trait LogOf[F[_]] {

def apply(source: String): F[Log[F]]
Expand Down

0 comments on commit 62a557d

Please sign in to comment.