Sfoglia il codice sorgente

1、支持实时查询详细执行日志;

xueli.xue 9 anni fa
parent
commit
39470eabbd

+ 1 - 0
README.md Vedi File

17
 	7、支持任务执行日志;
17
 	7、支持任务执行日志;
18
 	8、支持自定义参数;
18
 	8、支持自定义参数;
19
 	9、支持任务失败次数超阈值邮件报警;
19
 	9、支持任务失败次数超阈值邮件报警;
20
+	10、支持在线查看,执行器详细日志;
20
 
21
 
21
 # 新版本 V1.2.x,新特性
22
 # 新版本 V1.2.x,新特性
22
 	1、支持任务分组;
23
 	1、支持任务分组;

+ 76 - 0
xxl-job-admin/src/main/java/com/xxl/job/controller/JobLogController.java Vedi File

16
 import org.springframework.web.bind.annotation.RequestParam;
16
 import org.springframework.web.bind.annotation.RequestParam;
17
 import org.springframework.web.bind.annotation.ResponseBody;
17
 import org.springframework.web.bind.annotation.ResponseBody;
18
 
18
 
19
+import com.xxl.job.client.handler.HandlerRepository;
20
+import com.xxl.job.client.util.HttpUtil;
19
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
21
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
22
+import com.xxl.job.client.util.JacksonUtil;
20
 import com.xxl.job.core.constant.Constants.JobGroupEnum;
23
 import com.xxl.job.core.constant.Constants.JobGroupEnum;
24
+import com.xxl.job.core.model.ReturnT;
21
 import com.xxl.job.core.model.XxlJobLog;
25
 import com.xxl.job.core.model.XxlJobLog;
22
 import com.xxl.job.dao.IXxlJobLogDao;
26
 import com.xxl.job.dao.IXxlJobLogDao;
23
 
27
 
88
 		return callBack;
92
 		return callBack;
89
 	}
93
 	}
90
 	
94
 	
95
+	@RequestMapping("/logDetail")
96
+	@ResponseBody
97
+	public ReturnT<String> logDetail(int id){
98
+		// base check
99
+		XxlJobLog log = xxlJobLogDao.load(id);
100
+		if (log == null) {
101
+			return new ReturnT<String>(500, "参数异常");
102
+		}
103
+		
104
+		// server address
105
+		@SuppressWarnings("unchecked")
106
+		Map<String, String> jobDataMap = JacksonUtil.readValue(log.getJobData(), Map.class);
107
+		String handler_address = jobDataMap.get(HandlerRepository.HANDLER_ADDRESS);
108
+		if (!handler_address.startsWith("http")){
109
+			handler_address = "http://" + handler_address + "/";
110
+		}
111
+		// trigger id, trigger time
112
+		Map<String, String> reqMap = new HashMap<String, String>();
113
+		reqMap.put(HandlerRepository.NAMESPACE, HandlerRepository.NameSpaceEnum.LOG.name());
114
+		reqMap.put(HandlerRepository.TRIGGER_LOG_ID, String.valueOf(id));
115
+		reqMap.put(HandlerRepository.TRIGGER_TIMESTAMP, String.valueOf(log.getTriggerTime().getTime()));
116
+		
117
+		RemoteCallBack callBack = HttpUtil.post(handler_address, reqMap);
118
+		if (HttpUtil.RemoteCallBack.SUCCESS.equals(callBack.getStatus())) {
119
+			return new ReturnT<String>(callBack.getMsg());
120
+		} else {
121
+			return new ReturnT<String>(500, callBack.getMsg());
122
+		}
123
+	}
124
+	
125
+	@RequestMapping("/logDetailPage")
126
+	public String logDetailPage(int id, Model model){
127
+		ReturnT<String> data = logDetail(id);
128
+		model.addAttribute("result", data);
129
+		return "joblog/logdetail";
130
+	}
131
+	
132
+	@RequestMapping("/logKill")
133
+	@ResponseBody
134
+	public ReturnT<String> logKill(int id){
135
+		// base check
136
+		XxlJobLog log = xxlJobLogDao.load(id);
137
+		if (log == null) {
138
+			return new ReturnT<String>(500, "参数异常");
139
+		}
140
+		
141
+		// server address
142
+		@SuppressWarnings("unchecked")
143
+		Map<String, String> jobDataMap = JacksonUtil.readValue(log.getJobData(), Map.class);
144
+		String handler_address = jobDataMap.get(HandlerRepository.HANDLER_ADDRESS);
145
+		if (!handler_address.startsWith("http")){
146
+			handler_address = "http://" + handler_address + "/";
147
+		}
148
+		String handler_name = jobDataMap.get(HandlerRepository.HANDLER_NAME);
149
+		
150
+		// trigger id, trigger time
151
+		Map<String, String> reqMap = new HashMap<String, String>();
152
+		reqMap.put(HandlerRepository.NAMESPACE, HandlerRepository.NameSpaceEnum.KILL.name());
153
+		reqMap.put(HandlerRepository.HANDLER_NAME, handler_name);
154
+		reqMap.put(HandlerRepository.TRIGGER_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
155
+		
156
+		RemoteCallBack callBack = HttpUtil.post(handler_address, reqMap);
157
+		if (HttpUtil.RemoteCallBack.SUCCESS.equals(callBack.getStatus())) {
158
+			log.setHandleStatus(HttpUtil.RemoteCallBack.FAIL);
159
+			log.setHandleMsg("人为操作主动终止");
160
+			log.setHandleTime(new Date());
161
+			xxlJobLogDao.updateHandleInfo(log);
162
+			return new ReturnT<String>(callBack.getMsg());
163
+		} else {
164
+			return new ReturnT<String>(500, callBack.getMsg());
165
+		}
166
+	}
91
 }
