Skip to content

Commit

Permalink
Merge pull request #7 from llfrometa89/ref-logger-test
Browse files Browse the repository at this point in the history
Update docs | Add LazyLogging | Add basic example with LazyLogging
  • Loading branch information
llfrometa89 authored Feb 14, 2019
2 parents 990c962 + 47e5a06 commit 799c118
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 67 deletions.
82 changes: 68 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,39 @@
# logger4s
# Logger4s

logger4s is purely functional Logger library for Scala. It's easy to use and does not force a specific target context. You can run your computations in any type `F[_]` that has an instance of cats-effect's `Sync[F]`.
Logger4s is a wrapping [SLF4J](https://www.slf4j.org/) library purely functional for Scala.
It's easy to use and does not force a specific target context.
You can run your computations in any type `F[_]` that has an instance of cats-effect's `Sync[F]`.

## Installation
## Prerequisites ##

Add the following to your `build.sbt`.
* Java 6 or higher
* Scala 2.11 or 2.12
* Logging backend compatible with [SLF4J](https://www.slf4j.org/)

A compatible logging backend is [Logback](http://logback.qos.ch), add it to your sbt build definition:

```scala
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.3"
```

## Getting Logger4s ##

Logger4s is published to Sonatype OSS and Maven Central:

- Group id / organization: *org.pure4s*
- Artifact id / name: *logger4s*
- Latest version is 0.2.0

Usage with SBT, adding a dependency to the latest version of Logger4s to your `build.sbt`:

```scala
// For Scala 2.11, or 2.12
libraryDependencies += "org.pure4s" %% "logger4s" % "0.1.1"
libraryDependencies += "org.pure4s" %% "logger4s" % "0.2.0"
```
## Usage

Example 1:
## Using Logger4s ##

Basic example:
```scala
import cats.effect.{IO, Sync}
import cats.implicits._
Expand All @@ -31,18 +52,53 @@ object BasicExampleMain extends App {

val service = new UserService[IO]
service.findByEmail("example@example.com").unsafeRunSync()
//2019-01-27 21:40:40.557 [UserService][INFO ] Hello word, functional logger (example@example.com)
//2019-01-27 21:40:40.557 [UserService][INFO] - Hello word, functional logger (example@example.com)
}
```

Basic example with LazyLogging
```scala
import cats.effect.{IO, Sync}
import cats.implicits._
import org.pure4s.logger4s.{LazyLogging, Logger}
import org.pure4s.logger4s.Logger._

case class Session(email: String, token: String)

class AuthService[F[_] : Sync] extends LazyLogging {

def login(email: String, password: String): F[Session] = {

def recoveryStrategy: PartialFunction[Throwable, F[Session]] = {
case error =>
Logger[F].error(s"Error creating session", error) *> Sync[F].raiseError(error)
}

val computation = for {
_ <- Logger[F].info(s"Login with email = $email and password = $password")
session <- Session(email, "token").pure[F]
_ <- Logger[F].info(s"Success login with session = $session")
} yield session

computation recoverWith recoveryStrategy
}
}

object BasicLazyLoggingExampleMain extends App {
val service = new AuthService[IO]
service.login("example@example.com","123").unsafeRunSync()
}
```

Example 2:
Complex example:
```scala
import cats.Show
import cats.effect.{IO, Sync}
import cats.implicits._
import org.json4s.{Formats, NoTypeHints}
import org.json4s.native.Serialization
import org.pure4s.logger4s.Logger
import org.pure4s.logger4s.{LazyLogging, Logger}
import org.pure4s.logger4s.Logger._
import org.json4s.native.Serialization.write

case class Client(email: String)
Expand All @@ -54,7 +110,7 @@ object Client {
}
}

class ClientService[F[_] : Sync : Logger] {
class ClientService[F[_] : Sync] extends LazyLogging{
import Client._

def findByEmail(email: String): F[Option[Client]] = {
Expand All @@ -64,11 +120,9 @@ class ClientService[F[_] : Sync : Logger] {
}

object ComplexExampleMain extends App {
implicit val instance: Logger[IO] = Logger.instance[IO](classOf[ClientService[IO]])

val service = new ClientService[IO]
service.findByEmail("example@example.com").unsafeRunSync()
//2019-01-27 21:25:26.150 [ClientService][INFO ] {"email":"example@example.com"}
//2019-01-27 21:25:26.150 [ClientService][INFO] - {"email":"example@example.com"}
}
```

Expand Down
32 changes: 10 additions & 22 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import microsites._
import ReleaseTransformations._
import sbtcrossproject.{crossProject, CrossType}

inThisBuild(
Expand Down Expand Up @@ -28,6 +26,7 @@ lazy val V = new {
val loggingScalaVersion = "3.5.0"
val logbackClassicVersion = "1.2.3"
val json4sVersion = "3.6.4"
val mockitoVersion = "1.10.19"
}

val noPublishSettings = Seq(
Expand All @@ -51,16 +50,8 @@ val commonDependencies = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % V.catsVersion,
"org.typelevel" %% "cats-effect" % V.catsEffectVersion,
"org.scalatest" %% "scalatest" % V.scalaTestVersion % Test
)
)

val compilerPlugins = Seq(
libraryDependencies ++= Seq(
compilerPlugin(
"org.scalamacros" %% "paradise" % V.macroParadiseVersion cross CrossVersion.full),
compilerPlugin(
"org.spire-math" %% "kind-projector" % V.kindProjectorVersion)
"org.scalatest" %% "scalatest" % V.scalaTestVersion % Test,
"org.mockito" % "mockito-all" % V.mockitoVersion % Test
)
)

Expand All @@ -77,24 +68,21 @@ lazy val core = crossProject(JVMPlatform)
.settings(moduleName := "logger4s")
.settings(buildSettings)
.settings(commonDependencies)
.settings(compilerPlugins)
.jvmSettings(
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % V.loggingScalaVersion,
"ch.qos.logback" % "logback-classic" % V.logbackClassicVersion
))
.jvmSettings(libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % V.logbackClassicVersion
))

lazy val coreJVM = core.jvm

lazy val example = project
.in(file("example"))
.settings(buildSettings)
.settings(noPublishSettings)
.settings(compilerPlugins)
.dependsOn(coreJVM)
.settings(libraryDependencies ++= Seq(
"org.json4s" %% "json4s-native" % V.json4sVersion
))
.settings(
libraryDependencies ++= Seq(
"org.json4s" %% "json4s-native" % V.json4sVersion
))

addCommandAlias(
"validateScalafmt",
Expand Down
13 changes: 13 additions & 0 deletions core/shared/src/main/scala/org/pure4s/logger4s/GLogger.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.pure4s.logger4s

import cats.Show

trait GLogger[F[_]] {

def error[A: Show](msg: A): F[Unit]
def error[A: Show](msg: A, err: Throwable): F[Unit]
def warn[A: Show](msg: A): F[Unit]
def info[A: Show](msg: A): F[Unit]
def debug[A: Show](msg: A): F[Unit]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.pure4s.logger4s

import org.slf4j
import org.slf4j.LoggerFactory

trait LazyLogging {
implicit lazy val logger: slf4j.Logger = LoggerFactory.getLogger(getClass.getName)
}
46 changes: 29 additions & 17 deletions core/shared/src/main/scala/org/pure4s/logger4s/Logger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,41 @@ import org.slf4j
import org.slf4j.LoggerFactory
import cats.implicits._

trait Logger[F[_]] {
def error[A: Show](msg: A): F[Unit]
def error[A: Show](msg: A, err: Throwable): F[Unit]
def warn[A: Show](msg: A): F[Unit]
def info[A: Show](msg: A): F[Unit]
def debug[A: Show](msg: A): F[Unit]
}
trait Logger[F[_]] extends SLogger[F] with GLogger[F]

object Logger {

implicit val showString = new Show[String] {
override def show(t: String): String = t.show
def apply[F[_]](implicit F: Logger[F]): Logger[F] = F

def instance[F[_] : Sync](clazz: Class[_])
(implicit logger: slf4j.Logger = LoggerFactory.getLogger(clazz)): Logger[F] = new Logger[F] {

def error(msg: String): F[Unit] = Sync[F].delay(logger.error(msg))
def error(msg: String, err: Throwable): F[Unit] = Sync[F].delay(logger.error(msg, err))
def warn(msg: String): F[Unit] = Sync[F].delay(logger.warn(msg))
def info(msg: String): F[Unit] = Sync[F].delay(logger.info(msg))
def debug(msg: String): F[Unit] = Sync[F].delay(logger.debug(msg))

def error[A: Show](msg: A): F[Unit] = error(msg.show)
def error[A: Show](msg: A, err: Throwable): F[Unit] = error(msg.show, err)
def warn[A: Show](msg: A): F[Unit] = warn(msg.show)
def info[A: Show](msg: A): F[Unit] = info(msg.show)
def debug[A: Show](msg: A): F[Unit] = debug(msg.show)

}

def apply[F[_]](implicit F: Logger[F]): Logger[F] = F
implicit def instance[F[_] : Sync](implicit logger: slf4j.Logger): Logger[F] = new Logger[F] {

def instance[F[_] : Sync](clazz: Class[_]): Logger[F] = new Logger[F] {
val log: slf4j.Logger = LoggerFactory.getLogger(clazz)
def error(msg: String): F[Unit] = Sync[F].delay(logger.error(msg))
def error(msg: String, err: Throwable): F[Unit] = Sync[F].delay(logger.error(msg, err))
def warn(msg: String): F[Unit] = Sync[F].delay(logger.warn(msg))
def info(msg: String): F[Unit] = Sync[F].delay(logger.info(msg))
def debug(msg: String): F[Unit] = Sync[F].delay(logger.debug(msg))

override def error[A: Show](msg: A): F[Unit] = Sync[F].delay(log.error(msg.show))
override def error[A: Show](msg: A, err: Throwable): F[Unit] = Sync[F].delay(log.error(msg.show))
override def warn[A: Show](msg: A): F[Unit] = Sync[F].delay(log.warn(msg.show))
override def info[A: Show](msg: A): F[Unit] = Sync[F].delay(log.info(msg.show))
override def debug[A: Show](msg: A): F[Unit] = Sync[F].delay(log.debug(msg.show))
def error[A: Show](msg: A): F[Unit] = error(msg.show)
def error[A: Show](msg: A, err: Throwable): F[Unit] = error(msg.show, err)
def warn[A: Show](msg: A): F[Unit] = warn(msg.show)
def info[A: Show](msg: A): F[Unit] = info(msg.show)
def debug[A: Show](msg: A): F[Unit] = debug(msg.show)
}
}
11 changes: 11 additions & 0 deletions core/shared/src/main/scala/org/pure4s/logger4s/SLogger.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.pure4s.logger4s

trait SLogger[F[_]] {

def error(msg: String): F[Unit]
def error(msg: String, err: Throwable): F[Unit]
def warn(msg: String): F[Unit]
def info(msg: String): F[Unit]
def debug(msg: String): F[Unit]

}
84 changes: 78 additions & 6 deletions core/shared/src/test/scala/org/pure4s/logger4s/LoggerSpec.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,87 @@
package org.pure4s.logger4s
import org.pure4s.logger4s.Logger.showString
import cats.effect.IO
import org.scalatest._

class LoggerSpec extends FunSpec with Matchers {
import cats.Show
import cats.implicits._
import cats.effect.IO
import org.mockito.Mockito._
import org.scalatest.{Matchers, _}
import org.scalatest.mockito.MockitoSugar
import org.slf4j.{Logger => Underlying}

implicit val instance: Logger[IO] = Logger.instance[IO](classOf[LoggerSpec])
class LoggerSpec extends FunSpec with Matchers with MockitoSugar {

describe("Logger[F[_]].info") {
val f = fixture(_.isInfoEnabled, isEnabled = true)
import f._
it(s"Success expected info message") {
Logger[IO].info("Hello Word Info").unsafeRunSync()
Logger[IO].info(msg).unsafeRunSync()
verify(underlying).info(msg)
}
it(s"Success expected info generic message") {
Logger[IO].info(msgObj).unsafeRunSync()
verify(underlying).info(msgObj.show)
}
}

describe("Logger[F[_]].warn") {
val f = fixture(_.isWarnEnabled, isEnabled = true)
import f._
it(s"Success expected warn message") {
Logger[IO].warn(msg).unsafeRunSync()
verify(underlying).warn(msg)
}
it(s"Success expected warn generic message") {
Logger[IO].warn(msgObj).unsafeRunSync()
verify(underlying).warn(msgObj.show)
}
}

describe("Logger[F[_]].debug") {
val f = fixture(_.isDebugEnabled, isEnabled = true)
import f._
it(s"Success expected debug message") {
Logger[IO].debug(msg).unsafeRunSync()
verify(underlying).debug(msg)
}
it(s"Success expected debug generic message") {
Logger[IO].debug(msgObj).unsafeRunSync()
verify(underlying).debug(msgObj.show)
}
}

describe("Logger[F[_]].error") {
val f = fixture(_.isErrorEnabled, isEnabled = true)
import f._
lazy val exception = new Exception
it(s"Success expected error message") {
Logger[IO].error(msg).unsafeRunSync()
verify(underlying).error(msg)
}
it(s"Success expected error message with Throwable") {
Logger[IO].error(msg, exception).unsafeRunSync()
verify(underlying).error(msg, exception)
}
it(s"Success expected error generic message") {
Logger[IO].error(msgObj).unsafeRunSync()
verify(underlying).error(msgObj.show)
}
it(s"Success expected error generic message with Throwable") {
Logger[IO].error(msgObj, exception).unsafeRunSync()
verify(underlying).error(msgObj.show, exception)
}
}

def fixture(p: Underlying => Boolean, isEnabled: Boolean) =
new {

val msg = "msg"
case class Msg(value: String)
implicit val showMsg = new Show[Msg] { def show(t: Msg): String = t.toString }
val msgObj = Msg(msg)

implicit val underlying: Underlying = mock[org.slf4j.Logger]
implicit val logger: Logger[IO] = Logger.instance[IO](classOf[LoggerSpec])

when(p(underlying)).thenReturn(isEnabled)
}
}
Loading

0 comments on commit 799c118

Please sign in to comment.