async def coro(runner, interval):
     log.info(f"started logger on hostname {socket.gethostname()}")
     learner = runner.learner
     npoints_start = _get_npoints(learner)
     log.info("npoints at start", npoints=npoints_start)
     while runner.status() == "running":
         await asyncio.sleep(interval)
         info = _get_log_entry(runner, npoints_start)
         log.info("current status", **info)
     log.info("runner status changed", status=runner.status())
     log.info("current status", **_get_log_entry(runner, npoints_start))
def _get_log_entry(runner: AsyncRunner, npoints_start: int) -> Dict[str, Any]:
    learner = runner.learner
    info: Dict[str, Union[int, float, str]] = {}
    Δt = datetime.timedelta(seconds=runner.elapsed_time())
    info["elapsed_time"] = str(Δt)
    info["overhead"] = runner.overhead()
    npoints = _get_npoints(learner)
    if npoints is not None:
        info["npoints"] = _get_npoints(learner)
        Δnpoints = npoints - npoints_start
        with suppress(ZeroDivisionError):
            # Δt.seconds could be zero if the job is done when starting
            info["npoints/s"] = Δnpoints / Δt.seconds
    with suppress(Exception):
        info["latest_loss"] = learner._cache["loss"]
    with suppress(AttributeError):
        info["nlearners"] = len(learner.learners)
        if "npoints" in info:
            info["npoints/learner"] = info["npoints"] / info["nlearners"]
    info["cpu_usage"] = psutil.cpu_percent()
    info["mem_usage"] = psutil.virtual_memory().percent
    return info