async def __init__(hub):
    hub.log.LOGGER = {}
    hub.log.HANDLER = []
    hub.log.INT_LEVEL = hub.lib.logging.INFO

    # Set up aliases for each log function
    hub.log.trace = hub.log.init.trace
    hub.log.info = hub.log.init.info
    hub.log.debug = hub.log.init.debug
    hub.log.error = hub.log.init.error
    hub.log.warning = hub.log.init.warning
    hub.log.warn = hub.log.warning
    hub.log.critical = hub.log.init.critical
    hub.log.fatal = hub.log.critical


async def get_caller(hub) -> tuple[str, int]:
    """
    Inspect the stack and get the function that called get_caller
    """
    try:
        # Grab the third ref back, fall back to closer refs
        ref, stack_summary = (hub._call_stack.get()[-3:] or [None])[0]
        filename, lineno, *_ = stack_summary
        return ref, lineno
    except Exception:
        return "hub", 0


async def get_logger(hub, name: str):
    """
    Create a logger for the given ref with all the configured handlers
    """
    if name not in hub.log.LOGGER:
        if hub.log.HANDLER:
            logger = hub.lib.aiologger.Logger(name=name, level=hub.log.INT_LEVEL)
            for handler in hub.log.HANDLER:
                handler.level = hub.log.INT_LEVEL
                logger.add_handler(handler)
        else:
            logger = hub.lib.aiologger.Logger.with_default_handlers(name=name, level=hub.log.INT_LEVEL)

        hub.log.LOGGER[name] = logger

    return hub.log.LOGGER[name]


async def setup(hub, log_plugin: str = "init", *, log_level: str, log_file: str, **kwargs):
    """
    Initialize the logger with the named plugin
    """
    if hub.log.HANDLER:
        # We already set up the logger
        return

    # Set up trace logger
    hub.lib.aiologger.levels.LEVEL_TO_NAME[5] = "TRACE"
    levels = hub.lib.logging.getLevelNamesMapping()
    levels["TRACE"] = 5
    hub.lib.logging.addLevelName(5, "TRACE")

    # Convert log level to integer
    if str(log_level).isdigit():
        hub.log.INT_LEVEL = int(log_level)
    else:
        hub.log.INT_LEVEL = levels[log_level.upper()]

    if log_plugin != "init":
        # Create the log file
        if log_file:
            path = await hub.lib.aiopath.Path(log_file).expanduser()
            if not await path.parent.exists():
                await path.parent.mkdir(parents=True, exist_ok=True)
            await path.touch(exist_ok=True)
            log_file = str(path)
        await hub.log[log_plugin].setup(log_file=log_file, **kwargs)


async def trace(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger._log(5, msg, args, **kwargs, extra={"lineno": lineno})


async def debug(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger.debug(msg, *args, **kwargs, extra={"lineno": lineno})


async def info(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger.info(msg, *args, **kwargs, extra={"lineno": lineno})


async def warning(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger.warning(msg, *args, **kwargs, extra={"lineno": lineno})


async def error(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger.error(msg, *args, **kwargs, extra={"lineno": lineno})


async def critical(hub, msg: str, *args, **kwargs):
    ref, lineno = await hub.log.init.get_caller()
    logger = await hub.log.init.get_logger(ref)
    await logger.critical(msg, *args, **kwargs, extra={"lineno": lineno})
