Repository: http4s/http4s
Pull request #6664 (2022-09-14 18:02:18)

Merge `series/0.23` into `main`
There were a couple of files affected by merge conflicts. If I'm not wrong, these all changes belong to @armanbilge work. It'd be much appreciated if you'd reviewed this. 🙇🏻‍♂️ 


Patch:
diff --git a/build.sbt b/build.sbt
index 819b44161b..4a76d8759f 100644
--- a/build.sbt
+++ b/build.sbt
@@ -116,16 +116,20 @@ lazy val core = libraryCrossProject("core")
     unusedCompileDependenciesFilter -= moduleFilter("org.scala-lang", "scala-reflect"),
   )
   .jvmSettings(
+    libraryDependencies ++= {
+      Seq(log4catsSlf4j)
+    },
     libraryDependencies ++= {
       if (tlIsScala3.value) Seq.empty
       else
         Seq(
           slf4jApi // residual dependency from macros
         )
-    }
+    },
   )
   .jsSettings(
     libraryDependencies ++= Seq(
+      log4catsNoop.value,
       scalaJavaLocalesEnUS.value,
       scalaJavaTime.value,
     )
diff --git a/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala b/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
index 36f01ca7ce..2e67956670 100644
--- a/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
+++ b/circe/src/main/scala/org/http4s/circe/middleware/JsonDebugErrorHandler.scala
@@ -27,9 +27,13 @@ import org.typelevel.ci._
 
 object JsonDebugErrorHandler {
   private[this] val messageFailureLogger =
-    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.message-failures")
+    Platform.loggerFactory.getLoggerFromName(
+      "org.http4s.circe.middleware.jsondebugerrorhandler.message-failures"
+    )
   private[this] val serviceErrorLogger =
-    org.log4s.getLogger("org.http4s.circe.middleware.jsondebugerrorhandler.service-errors")
+    Platform.loggerFactory.getLoggerFromName(
+      "org.http4s.circe.middleware.jsondebugerrorhandler.service-errors"
+    )
 
   // Can be parametric on my other PR is merged.
   def apply[F[_]: Concurrent, G[_]](
@@ -46,10 +50,12 @@ object JsonDebugErrorHandler {
         .run(req)
         .handleErrorWith {
           case mf: MessageFailure =>
-            messageFailureLogger.debug(mf)(
-              s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
-                  .getOrElse("<unknown>")}"""
-            )
+            messageFailureLogger
+              .debug(mf)(
+                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,
@@ -57,10 +63,12 @@ object JsonDebugErrorHandler {
               headers = firstResp.headers.redactSensitive(redactWhen),
             ).withEntity(JsonErrorHandlerResponse[G](req, mf)).pure[F]
           case t =>
-            serviceErrorLogger.error(t)(
-              s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
-                  .getOrElse("<unknown>")}"""
-            )
+            serviceErrorLogger
+              .error(t)(
+                s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
+                    .getOrElse("<unknown>")}"""
+              )
+              .unsafeRunSync()
             Response[G](
               Status.InternalServerError,
               req.httpVersion,
diff --git a/client/shared/src/main/scala/org/http4s/client/Client.scala b/client/shared/src/main/scala/org/http4s/client/Client.scala
index e80811985f..51868c605a 100644
--- a/client/shared/src/main/scala/org/http4s/client/Client.scala
+++ b/client/shared/src/main/scala/org/http4s/client/Client.scala
@@ -79,6 +79,7 @@ trait Client[F[_]] {
       d: EntityDecoder[F, A]
   ): F[A]
 
+  @deprecated("Use req.flatMap(expect(_))", "0.23.16")
   def expect[A](req: F[Request[F]])(implicit d: EntityDecoder[F, A]): F[A]
 
   def expectOr[A](uri: Uri)(onError: Response[F] => F[Throwable])(implicit
@@ -117,12 +118,14 @@ trait Client[F[_]] {
     * The underlying HTTP connection is released at the completion of the
     * decoding.
     */
+  @deprecated("Use req.flatMap(fetchAs(_))", "0.23.16")
   def fetchAs[A](req: F[Request[F]])(implicit d: EntityDecoder[F, A]): F[A]
 
   /** Submits a request and returns the response status */
   def status(req: Request[F]): F[Status]
 
   /** Submits a request and returns the response status */
+  @deprecated("Use req.flatMap(status(_))", "0.23.16")
   def status(req: F[Request[F]]): F[Status]
 
   /** Submits a GET request to the URI and returns the response status */
@@ -139,6 +142,7 @@ trait Client[F[_]] {
   /** Submits a request and returns true if and only if the response status is
     * successful
     */
+  @deprecated("Use req.flatMap(successful(_))", "0.23.16")
   def successful(req: F[Request[F]]): F[Boolean]
 
   /** Submits a GET request, and provides a callback to process the response.
diff --git a/client/shared/src/main/scala/org/http4s/client/middleware/Logger.scala b/client/shared/src/main/scala/org/http4s/client/middleware/Logger.scala
index 995de4ab55..862bfa7bf2 100644
--- a/client/shared/src/main/scala/org/http4s/client/middleware/Logger.scala
+++ b/client/shared/src/main/scala/org/http4s/client/middleware/Logger.scala
@@ -25,10 +25,13 @@ import org.typelevel.ci.CIString
 /** Simple Middleware for Logging All Requests and Responses
   */
 object Logger {
+  def defaultRedactHeadersWhen(name: CIString): Boolean =
+    Headers.SensitiveHeaders.contains(name) || name.toString.toLowerCase.contains("token")
+
   def apply[F[_]: Async](
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(client: Client[F]): Client[F] =
     ResponseLogger.apply(logHeaders, logBody, redactHeadersWhen, logAction)(
@@ -40,7 +43,7 @@ object Logger {
   def logBodyText[F[_]: Async](
       logHeaders: Boolean,
       logBody: Stream[F, Byte] => Option[F[String]],
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(client: Client[F]): Client[F] =
     ResponseLogger.logBodyText(logHeaders, logBody, redactHeadersWhen, logAction)(
@@ -52,7 +55,7 @@ object Logger {
   def logMessage[F[_]](message: Message[F])(
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
   )(log: String => F[Unit])(implicit F: Async[F]): F[Unit] =
     org.http4s.internal.Logger
       .logMessage(message)(logHeaders, logBody, redactHeadersWhen)(log)
@@ -60,7 +63,7 @@ object Logger {
   def colored[F[_]: Async](
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       requestColor: String = RequestLogger.defaultRequestColor,
       responseColor: Response[F] => String = ResponseLogger.defaultResponseColor[F] _,
       logAction: Option[String => F[Unit]] = None,
diff --git a/client/shared/src/main/scala/org/http4s/client/middleware/RequestLogger.scala b/client/shared/src/main/scala/org/http4s/client/middleware/RequestLogger.scala
index d50676ee8f..b97ca771d8 100644
--- a/client/shared/src/main/scala/org/http4s/client/middleware/RequestLogger.scala
+++ b/client/shared/src/main/scala/org/http4s/client/middleware/RequestLogger.scala
@@ -22,15 +22,14 @@ import cats.effect._
 import cats.syntax.all._
 import fs2._
 import org.http4s.internal.{Logger => InternalLogger}
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 
 /** Simple Middleware for Logging Requests As They Are Processed
   */
 object RequestLogger {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
-  private def defaultLogAction[F[_]: Sync](s: String): F[Unit] = Sync[F].delay(logger.info(s))
+  private def defaultLogAction[F[_]: Sync](s: String): F[Unit] = logger.info(s).to[F]
 
   def apply[F[_]: Async](
       logHeaders: Boolean,
@@ -88,7 +87,7 @@ object RequestLogger {
                   .ifM(
                     F.unit,
                     logMessage(req.withBodyStream(newBody)).handleErrorWith { case t =>
-                      F.delay(logger.error(t)("Error logging request body"))
+                      logger.error(t)("Error logging request body").to[F]
                     },
                   )
 
@@ -110,7 +109,7 @@ object RequestLogger {
             case Entity.Strict(_) | Entity.Empty =>
               logMessage(req)
                 .handleErrorWith { t =>
-                  F.delay(logger.error(t)("Error logging request body"))
+                  logger.error(t)("Error logging request body").to[F]
                 }
                 .as(client.run(req))
           }
diff --git a/client/shared/src/main/scala/org/http4s/client/middleware/ResponseLogger.scala b/client/shared/src/main/scala/org/http4s/client/middleware/ResponseLogger.scala
index 073e7f2a8e..466887d5fe 100644
--- a/client/shared/src/main/scala/org/http4s/client/middleware/ResponseLogger.scala
+++ b/client/shared/src/main/scala/org/http4s/client/middleware/ResponseLogger.scala
@@ -23,7 +23,6 @@ import cats.effect._
 import cats.syntax.all._
 import fs2._
 import org.http4s.internal.{Logger => InternalLogger}
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 
 /** Client middlewares that logs the HTTP responses it receives as soon as they are received locally.
@@ -31,9 +30,9 @@ import org.typelevel.ci.CIString
   * The "logging" is represented as an effectful action `String => F[Unit]`
   */
 object ResponseLogger {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
-  private def defaultLogAction[F[_]: Sync](s: String): F[Unit] = Sync[F].delay(logger.info(s))
+  private def defaultLogAction[F[_]: Sync](s: String): F[Unit] = logger.info(s).to[F]
 
   def apply[F[_]: Async](
       logHeaders: Boolean,
@@ -93,7 +92,7 @@ object ResponseLogger {
                 ) { _ =>
                   val newBody = Stream.eval(vec.get).flatMap(Stream.emits).unchunks
                   logMessage(response.withBodyStream(newBody))
-                    .handleErrorWith(t => F.delay(logger.error(t)("Error logging response body")))
+                    .handleErrorWith(t => logger.error(t)("Error logging response body").to[F])
                 }
               }
             }
@@ -101,7 +100,7 @@ object ResponseLogger {
           case Entity.Empty | Entity.Strict(_) =>
             Resource.eval(
               logMessage(response)
-                .handleErrorWith(t => F.delay(logger.error(t)("Error logging response body")))
+                .handleErrorWith(t => logger.error(t)("Error logging response body").to[F])
                 .as(response)
             )
         }
diff --git a/client/shared/src/main/scala/org/http4s/client/middleware/Retry.scala b/client/shared/src/main/scala/org/http4s/client/middleware/Retry.scala
index 9bff701982..4c430e50eb 100644
--- a/client/shared/src/main/scala/org/http4s/client/middleware/Retry.scala
+++ b/client/shared/src/main/scala/org/http4s/client/middleware/Retry.scala
@@ -25,7 +25,6 @@ import cats.syntax.all._
 import org.http4s.Status._
 import org.http4s.headers.`Idempotency-Key`
 import org.http4s.headers.`Retry-After`
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 import org.typelevel.vault.Key
 
@@ -37,7 +36,7 @@ import scala.math.pow
 import scala.math.random
 
 object Retry {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   /** This key tracks the attempt count, the retry middleware starts the very
     * first request with 1 as the first request, even with no retries
@@ -103,9 +102,11 @@ object Retry {
               policy(req, Right(response), attempts) match {
                 case Some(duration) =>
                   if (logRetries)
-                    logger.info(
-                      s"Request ${showRequest(req, redactHeaderWhen)} has failed on attempt #${attempts} with reason ${response.status}. Retrying after ${duration}."
-                    )
+                    logger
+                      .info(
+                        s"Request ${showRequest(req, redactHeaderWhen)} has failed on attempt #${attempts} with reason ${response.status}. Retrying after ${duration}."
+                      )
+                      .unsafeRunSync()
                   nextAttempt(req, attempts, duration, response.headers.get[`Retry-After`], hotswap)
                 case None =>
                   F.pure(response)
@@ -116,15 +117,19 @@ object Retry {
                 case Some(duration) =>
                   // info instead of error(e), because e is not discarded
                   if (logRetries)
-                    logger.info(e)(
-                      s"Request threw an exception on attempt #$attempts. Retrying after $duration"
-                    )
+                    logger
+                      .info(e)(
+                        s"Request threw an exception on attempt #$attempts. Retrying after $duration"
+                      )
+                      .unsafeRunSync()
                   nextAttempt(req, attempts, duration, None, hotswap)
                 case None =>
                   if (logRetries)
-                    logger.info(e)(
-                      s"Request ${showRequest(req, redactHeaderWhen)} threw an exception on attempt #$attempts. Giving up."
-                    )
+                    logger
+                      .info(e)(
+                        s"Request ${showRequest(req, redactHeaderWhen)} threw an exception on attempt #$attempts. Giving up."
+                      )
+                      .unsafeRunSync()
                   F.raiseError(e)
               }
           }
diff --git a/core/js/src/main/scala/org/http4s/Platform.scala b/core/js/src/main/scala/org/http4s/Platform.scala
index 63f68b2113..c2b4ec0548 100644
--- a/core/js/src/main/scala/org/http4s/Platform.scala
+++ b/core/js/src/main/scala/org/http4s/Platform.scala
@@ -16,7 +16,12 @@
 
 package org.http4s
 
+import cats.effect.SyncIO
+import org.typelevel.log4cats
+
 private[http4s] object Platform {
   final val isJvm = false
   final val isJs = true
+
+  lazy val loggerFactory: log4cats.LoggerFactory[SyncIO] = log4cats.noop.NoOpFactory[SyncIO]
 }
diff --git a/core/jvm/src/main/scala/org/http4s/Platform.scala b/core/jvm/src/main/scala/org/http4s/Platform.scala
index fb87630f7f..9ffead0137 100644
--- a/core/jvm/src/main/scala/org/http4s/Platform.scala
+++ b/core/jvm/src/main/scala/org/http4s/Platform.scala
@@ -16,7 +16,13 @@
 
 package org.http4s
 
+import cats.effect.SyncIO
+import org.typelevel.log4cats
+
 private[http4s] object Platform {
   final val isJvm = true
   final val isJs = false
+
+  lazy val loggerFactory: log4cats.LoggerFactory[SyncIO] =
+    log4cats.slf4j.loggerFactoryforSync[SyncIO]
 }
diff --git a/core/shared/src/main/scala/org/http4s/Message.scala b/core/shared/src/main/scala/org/http4s/Message.scala
index 952fe42bb6..5a5ad7fbd9 100644
--- a/core/shared/src/main/scala/org/http4s/Message.scala
+++ b/core/shared/src/main/scala/org/http4s/Message.scala
@@ -35,7 +35,6 @@ import org.http4s.Message.EntityStreamException
 import org.http4s.headers._
 import org.http4s.internal.CurlConverter
 import org.http4s.syntax.KleisliSyntax
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 import org.typelevel.vault._
 import scodec.bits.ByteVector
@@ -92,7 +91,9 @@ sealed trait Message[+F[_]] extends Media[F] { self =>
           .fromLong(l)
           .fold(
             _ => {
-              Message.logger.warn(s"Attempt to provide a negative content length of $l")
+              Message.logger
+                .warn(s"Attempt to provide a negative content length of $l")
+                .unsafeRunSync()
               None
             },
             Some(_),
@@ -302,7 +303,7 @@ object Message {
       EntityStreamException(s"Entity stream has exceeded the maximum of $maxBytes bytes")
   }
 
-  private[http4s] val logger = getLogger
+  private[http4s] val logger = Platform.loggerFactory.getLogger
   object Keys {
     private[this] val trailerHeaders: Key[Any] = Key.newKey[SyncIO, Any].unsafeRunSync()
     def TrailerHeaders[F[_]]: Key[F[Headers]] = trailerHeaders.asInstanceOf[Key[F[Headers]]]
diff --git a/core/shared/src/main/scala/org/http4s/StaticFile.scala b/core/shared/src/main/scala/org/http4s/StaticFile.scala
index d5de555228..185748bb52 100644
--- a/core/shared/src/main/scala/org/http4s/StaticFile.scala
+++ b/core/shared/src/main/scala/org/http4s/StaticFile.scala
@@ -32,14 +32,13 @@ import fs2.io.file.Path
 import org.http4s.Status.NotModified
 import org.http4s.headers._
 import org.http4s.syntax.header._
-import org.log4s.getLogger
 import org.typelevel.vault._
 
 import java.io._
 import java.net.URL
 
 object StaticFile {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   val DefaultBufferSize = 10240
 
@@ -215,7 +214,7 @@ object StaticFile {
                     attributes = Vault.empty.insert(staticPathKey, f),
                   )
 
-                  logger.trace(s"Static file generated response: $r")
+                  logger.trace(s"Static file generated response: $r").unsafeRunSync()
                   r.some
                 })
               }
diff --git a/core/shared/src/main/scala/org/http4s/internal/InternalPlatform.scala b/core/shared/src/main/scala/org/http4s/internal/InternalPlatform.scala
new file mode 100644
index 0000000000..a69d3e97ea
--- /dev/null
+++ b/core/shared/src/main/scala/org/http4s/internal/InternalPlatform.scala
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 http4s.org
+ *
+ * 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.http4s.internal
+
+import cats.effect.kernel.Sync
+import org.log4s
+
+private[internal] trait InternalPlatform {
+
+  @deprecated("log4s will be removed from http4s-core in 1.0", "0.23.15")
+  private[http4s] def loggingAsyncCallback[F[_], A](
+      logger: log4s.Logger
+  )(attempt: Either[Throwable, A])(implicit F: Sync[F]): F[Unit] =
+    attempt match {
+      case Left(e) => F.delay(logger.error(e)("Error in asynchronous callback"))
+      case Right(_) => F.unit
+    }
+
+}
diff --git a/core/shared/src/main/scala/org/http4s/internal/package.scala b/core/shared/src/main/scala/org/http4s/internal/package.scala
index c8bd32a882..b0f6dc9f90 100644
--- a/core/shared/src/main/scala/org/http4s/internal/package.scala
+++ b/core/shared/src/main/scala/org/http4s/internal/package.scala
@@ -27,7 +27,6 @@ import fs2.Pipe
 import fs2.Pull
 import fs2.RaiseThrowable
 import fs2.Stream
-import org.log4s.Logger
 
 import java.nio.ByteBuffer
 import java.nio.CharBuffer
@@ -39,15 +38,7 @@ import java.util.concurrent.CompletionException
 import java.util.concurrent.CompletionStage
 import scala.util.control.NoStackTrace
 
-package object internal {
-
-  private[http4s] def loggingAsyncCallback[F[_], A](
-      logger: Logger
-  )(attempt: Either[Throwable, A])(implicit F: Sync[F]): F[Unit] =
-    attempt match {
-      case Left(e) => F.delay(logger.error(e)("Error in asynchronous callback"))
-      case Right(_) => F.unit
-    }
+package object internal extends InternalPlatform {
 
   /** Hex encoding digits. Adapted from apache commons Hex.encodeHex */
   private val Digits: Array[Char] =
diff --git a/ember-client/shared/src/main/scala/org/http4s/ember/client/EmberClientBuilder.scala b/ember-client/shared/src/main/scala/org/http4s/ember/client/EmberClientBuilder.scala
index d55278348d..66de19ab7e 100644
--- a/ember-client/shared/src/main/scala/org/http4s/ember/client/EmberClientBuilder.scala
+++ b/ember-client/shared/src/main/scala/org/http4s/ember/client/EmberClientBuilder.scala
@@ -41,7 +41,7 @@ import org.typelevel.log4cats.Logger
 import scala.concurrent.duration.Duration
 import scala.concurrent.duration._
 
-final class EmberClientBuilder[F[_]: Async] private (
+final class EmberClientBuilder[F[_]: Async: Network] private (
     private val tlsContextOpt: Option[TLSContext[F]],
     private val sgOpt: Option[SocketGroup[F]],
     val maxTotal: Int,
@@ -352,7 +352,7 @@ final class EmberClientBuilder[F[_]: Async] private (
 
 object EmberClientBuilder extends EmberClientBuilderCompanionPlatform {
 
-  def default[F[_]: Async] =
+  def default[F[_]: Async: Network] =
     new EmberClientBuilder[F](
       tlsContextOpt = None,
       sgOpt = None,
@@ -373,6 +373,10 @@ object EmberClientBuilder extends EmberClientBuilderCompanionPlatform {
       pushPromiseSupport = None,
     )
 
+  @deprecated("Use the overload which accepts a Network", "0.23.16")
+  def default[F[_]](async: Async[F]): EmberClientBuilder[F] =
+    default(async, Network.forAsync(async))
+
   private object Defaults {
     val acgFixedThreadPoolSize: Int = 100
     val chunkSize: Int = 32 * 1024
diff --git a/ember-server/shared/src/main/scala/org/http4s/ember/server/EmberServerBuilder.scala b/ember-server/shared/src/main/scala/org/http4s/ember/server/EmberServerBuilder.scala
index cc6c11e9f4..48abc6cad3 100644
--- a/ember-server/shared/src/main/scala/org/http4s/ember/server/EmberServerBuilder.scala
+++ b/ember-server/shared/src/main/scala/org/http4s/ember/server/EmberServerBuilder.scala
@@ -36,7 +36,7 @@ import org.http4s.server.websocket.WebSocketBuilder
 
 import scala.concurrent.duration._
 
-final class EmberServerBuilder[F[_]: Async] private (
+final class EmberServerBuilder[F[_]: Async: Network] private (
     val host: Option[Host],
     val port: Port,
     private val httpApp: WebSocketBuilder[F] => HttpApp[F],
@@ -234,7 +234,7 @@ final class EmberServerBuilder[F[_]: Async] private (
 }
 
 object EmberServerBuilder extends EmberServerBuilderCompanionPlatform {
-  def default[F[_]: Async]: EmberServerBuilder[F] =
+  def default[F[_]: Async: Network]: EmberServerBuilder[F] =
     new EmberServerBuilder[F](
       host = Host.fromString(Defaults.host),
       port = Port.fromInt(Defaults.port).get,
@@ -255,6 +255,10 @@ object EmberServerBuilder extends EmberServerBuilderCompanionPlatform {
       false,
     )
 
+  @deprecated("Use the overload which accepts a Network", "0.23.16")
+  def default[F[_]](async: Async[F]): EmberServerBuilder[F] =
+    default(async, Network.forAsync(async))
+
   private object Defaults {
     val host: String = server.defaults.IPv4Host
     val port: Int = server.defaults.HttpPort
diff --git a/flake.lock b/flake.lock
index a98e073c03..cb9cf08661 100644
--- a/flake.lock
+++ b/flake.lock
@@ -6,11 +6,11 @@
         "nixpkgs": "nixpkgs"
       },
       "locked": {
-        "lastModified": 1658746384,
-        "narHash": "sha256-CCJcoMOcXyZFrV1ag4XMTpAPjLWb4Anbv+ktXFI1ry0=",
+        "lastModified": 1660811669,
+        "narHash": "sha256-V6lmsaLNFz41myppL0yxglta92ijkSvpZ+XVygAh+bU=",
         "owner": "numtide",
         "repo": "devshell",
-        "rev": "0ffc7937bb5e8141af03d462b468bd071eb18e1b",
+        "rev": "c2feacb46ee69949124c835419861143c4016fb5",
         "type": "github"
       },
       "original": {
@@ -83,11 +83,11 @@
     },
     "nixpkgs_2": {
       "locked": {
-        "lastModified": 1660494700,
-        "narHash": "sha256-OU9ceb6JvwqUVqFYvYOtpNDwJpHgC3z/dy6GRoaiGgM=",
+        "lastModified": 1662934689,
+        "narHash": "sha256-mXi8hmhiunOVTeHiuouWXb0vTqjzp9v9kshMmI561Us=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "bc4b4a50c7a105c56f1b712a87818678298deef3",
+        "rev": "a0b7e70db7a55088d3de0cc370a59f9fbcc906c3",
         "type": "github"
       },
       "original": {
@@ -118,11 +118,11 @@
         "nixpkgs": "nixpkgs_2"
       },
       "locked": {
-        "lastModified": 1660579530,
-        "narHash": "sha256-KaIX+bY+M27gNBkt/J2EI98jUlPxxAmsO+1lpI7Qag4=",
+        "lastModified": 1662988593,
+        "narHash": "sha256-0wA+0Kz7wyseE+tm4sGIF6fJYiHYZCJnG58P3mWi5S8=",
         "owner": "rossabaker",
         "repo": "typelevel-nix",
-        "rev": "323f642c6dfe7d46c1623f429c96cc16ee67bc8f",
+        "rev": "a29092a6b99664f9fb8414e8b78dd4c907573917",
         "type": "github"
       },
       "original": {
diff --git a/project/Http4sPlugin.scala b/project/Http4sPlugin.scala
index 1a6fee0ba5..278a7fdfaf 100644
--- a/project/Http4sPlugin.scala
+++ b/project/Http4sPlugin.scala
@@ -94,7 +94,7 @@ object Http4sPlugin extends AutoPlugin {
     // reference of all the projects we depend on, and hopefully will reduce
     // error-prone merge conflicts in the dependencies below.
     val blaze = "0.15.3"
-    val caseInsensitive = "1.2.0"
+    val caseInsensitive = "1.3.0"
     val cats = "2.8.0"
     val catsEffect = "3.3.14"
     val catsParse = "0.3.8"
@@ -102,7 +102,7 @@ object Http4sPlugin extends AutoPlugin {
     val crypto = "0.2.3"
     val cryptobits = "1.3"
     val disciplineCore = "1.5.1"
-    val fs2 = "3.2.12"
+    val fs2 = "3.2.14"
     val ip4s = "3.1.3"
     val hpack = "1.0.3"
     val javaWebSocket = "1.5.3"
@@ -114,13 +114,13 @@ object Http4sPlugin extends AutoPlugin {
     val logback = "1.2.6"
     val log4cats = "2.4.0"
     val log4s = "1.10.0"
-    val munit = "0.7.29"
-    val munitCatsEffect = "1.0.7"
-    val munitDiscipline = "1.0.9"
-    val netty = "4.1.79.Final"
+    val munit = "1.0.0-M6"
+    val munitCatsEffect = "2.0.0-M2"
+    val munitDiscipline = "2.0.0-M3"
+    val netty = "4.1.82.Final"
     val quasiquotes = "2.1.0"
     val scalacheck = "1.16.0"
-    val scalacheckEffect = "1.0.4"
+    val scalacheckEffect = "2.0.0-M1"
     val scalaJavaLocales = "1.4.1"
     val scalaJavaTime = "2.4.0"
     val scodecBits = "1.1.34"
@@ -170,7 +170,7 @@ object Http4sPlugin extends AutoPlugin {
   lazy val logbackClassic = "ch.qos.logback" % "logback-classic" % V.logback
   lazy val munit = Def.setting("org.scalameta" %%% "munit" % V.munit)
   lazy val munitCatsEffect =
-    Def.setting("org.typelevel" %%% "munit-cats-effect-3" % V.munitCatsEffect)
+    Def.setting("org.typelevel" %%% "munit-cats-effect" % V.munitCatsEffect)
   lazy val munitDiscipline = Def.setting("org.typelevel" %%% "discipline-munit" % V.munitDiscipline)
   lazy val nettyBuffer = "io.netty" % "netty-buffer" % V.netty
   lazy val nettyCodecHttp = "io.netty" % "netty-codec-http" % V.netty
diff --git a/project/build.sbt b/project/build.sbt
index 5a1645db69..95dd8c63bd 100644
--- a/project/build.sbt
+++ b/project/build.sbt
@@ -7,8 +7,8 @@ scalacOptions := Seq(
 libraryDependencies ++= List(
   "com.eed3si9n" %% "treehugger" % "0.4.4",
   "io.circe" %% "circe-generic" % "0.14.2",
-  "org.http4s" %% "http4s-ember-client" % "0.23.14",
-  "org.http4s" %% "http4s-circe" % "0.23.14",
+  "org.http4s" %% "http4s-ember-client" % "0.23.15",
+  "org.http4s" %% "http4s-circe" % "0.23.15",
 )
 
 // native packager and scoverage are conflicting...
diff --git a/server/shared/src/main/scala/org/http4s/server/Server.scala b/server/shared/src/main/scala/org/http4s/server/Server.scala
index 4973007f8f..30bd542d7b 100644
--- a/server/shared/src/main/scala/org/http4s/server/Server.scala
+++ b/server/shared/src/main/scala/org/http4s/server/Server.scala
@@ -18,10 +18,9 @@ package org.http4s
 package server
 
 import com.comcast.ip4s
-import org.log4s.getLogger
 
 abstract class Server {
-  private[server] val logger = getLogger
+  private[server] val logger = Platform.loggerFactory.getLogger
 
   def address: ip4s.SocketAddress[ip4s.IpAddress]
 
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/CORS.scala b/server/shared/src/main/scala/org/http4s/server/middleware/CORS.scala
index 2c775b2f7f..4ee434cb79 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/CORS.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/CORS.scala
@@ -25,7 +25,6 @@ import cats.syntax.all._
 import org.http4s.Method.OPTIONS
 import org.http4s.headers._
 import org.http4s.syntax.header._
-import org.log4s.getLogger
 import org.typelevel.ci._
 
 import scala.annotation.nowarn
@@ -149,7 +148,7 @@ object CORSConfig {
   * @see [[https://fetch.spec.whatwg.org/#http-cors-protocol CORS protocol specification]]
   */
 object CORS {
-  private[CORS] val logger = getLogger
+  private[CORS] val logger = Platform.loggerFactory.getLogger
 
   /** The default CORS policy:
     * - Sends `Access-Control-Allow-Origin: *`
@@ -198,9 +197,11 @@ object CORS {
       F: Applicative[F]
   ): Http[F, G] = {
     if (config.anyOrigin && config.allowCredentials)
-      logger.warn(
-        "Insecure CORS config detected: `anyOrigin=true` and `allowCredentials=true` are mutually exclusive. `Access-Control-Allow-Credentials` header will not be sent. Change either flag to false to remove this warning."
-      )
+      logger
+        .warn(
+          "Insecure CORS config detected: `anyOrigin=true` and `allowCredentials=true` are mutually exclusive. `Access-Control-Allow-Credentials` header will not be sent. Change either flag to false to remove this warning."
+        )
+        .unsafeRunSync()
     Kleisli { req =>
       // In the case of an options request we want to return a simple response with the correct Headers set.
       def createOptionsResponse(
@@ -261,16 +262,16 @@ object CORS {
         req.headers.get[`Access-Control-Request-Method`],
       ) match {
         case (OPTIONS, Some(origin), Some(acrm)) if allowCORS(origin, acrm.method) =>
-          logger.debug(s"Serving OPTIONS with CORS headers for $acrm ${req.uri}")
+          logger.debug(s"Serving OPTIONS with CORS headers for $acrm ${req.uri}").unsafeRunSync()
           createOptionsResponse(origin, acrm).pure[F]
         case (_, Some(origin), _) =>
           if (allowCORS(origin, req.method))
             http(req).map { resp =>
-              logger.debug(s"Adding CORS headers to ${req.method} ${req.uri}")
+              logger.debug(s"Adding CORS headers to ${req.method} ${req.uri}").unsafeRunSync()
               corsHeaders(origin, req.method, isPreflight = false)(resp)
             }
           else {
-            logger.debug(s"CORS headers were denied for ${req.method} ${req.uri}")
+            logger.debug(s"CORS headers were denied for ${req.method} ${req.uri}").unsafeRunSync()
             Response[G](status = Status.Forbidden).pure[F]
           }
         case _ =>
@@ -514,9 +515,11 @@ sealed class CORSPolicy(
       }
 
     if (allowOrigin == AllowOrigin.All && allowCredentials == AllowCredentials.Allow) {
-      logger.warn(
-        "CORS disabled due to insecure config prohibited by spec. Call withCredentials(false) to avoid sharing credential-tainted responses with arbitrary origins, or call withAllowOrigin* method to be explicit who you trust with credential-tainted responses."
-      )
+      logger
+        .warn(
+          "CORS disabled due to insecure config prohibited by spec. Call withCredentials(false) to avoid sharing credential-tainted responses with arbitrary origins, or call withAllowOrigin* method to be explicit who you trust with credential-tainted responses."
+        )
+        .unsafeRunSync()
       http
     } else
       Kleisli(dispatch)
@@ -719,7 +722,7 @@ sealed class CORSPolicy(
 }
 
 object CORSPolicy {
-  private val logger = getLogger
+  private val logger = Platform.loggerFactory.getLogger
 
   private object CommonHeaders {
     val someAllowOriginWildcard: Option[Header.Raw] =
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/GZip.scala b/server/shared/src/main/scala/org/http4s/server/middleware/GZip.scala
index 9d4c66e582..6636f2d64f 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/GZip.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/GZip.scala
@@ -23,10 +23,9 @@ import cats.data.Kleisli
 import cats.syntax.all._
 import fs2.compression._
 import org.http4s.headers._
-import org.log4s.getLogger
 
 object GZip {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   // TODO: It could be possible to look for F.pure type bodies, and change the Content-Length header after
   // TODO      zipping and buffering all the input. Just a thought.
@@ -84,7 +83,7 @@ object GZip {
           header = ZLibParams.Header.GZIP,
         ),
       )
-    logger.trace("GZip middleware encoding content")
+    logger.trace("GZip middleware encoding content").unsafeRunSync()
     resp
       .removeHeader[`Content-Length`]
       .putHeaders(`Content-Encoding`(ContentCoding.gzip))
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/HttpsRedirect.scala b/server/shared/src/main/scala/org/http4s/server/middleware/HttpsRedirect.scala
index f8c1b371fa..2ac4fffd7e 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/HttpsRedirect.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/HttpsRedirect.scala
@@ -30,7 +30,6 @@ import org.http4s.headers.Host
 import org.http4s.headers.Location
 import org.http4s.headers.`Content-Type`
 import org.http4s.syntax.header._
-import org.log4s.getLogger
 import org.typelevel.ci._
 
 /** [[Middleware]] to redirect http traffic to https.
@@ -40,14 +39,14 @@ import org.typelevel.ci._
   * which does not support such redirect feature, e.g. Heroku.
   */
 object HttpsRedirect {
-  private[HttpsRedirect] val logger = getLogger
+  private[HttpsRedirect] val logger = Platform.loggerFactory.getLogger
 
   def apply[F[_], G[_]](http: Http[F, G])(implicit F: Applicative[F]): Http[F, G] =
     Kleisli { req =>
       (req.headers.get(ci"X-Forwarded-Proto"), req.headers.get[Host]) match {
         case (Some(NonEmptyList(proto, _)), Some(host))
             if Scheme.fromString(proto.value).contains(Scheme.http) =>
-          logger.debug(s"Redirecting ${req.method} ${req.uri} to https on $host")
+          logger.debug(s"Redirecting ${req.method} ${req.uri} to https on $host").unsafeRunSync()
           val authority = Authority(host = RegName(host.value))
           val location = req.uri.copy(scheme = Some(Scheme.https), authority = Some(authority))
           val headers = Headers(Location(location), `Content-Type`(MediaType.text.xml))
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/Jsonp.scala b/server/shared/src/main/scala/org/http4s/server/middleware/Jsonp.scala
index 84913a2f9d..eb6b28b2e3 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/Jsonp.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/Jsonp.scala
@@ -24,7 +24,6 @@ import cats.syntax.all._
 import fs2.Chunk
 import fs2.Stream._
 import org.http4s.headers._
-import org.log4s.getLogger
 
 import java.nio.charset.StandardCharsets
 
@@ -38,7 +37,7 @@ import java.nio.charset.StandardCharsets
   * applied.
   */
 object Jsonp {
-  private val logger = getLogger
+  private val logger = Platform.loggerFactory.getLogger
 
   // A regex to match a valid javascript function name to shield the client from some jsonp related attacks
   private val ValidCallback =
@@ -51,7 +50,9 @@ object Jsonp {
             if (hasJsonContent(response)) jsonp(response, callback) else response
           }
         case Some(invalidCallback) =>
-          logger.warn(s"Jsonp requested with invalid callback function name $invalidCallback")
+          logger
+            .warn(s"Jsonp requested with invalid callback function name $invalidCallback")
+            .unsafeRunSync()
           Response[G](Status.BadRequest).withEntity(s"Not a valid callback name.").pure[F]
         case None => http(req)
       }
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/Logger.scala b/server/shared/src/main/scala/org/http4s/server/middleware/Logger.scala
index 9b35ae9fb1..59c0d7af06 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/Logger.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/Logger.scala
@@ -25,23 +25,25 @@ import cats.effect.kernel.MonadCancelThrow
 import cats.syntax.all._
 import cats.~>
 import fs2.Stream
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 
 /** Simple Middleware for Logging All Requests and Responses
   */
 object Logger {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
+
+  def defaultRedactHeadersWhen(name: CIString): Boolean =
+    Headers.SensitiveHeaders.contains(name) || name.toString.toLowerCase.contains("token")
 
   def apply[G[_], F[_]](
       logHeaders: Boolean,
       logBody: Boolean,
       fk: F ~> G,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(http: Http[G, F])(implicit G: MonadCancelThrow[G], F: Async[F]): Http[G, F] = {
     val log: String => F[Unit] = logAction.getOrElse { s =>
-      F.delay(logger.info(s))
+      logger.info(s).to[F]
     }
     ResponseLogger(logHeaders, logBody, fk, redactHeadersWhen, log.pure[Option])(
       RequestLogger(logHeaders, logBody, fk, redactHeadersWhen, log.pure[Option])(http)
@@ -52,11 +54,11 @@ object Logger {
       logHeaders: Boolean,
       logBody: Stream[F, Byte] => Option[F[String]],
       fk: F ~> G,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(http: Http[G, F])(implicit G: MonadCancelThrow[G], F: Async[F]): Http[G, F] = {
     val log: String => F[Unit] = logAction.getOrElse { s =>
-      F.delay(logger.info(s))
+      logger.info(s).to[F]
     }
     ResponseLogger.impl(logHeaders, Right(logBody), fk, redactHeadersWhen, log.pure[Option])(
       RequestLogger.impl(logHeaders, Right(logBody), fk, redactHeadersWhen, log.pure[Option])(http)
@@ -66,7 +68,7 @@ object Logger {
   def httpApp[F[_]: Async](
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(httpApp: HttpApp[F]): HttpApp[F] =
     apply(logHeaders, logBody, FunctionK.id[F], redactHeadersWhen, logAction)(httpApp)
@@ -74,7 +76,7 @@ object Logger {
   def httpAppLogBodyText[F[_]: Async](
       logHeaders: Boolean,
       logBody: Stream[F, Byte] => Option[F[String]],
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(httpApp: HttpApp[F]): HttpApp[F] =
     logBodyText(logHeaders, logBody, FunctionK.id[F], redactHeadersWhen, logAction)(httpApp)
@@ -82,7 +84,7 @@ object Logger {
   def httpRoutes[F[_]: Async](
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(httpRoutes: HttpRoutes[F]): HttpRoutes[F] =
     apply(logHeaders, logBody, OptionT.liftK[F], redactHeadersWhen, logAction)(httpRoutes)
@@ -90,7 +92,7 @@ object Logger {
   def httpRoutesLogBodyText[F[_]: Async](
       logHeaders: Boolean,
       logBody: Stream[F, Byte] => Option[F[String]],
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
       logAction: Option[String => F[Unit]] = None,
   )(httpRoutes: HttpRoutes[F]): HttpRoutes[F] =
     logBodyText(logHeaders, logBody, OptionT.liftK[F], redactHeadersWhen, logAction)(httpRoutes)
@@ -98,7 +100,7 @@ object Logger {
   def logMessage[F[_], A <: Message[F]](message: A)(
       logHeaders: Boolean,
       logBody: Boolean,
-      redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
+      redactHeadersWhen: CIString => Boolean = defaultRedactHeadersWhen,
   )(log: String => F[Unit])(implicit F: Async[F]): F[Unit] =
     org.http4s.internal.Logger
       .logMessage(message)(logHeaders, logBody, redactHeadersWhen)(log)
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/PushSupport.scala b/server/shared/src/main/scala/org/http4s/server/middleware/PushSupport.scala
index d746891a60..5af1055f3d 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/PushSupport.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/PushSupport.scala
@@ -22,12 +22,11 @@ import cats.Monad
 import cats.data.Kleisli
 import cats.effect.SyncIO
 import cats.syntax.all._
-import org.log4s.getLogger
 import org.typelevel.vault._
 
 @deprecated("Obsolete. Not implemented by any backends.", "0.23.12")
 object PushSupport {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   implicit def http4sPushOps[F[_]](response: Response[F]): PushOps[F] =
     new PushOps[F](response)
@@ -43,7 +42,7 @@ object PushSupport {
         } else url
       }
 
-      logger.trace(s"Adding push resource: $newUrl")
+      logger.trace(s"Adding push resource: $newUrl").unsafeRunSync()
 
       val newPushResouces = response.attributes
         .lookup(pushLocationKey)
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/RequestLogger.scala b/server/shared/src/main/scala/org/http4s/server/middleware/RequestLogger.scala
index 20ddadf9e8..4dcbbbf182 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/RequestLogger.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/RequestLogger.scala
@@ -25,19 +25,17 @@ import cats.effect.implicits._
 import cats.effect.kernel.Async
 import cats.effect.kernel.MonadCancelThrow
 import cats.effect.kernel.Outcome
-import cats.effect.kernel.Sync
 import cats.syntax.all._
 import cats.~>
 import fs2.Chunk
 import fs2.Pipe
 import fs2.Stream
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 
 /** Simple Middleware for Logging Requests As They Are Processed
   */
 object RequestLogger {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   def apply[G[_], F[_]](
       logHeaders: Boolean,
@@ -62,7 +60,7 @@ object RequestLogger {
       G: MonadCancelThrow[G],
   ): Http[G, F] = {
     val log = logAction.fold { (s: String) =>
-      Sync[F].delay(logger.info(s))
+      logger.info(s).to[F]
     }(identity)
 
     def logMessage(r: Request[F]): F[Unit] =
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/ResponseLogger.scala b/server/shared/src/main/scala/org/http4s/server/middleware/ResponseLogger.scala
index 76d16cb1f3..616e8912a4 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/ResponseLogger.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/ResponseLogger.scala
@@ -24,20 +24,18 @@ import cats.data.OptionT
 import cats.effect.kernel.Async
 import cats.effect.kernel.MonadCancelThrow
 import cats.effect.kernel.Outcome
-import cats.effect.kernel.Sync
 import cats.effect.syntax.all._
 import cats.syntax.all._
 import cats.~>
 import fs2.Chunk
 import fs2.Pipe
 import fs2.Stream
-import org.log4s.getLogger
 import org.typelevel.ci.CIString
 
 /** Simple middleware for logging responses as they are processed
   */
 object ResponseLogger {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   def apply[G[_], F[_], A](
       logHeaders: Boolean,
@@ -59,7 +57,7 @@ object ResponseLogger {
   )(
       http: Kleisli[G, A, Response[F]]
   )(implicit G: MonadCancelThrow[G], F: Async[F]): Kleisli[G, A, Response[F]] = {
-    val fallback: String => F[Unit] = s => Sync[F].delay(logger.info(s))
+    val fallback: String => F[Unit] = s => logger.info(s).to[F]
     val log = logAction.fold(fallback)(identity)
 
     def logMessage(resp: Response[F]): F[Unit] =
diff --git a/server/shared/src/main/scala/org/http4s/server/package.scala b/server/shared/src/main/scala/org/http4s/server/package.scala
index 57f6556aeb..fac309f950 100644
--- a/server/shared/src/main/scala/org/http4s/server/package.scala
+++ b/server/shared/src/main/scala/org/http4s/server/package.scala
@@ -25,7 +25,6 @@ import cats.syntax.all._
 import com.comcast.ip4s
 import org.http4s.headers.Connection
 import org.http4s.headers.`Content-Length`
-import org.log4s.getLogger
 import org.typelevel.vault._
 
 import java.net.InetAddress
@@ -150,8 +149,10 @@ package object server {
         }
   }
 
-  private[this] val messageFailureLogger = getLogger("org.http4s.server.message-failures")
-  private[this] val serviceErrorLogger = getLogger("org.http4s.server.service-errors")
+  private[this] val messageFailureLogger =
+    Platform.loggerFactory.getLoggerFromName("org.http4s.server.message-failures")
+  private[this] val serviceErrorLogger =
+    Platform.loggerFactory.getLoggerFromName("org.http4s.server.service-errors")
 
   type ServiceErrorHandler[F[_]] = Request[F] => PartialFunction[Throwable, F[Response[F]]]
 
@@ -165,16 +166,20 @@ package object server {
   ): Request[G] => PartialFunction[Throwable, F[Response[G]]] =
     req => {
       case mf: MessageFailure =>
-        messageFailureLogger.debug(mf)(
-          s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
-              .getOrElse("<unknown>")}"""
-        )
+        messageFailureLogger
+          .debug(mf)(
+            s"""Message failure handling request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
+                .getOrElse("<unknown>")}"""
+          )
+          .unsafeRunSync()
         mf.toHttpResponse[G](req.httpVersion).pure[F]
       case NonFatal(t) =>
-        serviceErrorLogger.error(t)(
-          s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
-              .getOrElse("<unknown>")}"""
-        )
+        serviceErrorLogger
+          .error(t)(
+            s"""Error servicing request: ${req.method} ${req.pathInfo} from ${req.remoteAddr
+                .getOrElse("<unknown>")}"""
+          )
+          .unsafeRunSync()
         F.pure(
           Response(
             Status.InternalServerError,
diff --git a/server/shared/src/main/scala/org/http4s/server/staticcontent/FileService.scala b/server/shared/src/main/scala/org/http4s/server/staticcontent/FileService.scala
index 29567741f2..bf9dc3b53c 100644
--- a/server/shared/src/main/scala/org/http4s/server/staticcontent/FileService.scala
+++ b/server/shared/src/main/scala/org/http4s/server/staticcontent/FileService.scala
@@ -29,13 +29,12 @@ import fs2.io.file.Path
 import org.http4s.headers.Range.SubRange
 import org.http4s.headers._
 import org.http4s.server.middleware.TranslateUri
-import org.log4s.getLogger
 import org.typelevel.ci._
 
 import scala.util.control.NoStackTrace
 
 object FileService {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   type PathCollector[F[_]] = (Path, Config[F], Request[F]) => OptionT[F, Response[F]]
 
@@ -99,21 +98,25 @@ object FileService {
     }
 
     val readPath: F[Path] = Files[F].realPath(Path(config.systemPath))
-    val inner: F[HttpRoutes[F]] = readPath.attempt.map {
+    val inner: F[HttpRoutes[F]] = readPath.attempt.flatMap {
       case Right(rootPath) =>
-        TranslateUri(config.pathPrefix)(Kleisli(withPath(rootPath)))
+        TranslateUri(config.pathPrefix)(Kleisli(withPath(rootPath))).pure
 
       case Left(_: NoSuchFileException) =>
-        logger.error(
-          s"Could not find root path from FileService config: systemPath = ${config.systemPath}, pathPrefix = ${config.pathPrefix}. All requests will return none."
-        )
-        HttpRoutes.empty
+        logger
+          .error(
+            s"Could not find root path from FileService config: systemPath = ${config.systemPath}, pathPrefix = ${config.pathPrefix}. All requests will return none."
+          )
+          .to[F]
+          .as(HttpRoutes.empty)
 
       case Left(e) =>
-        logger.error(e)(
-          s"Could not resolve root path from FileService config: systemPath = ${config.systemPath}, pathPrefix = ${config.pathPrefix}. All requests will fail with a 500."
-        )
-        HttpRoutes.pure(Response(Status.InternalServerError))
+        logger
+          .error(e)(
+            s"Could not resolve root path from FileService config: systemPath = ${config.systemPath}, pathPrefix = ${config.pathPrefix}. All requests will fail with a 500."
+          )
+          .to[F]
+          .as(HttpRoutes.pure(Response(Status.InternalServerError)))
     }
 
     Kleisli((_: Any) => OptionT.liftF(inner)).flatten
diff --git a/server/shared/src/main/scala/org/http4s/server/staticcontent/MemoryCache.scala b/server/shared/src/main/scala/org/http4s/server/staticcontent/MemoryCache.scala
index 6be193f07b..6e2e973ec8 100644
--- a/server/shared/src/main/scala/org/http4s/server/staticcontent/MemoryCache.scala
+++ b/server/shared/src/main/scala/org/http4s/server/staticcontent/MemoryCache.scala
@@ -20,7 +20,6 @@ package staticcontent
 
 import cats.effect.Concurrent
 import cats.syntax.functor._
-import org.log4s.getLogger
 import scodec.bits.ByteVector
 
 import java.util.concurrent.ConcurrentHashMap
@@ -31,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap
   * to avoid disk access.
   */
 class MemoryCache[F[_]] extends CacheStrategy[F] {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
   private val cacheMap = new ConcurrentHashMap[Uri.Path, Response[F]]()
 
   override def cache(uriPath: Uri.Path, resp: Response[F])(implicit
@@ -40,11 +39,11 @@ class MemoryCache[F[_]] extends CacheStrategy[F] {
     if (resp.status == Status.Ok)
       Option(cacheMap.get(uriPath)) match {
         case Some(r) if r.headers.headers == resp.headers.headers =>
-          logger.debug(s"Cache hit: $resp")
+          logger.debug(s"Cache hit: $resp").unsafeRunSync()
           F.pure(r)
 
         case _ =>
-          logger.debug(s"Cache miss: $resp")
+          logger.debug(s"Cache miss: $resp").unsafeRunSync()
           collectResource(uriPath, resp) /* otherwise cache the response */
       }
     else F.pure(resp)
diff --git a/server/shared/src/main/scala/org/http4s/server/staticcontent/ResourceService.scala b/server/shared/src/main/scala/org/http4s/server/staticcontent/ResourceService.scala
index 0f642ccefb..017d3c6d7f 100644
--- a/server/shared/src/main/scala/org/http4s/server/staticcontent/ResourceService.scala
+++ b/server/shared/src/main/scala/org/http4s/server/staticcontent/ResourceService.scala
@@ -23,7 +23,6 @@ import cats.data.OptionT
 import cats.effect.Async
 import cats.syntax.all._
 import org.http4s.server.middleware.TranslateUri
-import org.log4s.getLogger
 
 import java.nio.file.Paths
 import scala.util.Failure
@@ -48,7 +47,7 @@ class ResourceServiceBuilder[F[_]] private (
     preferGzipped: Boolean,
     classLoader: Option[ClassLoader],
 ) {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   private def copy(
       basePath: String = basePath,
@@ -118,9 +117,11 @@ class ResourceServiceBuilder[F[_]] private (
         })
 
       case Failure(e) =>
-        logger.error(e)(
-          s"Could not get root path from ResourceService config: basePath = $basePath, pathPrefix = $pathPrefix. All requests will fail."
-        )
+        logger
+          .error(e)(
+            s"Could not get root path from ResourceService config: basePath = $basePath, pathPrefix = $pathPrefix. All requests will fail."
+          )
+          .unsafeRunSync()
         Kleisli(_ => OptionT.pure(Response(Status.InternalServerError)))
     }
   }
@@ -139,7 +140,7 @@ object ResourceServiceBuilder {
 }
 
 object ResourceService {
-  private[this] val logger = getLogger
+  private[this] val logger = Platform.loggerFactory.getLogger
 
   /** [[org.http4s.server.staticcontent.ResourceService]] configuration
     *
@@ -194,9 +195,11 @@ object ResourceService {
         })
 
       case Failure(e) =>
-        logger.error(e)(
-          s"Could not get root path from ResourceService config: basePath = ${config.basePath}, pathPrefix = ${config.pathPrefix}. All requests will fail."
-        )
+        logger
+          .error(e)(
+            s"Could not get root path from ResourceService config: basePath = ${config.basePath}, pathPrefix = ${config.pathPrefix}. All requests will fail."
+          )
+          .unsafeRunSync()
         Kleisli(_ => OptionT.pure(Response(Status.InternalServerError)))
     }
   }


Test patch:
diff --git a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/HandlersToNettyAdapter.scala b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/HandlersToNettyAdapter.scala
index 9f756ca029..bff9ef1d37 100644
--- a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/HandlersToNettyAdapter.scala
+++ b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/HandlersToNettyAdapter.scala
@@ -25,7 +25,6 @@ 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.log4s.getLogger
 
 import java.net.URI
 
@@ -47,7 +46,7 @@ private[http4s] class HandlersToNettyAdapter private (
     fallbackHandler: Handler,
 ) extends SimpleChannelInboundHandler[HttpObject] {
 
-  private val logger = getLogger(this.getClass)
+  private val logger = org.http4s.Platform.loggerFactory.getLoggerFromClass(this.getClass)
 
   private var currentRequest: HttpRequest = null
   private var currentHandler: Handler = null
@@ -56,9 +55,11 @@ private[http4s] class HandlersToNettyAdapter private (
 
     msg match {
       case request: HttpRequest =>
-        logger.trace(
-          s"Recieved [${request.method()}] [${request.uri()}] request from [${ctx.channel.remoteAddress()}]."
-        )
+        logger
+          .trace(
+            s"Recieved [${request.method()}] [${request.uri()}] request from [${ctx.channel.remoteAddress()}]."
+          )
+          .unsafeRunSync()
         if (HttpUtil.is100ContinueExpected(request)) {
           ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE))
         } else {
@@ -74,14 +75,14 @@ private[http4s] class HandlersToNettyAdapter private (
 
     msg match {
       case content: HttpContent =>
-        logger.trace("Recieved content.")
+        logger.trace("Recieved content.").unsafeRunSync()
         currentHandler.onContent(ctx, currentRequest, content)
       case _ =>
     }
 
     msg match {
       case _: LastHttpContent =>
-        logger.trace("Request finished.")
+        logger.trace("Request finished.").unsafeRunSync()
         currentHandler.onRequestEnd(ctx, currentRequest)
         currentRequest = null
         currentHandler = null
@@ -95,7 +96,7 @@ private[http4s] class HandlersToNettyAdapter private (
   }
 
   override def exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable): Unit = {
-    logger.warn(cause)("")
+    logger.warn(cause)("").unsafeRunSync()
     ctx.close()
     ()
   }
diff --git a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/NettyTestServer.scala b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/NettyTestServer.scala
index 779c9c79f2..b2fec82b12 100644
--- a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/NettyTestServer.scala
+++ b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/NettyTestServer.scala
@@ -34,7 +34,6 @@ import io.netty.handler.logging.LogLevel
 import io.netty.handler.logging.LoggingHandler
 import io.netty.handler.ssl.SslHandler
 import org.http4s.Uri
-import org.log4s.getLogger
 
 import java.net.InetAddress
 import java.net.InetSocketAddress
@@ -59,7 +58,7 @@ private[http4s] class NettyTestServer[F[_]](
 }
 
 private[http4s] object NettyTestServer {
-  private val logger = getLogger(this.getClass)
+  private val logger = org.http4s.Platform.loggerFactory.getLoggerFromClass(this.getClass)
 
   def apply[F[_]: Async](
       port: Int,
@@ -78,7 +77,7 @@ private[http4s] object NettyTestServer {
       .handler(new LoggingHandler(LogLevel.INFO))
       .childHandler(new ChannelInitializer[NioSocketChannel]() {
         def initChannel(ch: NioSocketChannel): Unit = {
-          logger.trace(s"Accepted new connection from [${ch.remoteAddress()}].")
+          logger.trace(s"Accepted new connection from [${ch.remoteAddress()}].").unsafeRunSync()
           dispatcher.unsafeRunSync(establishedConnections.update(_ + 1))
           sslContext.foreach { sslContext =>
             val engine = sslContext.createSSLEngine()
diff --git a/client-testkit/shared/src/main/scala/org/http4s/client/testkit/ClientRouteTestBattery.scala b/client-testkit/shared/src/main/scala/org/http4s/client/testkit/ClientRouteTestBattery.scala
index 5a5887c224..277f1be6b5 100644
--- a/client-testkit/shared/src/main/scala/org/http4s/client/testkit/ClientRouteTestBattery.scala
+++ b/client-testkit/shared/src/main/scala/org/http4s/client/testkit/ClientRouteTestBattery.scala
@@ -22,6 +22,7 @@ import cats.effect._
 import cats.syntax.all._
 import fs2._
 import munit._
+import munit.catseffect._
 import org.http4s.client.dsl.Http4sClientDsl
 import org.http4s.client.testkit.scaffold.ServerScaffold
 import org.http4s.client.testkit.testroutes.GetRoutes
@@ -54,142 +55,126 @@ private[http4s] abstract class ClientRouteTestBattery(name: String)
       Ok(req.body)
   }
 
-  val serverClient: Fixture[IO[(() => ServerScaffold[IO], () => Client[IO])]] =
-    resourceSuiteDeferredFixture(
-      "serverClient",
-      ServerScaffold[IO](1, false, testHandler)
-        .map(() => _)
-        .product(clientResource.map(() => _)),
+  val server: IOFixture[ServerScaffold[IO]] =
+    resourceSuiteFixture(
+      "server",
+      ServerScaffold[IO](1, false, testHandler),
+    )
+
+  val client: IOFixture[Client[IO]] =
+    resourceSuiteFixture(
+      "client",
+      clientResource,
     )
 
   test(s"$name Repeat a simple request") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val path = GetRoutes.SimplePath
+    val address = server().addresses.head
+    val path = GetRoutes.SimplePath
 
-      def fetchBody =
-        client().toKleisli(_.as[String]).local { (uri: Uri) =>
-          Request(uri = uri)
-        }
+    def fetchBody =
+      client().toKleisli(_.as[String]).local { (uri: Uri) =>
+        Request(uri = uri)
+      }
+
+    val url = Uri.fromString(s"http://$address$path").yolo
+    (0 until 10).toVector
+      .parTraverse(_ => fetchBody.run(url).map(_.length))
+      .map(_.forall(_ =!= 0))
+      .assert
 
-      val url = Uri.fromString(s"http://$address$path").yolo
-      (0 until 10).toVector
-        .parTraverse(_ => fetchBody.run(url).map(_.length))
-        .map(_.forall(_ =!= 0))
-        .assert
-    }
   }
 
   test(s"$name POST an empty body") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val uri = Uri.fromString(s"http://$address/echo").yolo
-      val req = POST(uri)
-      val body = client().expect[String](req)
-      body.assertEquals("")
-    }
+    val address = server().addresses.head
+    val uri = Uri.fromString(s"http://$address/echo").yolo
+    val req = POST(uri)
+    val body = client().expect[String](req)
+    body.assertEquals("")
   }
 
   test(s"$name POST a normal body") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val uri = Uri.fromString(s"http://$address/echo").yolo
-      val req = POST("This is normal.", uri)
-      val body = client().expect[String](req)
-      body.assertEquals("This is normal.")
-    }
+    val address = server().addresses.head
+    val uri = Uri.fromString(s"http://$address/echo").yolo
+    val req = POST("This is normal.", uri)
+    val body = client().expect[String](req)
+    body.assertEquals("This is normal.")
   }
 
   test(s"$name POST a chunked body".flaky) {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val uri = Uri.fromString(s"http://$address/echo").yolo
-      val req = POST(Stream.emits("This is chunked.".toSeq.map(_.toString)), uri)
-      val body = client().expect[String](req)
-      body.assertEquals("This is chunked.")
-    }
+    val address = server().addresses.head
+    val uri = Uri.fromString(s"http://$address/echo").yolo
+    val req = POST(Stream.emits("This is chunked.".toSeq.map(_.toString)).covary[IO], uri)
+    val body = client().expect[String](req)
+    body.assertEquals("This is chunked.")
   }
 
   test(s"$name POST a multipart body") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val uri = Uri.fromString(s"http://$address/echo").yolo
-      Multiparts
-        .forSync[IO]
-        .flatMap { multiparts =>
-          multiparts.multipart(Vector(Part.formData("text", "This is text."))).flatMap {
-            multipart =>
-              val req = POST(multipart, uri).withHeaders(multipart.headers)
-              val body = client().expect[String](req)
-              body.map(_.contains("This is text."))
-          }
+    val address = server().addresses.head
+    val uri = Uri.fromString(s"http://$address/echo").yolo
+    Multiparts
+      .forSync[IO]
+      .flatMap { multiparts =>
+        multiparts.multipart(Vector(Part.formData("text", "This is text."))).flatMap { multipart =>
+          val req = POST(multipart, uri).withHeaders(multipart.headers)
+          val body = client().expect[String](req)
+          body.map(_.contains("This is text."))
         }
-        .assert
-    }
+      }
+      .assert
   }
 
   GetRoutes.getPaths.toList.foreach { case (path, expected) =>
     test(s"$name Execute GET $path") {
-      serverClient().flatMap { case (server, client) =>
-        val address = server().addresses.head
-        val req = Request[IO](uri = Uri.fromString(s"http://$address$path").yolo)
-        client()
-          .run(req)
-          .use(resp => expected.flatMap(checkResponse(resp, _)))
-          .assert
-      }
+      val address = server().addresses.head
+      val req = Request[IO](uri = Uri.fromString(s"http://$address$path").yolo)
+      client()
+        .run(req)
+        .use(resp => expected.flatMap(checkResponse(resp, _)))
+        .assert
     }
   }
 
   test("Mitigates request splitting attack in URI path") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val name = address.host.toString
-      val port = address.port.value
-      val req = Request[IO](
-        uri = Uri(
-          authority = Uri.Authority(None, Uri.RegName(name), port = port.some).some,
-          path = Uri.Path.Root / Uri.Path.Segment.encoded(
-            "request-splitting HTTP/1.0\r\nEvil:true\r\nHide-Protocol-Version:"
-          ),
-        )
+    val address = server().addresses.head
+    val name = address.host.toString
+    val port = address.port.value
+    val req = Request[IO](
+      uri = Uri(
+        authority = Uri.Authority(None, Uri.RegName(name), port = port.some).some,
+        path = Uri.Path.Root / Uri.Path.Segment.encoded(
+          "request-splitting HTTP/1.0\r\nEvil:true\r\nHide-Protocol-Version:"
+        ),
       )
-      client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
-    }
+    )
+    client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
   }
 
   test("Mitigates request splitting attack in URI RegName") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val name = address.host
-      val port = address.port.value
-      val req = Request[IO](uri =
-        Uri(
-          authority =
-            Uri.Authority(None, Uri.RegName(s"${name}\r\nEvil:true\r\n"), port = port.some).some,
-          path = path"/request-splitting",
-        )
+    val address = server().addresses.head
+    val name = address.host
+    val port = address.port.value
+    val req = Request[IO](uri =
+      Uri(
+        authority =
+          Uri.Authority(None, Uri.RegName(s"${name}\r\nEvil:true\r\n"), port = port.some).some,
+        path = path"/request-splitting",
       )
-      client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
-    }
+    )
+    client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
   }
 
   test("Mitigates request splitting attack in field name") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val req = Request[IO](uri = Uri.fromString(s"http://$address/request-splitting").yolo)
-        .putHeaders(Header.Raw(ci"Fine:\r\nEvil:true\r\n", "oops"))
-      client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
-    }
+    val address = server().addresses.head
+    val req = Request[IO](uri = Uri.fromString(s"http://$address/request-splitting").yolo)
+      .putHeaders(Header.Raw(ci"Fine:\r\nEvil:true\r\n", "oops"))
+    client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
   }
 
   test("Mitigates request splitting attack in field value") {
-    serverClient().flatMap { case (server, client) =>
-      val address = server().addresses.head
-      val req = Request[IO](uri = Uri.fromString(s"http://$address/request-splitting").yolo)
-        .putHeaders(Header.Raw(ci"X-Carrier", "\r\nEvil:true\r\n"))
-      client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
-    }
+    val address = server().addresses.head
+    val req = Request[IO](uri = Uri.fromString(s"http://$address/request-splitting").yolo)
+      .putHeaders(Header.Raw(ci"X-Carrier", "\r\nEvil:true\r\n"))
+    client().status(req).handleError(_ => Status.Ok).assertEquals(Status.Ok)
   }
 
   private def checkResponse(rec: Response[IO], expected: Response[IO]): IO[Boolean] = {
@@ -210,23 +195,23 @@ private[http4s] abstract class ClientRouteTestBattery(name: String)
     } yield true
   }
 
-  private def registerSuiteFixture[A](fixture: Fixture[A]): Fixture[A] = {
+  private def registerSuiteFixture[A](fixture: IOFixture[A]): IOFixture[A] = {
     suiteFixtures += fixture
     fixture
   }
 
-  private def resourceSuiteDeferredFixture[A](
+  private def resourceSuiteFixture[A](
       name: String,
       resource: Resource[IO, A],
-  ): Fixture[IO[A]] =
-    registerSuiteFixture(UnsafeResourceSuiteLocalDeferredFixture(name, resource))
+  ): IOFixture[A] =
+    registerSuiteFixture(ResourceSuiteLocalFixture(name, resource))
 
   implicit private class ParseResultSyntax[A](self: ParseResult[A]) {
     def yolo: A = self.valueOr(e => sys.error(e.toString))
   }
 
-  private[this] lazy val suiteFixtures = List.newBuilder[Fixture[_]]
+  private[this] lazy val suiteFixtures = List.newBuilder[IOFixture[_]]
 
-  override def munitFixtures: Seq[Fixture[_]] = suiteFixtures.result()
+  override def munitFixtures: Seq[IOFixture[_]] = suiteFixtures.result()
 
 }
diff --git a/client/shared/src/test/scala/org/http4s/client/ClientSyntaxSuite.scala b/client/shared/src/test/scala/org/http4s/client/ClientSyntaxSuite.scala
index 09b6fe145e..4cdf19999e 100644
--- a/client/shared/src/test/scala/org/http4s/client/ClientSyntaxSuite.scala
+++ b/client/shared/src/test/scala/org/http4s/client/ClientSyntaxSuite.scala
@@ -30,6 +30,8 @@ import org.http4s.client.dsl.Http4sClientDsl
 import org.http4s.headers.Accept
 import org.http4s.syntax.all._
 
+import scala.annotation.nowarn
+
 class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   private val app = HttpRoutes
     .of[IO] {
@@ -156,7 +158,9 @@ class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   }
 
   test("Client should fetch request tasks with expect") {
-    client.expect[String](IO.pure(req)).assertEquals("hello")
+    @nowarn("cat=deprecation")
+    val test = client.expect[String](IO.pure(req)).assertEquals("hello")
+    test
   }
 
   test("Client should fetch request tasks with expectOr") {
@@ -172,7 +176,9 @@ class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   }
 
   test("Client should status returns the status for a request task") {
-    client.status(IO.pure(req)).assertEquals(Status.Ok)
+    @nowarn("cat=deprecation")
+    val test = client.status(IO.pure(req)).assertEquals(Status.Ok)
+    test
   }
 
   test("Client should successful returns the success of the status for a request") {
@@ -180,7 +186,9 @@ class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   }
 
   test("Client should successful returns the success of the status for a request task") {
-    client.successful(IO.pure(req)).assert
+    @nowarn("cat=deprecation")
+    val test = client.successful(IO.pure(req)).assert
+    test
   }
 
   test("Client should status returns the status for a request") {
@@ -188,7 +196,9 @@ class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   }
 
   test("Client should status returns the status for a request task") {
-    client.status(IO.pure(req)).assertEquals(Status.Ok)
+    @nowarn("cat=deprecation")
+    val test = client.status(IO.pure(req)).assertEquals(Status.Ok)
+    test
   }
 
   test("Client should successful returns the success of the status for a request") {
@@ -196,7 +206,9 @@ class ClientSyntaxSuite extends Http4sSuite with Http4sClientDsl[IO] {
   }
 
   test("Client should successful returns the success of the status for a request task") {
-    client.successful(IO.pure(req)).assert
+    @nowarn("cat=deprecation")
+    val test = client.successful(IO.pure(req)).assert
+    test
   }
 
   test(
diff --git a/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerMtlsSuite.scala b/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerMtlsSuite.scala
index 4435b3895e..884d719446 100644
--- a/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerMtlsSuite.scala
+++ b/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerMtlsSuite.scala
@@ -106,7 +106,7 @@ class EmberServerMtlsSuite extends Http4sSuite {
     (server(tlsParams), client(clientTlsContext)).mapN(FunFixture.map2(_, _))
 
   private def client(tlsContextResource: Resource[IO, TLSContext[IO]]) =
-    ResourceFixture(clientResource(tlsContextResource))
+    ResourceFunFixture(clientResource(tlsContextResource))
 
   private def clientResource(tlsContextResource: Resource[IO, TLSContext[IO]]) =
     for {
@@ -119,7 +119,7 @@ class EmberServerMtlsSuite extends Http4sSuite {
     } yield emberClient
 
   private def server(tlsParams: TLSParameters) =
-    ResourceFixture(serverResource(tlsParams))
+    ResourceFunFixture(serverResource(tlsParams))
 
   private def serverResource(tlsParams: TLSParameters) =
     for {
diff --git a/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerWebSocketSuite.scala b/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerWebSocketSuite.scala
index 0beb941e90..d7ccc831e0 100644
--- a/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerWebSocketSuite.scala
+++ b/ember-server/jvm/src/test/scala/org/http4s/ember/server/EmberServerWebSocketSuite.scala
@@ -83,7 +83,7 @@ class EmberServerWebSocketSuite extends Http4sSuite with DispatcherIOFixture {
       .withHttpWebSocketApp(service[IO])
       .build
 
-  private def fixture = (ResourceFixture(serverResource), dispatcher).mapN(FunFixture.map2(_, _))
+  private def fixture = (ResourceFunFixture(serverResource), dispatcher).mapN(FunFixture.map2(_, _))
 
   sealed case class Client(
       waitOpen: Deferred[IO, Option[Throwable]],
diff --git a/ember-server/shared/src/test/scala/org/http4s/ember/server/ConnectionSuite.scala b/ember-server/shared/src/test/scala/org/http4s/ember/server/ConnectionSuite.scala
index ab4eb003e8..cdaad0113e 100644
--- a/ember-server/shared/src/test/scala/org/http4s/ember/server/ConnectionSuite.scala
+++ b/ember-server/shared/src/test/scala/org/http4s/ember/server/ConnectionSuite.scala
@@ -89,7 +89,7 @@ class ConnectionSuite extends Http4sSuite {
   private def fixture(
       idleTimeout: FiniteDuration = 60.seconds,
       headerTimeout: FiniteDuration = 60.seconds,
-  ) = ResourceFixture(
+  ) = ResourceFunFixture(
     for {
       server <- serverResource(idleTimeout, headerTimeout)
       client <- clientResource(server.address)
diff --git a/ember-server/shared/src/test/scala/org/http4s/ember/server/EmberServerSuite.scala b/ember-server/shared/src/test/scala/org/http4s/ember/server/EmberServerSuite.scala
index e7e2411982..7070f8c520 100644
--- a/ember-server/shared/src/test/scala/org/http4s/ember/server/EmberServerSuite.scala
+++ b/ember-server/shared/src/test/scala/org/http4s/ember/server/EmberServerSuite.scala
@@ -54,9 +54,9 @@ class EmberServerSuite extends Http4sSuite {
       .withHttpApp(service[IO])
       .build
 
-  private val client = ResourceFixture(EmberClientBuilder.default[IO].build)
+  private val client = ResourceFunFixture(EmberClientBuilder.default[IO].build)
 
-  private def server(receiveBufferSize: Int = 256 * 1024) = ResourceFixture(
+  private def server(receiveBufferSize: Int = 256 * 1024) = ResourceFunFixture(
     EmberServerBuilder
       .default[IO]
       .withHttpApp(service[IO])
diff --git a/tests/jvm/src/main/scala/org/http4s/Http4sSuitePlatform.scala b/tests/jvm/src/main/scala/org/http4s/Http4sSuitePlatform.scala
index 3351e84ad0..b226363d99 100644
--- a/tests/jvm/src/main/scala/org/http4s/Http4sSuitePlatform.scala
+++ b/tests/jvm/src/main/scala/org/http4s/Http4sSuitePlatform.scala
@@ -16,15 +16,7 @@
 
 package org.http4s
 
-import cats.effect.IO
-import cats.effect.Resource
-
 trait Http4sSuitePlatform { this: Http4sSuite =>
-
-  def resourceSuiteFixture[A](name: String, resource: Resource[IO, A]) = registerSuiteFixture(
-    ResourceSuiteLocalFixture(name, resource)
-  )
-
   // allow flaky tests on ci
-  override def munitFlakyOK = sys.env.contains("CI")
+  override def munitFlakyOK: Boolean = sys.env.contains("CI")
 }
diff --git a/tests/shared/src/main/scala/org/http4s/Http4sSuite.scala b/tests/shared/src/main/scala/org/http4s/Http4sSuite.scala
index 5cce9d896b..2ea7d3823b 100644
--- a/tests/shared/src/main/scala/org/http4s/Http4sSuite.scala
+++ b/tests/shared/src/main/scala/org/http4s/Http4sSuite.scala
@@ -22,7 +22,7 @@ import cats.syntax.all._
 import fs2._
 import fs2.text.utf8
 import munit._
-import org.scalacheck.Prop
+import munit.catseffect._
 
 /** Common stack for http4s' munit based tests
   */
@@ -32,23 +32,17 @@ trait Http4sSuite
     with munit.ScalaCheckEffectSuite
     with Http4sSuitePlatform {
 
-  private[this] val suiteFixtures = List.newBuilder[Fixture[_]]
+  private[this] val suiteFixtures = List.newBuilder[IOFixture[_]]
 
-  override def munitFixtures: Seq[Fixture[_]] = suiteFixtures.result()
+  override def munitFixtures: Seq[IOFixture[_]] = suiteFixtures.result()
 
-  // Override to remove implicit modifier
-  override def unitToProp: Unit => Prop = super.unitToProp
-
-  // Scala 3 likes this better
-  implicit def saneUnitToProp(unit: Unit): Prop = unitToProp(unit)
-
-  def registerSuiteFixture[A](fixture: Fixture[A]): Fixture[A] = {
+  def registerSuiteFixture[A](fixture: IOFixture[A]): IOFixture[A] = {
     suiteFixtures += fixture
     fixture
   }
 
-  def resourceSuiteDeferredFixture[A](name: String, resource: Resource[IO, A]): Fixture[IO[A]] =
-    registerSuiteFixture(UnsafeResourceSuiteLocalDeferredFixture(name, resource))
+  def resourceSuiteFixture[A](name: String, resource: Resource[IO, A]): IOFixture[A] =
+    registerSuiteFixture(ResourceSuiteLocalFixture(name, resource))
 
   implicit class ParseResultSyntax[A](self: ParseResult[A]) {
     def yolo: A = self.valueOr(e => sys.error(e.toString))
diff --git a/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala b/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
index 6958418f42..be07923f1b 100644
--- a/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
+++ b/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
@@ -23,6 +23,6 @@ import munit.CatsEffectSuite
 
 trait DispatcherIOFixture { this: CatsEffectSuite =>
 
-  def dispatcher: SyncIO[FunFixture[Dispatcher[IO]]] = ResourceFixture(Dispatcher[IO])
+  def dispatcher: SyncIO[FunFixture[Dispatcher[IO]]] = ResourceFunFixture(Dispatcher[IO])
 
 }
diff --git a/tests/shared/src/test/scala/org/http4s/UriSpec.scala b/tests/shared/src/test/scala/org/http4s/UriSpec.scala
index 5eade5f24e..41d5ef56e5 100644
--- a/tests/shared/src/test/scala/org/http4s/UriSpec.scala
+++ b/tests/shared/src/test/scala/org/http4s/UriSpec.scala
@@ -37,7 +37,6 @@ import org.scalacheck.Gen
 import org.scalacheck.Prop._
 import org.typelevel.ci._
 
-import java.nio.file.Paths
 import scala.collection.immutable.Seq
 
 // TODO: this needs some more filling out
@@ -1166,8 +1165,8 @@ class UriSpec extends Http4sSuite {
     forAll(pathGen) { (input: String) =>
       val prefix = "/this/isa/prefix/"
       val processed = Uri.removeDotSegments(Uri.Path.unsafeFromString(input)).renderString
-      val path = Paths.get(prefix, processed).normalize
-      assert(path.startsWith(Paths.get(prefix)))
+      val path = (fs2.io.file.Path(prefix) / processed).normalize
+      assert(path.startsWith(fs2.io.file.Path(prefix)))
       assert(!processed.contains("./"))
       assert(!processed.contains("../"))
     }