Skip to content
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

Expunge all unsafe logging [resolve conflicts] #7122

Merged
merged 11 commits into from
Jun 2, 2023
32 changes: 8 additions & 24 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ lazy val core = libraryCrossProject("core")
fs2Io.value,
ip4sCore.value,
literally.value,
log4catsCore.value,
munit.value % Test,
scodecBits.value,
vault.value,
Expand All @@ -125,17 +126,11 @@ lazy val core = libraryCrossProject("core")
if (tlIsScala3.value) Seq.empty
else
Seq(
slf4jApi, // residual dependency from macros
scalaReflect(scalaVersion.value) % Provided,
scalaReflect(scalaVersion.value) % Provided
)
},
unusedCompileDependenciesFilter -= moduleFilter("org.scala-lang", "scala-reflect"),
)
.platformsSettings(JVMPlatform, JSPlatform)(
libraryDependencies ++= Seq(
log4s.value
)
)
.platformsSettings(JSPlatform, NativePlatform)(
libraryDependencies ++= Seq(
log4catsNoop.value,
Expand Down Expand Up @@ -185,6 +180,7 @@ lazy val tests = libraryCrossProject("tests")
description := "Tests for core project",
startYear := Some(2013),
libraryDependencies ++= Seq(
log4catsNoop.value,
munitCatsEffect.value,
munitDiscipline.value,
scalacheck.value,
Expand Down Expand Up @@ -234,6 +230,7 @@ lazy val clientTestkit = libraryCrossProject("client-testkit")
description := "Client testkit for building http4s clients",
startYear := Some(2014),
libraryDependencies ++= Seq(
log4catsNoop.value,
munit.value,
munitCatsEffect.value,
),
Expand All @@ -243,6 +240,7 @@ lazy val clientTestkit = libraryCrossProject("client-testkit")
libraryDependencies ++= Seq(
nettyBuffer,
nettyCodecHttp,
log4catsSlf4j,
)
)
.dependsOn(client, theDsl)
Expand Down Expand Up @@ -274,16 +272,10 @@ lazy val emberServer = libraryCrossProject("ember-server")
)
.jvmSettings(
libraryDependencies ++= Seq(
log4catsSlf4j,
javaWebSocket % Test,
jnrUnixSocket % Test, // Necessary for jdk < 16
)
)
.jsSettings(
libraryDependencies ++= Seq(
log4catsNoop.value
)
)
.dependsOn(
emberCore % "compile;test->test",
server % "compile;test->test",
Expand All @@ -298,16 +290,6 @@ lazy val emberClient = libraryCrossProject("ember-client")
keypool.value
),
)
.jvmSettings(
libraryDependencies ++= Seq(
log4catsSlf4j
)
)
.jsSettings(
libraryDependencies ++= Seq(
log4catsNoop.value
)
)
.dependsOn(emberCore % "compile;test->test", client, clientTestkit % Test)

