{"record":{"id":"0dd649f0ad3a6848","repo":"nsqio/nsq","slug":"missing-rev-in-filename-format-when-gzip-rota","errorCode":null,"errorMessage":"missing <REV> in --filename-format when gzip, rotation, or work dir enabled","messagePattern":"missing <REV> in --filename-format when gzip, rotation, or work dir enabled","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/nsq_to_file/file_logger.go","lineNumber":407,"sourceCode":"}\n\nfunc computeFilenameFormat(opts *Options, topic string) (string, error) {\n\thostname, err := os.Hostname()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tshortHostname := strings.Split(hostname, \".\")[0]\n\n\tidentifier := shortHostname\n\tif len(opts.HostIdentifier) != 0 {\n\t\tidentifier = strings.ReplaceAll(opts.HostIdentifier, \"<SHORT_HOST>\", shortHostname)\n\t\tidentifier = strings.ReplaceAll(identifier, \"<HOSTNAME>\", hostname)\n\t}\n\n\tcff := opts.FilenameFormat\n\tif opts.GZIP || opts.RotateSize > 0 || opts.RotateInterval > 0 || opts.WorkDir != opts.OutputDir {\n\t\tif !strings.Contains(cff, \"<REV>\") {\n\t\t\treturn \"\", errors.New(\"missing <REV> in --filename-format when gzip, rotation, or work dir enabled\")\n\t\t}\n\t} else {\n\t\t// remove <REV> as we don't need it\n\t\tcff = strings.ReplaceAll(cff, \"<REV>\", \"\")\n\t}\n\tcff = strings.ReplaceAll(cff, \"<TOPIC>\", topic)\n\tcff = strings.ReplaceAll(cff, \"<HOST>\", identifier)\n\tcff = strings.ReplaceAll(cff, \"<PID>\", fmt.Sprintf(\"%d\", os.Getpid()))\n\tif opts.GZIP && !strings.HasSuffix(cff, \".gz\") {\n\t\tcff = cff + \".gz\"\n\t}\n\n\treturn cff, nil\n}\n","sourceCodeStart":389,"sourceCodeEnd":422,"githubUrl":"https://github.com/nsqio/nsq/blob/85cf10c09c6c3c86160d6f0eb156f62d0efc1648/apps/nsq_to_file/file_logger.go#L389-L422","documentation":"nsq_to_file builds output filenames from --filename-format by substituting <TOPIC>, <HOST>, <PID> and <REV>. When gzip (-gzip), size/interval rotation (-rotate-size/-rotate-interval) or a separate work dir (-work-dir != -output-dir) is enabled, the <REV> placeholder is mandatory because each rotation/gzip cycle must produce a distinct filename via a monotonically increasing revision number. Without it, rotated files would overwrite each other, so generateFileName refuses to run.","triggerScenarios":"Starting nsq_to_file with a custom --filename-format that omits <REV> (e.g. '<TOPIC>.log') while any of --gzip, --rotate-size > 0, --rotate-interval > 0, or --work-dir different from --output-dir is set. Without rotation features <REV> is simply stripped from the format, which is why the check only fires in this branch.","commonSituations":"Customizing filename-format for log-shipping pipelines (e.g. to feed fluentd/logrotate) and then later enabling gzip or rotation; setting -work-dir for atomic rename-based writes while keeping a pretty filename format.","solutions":["Add <REV> to --filename-format, e.g. --filename-format=<TOPIC>.<HOST>.<PID>.<REV>.log","Keep <REV> adjacent to the extension so rotated files sort naturally (<REV> expands to .000000001-style zero-padded numbers).","If you truly never rotate, disable --gzip, set --rotate-size=0 and --rotate-interval=0, and leave --work-dir equal to --output-dir so <REV> becomes optional."],"exampleFix":"# before\nnsq_to_file --topic=test --channel=ch --filename-format=<TOPIC>.<HOST>.log --gzip --output-dir=/data\n# after\nnsq_to_file --topic=test --channel=ch --filename-format=<TOPIC>.<HOST>.<REV>.log.gz --gzip --output-dir=/data","handlingStrategy":"validation","validationCode":"func validateFilenameFormat(format string, gzip bool, rotateSize, rotateInterval int64, workDir, outputDir string) error {\n\tneedsRev := gzip || rotateSize > 0 || rotateInterval > 0 || workDir != outputDir\n\tif needsRev && !strings.Contains(format, \"<REV>\") {\n\t\treturn fmt.Errorf(\"filename-format %q needs <REV> when gzip/rotation/work-dir enabled\", format)\n\t}\n\treturn nil\n}\n\n// run before exec'ing nsq_to_file\nif err := validateFilenameFormat(*filenameFormat, *gzip, *rotateSize, *rotateInterval, *workDir, *outputDir); err != nil {\n\tlog.Fatal(err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardize on --filename-format=<TOPIC>.<HOST>.<PID>.<REV>.log in shared templates so <REV> is never omitted.","When enabling gzip or rotation later on an existing deployment, diff the full flag set against the filename format."],"tags":["go","nsq","cli","config","file-rotation","gzip"],"backgroundTag":null,"analyzedSha":"85cf10c09c6c3c86160d6f0eb156f62d0efc1648","analyzedAt":"2026-08-16T00:53:05.009Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}