def setup(
            cls,
            debug=UNSET,
            dryrun=UNSET,
            level=UNSET,
            appname=UNSET,
            basename=UNSET,
            clean_handlers=UNSET,
            context_format=UNSET,
            dev=UNSET,
            greetings=UNSET,
            timezone=UNSET,
            tmp=UNSET,
            console_format=UNSET,
            console_level=UNSET,
            console_stream=UNSET,
            file_format=UNSET,
            file_level=UNSET,
            file_location=UNSET,
            locations=UNSET,
            rotate=UNSET,
            rotate_count=UNSET,
    ):
        """
        Args:
            debug (bool): Enable debug level logging (overrides other specified levels)
            dryrun (bool): Enable dryrun
            level (int | None): Shortcut to set both `console_level` and `file_level` at once
            appname (str | unicode | None): Program's base name, not used directly, just as reference for default 'basename'
            basename (str | unicode | None): Base name of target log file, not used directly, just as reference for default 'locations'
            clean_handlers (bool): Remove any existing logging.root.handlers
            context_format (str | unicode | None): Format to use for contextual log, use None to deactivate
            dev (str | unicode | None): Custom folder to use when running from a development venv (auto-determined if None)
            greetings (str | unicode | list[str | unicode] | None): Optional greetings message(s) to log
            timezone (str | unicode | None): Time zone, use None to deactivate time zone logging
            tmp (str | unicode | None): Optional temp folder to use (auto determined)
            console_format (str | unicode | None): Format to use for console log, use None to deactivate
            console_level (int | None): Level to use for console logging
            console_stream (TextIOWrapper | None): Stream to use for console log (eg: sys.stderr), use None to deactivate
            file_format (str | unicode | None): Format to use for file log, use None to deactivate
            file_level (str | unicode | None): Level to use for file logging
            file_location (str | unicode | None): Desired custom file location (overrides {locations} search, handy as a --log cli flag)
            locations (list[str | unicode]|None): List of candidate folders for file logging (None: deactivate file logging)
            rotate (str | unicode | None): How to rotate log file (None: no rotation, "time:1d" time-based, "size:50m" size-based)
            rotate_count (int): How many rotations to keep
        """
        with cls._lock:
            if cls.handlers is not None:
                raise Exception("Please call runez.log.setup() only once")

            cls.handlers = []
            if dryrun is not UNSET:
                if dryrun and (debug is None or debug is UNSET):
                    # Automatically turn debug on (if not explicitly specified) with dryrun,
                    # as many of the "Would ..." messages are at debug level
                    debug = True
                runez.system.set_dryrun(dryrun)

            cls.debug = debug
            cls.spec.set(
                appname=appname,
                basename=basename,
                clean_handlers=clean_handlers,
                context_format=context_format,
                dev=dev,
                greetings=greetings,
                timezone=timezone,
                tmp=tmp,
                console_format=console_format,
                console_level=console_level or level,
                console_stream=console_stream,
                file_format=file_format,
                file_level=file_level or level,
                file_location=file_location,
                locations=locations,
                rotate=rotate,
                rotate_count=rotate_count,
            )

            cls._auto_fill_defaults()

            if debug:
                root_level = logging.DEBUG
                cls.spec.console_level = logging.DEBUG
                cls.spec.file_level = logging.DEBUG

            else:
                root_level = min(flattened([cls.spec.console_level, cls.spec.file_level], split=SANITIZED))

            logging.root.setLevel(root_level)

            if cls.spec.clean_handlers:
                for h in logging.root.handlers:
                    logging.root.removeHandler(h)

            if cls.spec.console_stream and cls.spec.console_format:
                cls.console_handler = logging.StreamHandler(cls.spec.console_stream)
                cls._add_handler(cls.console_handler, cls.spec.console_format, cls.spec.console_level)

            if cls.spec.should_log_to_file:
                cls.actual_location = cls.spec.usable_location()
                if cls.actual_location:
                    cls.file_handler = _get_file_handler(cls.actual_location, cls.spec.rotate, cls.spec.rotate_count)
                    cls._add_handler(cls.file_handler, cls.spec.file_format, cls.spec.file_level)

            if cls.is_using_format("%(context)s"):
                cls.context.enable()

            cls._fix_logging_shortcuts()

            if cls.context.filter:
                for handler in cls.handlers:
                    handler.addFilter(cls.context.filter)

            if cls.spec.greetings:
                for msg in flattened(cls.spec.greetings, split=SANITIZED):
                    message = cls.formatted_greeting(msg)
                    if message:
                        LOG.debug(message)