-rw-rw-r-- 1 admin admin 183570494 Jan 9 16:00 a...
생성일: 2026년 1월 9일
생성일: 2026년 1월 9일
-rw-rw-r-- 1 admin admin 183570494 Jan 9 16:00 access.log
-rw-r--r-- 1 admin admin 1073741901 Sep 15 03:26 application-1.log
-rw-r--r-- 1 admin admin 1073741971 Sep 15 09:26 application-2.log
-rw-r--r-- 1 admin admin 1073741927 Sep 17 17:21 application-3.log
-rw-r--r-- 1 admin admin 1073741916 Nov 27 09:41 application-4.log
-rw-r--r-- 1 admin admin 584889184 Jan 9 16:02 application.log
drwxr-xr-x 2 admin admin 4096 May 6 2024 deeplog-offllogmanager
-rw-r--r-- 1 admin admin 1073741918 Sep 15 03:25 error-1.log
-rw-r--r-- 1 admin admin 1073741904 Sep 15 10:25 error-2.log
springboot 里面,access.log 和 application.log error.log 有什么区别呢? 分别写入的是什么日志?
在 Spring Boot(通常是内嵌 Tomcat / 使用 Logback) 里,这三类日志关注点不同,可以理解为 访问层 / 应用层 / 异常层。结合你给出的文件名和大小,我按最常见配置给你解释(你这个看起来是典型的生产环境滚动日志)。
是谁写的
记录什么
典型内容
text127.0.0.1 - - [09/Jan/2026:16:00:01 +0800] "GET /api/user/1 HTTP/1.1" 200 123 "-" "Mozilla/5.0"
一般包含
用途
✅ 特点
是谁写的
Logger(Logback / Log4j2)javalog.info("create order, userId={}", userId); log.debug("param={}", param);
记录什么
典型内容
text2026-01-09 16:02:11.345 INFO [http-nio-8080-exec-3] c.xxx.OrderService - create order success, orderId=123
一般包含
用途
✅ 特点
有些项目叫
error.log,有些是application-error.log,本质一样
是谁写的
记录什么
典型内容
text2026-01-09 16:02:15.789 ERROR [http-nio-8080-exec-5] c.xxx.OrderController - create order failed java.lang.NullPointerException at com.xxx.OrderService.create(OrderService.java:45) ...
用途
✅ 特点
| 日志文件 | 关注点 | 谁写的 | 主要内容 | 常见用途 |
|---|---|---|---|---|
| access.log | 请求 | Web 容器 | HTTP 请求 | 流量、接口、性能 |
| application.log | 业务 | 应用 | 业务日志 | 逻辑排查 |
| error.log | 异常 | 应用 | ERROR + 堆栈 | 故障定位 |
你这里看到:
application-1.log ~ application-4.logerror-1.log ~ error-2.log👉 说明
❌ access.log 里 一般看不到 Java 异常堆栈
❌ error.log 里 一般看不到每一次请求
很多新人第一反应是:
“接口 500 了,我去 application.log 里搜 URL”
正确姿势
access.log 找到 请求时间 & 请求路径error.log 看异常application.log 看业务上下文如果你愿意,我可以:
你可以直接说你现在是 运维 / 开发 / 排障中 的哪种场景。
2025-12-30 10:41:19.927 [ERROR] [http-nio-0.0.0.0-8087-exec-6] [ExceptionHandler:resolveException(32)] - Request method 'GET' not supported
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:200) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:422) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:368) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:65) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:401) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1231) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1014) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:687) ~[hadoop-jdufs-cdn-1.0.5.jar:?]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) ~[hadoop-jdufs-cdn-1.0.5.jar:?]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.13.jar!/:9.0.13]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.13.jar!/:9.0.13]
at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:262) ~[spring-web-5.1.3.RELEASE.jar!/:5.1.3.RELEASE]
@Override
public BaseResponse setTaskFailed(String targetName) {
BaseResponse response = new BaseResponse();
String monitorKey = taskRedisProperties.getMonitorKey();
texttry { String taskJson = cluster.hGet(monitorKey, targetName); if (taskJson == null) { response.getStatus().setCode(StatusResponse.ResponseCode.ClientError); response.getStatus().setMsg("Task not found: " + targetName); @RequestMapping(value = "setTaskFailed", method = RequestMethod.POST) BaseResponse setTaskFailed(@RequestParam("targetName") String targetName) { return vipUploadService.setTaskFailed(targetName); } return response; } logger.info("The content of task before setting task as failed: {}", taskJson); Properties properties = JSON.parseObject(taskJson, Properties.class); // 添加当前时间戳(精确到分钟)到reason字段 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String timestamp = sdf.format(new Date()); properties.put("status", "failed"); properties.put("reason", "Manually marked as failed due to worker failure at " + timestamp); // 使用DisableCircularReferenceDetect避免循环引用问题,同时保留类型信息 String value = JSON.toJSONString(properties, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteClassName); // 这个特性可以保留Long类型信息 logger.info("The content of task after setting task as failed: {}", value); cluster.hSet(monitorKey, targetName, value); logger.info("Task {} has been manually set to failed status", targetName); response.getStatus().setMsg("Task has been successfully marked as failed"); } catch (Exception e) { logger.error("Failed to set task {} as failed: {}", targetName, e.getMessage(), e); response.getStatus().setCode(StatusResponse.ResponseCode.ServerError); response.getStatus().setMsg("Error setting task as failed: " + e.getMessage()); } return response; }
日志:2025-12-30 10:41:19.766 [INFO] [http-nio-0.0.0.0-8087-exec-5] [RequestLoggingFilterConfig:afterRequest(28)] - Afteruri=/vipUpload/setTaskFailed?targetName=domainStorage_183_31_62/amNsb3VkX2F5Z2ZzZUU=/storage.jd.com/20251230/2025123007/2025123007_part23.gz]
似乎 我这个是post 的方法,但是在相同的时间戳里面,我却看到了 get 分析一下 这个报错信息
从你贴的两段日志来看,这个报错不是“你明明 POST 了却被 Spring 误判成 GET”,而是:** 同一个 URL 在同一时间附近,被至少两个请求打到了,其中有一个是 GET**,而你的接口只允许 POST,所以 Spring 抛了 HttpRequestMethodNotSupportedException: Request method 'GET' not supported。
下面把证据和最常见原因拆开讲清楚。
报错核心是这一句:
Request method 'GET' not supportedorg.springframework.web.HttpRequestMethodNotSupportedException这类异常是在 Spring MVC 找到了路径(或接近匹配的路径),但 HTTP Method 不匹配 时抛出的。
而你的 Controller 显式限制了:
java@RequestMapping(value = "setTaskFailed", method = RequestMethod.POST) BaseResponse setTaskFailed(...)
所以只要有人用 GET /vipUpload/setTaskFailed?... 访问,就会抛这个异常。
你给的访问日志是:
Afteruri=/vipUpload/setTaskFailed?targetName=...
注意:它只记录了 URI,没有记录 HTTP method(GET/POST)。
所以这条日志完全可能来自 GET 请求,也可能来自 POST 请求 —— 单凭它不能断定“这是 post 的”。
另外也很关键:你两条日志的线程不一样:
http-nio-...-exec-6http-nio-...-exec-5不同线程 = 不同请求(Tomcat 一个请求通常由一个 worker 线程处理)。
所以“同一时间戳附近看到 GET 和 Afteruri”非常符合:并发/紧邻的两个请求。
最常见的几种来源(按概率从高到低):
很多人为了“触发一下接口”,把:
/vipUpload/setTaskFailed?targetName=...
直接粘贴到浏览器地址栏回车——这必然是 GET,然后就报你这个错。
比如 Prometheus/黑盒探测、ELB/SLB 健康检查、某个网关路由探测,会用 GET 去探测 URL 是否可达。
例如 axios/fetch 配置错了 method,或把参数拼 query string 后误用了 GET。
如果中间有 301/302,某些客户端会把原请求方法改成 GET(尤其历史行为)。
但你这里异常是 Spring 直接抛的“GET 不支持”,更像请求一开始就是 GET。
你这里明确是 GET,所以不是预检。
要快速锁定来源,建议你把 access log 或 request logging 打到包含 method + remoteAddr + status。
你已经有 RequestLoggingFilterConfig 了,但它现在只打印了 uri。把它改成至少包含:
这样你立刻能确认:那条 Afteruri 其实是不是 GET、GET 来自哪里。
最直接的操作(不改代码也能查):
access.log 里按时间 2025-12-30 10:41:19 附近 grep setTaskFailed"GET /vipUpload/setTaskFailed?...""POST /vipUpload/setTaskFailed?..."如果你能贴一行 access.log(脱敏 IP 也行),我可以帮你判断几乎 100% 是哪一类来源。
对 HttpRequestMethodNotSupportedException 这种客户端误用,很多团队会:
例如全局异常里把它单独处理(不打 ERROR 堆栈),避免污染 error.log。
优先做两件事:
你看到的“POST 接口却报 GET not supported”,本质是:同一个 URL 有请求以 GET 方式打进来了;你那条 Afteruri=... 日志没有 method,而且线程不同,说明不是同一个请求。
你把 access.log 里那一行 GET/POST 的原始记录贴出来(1~2 行就够,IP 可打码),我就能帮你判断是浏览器误触发、探活、还是调用方写错。
2025-12-29 11:49:48.002 [INFO] [scheduling3] [LeaderWrapper:tryTakeOverLeader(94)] - tryTakeOverLeader key offline-upload-master-locker,redis msg I'm leader,and expire time update success!
2025-12-29 11:49:50.328 [ERROR] [http-nio-0.0.0.0-8087-exec-9] [ExceptionHandler:resolveException(32)] - domainStorage_183_31_62/c2hlbnpoZW5neWlkb25nMDM=/v96-sz-daily-b.douyinvod.com/20251228/2025122805/202512280520_part2.gz 任务不处于失败状态,当前状态为running
com.jdcloud.pid.taskmanager.exception.BizException: domainStorage_183_31_62/c2hlbnpoZW5neWlkb25nMDM=/v96-sz-daily-b.douyinvod.com/20251228/2025122805/202512280520_part2.gz任务不处于失败状态,当前状态为running
at
这个情况是,脚本会对 failed 状态的任务进行 调整,把他们设为running状态;然后在设置前的过程中,会判断 任务是否在失败。结果发现还在运行,似乎就把 这个redis 任务信息 直接给搞没了。分析一下
从你这条日志本身来看,Spring 报错的根因不是“HTTP 方法不对”,而是你们业务自己抛了 BizException:
任务不处于失败状态,当前状态为running
com.jdcloud.pid.taskmanager.exception.BizException ...
也就是说:脚本(或接口)想“只对 failed 的任务做调整”,但读到 Redis 里该任务状态是 running,于是按你们的业务规则直接拒绝了这次操作。
你担心的点是:“拒绝了以后,Redis 里的任务信息却没了(被搞没了)”。这通常不是 BizException 本身造成的,而是典型的两类问题:** 并发竞态 + 非原子写法/清理逻辑**。
text11:49:48 tryTakeOverLeader ... I'm leader ... 11:49:50 BizException ... 状态为 running
这意味着在 Leader 刚切换成功后 2 秒,就有请求/脚本来做“失败任务调整”。
在这种架构里,经常存在:
→ 同一个 task 的状态可能在 very short window 内被不同线程/不同实例反复改。
所以你看到“我以为它 failed,结果读到 running”很符合竞态。
常见伪代码长这样:
javatry { task = hget(...) check failed // do update } catch (Exception e) { // 记录日志 } finally { hdel(monitorKey, targetName); // ❌ 为了“处理完就清掉”,但异常也会走到这里 }
或者:
javaif (!failed) { throw new BizException(...) } hdel(...) // ❌ 放在后面,但上层 catch 了 BizException 后仍会继续执行清理流程
结果:一旦发现不是 failed(running),抛异常,但后续“清理”仍执行 → 任务被删。
你现在看到的现象“发现还在 running,就把任务信息搞没了”——这个模式非常吻合。
✅ 怎么验证
搜代码里对该 task 的 Redis 操作,重点找:
hDel(monitorKey, targetName)del(monitorKey)(更危险,会删整张 hash)expire(monitorKey, ...)(把整张 hash 设置了短 TTL,导致“像被删了一样”)如果你们逻辑是:
hGet 读出 JSONhSet 写回在并发下可能发生:
"null" / ""(取决于代码),最终表现为“任务信息没了/不可解析”。✅ 怎么验证
看日志里是否有类似:
taskJson == null 分支null、{}你们的任务通常是 monitorKey 下面的 hash field(targetName)。如果某段逻辑对 monitorKey 这个 key 做了 expire,并且 TTL 较短,那会导致:
expire(monitorKey, 60)你说“脚本会对 failed 状态任务进行调整,把他们设为 running”,但实际上读到的是 running,说明至少存在一种情况:
这就会导致:脚本带着“应该是 failed”的假设来改,结果现场已经变了。
用 Redis Lua / CAS(WATCH-MULTI-EXEC)实现:
这能一把解决“并发下误删/误覆盖”。
状态不匹配本质是正常并发现象(或调用方选错任务),建议:
至少包含:
这样你能在日志里直接抓到“是谁删的”。
在同一时间窗口(11:49:48 ~ 11:50:30)grep:
hdeldelexpirecleanremovemonitorKeytargetName(完整那串域名路径)看是否存在两类线程/实例:
scheduling*(定时扫描/leader 任务)http-nio-*(接口触发)如果你们有 access.log:查这秒有没有人调用“调整接口”(看来源 IP / UA)——能区分是脚本还是人工。
如果你把调整脚本/接口里涉及 Redis 的删除/过期相关代码段(尤其是 catch/finally 部分)再贴一小段出来,我可以直接帮你指出哪一行会导致“running 时被清掉”,以及怎么改成原子安全的写法。
这个补传脚本是,似乎不涉及把redis 直接扔掉的逻辑
import time
import requests
from apscheduler.schedulers.blocking import BlockingScheduler
def autoMakeUp():
url = "http://offline-logup-deeplog.jdcloud.com/vipUpload/getRunningTasks"
resp = requests.get(url)
jsonData = resp.json()
data = jsonData['data']
for item in data:
textif item['status'] == 'failed': targetName = item['targetName'] reRun_url = "http://offline-logup-deeplog.jdcloud.com/vipUpload/reRunJob?targetName=" requests.get(reRun_url + targetName) print "reRunJob:" + targetName time.sleep(1) else: print "skip normal"
if name == 'main':
textscheduler = BlockingScheduler() scheduler.add_job(autoMakeUp, 'cron', id='auto_makeup', hour='*') try: scheduler.start() except (KeyboardInterrupt, SystemExit): scheduler.shutdown() print ("Exit....")
你这段补传脚本本身确实没有任何“删 Redis”的动作,它只做了两件事:
GET /vipUpload/getRunningTasks 拉任务列表status == failed 的任务,再 GET /vipUpload/reRunJob?targetName=... 触发重跑所以如果你观察到“Redis 里的任务信息没了”,更大的概率是:
getRunningTasks 返回的数据并不是“Redis 当前实时状态”(可能是缓存/聚合后的视图)下面我把你现在现象里最关键的点拆出来讲。
你之前贴过一个接口是 setTaskFailed 只允许 POST。而你这个脚本对重跑是:
pythonrequests.get(reRun_url + targetName)
也就是说 reRunJob 这个接口很可能也是一个“应该用 POST”的接口,但你用 GET 调了。如果后端严格限制 method,就会出现你之前那种:
Request method 'GET' not supported(405)✅ 你现在贴的 BizException(“不处于失败,当前 running”)非常像:
reRunJob(或它内部调用的服务)在执行前做了状态校验:只允许 failed → running,结果读到 running,就抛异常。
这个在你当前脚本逻辑里很容易发生:你先拉一次列表,然后逐个处理,中间还有 sleep(1)。
典型竞态场景:
所以“列表里 failed,但执行时 running”并不矛盾,反而很常见。
你脚本不删 Redis,但后端可能在这些地方让你感觉“没了”:
比如:
failedTasks(或 monitorKey)挪到 runningTasks你之前的担忧“发现 running 就给搞没了”,最符合的是这种代码味道:
脚本不会触发删除,但脚本触发了接口,接口里可能删。
这会表现为:某个时间点之后你发现“全没了”,不是单个没。
你 targetName 看起来包含很多特殊字符:/、= 等(你之前日志里就是这种)。
你现在直接拼接:
pythonrequests.get(reRun_url + targetName)
如果 targetName 里有 &、?、% 之类,或者某些网关对 /、= 处理不一致,会导致:
✅ 正确做法是用 params= 让 requests 帮你编码:
pythonrequests.get(reRun_url, params={"targetName": targetName})
这条非常重要:很多“玄学丢任务/查不到/错任务”最后都是参数没编码。
看后端 Controller 映射,或直接看 access.log 是否出现:
"GET /vipUpload/reRunJob?..."
以及返回码(200/405/500)。如果 reRunJob 实际只支持 POST,你现在用 GET 调,后端要么 405,要么走了异常处理器。
把同一秒内的请求串起来看:
重点看来源 IP / UA:一眼就能分辨是脚本机器还是别的系统。
在它“消失”的时间窗,搜这几类关键词(随便一个命中就有方向):
hdel / del / expireremove task / clean / deletemove / transfer / take(迁移/领取)params 编码python# coding=UTF-8 import time import requests from apscheduler.schedulers.blocking import BlockingScheduler BASE = "http://offline-logup-deeplog.jdcloud.com" def autoMakeUp(): url = BASE + "/vipUpload/getRunningTasks" resp = requests.get(url, timeout=10) resp.raise_for_status() data = resp.json().get("data", []) for item in data: if item.get("status") == "failed": targetName = item.get("targetName") rerun_url = BASE + "/vipUpload/reRunJob" r = requests.get(rerun_url, params={"targetName": targetName}, timeout=10) print("reRunJob status=%s target=%s resp=%s" % (r.status_code, targetName, r.text[:200])) time.sleep(1) else: print("skip normal") if __name__ == '__main__': scheduler = BlockingScheduler() scheduler.add_job(autoMakeUp, 'cron', id='auto_makeup', hour='*') try: scheduler.start() except (KeyboardInterrupt, SystemExit): scheduler.shutdown() print("Exit....")
如果你能再贴两样东西(任意一个都行),我就能把“Redis 被清掉/被迁移”的责任点定位到具体模块:
reRunJob 的 Controller 映射(method、内部调用哪个 service)reRunJob 那条记录(含 method/status/来源 IP)