{"record":{"id":"98d4a185e7f7f3f6","repo":"router-for-me/CLIProxyAPI","slug":"invalid-log-file","errorCode":null,"errorMessage":"invalid log file","messagePattern":"invalid log file","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"internal/api/handlers/management/logs.go","lineNumber":876,"sourceCode":"\t}\n\treturn false\n}\n\nfunc logFileChangedAfterCursor(path string, cursor logCursor) bool {\n\tinfo, errStat := os.Stat(path)\n\tif errStat != nil || info.IsDir() || info.Size() == 0 {\n\t\treturn false\n\t}\n\treturn info.ModTime().UnixNano() > cursorModTimeUnixNano(cursor)\n}\n\nfunc logFileMatchesCursor(path string, cursor logCursor) (bool, bool, error) {\n\tinfo, errStat := os.Stat(path)\n\tif errStat != nil {\n\t\treturn false, false, errStat\n\t}\n\tif info.IsDir() {\n\t\treturn false, false, fmt.Errorf(\"invalid log file\")\n\t}\n\tif info.Size() < cursor.Offset {\n\t\treturn false, true, nil\n\t}\n\tboundary := cursorFingerprintBoundary(cursor)\n\tif info.Size() < boundary {\n\t\treturn false, true, nil\n\t}\n\tfingerprint, errFingerprint := logFileFingerprint(path, boundary)\n\tif errFingerprint != nil {\n\t\treturn false, false, errFingerprint\n\t}\n\treturn fingerprint == cursor.Fingerprint, false, nil\n}\n\nfunc encodeLogCursor(cursor logCursor) (string, error) {\n\traw, err := json.Marshal(cursor)\n\tif err != nil {","sourceCodeStart":858,"sourceCodeEnd":894,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/api/handlers/management/logs.go#L858-L894","documentation":"logFileMatchesCursor os.Stat'ed a path that is a directory, which cannot be a log file for cursor-based log listing/polling. The cursor logic compares size, modification time, and content fingerprints of regular files; a directory breaks those invariants, so it is rejected explicitly instead of returning misleading size/modtime data.","triggerScenarios":"Log-tail or poll endpoints where the computed log path, or an entry in the log listing, resolves to a directory — log-file config set to a directory, a container mount mapping a directory over the configured log filename, or a request path parameter that names a directory.","commonSituations":"Misconfigured logging section pointing log-file at a directory; container volume mounts overlaying the log path; log-rotation tooling replacing files with directories; users passing arbitrary paths to the log download API.","solutions":["Check the configured log-file path and make sure it names a regular file (ls -ld on the exact path)","Fix mount points that overlay the log path with a directory in containers","Reconfigure the logger to write to a proper filename (e.g. logs/app.log, not logs/)","If passing paths as request parameters, reject directory paths client-side before calling the logs API"],"exampleFix":"# before (config.yaml)\nlog-file: /app/logs        # a directory\n# after (config.yaml)\nlog-file: /app/logs/app.log","handlingStrategy":"validation","validationCode":"info, err := os.Stat(path)\nif err == nil && info.IsDir() {\n    return fmt.Errorf(\"%s is a directory; configure a regular file for logging\", path)\n}","typeGuard":"func isRegularFile(path string) bool {\n    info, err := os.Stat(path)\n    return err == nil && info.Mode().IsRegular()\n}","tryCatchPattern":"if _, _, err := logFileMatchesCursor(path, cursor); err != nil {\n    if strings.Contains(err.Error(), \"invalid log file\") { /* fix log-file config; skip this path */ }\n}","preventionTips":["Point log-file at a filename, not a directory","In containers, mount the parent dir rather than overlaying the file path","Validate any path parameter client-side against directory entries"],"tags":["logging","filesystem","configuration"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}