goharbor/harbor · error · Exception

log level must be one of debug, info, warning, error, fatal

Error message

log level must be one of debug, info, warning, error, fatal

What it means

Thrown by parse_yaml_config() in make/photon/prepare/utils/configs.py when harbor.yml's 'log.level' is not one of the literal strings debug, info, warning, error, fatal. The membership test runs on the raw value before log_level.lower() is applied, so the check is case-sensitive: 'INFO' or 'Info' fail even though they look correct. 'warn' (a common shorthand elsewhere) also fails — Harbor requires 'warning'.

Source

Thrown at make/photon/prepare/utils/configs.py:247

    value = config_dict["max_job_duration_hours"]
    if not isinstance(value, int) or value < 24:
        config_dict["max_job_duration_hours"] = 24
    config_dict['job_loggers'] = js_config["job_loggers"]
    config_dict['logger_sweeper_duration'] = js_config["logger_sweeper_duration"]
    config_dict['jobservice_secret'] = generate_random_string(16)

    # notification config
    notification_config = configs.get('notification') or {}
    config_dict['notification_webhook_job_max_retry'] = notification_config["webhook_job_max_retry"]
    config_dict['notification_webhook_job_http_client_timeout'] = notification_config["webhook_job_http_client_timeout"]

    # Log configs
    allowed_levels = ['debug', 'info', 'warning', 'error', 'fatal']
    log_configs = configs.get('log') or {}

    log_level = log_configs['level']
    if log_level not in allowed_levels:
        raise Exception('log level must be one of debug, info, warning, error, fatal')
    config_dict['log_level'] = log_level.lower()

    # parse local log related configs
    local_logs = log_configs.get('local') or {}
    if local_logs:
        config_dict['log_location'] = local_logs.get('location') or '/var/log/harbor'
        config_dict['log_rotate_count'] = local_logs.get('rotate_count') or 50
        config_dict['log_rotate_size'] = local_logs.get('rotate_size') or '200M'

    # parse external log endpoint related configs
    if log_configs.get('external_endpoint'):
        config_dict['log_external'] = True
        config_dict['log_ep_protocol'] = log_configs['external_endpoint']['protocol']
        config_dict['log_ep_host'] = log_configs['external_endpoint']['host']
        config_dict['log_ep_port'] = log_configs['external_endpoint']['port']
    else:
        config_dict['log_external'] = False

View on GitHub (pinned to 7b2fd08cc5)

Solutions

  1. Set the level to an exact lowercase value from the allowed set, e.g. 'info' or 'debug'
  2. Use 'warning' instead of 'warn'
  3. Re-run ./install.sh

Example fix

# harbor.yml (before)
log:
  level: INFO

# harbor.yml (after)
log:
  level: info
Defensive patterns

Strategy: validation

Validate before calling

ALLOWED = {'debug', 'info', 'warning', 'error', 'fatal'}
level = (cfg.get('log') or {}).get('level', 'info')
if level not in ALLOWED:
    raise SystemExit('log.level %r must be one of %s (lowercase)' % (level, sorted(ALLOWED)))

Prevention

When it happens

Trigger: harbor.yml contains log: level: INFO (uppercase), level: warn, or a typo like 'eror'. The comparison log_level not in ['debug','info','warning','error','fatal'] is exact-match on the raw YAML string.

Common situations: Values copied from docker logging drivers or logrus examples that use uppercase or 'warn'; editing generated configs where other sections accept mixed case; template variables injecting 'DEBUG' from CI environment variables.

Related errors


AI-assisted analysis of goharbor/harbor@7b2fd08cc5 (2026-08-16). Data as JSON: /api/errors/71c9c43d77437167. Report an issue: GitHub.