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

Slf4j bridge - fiber propagation #718

Merged
merged 8 commits into from
May 16, 2023

Conversation

justcoon
Copy link
Contributor

@justcoon justcoon commented May 11, 2023

FiberRefs propagation using ThreadLocal

@adamgfraser
Copy link
Contributor

@justcoon Great working pushing this forward!

I think the question here is whether we need to support propagating all FiberRef values or can just propagate a known subset of FiberRef. For example, the LogContext itself is backed by a single FiberRef and is used to power the LogAnnotation data type. So we could make this a TrackingFiberRef and use a TrackingSupervisor so that these FiberRef values are propagated automatically and we don't need to use any special operator.

If we really do want to support propagating all FiberRef values then I think we will need an operator for this, perhaps withFiberRefs.

@justcoon
Copy link
Contributor Author

@adamgfraser
i think we need to propagate also some other FiberRef-s than LogContext, for example FiberRef.currentLogAnnotations, FiberRef.currentLogSpan

also user of library itself can create own FiberRef, which may be used in logging
but, i think it could be good if we will not propagate all FiberRef-s, we should probably exclude ZEnvironment

but i am not sure if there are some functions in FiberRefs, which can be easily exclude multiple FiberRef-s from FiberRefs with one call

@adamgfraser
Copy link
Contributor

@justcoon Okay, I think we can propagate the FiberRefs without the user having to use any special operator like this:

object Example extends ZIOAppDefault {

  /**
    * Accesses the current `FiberRefs` of the fiber executing this code. This
    * will only return the current `FiberRefs` if called from code being
    * executed by a ZIO fiber with `RuntimeFlag.CurrentFiber` enabled.
    */
  def unsafeGetFiberRefs()(implicit unsafe: Unsafe): Option[FiberRefs] = {
    val currentFiber = Fiber._currentFiber.get()
    if (currentFiber eq null) None
    else Some(currentFiber.unsafe.getFiberRefs())
  }

  override val bootstrap =
    Runtime.enableCurrentFiber

  val run =
    for {
      fiberRef <- FiberRef.make(0)
      _        <- fiberRef.update(_ + 42)
      _        <- ZIO.debug(unsafeGetFiberRefs()(Unsafe.unsafe))
    } yield ()
}

@justcoon justcoon marked this pull request as ready for review May 15, 2023 18:57
@justcoon justcoon requested a review from a team as a code owner May 15, 2023 18:57
@@ -41,6 +41,10 @@ import zio.logging.consoleJsonLogger
val logger = Runtime.removeDefaultLoggers >>> consoleJsonLogger() >+> Slf4jBridge.initialize
```

To enable log annotations and span propagation to underlying code which using SLF4J api,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we build the current fiber integration integration into an existing layer such as Slf4jBridge.initialize? It is great to have the ability to customize when necessary but it is also good to make common use cases easy and enableCurrentFiber is really an implementation detail where if the user is going to do something that requires it (basically propagating context from FiberRef values to ThreadLocal values we should just do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i think we can put it to Slf4jBridge.initialize
however there should be option to disable that as enableCurrentFiber may have impact to performance

which probably ends in something like

object Slf4jBridge {

  def initialize(propagateFibers: Boolean = true): ZLayer[Any, Nothing, Unit] = ???

Copy link
Contributor

@adamgfraser adamgfraser May 16, 2023

Choose a reason for hiding this comment

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

Makes sense. I might split it into two layers so it can be a val and you don't need the empty parentheses when you call it.

object Slf4jBridge {

  val initialize: ZLayer[Any, Nothing, Unit] =
    ???

  val initializeWithoutFiberRefPropagation: ZLayer[Any, Nothing, Unit] =
    ???
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@adamgfraser

i added layers as you proposed, probably it is good that this propagation is enabled by default,
however it is also changing default behavior

Copy link
Contributor

Choose a reason for hiding this comment

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

@justcoon Sounds good.My thought is that EnableCurrentFiber is necessary for integration between FiberRef and ThreadLocal values and that is exactly what you are asking us to do with the Slf4jBridge so that makes sense as the default and will give the "correct", though potentially not as performant value, so that should be the default. But if you think we should do it the other way definitely open to your thoughts on that.

Otherwise looks good to me subject to my comment about avoiding unnecessary ZIO workflows which we will handle separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thank you very much for hints and review

@@ -43,7 +43,13 @@ class ZioLoggerFactory extends ILoggerFactory {
private[impl] def run(f: ZIO[Any, Nothing, Any]): Unit =
if (runtime != null) {
zio.Unsafe.unsafe { implicit u =>
runtime.unsafe.run(f)
runtime.unsafe.run {
val fiberRefs = Fiber.currentFiber().map(_.asInstanceOf[Fiber.Runtime[_, _]].unsafe.getFiberRefs())
Copy link
Contributor

Choose a reason for hiding this comment

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

You are within the ZIO name space so you could just use _currentFiber here to avoid the need for the cast. I will also look at specializing the return type of currentFiber.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i changed it slf4j v2 bridge to use _currentFiber, as it is in zio namespace

runtime.unsafe.run {
val fiberRefs = Fiber.currentFiber().map(_.asInstanceOf[Fiber.Runtime[_, _]].unsafe.getFiberRefs())
fiberRefs match {
case Some(fiberRefs) => ZIO.setFiberRefs(fiberRefs) *> f
Copy link
Contributor

Choose a reason for hiding this comment

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

This pattern of running a ZIO workflow just to log something is not very efficient. We can get the current loggers from the current FiberRefs and then we can just use those loggers to log directly without going through a ZIO workflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, i think you are right, we can probably do it better,
probably by using Fiber.log
but i propose to do it in next PR

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@adamgfraser adamgfraser left a comment

Choose a reason for hiding this comment

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

Much cleaner without the need for any new operators!

Made a couple of suggestions for further improvements.

@justcoon justcoon merged commit 27df706 into zio:master May 16, 2023
@justcoon justcoon deleted the slf4j_bridge_fiber_propagation branch May 26, 2023 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants