docs: 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)