sergeykolbasov on master
Update cats-effect to 2.3.3 (#2… (compare)
sergeykolbasov on master
Update sbt-mdoc to 2.2.18 (#253) (compare)
package oh.boi
. I put wow.doge
in one of my projects.
val loggers: PartialFunction[String, Logger[IO]] = {
case "org.asynchttpclient.netty.channel.DefaultChannelPool" =>
consoleLogger[IO](minLevel = Level.Warn)
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
}
Suppose I wanted to catch all classes under org.asynchttpclient under one case clause.2 - Is the logger instance supposed to be passed around, or should each class create it's own logger instance?
3 - I've been unable to integrate the Resource monad in my code that uses Monix Task, so I'm using the unsafe method for getting the logger instance -
val logger = consoleLogger().withAsyncUnsafe()
But I'm not sure what unsafe means here, does it refer to flushing the loggers buffer? Is it possible to flush the buffer in a shutdown hook thus making it safe?
Hey @rohan-sircar, thanks for the questions
val loggers: PartialFunction[String, Logger[IO]] = {
case asyncHttpClient if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
consoleLogger[IO](minLevel = Level.Warn)
case _ => //if wildcard case isn't provided, default logger is no-op
consoleLogger[IO]()
}
I'd suggest to have a single instance for everything, as it's just easier to manage. But you might have different needs for different classes, and in case you can't just route the message using stuff from io.odin.config._
to different loggers, just provide another instance
Resource
is hard to integrate for sure. What I could suggest is to have on the very top of you application (i.e. TaskApp.main
) a Resource
monad that you .use(_ => Task.never)
. Often you start multiple resources there, such as HTTP clients, some I/O channels like database connection etc etc. So instead of encapsulating all the I/O
initialization inside of implementations, making them side-effectful by that, you do that in the main. In Odin .unsafe
stuff doesn't use the shutdown hook sadly, but you could make it on your own though
Resource
that way, but without the .use(_ => Task.never)
part, it just free'd all the resources once the control reached the end of the Task
chain, and since I'm new to effect programming I was wondering what's up :)def makePrimaryStage(
backend: AppTypes.HttpBackend,
actorSystem: classic.ActorSystem
) = {
new PrimaryStage {
scene = new DefaultUI().scene
onCloseRequest = () => {
val f2 = actorSystem.terminate()
val f1 = backend.close().runToFuture
......
}
}
}
It's a JavaFX application. I was thinking I could close the logger here, but what method am I supposed to use? I couldn't find anything named stop, terminate or flush. Maybe I should go through the API docs.Can't you just extend i.e. TaskApp
or IOApp
in the main object of you application and run the resource allocation + .use
in the main
method?
it'll take care of the shutdown hook on its own for all the running resources.
Ie, actor system could be allocated as a resource as well, something like:
val acquire: Task[ActorSystem] = Task(createActorSystem)
def release(actorSystem: ActorSystem): Task[Unit] = Task(actorSystem.terminate())
Resource.make(acquire)(release) // Resource[Task, ActorSystem]
def init[F[_]: ConcurrentEffect: Timer: ContextShift]: Resource[F, Unit] =
for {
config <- Config.load[F]
logger <- InternalLogger.init[F](config.app)
oauthService <- OAuthService.init(config.oauth)
storageService = StorageService.noop[F]
contentService = ContentService.noop[F]
routes = Routes.all(storageService, contentService)
httpServer <- HttpServer.init(config.http, routes, logger)
_ <- httpServer.run
} yield {
()
}
Wouldn't this make it a little boilerplate-y, since almost every class would need to have the logger in it's constructor?
Yeah, it does. You might use implicit argument in constructor for it if you wish. On the positive side, you have a full control over the logger from outside of the class, would it be test, benchmarks or whatever. It's called composition, and composition is nice :)
Composition and greater control are what made me want to switch to using IO monads and ditch OO based DI and FXML injection, but the problem in this case is that I am trying to interface with a framework that doesn't support effect monads(JavaFX), and since I'm new to IO monads it's going to take me a while to figure things out. My goal was to try and run green threads on the JavaFX application thread to avoid nested Platform.runLater() calls.
I am unable to use IOApp/TaskApp because I need to use JFXApp, adding both causes a conflict in the main methods. This is what my main class looks like right now -
object Main extends JFXApp {
val logger = consoleLogger().withAsyncUnsafe()
lazy val schedulers: Schedulers = new Schedulers()
implicit lazy val defaultScheduler: Scheduler = schedulers.fx
lazy val backendTask = AsyncHttpClientMonixBackend()
lazy val actorSystemTask = Task {
classic.ActorSystem(
name = "FXActorSystem"
)
}
lazy val application = for {
_ <- logger.info("Starting application..")
backend <- backendTask
actorSystem <- actorSystemTask
appStage <- Task { makePrimaryStage(backend, actorSystem) }
_ <- Task {
// this stage refers to implicit jfx stage
// probably makes this impure, but I can't think of a better way right now
stage = appStage
}
... other stuff
} yield ()
application.timed.runToFuture
.onComplete(res =>
res match {
case Failure(exception) => {
println("Application start failed. Reason -")
exception.printStackTrace()
}
case Success((duration, _)) =>
println(
s"Application started successfully in ${duration.toSeconds} seconds"
)
}
)
If I wanted to switch to resource, I would have to see if .use(_ => Task.never)
works with the FX application thread or not.
Random educated guess, since I've never worked with ScalaFX
Try to pack the instance of JFXApp
into resource itself instead of extending it in your main. It seems that you need only stage
variable in the end, and run your application as a TaskApp
instead.
Essentially, my question is if you really must extend JFXApp
in your main object, and if you don't then you have all the power to do whatever you want
Try to pack the instance of JFXApp into resource itself instead of extending it in your main.
Thanks, this was the key. I had thought about getting rid of JFXApp before, but I couldn't figure out how. Apparently, since it's a trait it can be instantiated like a regular class.
I experimented a lot with it yesterday and I finally managed to account for everything.
object Main extends TaskApp {
override def run(args: List[String]): Task[ExitCode] = {
lazy val appResource = for {
logger <- consoleLogger().withAsync()
backend <- AsyncHttpClientMonixBackend.resource()
actorSystem <-
Resource.make(logger.info("Creating Actor System") >> Task {
classic.ActorSystem(
name = "FXActorSystem"
)
})(sys =>
logger.info("Shutting down actor system") >> Task.fromFuture(
sys.terminate()
) >> logger.info("Actor System terminated")
)
fxApp <- Resource.make(logger.info("Creating FX Application") >> Task {
val app: JFXApp = new JFXApp {
//fx scheduler
val application =
for {
appStage <- logger.info("Inside JFX application stage") >> Task(
makePrimaryStage(backend, actorSystem)
)
_ <- Task { stage = appStage }
// other stuff
}
} yield ()
application.timed.runAsync( ... )
override def stopApp() = {
Platform.exit()
// System.exit(0) not required
}
}
app
})(app => logger.info("Stopping FX Application") >> Task(app.stopApp()))
} yield (fxApp)
appResource
.use(fxApp => Task(fxApp.main(args.toArray)))
.as(ExitCode.Success)
}
Just as you said, it's a TaskApp main class, with a Resource
chain instantiating all the resources and passing them to my fx app, which has a task chain with all my previous app code. Everything else works the same as before.
An interesting thing to note is how the resource chain must be blocked with FXApp#main
. This blocks it until the close button of the GUI is pressed. Everything is shut down properly after that. Using Task.never
breaks the close button.
Resource
whereas the loggers
field of the bridge expects a Logger[IO]
Hi @rohan-sircar
Regarding the first one. How many logs do you generate? It might be possible that queue grows faster than it's cleared, so the heap would predictably grow.
You can have a file logger in external bridge I think, although it's tricky.
Run fileLogger.allocated
to get a pair of acquire-release tokens F[(Logger[F], F[Unit])]
, then just .unsafeRunSync
i.e. (in case you're using IO
) to get a pair of (Logger[F], IO[Unit])
.
Voilà, here is your logger. I'd also add a shutdown hook to the JVM to call the second part: IO[Unit]
on system exit, which is a cancellation token that safely releases resource.
Resource
-based loggers for SLF4J