{"record":{"id":"7cc7dbe26ddc3971","repo":"goharbor/harbor","slug":"missing-logger-config-of-job","errorCode":null,"errorMessage":"missing logger config of job","messagePattern":"missing logger config of job","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/jobservice/config/config.go","lineNumber":368,"sourceCode":"\t\t}\n\n\t\tif _, err := url.Parse(c.PoolConfig.RedisPoolCfg.RedisURL); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid redis URL: %s\", err.Error())\n\t\t}\n\n\t\tif utils.IsEmptyStr(c.PoolConfig.RedisPoolCfg.Namespace) {\n\t\t\treturn errors.New(\"namespace of redis worker is required\")\n\t\t}\n\t}\n\n\t// Job service loggers\n\tif len(c.LoggerConfigs) == 0 {\n\t\treturn errors.New(\"missing logger config of job service\")\n\t}\n\n\t// Job loggers\n\tif len(c.JobLoggerConfigs) == 0 {\n\t\treturn errors.New(\"missing logger config of job\")\n\t}\n\n\treturn nil // valid\n}\n\n// MaxUpdateDuration the max time for an execution can be updated by task\nfunc MaxUpdateDuration() time.Duration {\n\tif DefaultConfig != nil && DefaultConfig.ReaperConfig != nil && DefaultConfig.ReaperConfig.MaxUpdateHour > 24 {\n\t\treturn time.Duration(DefaultConfig.ReaperConfig.MaxUpdateHour) * time.Hour\n\t}\n\treturn 24 * time.Hour\n}\n\n// MaxDanglingHour the max time for an execution can be dangling state\nfunc MaxDanglingHour() int {\n\tif DefaultConfig != nil && DefaultConfig.ReaperConfig != nil && DefaultConfig.ReaperConfig.MaxDanglingHour > 24*7 {\n\t\treturn DefaultConfig.ReaperConfig.MaxDanglingHour\n\t}","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/jobservice/config/config.go#L350-L386","documentation":"Config.validate() (src/jobservice/config/config.go:368) requires at least one job logger (c.JobLoggerConfigs, YAML key 'job_loggers'). These sinks persist per-job log data (what GetJobLogData later returns); with none configured, job output could not be stored, so startup aborts. The default Harbor config ships two job loggers.","triggerScenarios":"A jobservice config whose 'job_loggers' array is empty or missing while the service-level 'loggers' is populated.","commonSituations":"Hand-written configs that only copy the loggers section; values files that trim job_loggers; programmatic config builders that fill LoggerConfigs but not JobLoggerConfigs.","solutions":["Restore the default job_loggers block from Harbor's stock jobservice config template (FILE sink for job output plus a second logger)","When configuring via env/template, ensure the job_loggers array renders with at least one entry","Validate the rendered config in CI before deploy"],"exampleFix":"# before\njob_loggers: []\n# after\njob_loggers:\n  - name: FILE\n    level: INFO\n    sweeper_duration: 1\n    settings:\n      output_file: /var/log/jobs/job_logs\n    backend: FILE","handlingStrategy":"validation","validationCode":"if len(cfg.JobLoggerConfigs) == 0 {\n    return errors.New(\"jobservice config must define at least one job logger\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the default job_loggers block (FILE sink for job output) in every environment","Assert on len(JobLoggerConfigs) in config tests, mirroring Harbor's own config_test.go","Never hand-delete logger sections when trimming config"],"tags":["configuration","logging","jobservice","harbor","startup"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}