167
 }

+ 5 - 0
xxl-job-admin/src/main/java/com/xxl/job/core/thread/JobMonitorHelper.java Vedi File

41
 						XxlJobLog log = DynamicSchedulerUtil.xxlJobLogDao.load(jobLogId);
41
 						XxlJobLog log = DynamicSchedulerUtil.xxlJobLogDao.load(jobLogId);
42
 						if (log!=null) {
42
 						if (log!=null) {
43
 							if (RemoteCallBack.SUCCESS.equals(log.getTriggerStatus()) && StringUtils.isBlank(log.getHandleStatus())) {
43
 							if (RemoteCallBack.SUCCESS.equals(log.getTriggerStatus()) && StringUtils.isBlank(log.getHandleStatus())) {
44
+								try {
45
+									TimeUnit.SECONDS.sleep(10);
46
+								} catch (InterruptedException e) {
47
+									e.printStackTrace();
48
+								}
44
 								JobMonitorHelper.monitor(jobLogId);
49
 								JobMonitorHelper.monitor(jobLogId);
45
 							}
50
 							}
46
 							if (RemoteCallBack.SUCCESS.equals(log.getTriggerStatus()) && RemoteCallBack.SUCCESS.equals(log.getHandleStatus())) {
51
 							if (RemoteCallBack.SUCCESS.equals(log.getTriggerStatus()) && RemoteCallBack.SUCCESS.equals(log.getHandleStatus())) {

+ 1 - 0
xxl-job-admin/src/main/java/com/xxl/job/service/job/RemoteHttpJobBean.java Vedi File

55
 		
55
 		
56
 		// trigger request
56
 		// trigger request
57
 		HashMap<String, String> params = new HashMap<String, String>();
57
 		HashMap<String, String> params = new HashMap<String, String>();
58
+		params.put(HandlerRepository.NAMESPACE, HandlerRepository.NameSpaceEnum.RUN.name());
58
 		params.put(HandlerRepository.TRIGGER_LOG_URL, PropertiesUtil.getString(HandlerRepository.TRIGGER_LOG_URL));
59
 		params.put(HandlerRepository.TRIGGER_LOG_URL, PropertiesUtil.getString(HandlerRepository.TRIGGER_LOG_URL));
59
 		params.put(HandlerRepository.TRIGGER_LOG_ID, String.valueOf(jobLog.getId()));
60
 		params.put(HandlerRepository.TRIGGER_LOG_ID, String.valueOf(jobLog.getId()));
60
 		params.put(HandlerRepository.TRIGGER_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
61
 		params.put(HandlerRepository.TRIGGER_TIMESTAMP, String.valueOf(System.currentTimeMillis()));

+ 1 - 1
xxl-job-admin/src/main/resources/mybatis-mapper/XxlJobLogMapper.xml Vedi File

29
 		t.job_cron,
29
 		t.job_cron,
30
 		t.job_desc,
30
 		t.job_desc,
31
 		t.job_class,
31
 		t.job_class,
32
-		t.job_desc,
32
+		t.job_data,
33
 		t.trigger_time,
33
 		t.trigger_time,
34
 		t.trigger_status,
34
 		t.trigger_status,
35
 		t.trigger_msg,
35
 		t.trigger_msg,

+ 1 - 1
xxl-job-admin/src/main/webapp/WEB-INF/template/jobinfo/index.ftl Vedi File

67
 					            		<th name="id" >id</th>
67
 					            		<th name="id" >id</th>
68
 					                	<th name="jobGroup" >任务组</th>
68
 					                	<th name="jobGroup" >任务组</th>
69
 					                  	<th name="jobName" >任务名</th>
69
 					                  	<th name="jobName" >任务名</th>
70
-					                  	<th name="jobCron" >Cron</th>
71
 					                  	<th name="jobDesc" >描述</th>
70
 					                  	<th name="jobDesc" >描述</th>
71
+					                  	<th name="jobCron" >Cron</th>
72
 					                  	<th name="jobClass" >JobBean</th>
72
 					                  	<th name="jobClass" >JobBean</th>
73
 					                  	<th name="jobData" >任务数据</th>
73
 					                  	<th name="jobData" >任务数据</th>
74
 					                  	<th name="addTime" >新增时间</th>
74
 					                  	<th name="addTime" >新增时间</th>

+ 1 - 0
xxl-job-admin/src/main/webapp/WEB-INF/template/joblog/index.ftl Vedi File

83
 					                  	<th name="handleTime" >执行时间</th>
83
 					                  	<th name="handleTime" >执行时间</th>
84
 					                  	<th name="handleStatus" >执行结果</th>
84
 					                  	<th name="handleStatus" >执行结果</th>
85
 					                  	<th name="handleMsg" >执行日志</th>
85
 					                  	<th name="handleMsg" >执行日志</th>
86
+					                  	<th name="handleMsg" >操作</th>
86
 					                </tr>
87
 					                </tr>
87
 				                </thead>
88
 				                </thead>
88
 				                <tbody></tbody>
89
 				                <tbody></tbody>

+ 7 - 0
xxl-job-admin/src/main/webapp/WEB-INF/template/joblog/logdetail.ftl Vedi File

1
+<body style="color:white;background-color:black;" >
2
+<pre>
3
+<br>
4
+<#if result.code == 200>${result.content}
5
+<#else>${result.msg}</#if>
6
+</pre>
7
+</body>

+ 10 - 11
xxl-job-admin/src/main/webapp/static/js/jobinfo.index.1.js Vedi File

18
 	                { "data": 'id', "bSortable": false, "visible" : false},
18
 	                { "data": 'id', "bSortable": false, "visible" : false},
19
 	                { 
19
 	                { 
20
 	                	"data": 'jobGroup', 
20
 	                	"data": 'jobGroup', 
21
+	                	"visible" : false,
21
 	                	"render": function ( data, type, row ) {
22
 	                	"render": function ( data, type, row ) {
22
 	            			var groupMenu = $("#jobGroup").find("option");
23
 	            			var groupMenu = $("#jobGroup").find("option");
23
 	            			for ( var index in $("#jobGroup").find("option")) {
24
 	            			for ( var index in $("#jobGroup").find("option")) {
29
 	            		}
30
 	            		}
30
             		},
31
             		},
31
 	                { "data": 'jobName'},
32
 	                { "data": 'jobName'},
33
+	                { "data": 'jobDesc', "visible" : true},
32
 	                { "data": 'jobCron', "visible" : true},
34
 	                { "data": 'jobCron', "visible" : true},
33
-	                { "data": 'jobDesc', "visible" : false},
34
-	                { "data": 'jobClass', "visible" : true},
35
+	                { "data": 'jobClass', "visible" : false},
35
 	                { 
36
 	                { 
36
 	                	"data": 'jobData',
37
 	                	"data": 'jobData',
37
 	                	"visible" : true,
38
 	                	"visible" : true,
38
 	                	"render": function ( data, type, row ) {
39
 	                	"render": function ( data, type, row ) {
39
-	                		return data?'<a class="logTips" href="javascript:;" >查看<span style="display:none;">'+ data +'</span></a>':"无";
40
+	                		var _jobData = eval('(' + data + ')');	// row.jobData
41
+	                		var html = "<p title='" + data + "'>执行器:" + _jobData.handler_name +
42
+	                			"<br>执行参数:" + _jobData.handler_params + 
43
+	                			"<br>执行机器:" + _jobData.handler_address + "</p>";
44
+	                		return html;
40
 	                	}
45
 	                	}
41
 	                },
46
 	                },
42
 	                { 
47
 	                { 
101
 	                							' handler_name="'+ jobDataMap.handler_name +'" '+
106
 	                							' handler_name="'+ jobDataMap.handler_name +'" '+
102
 	                							'>'+
107
 	                							'>'+
103
 	                					pause_resume +
108
 	                					pause_resume +
104
-										'<button class="btn btn-info btn-xs job_operate" type="job_trigger" type="button">执行</button>  '+
105
-										'<button class="btn btn-warning btn-xs update" type="button">编辑</button><br> '+
109
+										'<button class="btn btn-info btn-xs job_operate" type="job_trigger" type="button">执行</button>'+
110
+										'<button class="btn btn-warning btn-xs update" type="button">编辑</button><br>'+
106
 									  	'<button class="btn btn-warning btn-xs" type="job_del" type="button" '+
111
 									  	'<button class="btn btn-warning btn-xs" type="job_del" type="button" '+
107
 									  		'onclick="javascript:window.open(\'' + logUrl + '\')" >查看日志</button> '+
112
 									  		'onclick="javascript:window.open(\'' + logUrl + '\')" >查看日志</button> '+
108
 								  		'<button class="btn btn-danger btn-xs job_operate" type="job_del" type="button">删除</button>  '+
113
 								  		'<button class="btn btn-danger btn-xs job_operate" type="job_del" type="button">删除</button>  '+
140
 		}
145
 		}
141
 	});
146
 	});
142
 	
147
 	
143
-	// 日志弹框提示
144
-	$('#job_list').on('click', '.logTips', function(){
145
-		var msg = $(this).find('span').html();
146
-		ComAlertTec.show(msg);
147
-	});
148
-	
149
 	// 搜索按钮
148
 	// 搜索按钮
150
 	$('#searchBtn').on('click', function(){
149
 	$('#searchBtn').on('click', function(){
151
 		jobTable.fnDraw();
150
 		jobTable.fnDraw();

+ 79 - 4
xxl-job-admin/src/main/webapp/static/js/joblog.index.1.js Vedi File

48
 	                { "data": 'id', "bSortable": false, "visible" : false},
48
 	                { "data": 'id', "bSortable": false, "visible" : false},
49
 	                { 
49
 	                { 
50
 	                	"data": 'jobGroup', 
50
 	                	"data": 'jobGroup', 
51
+	                	"visible" : false, 
51
 	                	"bSortable": false, 
52
 	                	"bSortable": false, 
52
 	                	"render": function ( data, type, row ) {
53
 	                	"render": function ( data, type, row ) {
53
 	            			var groupMenu = $("#jobGroup").find("option");
54
 	            			var groupMenu = $("#jobGroup").find("option");
65
 	                { "data": 'jobClass', "visible" : false},
66
 	                { "data": 'jobClass', "visible" : false},
66
 	                { 
67
 	                { 
67
 	                	"data": 'jobData',
68
 	                	"data": 'jobData',
68
-	                	"visible" : false,
69
+	                	"visible" : true,
69
 	                	"render": function ( data, type, row ) {
70
 	                	"render": function ( data, type, row ) {
70
-	                		return data?'<a class="logTips" href="javascript:;" >查看<span style="display:none;">'+ data +'</span></a>':"无";
71
+	                		var _jobData = eval('(' + data + ')');	// row.jobData
72
+	                		var html = "<p title='" + data + "'>执行器:" + _jobData.handler_name +
73
+	                			"<br>执行参数:" + _jobData.handler_params + 
74
+	                			"<br>执行机器:" + _jobData.handler_address + "</p>";
75
+	                		
76
+	                		return data?'<a class="logMsg" href="javascript:;" >查看<span style="display:none;">'+ html +'</span></a>':"无";
71
 	                	}
77
 	                	}
72
 	                },
78
 	                },
73
 	                { 
79
 	                { 
95
 	                	"render": function ( data, type, row ) {
101
 	                	"render": function ( data, type, row ) {
96
 	                		return data?'<a class="logTips" href="javascript:;" >查看<span style="display:none;">'+ data +'</span></a>':"无";
102
 	                		return data?'<a class="logTips" href="javascript:;" >查看<span style="display:none;">'+ data +'</span></a>':"无";
97
 	                	}
103
 	                	}
104
+	                },
105
+	                { "data": 'handleMsg' , "bSortable": false,
106
+	                	"render": function ( data, type, row ) {
107
+	                		// better support expression or string, not function
108
+	                		return function () {
109
+	                			// local job do not support trigger detail log, now
110
+		                		var _jobData = eval('(' + row.jobData + ')'); 
111
+		                		if (!_jobData.handler_address) {
112
+		                			return;
113
+		                		}
114
+		                		
115
+		                		if (row.triggerStatus == 'SUCCESS'){
116
+		                			var temp = '<a href="javascript:;" class="logDetail" _id="'+ row.id +'">查看日志</a>';
117
+		                			if(!row.handleStatus){
118
+		                				temp += '<br><a href="javascript:;" class="logKill" _id="'+ row.id +'">终止任务</a>';
119
+		                			}
120
+		                			return temp;
121
+		                		}
122
+		                		return null;	
123
+	                		}
124
+	                	}
98
 	                }
125
 	                }
99
 	            ],
126
 	            ],
100
 		"language" : {
127
 		"language" : {
123
 		}
150
 		}
124
 	});
151
 	});
125
 	
152
 	
153
+	// 任务数据
154
+	$('#joblog_list').on('click', '.logMsg', function(){
155
+		var msg = $(this).find('span').html();
156
+		ComAlert.show(2, msg);
157
+	});
158
+	
126
 	// 日志弹框提示
159
 	// 日志弹框提示
127
 	$('#joblog_list').on('click', '.logTips', function(){
160
 	$('#joblog_list').on('click', '.logTips', function(){
128
 		var msg = $(this).find('span').html();
161
 		var msg = $(this).find('span').html();
129
 		ComAlertTec.show(msg);
162
 		ComAlertTec.show(msg);
130
 	});
163
 	});
131
 	
164
 	
132
-	
133
-	
134
 	// 搜索按钮
165
 	// 搜索按钮
135
 	$('#searchBtn').on('click', function(){
166
 	$('#searchBtn').on('click', function(){
136
 		logTable.fnDraw();
167
 		logTable.fnDraw();
137
 	});
168
 	});
138
 	
169
 	
170
+	// 查看执行器详细执行日志
171
+	$('#joblog_list').on('click', '.logDetail', function(){
172
+		var _id = $(this).attr('_id');
173
+		
174
+		window.open(base_url + 'joblog/logDetailPage?id=' + _id);
175
+		return;
176
+		
177
+		/*
178
+		$.ajax({
179
+			type : 'POST',
180
+			url : base_url + 'joblog/logDetail',
181
+			data : {"id":_id},
182
+			dataType : "json",
183
+			success : function(data){
184
+				if (data.code == 200) {
185
+					ComAlertTec.show('<pre style="color: white;background-color: black;width2:'+ $(window).width()*2/3 +'px;" >'+ data.content +'</pre>');
186
+				} else {
187
+					ComAlertTec.show(data.msg);
188
+				}
189
+			},
190
+		});
191
+		*/
192
+	});
193
+	
194
+	$('#joblog_list').on('click', '.logKill', function(){
195
+		var _id = $(this).attr('_id');
196
+		ComConfirm.show("确认主动终止任务?", function(){
197
+			$.ajax({
198
+				type : 'POST',
199
+				url : base_url + 'joblog/logKill',
200
+				data : {"id":_id},
201
+				dataType : "json",
202
+				success : function(data){
203
+					if (data.code == 200) {
204
+						ComAlert.show(1, '操作成功');
205
+						logTable.fnDraw();
206
+					} else {
207
+						ComAlert.show(2, data.msg);
208
+					}
209
+				},
210
+			});
211
+		});
212
+	});
213
+	
139
 });
214
 });

+ 4 - 2
xxl-job-client-demo/src/main/java/com/xxl/job/service/handler/DemoJobHandler.java Vedi File

1
 package com.xxl.job.service.handler;
1
 package com.xxl.job.service.handler;
2
 
2
 
3
-import java.util.Random;
4
 import java.util.concurrent.TimeUnit;
3
 import java.util.concurrent.TimeUnit;
5
 
4
 
6
 import org.slf4j.Logger;
5
 import org.slf4j.Logger;
25
 	@Override
24
 	@Override
26
 	public JobHandleStatus handle(String... params) throws Exception {
25
 	public JobHandleStatus handle(String... params) throws Exception {
27
 		logger.info(" ... params:" + params);
26
 		logger.info(" ... params:" + params);
28
-		TimeUnit.SECONDS.sleep(new Random().nextInt(5));
27
+		for (int i = 0; i < 60; i++) {
28
+			TimeUnit.SECONDS.sleep(1);
29
+			logger.info("handler run:{}", i);
30
+		}
29
 		return JobHandleStatus.SUCCESS;
31
 		return JobHandleStatus.SUCCESS;
30
 	}
32
 	}
31
 	
33
 	

+ 0 - 10
xxl-job-client-demo/src/main/resources/log4j.properties Vedi File

1
-log4j.rootLogger=info,console
2
-
3
-log4j.appender.console=org.apache.log4j.ConsoleAppender
4
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
5
-log4j.appender.console.layout.ConversionPattern=%d - xxl-job-client-demo - %p [%c] - <%m>%n
6
-
7
-log4j.appender.logFile=org.apache.log4j.DailyRollingFileAppender
8
-log4j.appender.logFile.File=${catalina.base}/logs/xxl-job-client-demo.log
9
-log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
10
-log4j.appender.logFile.layout.ConversionPattern=%d - xxl-job-client-demo - %p [%c] - <%m>%n

+ 46 - 0
xxl-job-client-demo/src/main/resources/log4j.xml Vedi File

1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE log4j:configuration PUBLIC "-//log4j/log4j Configuration//EN" "log4j.dtd">
3
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" threshold="null" debug="null">
4
+
5
+	<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
6
+		<param name="Target" value="System.out" />
7
+		<layout class="org.apache.log4j.PatternLayout">
8
+			<param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%t]-[%M]-[%L]-[%p] %m%n" />
9
+		</layout>
10
+	</appender>
11
+	
12
+    <appender name="ROOT" class="org.apache.log4j.DailyRollingFileAppender">
13
+        <param name="file" value="/logs/xxl-job-client-demo.log"/>
14
+        <param name="append" value="true"/>
15
+        <param name="encoding" value="UTF-8"/>
16
+        <layout class="org.apache.log4j.PatternLayout">
17
+            <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/>
18
+        </layout>
19
+    </appender>
20
+    
21
+    <appender name="xxl-job" class="com.xxl.job.client.log.XxlJobFileAppender">
22
+        <param name="filePath" value="/logs/xxl-job/"/>
23
+        <param name="append" value="true"/>
24
+        <param name="encoding" value="UTF-8"/>
25
+        <layout class="org.apache.log4j.PatternLayout">
26
+            <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/>
27
+        </layout>
28
+    </appender>
29
+    
30
+    <logger name="com.xxl.job.service.handler" additivity="false">
31
+    	<level value="INFO" />
32
+        <appender-ref ref="xxl-job"/>
33
+    </logger>
34
+    <logger name="com.xxl.job.client" additivity="false">
35
+    	<level value="INFO" />
36
+        <appender-ref ref="xxl-job"/>
37
+    </logger>
38
+
39
+    <root>
40
+        <level value="INFO" />
41
+        <appender-ref ref="CONSOLE" />
42
+        <appender-ref ref="ROOT" />
43
+        <appender-ref ref="xxl-job"/>
44
+    </root>
45
+    
46
+</log4j:configuration>