// `dsl` name conflicts with modern SBT
Expand Down Expand Up @@ -414,6 +396,7 @@ lazy val docs = http4sProject("site")
circeGeneric,
circeLiteral,
cryptobits,
log4catsSlf4j,
),
description := "Documentation for http4s",
tlFatalWarningsInCi := false,
Expand Down Expand Up @@ -458,6 +441,7 @@ lazy val examplesDocker = http4sProject("examples-docker")
.settings(
description := "Builds a docker image for a ember-server",
startYear := Some(2017),
libraryDependencies += log4catsSlf4j,
Docker / packageName := "http4s/ember-server",
Docker / maintainer := "http4s",
dockerUpdateLatest := true,
Expand Down Expand Up @@ -564,7 +548,7 @@ def exampleProject(name: String) =
http4sProject(name)
.in(file(name.replace("examples-", "examples/")))
.enablePlugins(NoPublishPlugin)
.settings(libraryDependencies += logbackClassic % Runtime)
.settings(libraryDependencies ++= Seq(log4catsSlf4j, logbackClassic % Runtime))
.dependsOn(examples)

lazy val commonSettings = Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,31 @@ package org.http4s.circe.middleware

import cats.data._
import cats.effect._
import cats.syntax.all._
import io.circe._
import io.circe.syntax._
import org.http4s._
import org.http4s.circe._
import org.http4s.headers.Connection
import org.typelevel.ci._
import org.typelevel.log4cats.LoggerFactory

object JsonDebugErrorHandler {
private[this] val messageFailureLogger =
Platform.loggerFactory.getLoggerFromName(
private[this] def messageFailureLogger[F[_]: LoggerFactory] =
LoggerFactory[F].getLoggerFromName(
"org.http4s.circe.middleware.jsondebugerrorhandler.message-failures"
)
private[this] val serviceErrorLogger =
Platform.loggerFactory.getLoggerFromName(
private[this] def serviceErrorLogger[F[_]: LoggerFactory] =
LoggerFactory[F].getLoggerFromName(
"org.http4s.circe.middleware.jsondebugerrorhandler.service-errors"
)

// Can be parametric on my other PR is merged.
def apply[F[_]: Concurrent, G[_]](
def apply[F[_]: Concurrent: LoggerFactory, G[_]](
service: Kleisli[F, Request[G], Response[G]],
redactWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
): Kleisli[F, Request[G], Response[G]] =
Kleisli { req =>
import cats.syntax.applicative._
import cats.syntax.applicativeError._
implicit def entEnc[M[_]]: EntityEncoder.Pure[JsonErrorHandlerResponse[M]] =
Copy link
Contributor Author

@iRevive iRevive May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to the PR, but it caught my eye.
The entEnc is always used with G type parameter. Technically, we can preallocate it too:

implicit val entityEncoder: EntityEncider.Pure[JsonErrorHandlerResponse[F]] = 
  JsonErrorHandlerResponse.entEnc[M](redactWhen)

On the other hand, since it's defined as def, no error = no allocation. The same is true for loggers.

JsonErrorHandlerResponse.entEnc[M](redactWhen)

Expand All @@ -55,27 +55,28 @@ object JsonDebugErrorHandler {
s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
.getOrElse("<unknown>")}"""
)
.unsafeRunSync()
val firstResp = mf.toHttpResponse[G](req.httpVersion)
Response[G](
status = firstResp.status,
httpVersion = firstResp.httpVersion,
headers = firstResp.headers.redactSensitive(redactWhen),
).withEntity(JsonErrorHandlerResponse[G](req, mf)).pure[F]
.as {
val firstResp = mf.toHttpResponse[G](req.httpVersion)
Response[G](
status = firstResp.status,
httpVersion = firstResp.httpVersion,
headers = firstResp.headers.redactSensitive(redactWhen),
).withEntity(JsonErrorHandlerResponse[G](req, mf))
}
case t =>
serviceErrorLogger
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, 'serviceErrorLogger' is defined as a def. Should we define it as a val right above the Kleisli?

): Kleisli[F, Request[G], Response[G]] = {
  val serviceErrorLogger = LoggerFactory[F].getLoggerFromName(...)
  Kleisli { req =>
  ...
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good point. We can probably do the same with messageFailureLogger?

Btw is there a meaningful difference between the effectful and non-effectful APIs for obtaining a logger?
/~https://github.com/typelevel/log4cats/blob/36bfbb6bd8d89e5f81575f85c78b0f8616de5c0b/core/shared/src/main/scala/org/typelevel/log4cats/LoggerFactoryGen.scala#L21-L26

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.error(t)(
s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
.getOrElse("<unknown>")}"""
)
.unsafeRunSync()
Response[G](
Status.InternalServerError,
req.httpVersion,
Headers(Connection.close),
)
.withEntity(JsonErrorHandlerResponse[G](req, t))
.pure[F]
.as(
Response[G](
Status.InternalServerError,
req.httpVersion,
Headers(Connection.close),
)
.withEntity(JsonErrorHandlerResponse[G](req, t))
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import org.http4s.implicits._
import org.http4s.multipart.Multiparts
import org.http4s.multipart.Part
import org.typelevel.ci._
import org.typelevel.log4cats.LoggerFactory
import org.typelevel.log4cats.noop.NoOpFactory

import java.util.Arrays
import java.util.Locale
Expand All @@ -39,6 +41,9 @@ import scala.concurrent.duration._
private[http4s] abstract class ClientRouteTestBattery(name: String)
extends CatsEffectSuite
with Http4sClientDsl[IO] {

implicit val loggerFactory: LoggerFactory[IO] = NoOpFactory[IO]

val timeout: FiniteDuration = 20.seconds

def clientResource: Resource[IO, Client[IO]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.http4s.client.testkit.scaffold

import cats.effect.Sync
import cats.effect.SyncIO
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled
import io.netty.channel._
Expand All @@ -25,6 +26,7 @@ import io.netty.handler.codec.http.HttpResponseStatus._
import io.netty.handler.codec.http.HttpVersion.HTTP_1_1
import io.netty.handler.codec.http._
import io.netty.util.CharsetUtil
import org.typelevel.log4cats.slf4j.Slf4jLogger

import java.net.URI

Expand All @@ -46,7 +48,7 @@ private[http4s] class HandlersToNettyAdapter private (
fallbackHandler: Handler,
) extends SimpleChannelInboundHandler[HttpObject] {

private val logger = org.http4s.Platform.loggerFactory.getLoggerFromClass(this.getClass)
private val logger = Slf4jLogger.getLogger[SyncIO]

private var currentRequest: HttpRequest = null
private var currentHandler: Handler = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import io.netty.handler.codec.http._
import io.netty.handler.logging.LoggingHandler
import io.netty.handler.ssl.SslHandler
import org.http4s.Uri
import org.typelevel.log4cats.LoggerFactory

import java.net.InetAddress
import java.net.InetSocketAddress
Expand All @@ -57,43 +58,47 @@ private[http4s] class NettyTestServer[F[_]](
}

private[http4s] object NettyTestServer {
private val logger = org.http4s.Platform.loggerFactory.getLoggerFromClass(this.getClass)

def apply[F[_]: Async](
def apply[F[_]: Async: LoggerFactory](
port: Int,
makeHandler: F[ChannelInboundHandler],
sslContext: Option[SSLContext],
dispatcher: Dispatcher[F],
): Resource[F, NettyTestServer[F]] = for {
bossGroup <- nioEventLoopGroup[F]
workerGroup <- nioEventLoopGroup[F]
establishedConnections <- Resource.eval(Ref[F].of(0L))
bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channelFactory(new ChannelFactory[NioServerSocketChannel] {
override def newChannel(): NioServerSocketChannel = new NioServerSocketChannel()
})
.handler(new LoggingHandler())
.childHandler(new ChannelInitializer[NioSocketChannel]() {
def initChannel(ch: NioSocketChannel): Unit = {
logger.trace(s"Accepted new connection from [${ch.remoteAddress()}].").unsafeRunSync()
dispatcher.unsafeRunSync(establishedConnections.update(_ + 1))
sslContext.foreach { sslContext =>
val engine = sslContext.createSSLEngine()
engine.setUseClientMode(false)
ch.pipeline().addLast(new SslHandler(engine))
): Resource[F, NettyTestServer[F]] = {
val logger = LoggerFactory[F].getLogger
for {
bossGroup <- nioEventLoopGroup[F]
workerGroup <- nioEventLoopGroup[F]
establishedConnections <- Resource.eval(Ref[F].of(0L))
bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channelFactory(new ChannelFactory[NioServerSocketChannel] {
override def newChannel(): NioServerSocketChannel = new NioServerSocketChannel()
})
.handler(new LoggingHandler())
.childHandler(new ChannelInitializer[NioSocketChannel]() {
def initChannel(ch: NioSocketChannel): Unit = {
dispatcher.unsafeRunSync(
logger.trace(
s"Accepted new connection from [${ch.remoteAddress()}]."
) *> establishedConnections.update(_ + 1)
)
sslContext.foreach { sslContext =>
val engine = sslContext.createSSLEngine()
engine.setUseClientMode(false)
ch.pipeline().addLast(new SslHandler(engine))
}
ch.pipeline()
.addLast(new HttpRequestDecoder())
.addLast(new HttpResponseEncoder())
.addLast(dispatcher.unsafeRunSync(makeHandler))
()
}
ch.pipeline()
.addLast(new HttpRequestDecoder())
.addLast(new HttpResponseEncoder())
.addLast(dispatcher.unsafeRunSync(makeHandler))
()
}
})
channel <- server[F](bootstrap, port)
localInetSocketAddress = channel.localAddress().asInstanceOf[InetSocketAddress]
localAddress <- Resource.eval(toSocketAddress(localInetSocketAddress).liftTo[F])
} yield new NettyTestServer(establishedConnections, localAddress, secure = sslContext.isDefined)
})
channel <- server[F](bootstrap, port)
localInetSocketAddress = channel.localAddress().asInstanceOf[InetSocketAddress]
localAddress <- Resource.eval(toSocketAddress(localInetSocketAddress).liftTo[F])
} yield new NettyTestServer(establishedConnections, localAddress, secure = sslContext.isDefined)
}

private def nioEventLoopGroup[F[_]](implicit F: Async[F]): Resource[F, NioEventLoopGroup] =
Resource.make(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.comcast.ip4s.SocketAddress
import io.netty.channel.ChannelInboundHandler
import io.netty.handler.codec.http.HttpMethod
import org.http4s.HttpRoutes
import org.typelevel.log4cats.LoggerFactory

import java.security.KeyStore
import java.security.Security
Expand All @@ -39,7 +40,7 @@ private[http4s] class ServerScaffold[F[_]] private (val servers: Vector[TestServ
private[http4s] object ServerScaffold {

// high-level API
def apply[F[_]](num: Int, secure: Boolean, routes: HttpRoutes[F])(implicit
def apply[F[_]: LoggerFactory](num: Int, secure: Boolean, routes: HttpRoutes[F])(implicit
F: Async[F]
): Resource[F, ServerScaffold[F]] =
for {
Expand All @@ -48,14 +49,18 @@ private[http4s] object ServerScaffold {
} yield scaffold

// mid-level API
def apply[F[_]](num: Int, secure: Boolean, handlers: Map[(HttpMethod, String), Handler])(implicit
def apply[F[_]: LoggerFactory](
num: Int,
secure: Boolean,
handlers: Map[(HttpMethod, String), Handler],
)(implicit
F: Async[F]
): Resource[F, ServerScaffold[F]] =
apply[F](num, secure, HandlersToNettyAdapter(handlers))

// low-level API
def apply[F[_]](num: Int, secure: Boolean, makeHandler: F[ChannelInboundHandler])(implicit
F: Async[F]
def apply[F[_]: LoggerFactory](num: Int, secure: Boolean, makeHandler: F[ChannelInboundHandler])(
implicit F: Async[F]
): Resource[F, ServerScaffold[F]] =
for {
dispatcher <- Dispatcher.parallel[F]
Expand Down
Loading