+ 86 - 21
xxl-job-client/src/main/java/com/xxl/job/client/handler/HandlerRepository.java Vedi File

1
 package com.xxl.job.client.handler;
1
 package com.xxl.job.client.handler;
2
 
2
 
3
+import java.util.Date;
3
 import java.util.Map;
4
 import java.util.Map;
4
 import java.util.concurrent.ConcurrentHashMap;
5
 import java.util.concurrent.ConcurrentHashMap;
5
 
6
 
7
 import org.slf4j.LoggerFactory;
8
 import org.slf4j.LoggerFactory;
8
 
9
 
9
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
10
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
11
+import com.xxl.job.client.log.XxlJobFileAppender;
10
 import com.xxl.job.client.util.JacksonUtil;
12
 import com.xxl.job.client.util.JacksonUtil;
11
 
13
 
12
 /**
14
 /**
16
 public class HandlerRepository {
18
 public class HandlerRepository {
17
 	private static Logger logger = LoggerFactory.getLogger(HandlerRepository.class);
19
 	private static Logger logger = LoggerFactory.getLogger(HandlerRepository.class);
18
 	
20
 	
21
+	public static final String NAMESPACE = "namespace";
22
+	public enum NameSpaceEnum{RUN, KILL, LOG}
23
+	
19
 	public static final String HANDLER_ADDRESS = "handler_address";
24
 	public static final String HANDLER_ADDRESS = "handler_address";
20
 	public static final String HANDLER_NAME = "handler_name";
25
 	public static final String HANDLER_NAME = "handler_name";
21
 	public static final String HANDLER_PARAMS = "handler_params";
26
 	public static final String HANDLER_PARAMS = "handler_params";
35
 	}
40
 	}
36
 	
41
 	
37
 	// handler push to queue
42
 	// handler push to queue
38
-	public static String pushHandleQueue(Map<String, String> _param) {
39
-		logger.info(">>>>>>>>>>> xxl-job pushHandleQueue start, _param:{}", new Object[]{_param});
43
+	public static String service(Map<String, String> _param) {
44
+		logger.info(">>>>>>>>>>> xxl-job service start, _param:{}", new Object[]{_param});
40
 		
45
 		
41
 		// callback
46
 		// callback
42
 		RemoteCallBack callback = new RemoteCallBack();
47
 		RemoteCallBack callback = new RemoteCallBack();
43
 		callback.setStatus(RemoteCallBack.FAIL);
48
 		callback.setStatus(RemoteCallBack.FAIL);
44
-		
45
-		// encryption check
46
-		long timestamp = _param.get(HandlerRepository.TRIGGER_TIMESTAMP)!=null?Long.valueOf(_param.get(HandlerRepository.TRIGGER_TIMESTAMP)):-1;
47
-		if (System.currentTimeMillis() - timestamp > 60000) {
48
-			callback.setMsg("Timestamp check failed.");
49
+
50
+		// check namespace
51
+		String namespace = _param.get(HandlerRepository.NAMESPACE);
52
+		if (namespace==null || namespace.trim().length()==0) {
53
+			callback.setMsg("param[NAMESPACE] can not be null.");
49
 			return JacksonUtil.writeValueAsString(callback);
54
 			return JacksonUtil.writeValueAsString(callback);
50
 		}
55
 		}
51
-				
52
-		// push data to queue
53
-		String handler_name = _param.get(HandlerRepository.HANDLER_NAME);
54
-		if (handler_name!=null && handler_name.trim().length()>0) {
55
-			HandlerThread handlerThread = handlerTreadMap.get(handler_name);
56
-			if (handlerThread != null) {
57
-				handlerThread.pushData(_param);
58
-				callback.setStatus(RemoteCallBack.SUCCESS);
59
-			} else {
60
-				callback.setMsg("handler[" + handler_name + "] not found.");
56
+		
57
+		// parse namespace
58
+		if (namespace.equals(HandlerRepository.NameSpaceEnum.RUN.name())) {
59
+			// encryption check
60
+			long timestamp = _param.get(HandlerRepository.TRIGGER_TIMESTAMP)!=null?Long.valueOf(_param.get(HandlerRepository.TRIGGER_TIMESTAMP)):-1;
61
+			if (System.currentTimeMillis() - timestamp > 60000) {
62
+				callback.setMsg("Timestamp check failed.");
63
+				return JacksonUtil.writeValueAsString(callback);
64
+			}
65
+					
66
+			// push data to queue
67
+			String handler_name = _param.get(HandlerRepository.HANDLER_NAME);
68
+			if (handler_name!=null && handler_name.trim().length()>0) {
69
+				HandlerThread handlerThread = handlerTreadMap.get(handler_name);
70
+				if (handlerThread != null) {
71
+					handlerThread.pushData(_param);
72
+					callback.setStatus(RemoteCallBack.SUCCESS);
73
+				} else {
74
+					callback.setMsg("handler[" + handler_name + "] not found.");
75
+				}
76
+			}else{
77
+				callback.setMsg("param[HANDLER_NAME] can not be null.");
78
+			}
79
+			
80
+		} else if (namespace.equals(HandlerRepository.NameSpaceEnum.LOG.name())) {
81
+			String trigger_log_id = _param.get(HandlerRepository.TRIGGER_LOG_ID);
82
+			String trigger_timestamp = _param.get(HandlerRepository.TRIGGER_TIMESTAMP);
83
+			if (trigger_log_id==null || trigger_timestamp==null) {
84
+				callback.setMsg("trigger_log_id | trigger_timestamp can not be null.");
85
+				return JacksonUtil.writeValueAsString(callback);
61
 			}
86
 			}
62
-		}else{
63
-			callback.setMsg("param[HANDLER_NAME] can not be null.");
87
+			int logId = -1;
88
+			Date triggerDate = null;
89
+			try {
90
+				logId = Integer.valueOf(trigger_log_id);
91
+				triggerDate = new Date(Long.valueOf(trigger_timestamp));
92
+			} catch (Exception e) {
93
+			}
94
+			if (logId<=0 || triggerDate==null) {
95
+				callback.setMsg("trigger_log_id | trigger_timestamp is not parsed valid.");
96
+				return JacksonUtil.writeValueAsString(callback);
97
+			}
98
+			String logConteng = XxlJobFileAppender.readLog(triggerDate, trigger_log_id);
99
+			callback.setStatus(RemoteCallBack.SUCCESS);
100
+			callback.setMsg(logConteng);
101
+		} else if (namespace.equals(HandlerRepository.NameSpaceEnum.KILL.name())) {
102
+			// encryption check
103
+			long timestamp = _param.get(HandlerRepository.TRIGGER_TIMESTAMP)!=null?Long.valueOf(_param.get(HandlerRepository.TRIGGER_TIMESTAMP)):-1;
104
+			if (System.currentTimeMillis() - timestamp > 60000) {
105
+				callback.setMsg("Timestamp check failed.");
106
+				return JacksonUtil.writeValueAsString(callback);
107
+			}
108
+			
109
+			// kill handlerThread, and create new one
110
+			String handler_name = _param.get(HandlerRepository.HANDLER_NAME);
111
+			if (handler_name!=null && handler_name.trim().length()>0) {
112
+				HandlerThread handlerThread = handlerTreadMap.get(handler_name);
113
+				if (handlerThread != null) {
114
+					IJobHandler handler = handlerThread.getHandler();
115
+					handlerThread.toStop();
116
+					handlerThread.interrupt();
117
+					regist(handler_name, handler);
118
+					callback.setStatus(RemoteCallBack.SUCCESS);
119
+				} else {
120
+					callback.setMsg("handler[" + handler_name + "] not found.");
121
+				}
122
+			}else{
123
+				callback.setMsg("param[HANDLER_NAME] can not be null.");
124
+			}
125
+						
126
+		} else {
127
+			callback.setMsg("param[NAMESPACE] is not valid.");
128
+			return JacksonUtil.writeValueAsString(callback);
64
 		}
129
 		}
65
 		
130
 		
66
-		logger.info(">>>>>>>>>>> xxl-job pushHandleQueue end, triggerData:{}", new Object[]{callback});
67
-		return JacksonUtil.writeValueAsString(callback);
131
+		logger.info(">>>>>>>>>>> xxl-job service end, triggerData:{}", new Object[]{callback});
132
+		return JacksonUtil.writeValueAsString(callback); 
68
 	}
133
 	}
69
 	
134
 	
70
 }
135
 }

+ 17 - 1
xxl-job-client/src/main/java/com/xxl/job/client/handler/HandlerThread.java Vedi File

12
 import org.slf4j.LoggerFactory;
12
 import org.slf4j.LoggerFactory;
13
 
13
 
14
 import com.xxl.job.client.handler.IJobHandler.JobHandleStatus;
14
 import com.xxl.job.client.handler.IJobHandler.JobHandleStatus;
15
+import com.xxl.job.client.log.XxlJobFileAppender;
15
 import com.xxl.job.client.util.HttpUtil;
16
 import com.xxl.job.client.util.HttpUtil;
16
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
17
 import com.xxl.job.client.util.HttpUtil.RemoteCallBack;
17
 
18
 
25
 	private IJobHandler handler;
26
 	private IJobHandler handler;
26
 	private LinkedBlockingQueue<Map<String, String>> handlerDataQueue;
27
 	private LinkedBlockingQueue<Map<String, String>> handlerDataQueue;
27
 	private ConcurrentHashSet<String> logIdSet;		// avoid repeat trigger for the same TRIGGER_LOG_ID
28
 	private ConcurrentHashSet<String> logIdSet;		// avoid repeat trigger for the same TRIGGER_LOG_ID
29
+	private boolean toStop = false;
28
 	
30
 	
29
 	public HandlerThread(IJobHandler handler) {
31
 	public HandlerThread(IJobHandler handler) {
30
 		this.handler = handler;
32
 		this.handler = handler;
32
 		logIdSet = new ConcurrentHashSet<String>();
34
 		logIdSet = new ConcurrentHashSet<String>();
33
 	}
35
 	}
34
 	
36
 	
37
+	public IJobHandler getHandler() {
38
+		return handler;
39
+	}
40
+	public void toStop() {
41
+		/**
42
+		 * Thread.interrupt只支持终止线程的阻塞状态(wait、join、sleep),
43
+		 * 在阻塞出抛出InterruptedException异常,但是并不会终止运行的线程本身;
44
+		 * 所以需要注意,此处彻底销毁本线程,需要通过共享变量方式;
45
+		 */
46
+		this.toStop = true;
47
+	}
48
+	
35
 	public void pushData(Map<String, String> param) {
49
 	public void pushData(Map<String, String> param) {
36
 		if (param.get(HandlerRepository.TRIGGER_LOG_ID)!=null && !logIdSet.contains(param.get(HandlerRepository.TRIGGER_LOG_ID))) {
50
 		if (param.get(HandlerRepository.TRIGGER_LOG_ID)!=null && !logIdSet.contains(param.get(HandlerRepository.TRIGGER_LOG_ID))) {
37
 			handlerDataQueue.offer(param);
51
 			handlerDataQueue.offer(param);
41
 	int i = 1;
55
 	int i = 1;
42
 	@Override
56
 	@Override
43
 	public void run() {
57
 	public void run() {
44
-		while(true){
58
+		while(!toStop){
45
 			try {
59
 			try {
46
 				Map<String, String> handlerData = handlerDataQueue.poll();
60
 				Map<String, String> handlerData = handlerDataQueue.poll();
47
 				if (handlerData!=null) {
61
 				if (handlerData!=null) {
63
 					JobHandleStatus _status = JobHandleStatus.FAIL;
77
 					JobHandleStatus _status = JobHandleStatus.FAIL;
64
 					String _msg = null;
78
 					String _msg = null;
65
 					try {
79
 					try {
80
+						XxlJobFileAppender.contextHolder.set(trigger_log_id);
66
 						_status = handler.handle(handlerParams);
81
 						_status = handler.handle(handlerParams);
67
 					} catch (Exception e) {
82
 					} catch (Exception e) {
68
 						logger.info("HandlerThread Exception:", e);
83
 						logger.info("HandlerThread Exception:", e);
100
 				logger.info("HandlerThread Exception:", e);
115
 				logger.info("HandlerThread Exception:", e);
101
 			}
116
 			}
102
 		}
117
 		}
118
+		logger.info(">>>>>>>>>>>> xxl-job handlerThrad stoped, hashCode:{}", Thread.currentThread());
103
 	}
119
 	}
104
 }
120
 }

+ 180 - 0
xxl-job-client/src/main/java/com/xxl/job/client/log/XxlJobFileAppender.java Vedi File

1
+package com.xxl.job.client.log;
2
+
3
+import java.io.BufferedReader;
4
+import java.io.File;
5
+import java.io.FileInputStream;
6
+import java.io.FileOutputStream;
7
+import java.io.IOException;
8
+import java.io.InputStream;
9
+import java.io.InputStreamReader;
10
+import java.text.SimpleDateFormat;
11
+import java.util.Date;
12
+
13
+import org.apache.log4j.AppenderSkeleton;
14
+import org.apache.log4j.Layout;
15
+import org.apache.log4j.spi.LoggingEvent;
16
+
17
+/**
18
+ * store trigger log in each log-file
19
+ * @author xuxueli 2016-3-12 19:25:12
20
+ */
21
+public class XxlJobFileAppender extends AppenderSkeleton {
22
+	
23
+	// for HandlerThread
24
+	public static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
25
+	public static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
26
+	
27
+	// trogger log file path
28
+	public static volatile String filePath;
29
+	public void setFilePath(String filePath) {
30
+		XxlJobFileAppender.filePath = filePath;
31
+	}
32
+	
33
+	@Override
34
+	protected void append(LoggingEvent event) {
35
+		String trigger_log_id = contextHolder.get();
36
+		if (trigger_log_id==null || trigger_log_id.trim().length()==0) {
37
+			return;
38
+		}
39
+		
40
+		// filePath/
41
+		File filePathDir = new File(filePath);	
42
+		if (!filePathDir.exists()) {
43
+			filePathDir.mkdirs();
44
+		}
45
+		
46
+		// filePath/yyyy-MM-dd/
47
+		String nowFormat = sdf.format(new Date());
48
+		File filePathDateDir = new File(filePathDir, nowFormat);	
49
+		if (!filePathDateDir.exists()) {
50
+			filePathDateDir.mkdirs();
51
+		}
52
+		
53
+		// filePath/yyyy-MM-dd/9999.log
54
+		String logFileName = trigger_log_id.concat(".log");
55
+		File logFile = new File(filePathDateDir, logFileName);	
56
+		if (!logFile.exists()) {
57
+			try {
58
+				logFile.createNewFile();
59
+			} catch (IOException e) {
60
+				e.printStackTrace();
61
+				return;
62
+			}
63
+		}
64
+		
65
+		// append file content
66
+		try {
67
+			FileOutputStream fos = null;
68
+			try {
69
+				fos = new FileOutputStream(logFile, true);
70
+				fos.write(layout.format(event).getBytes("utf-8"));
71
+				if (layout.ignoresThrowable()) {
72
+					String[] throwableInfo = event.getThrowableStrRep();
73
+					if (throwableInfo != null) {
74
+						for (int i = 0; i < throwableInfo.length; i++) {
75
+							fos.write(throwableInfo[i].getBytes("utf-8"));
76
+							fos.write(Layout.LINE_SEP.getBytes("utf-8"));
77
+						}
78
+					}
79
+				}
80
+				fos.flush();
81
+			} finally {
82
+				if (fos != null) {
83
+					try {
84
+						fos.close();
85
+					} catch (IOException e) {
86
+						e.printStackTrace();
87
+					}
88
+				}
89
+			} 
90
+		} catch (Exception e) {
91
+			e.printStackTrace();
92
+		}
93
+		
94
+	}
95
+
96
+	@Override
97
+	public void close() {
98
+		// TODO Auto-generated method stub
99
+		
100
+	}
101
+
102
+	@Override
103
+	public boolean requiresLayout() {
104
+		// TODO Auto-generated method stub
105
+		return false;
106
+	}
107
+	
108
+	/**
109
+	 * support read log-file
110
+	 * @param triggerDate
111
+	 * @param trigger_log_id
112
+	 * @return
113
+	 */
114
+	public static String readLog(Date triggerDate, String trigger_log_id ){
115
+		if (triggerDate==null || trigger_log_id==null || trigger_log_id.trim().length()==0) {
116
+			return null;
117
+		}
118
+		
119
+		// filePath/
120
+		File filePathDir = new File(filePath);	
121
+		if (!filePathDir.exists()) {
122
+			filePathDir.mkdirs();
123
+		}
124
+		
125
+		// filePath/yyyy-MM-dd/
126
+		String nowFormat = sdf.format(triggerDate);
127
+		File filePathDateDir = new File(filePathDir, nowFormat);	
128
+		if (!filePathDateDir.exists()) {
129
+			filePathDateDir.mkdirs();
130
+		}
131
+		
132
+		// filePath/yyyy-MM-dd/9999.log
133
+		String logFileName = trigger_log_id.concat(".log");
134
+		File logFile = new File(filePathDateDir, logFileName);	
135
+		if (!logFile.exists()) {
136
+			try {
137
+				logFile.createNewFile();
138
+			} catch (IOException e) {
139
+				e.printStackTrace();
140
+				return null;
141
+			}
142
+		}
143
+		
144
+		try {
145
+			InputStream ins = null;
146
+			BufferedReader reader = null;
147
+			try {
148
+				ins = new FileInputStream(logFile);
149
+				reader = new BufferedReader(new InputStreamReader(ins, "utf-8"));
150
+				if (reader != null) {
151
+					String content = null;
152
+					StringBuilder sb = new StringBuilder();
153
+					while ((content = reader.readLine()) != null) {
154
+						sb.append(content).append("\n");
155
+					}
156
+					return sb.toString();
157
+				}
158
+			} finally {
159
+				if (ins != null) {
160
+					try {
161
+						ins.close();
162
+					} catch (IOException e) {
163
+						e.printStackTrace();
164
+					}
165
+				}
166
+				if (reader != null) {
167
+					try {
168
+						reader.close();
169
+					} catch (IOException e) {
170
+						e.printStackTrace();
171
+					}
172
+				}
173
+			} 
174
+		} catch (Exception e) {
175
+			e.printStackTrace();
176
+		}
177
+		return null;
178
+	}
179
+	
180
+}

+ 1 - 1
xxl-job-client/src/main/java/com/xxl/job/client/netcom/jetty/XxlJobJettyServerHandler.java Vedi File

32
 			}
32
 			}
33
 		}
33
 		}
34
 
34
 
35
-		String resp = HandlerRepository.pushHandleQueue(_param);
35
+		String resp = HandlerRepository.service(_param);
36
 
36
 
37
 		httpServletResponse.setContentType("text/html;charset=utf-8");
37
 		httpServletResponse.setContentType("text/html;charset=utf-8");
38
 		httpServletResponse.setStatus(HttpServletResponse.SC_OK);
38
 		httpServletResponse.setStatus(HttpServletResponse.SC_OK);

+ 1 - 1
xxl-job-client/src/main/java/com/xxl/job/client/netcom/servlet/XxlJobServlet.java Vedi File

44
 			}
44
 			}
45
 		}
45
 		}
46
 		
46
 		
47
-		String resp = HandlerRepository.pushHandleQueue(_param);
47
+		String resp = HandlerRepository.service(_param);
48
 		response.getWriter().append(resp);
48
 		response.getWriter().append(resp);
49
 		return;
49
 		return;
50
 	}
50
 	}