parent
f2692c509e
commit
12fc71dd57
@ -1,27 +1,28 @@ |
||||
* [首页](/?t=1559633402239) |
||||
* [首页](/?t=1559803778913) |
||||
* 开发文档 |
||||
* [快速体验](files/10010_快速体验.md?t=1559633402241) |
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1559633402260) |
||||
* [新增接口](files/10020_新增接口.md?t=1559633402260) |
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1559633402260) |
||||
* [错误处理](files/10040_错误处理.md?t=1559633402260) |
||||
* [编写文档](files/10041_编写文档.md?t=1559633402260) |
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1559633402261) |
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1559633402261) |
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1559633402261) |
||||
* [ISV管理](files/10085_ISV管理.md?t=1559633402261) |
||||
* [路由授权](files/10090_路由授权.md?t=1559633402261) |
||||
* [接口限流](files/10092_接口限流.md?t=1559633402261) |
||||
* [SDK开发](files/10095_SDK开发.md?t=1559633402261) |
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1559633402261) |
||||
* [应用授权](files/10097_应用授权.md?t=1559633402261) |
||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1559633402262) |
||||
* [对接前端](files/10100_对接前端.md?t=1559633402262) |
||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1559633402262) |
||||
* [文件上传](files/10104_文件上传.md?t=1559633402262) |
||||
* [快速体验](files/10010_快速体验.md?t=1559803778920) |
||||
* [项目接入到SOP](files/10011_项目接入到SOP.md?t=1559803778940) |
||||
* [新增接口](files/10020_新增接口.md?t=1559803778940) |
||||
* [业务参数校验](files/10030_业务参数校验.md?t=1559803778940) |
||||
* [错误处理](files/10040_错误处理.md?t=1559803778940) |
||||
* [编写文档](files/10041_编写文档.md?t=1559803778941) |
||||
* [接口交互详解](files/10050_接口交互详解.md?t=1559803778941) |
||||
* [easyopen支持](files/10070_easyopen支持.md?t=1559803778941) |
||||
* [使用签名校验工具](files/10080_使用签名校验工具.md?t=1559803778941) |
||||
* [ISV管理](files/10085_ISV管理.md?t=1559803778941) |
||||
* [路由授权](files/10090_路由授权.md?t=1559803778941) |
||||
* [接口限流](files/10092_接口限流.md?t=1559803778941) |
||||
* [监控日志](files/10093_监控日志.md?t=1559803778941) |
||||
* [SDK开发](files/10095_SDK开发.md?t=1559803778941) |
||||
* [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1559803778942) |
||||
* [应用授权](files/10097_应用授权.md?t=1559803778942) |
||||
* [更改数据节点名称](files/10099_更改数据节点名称.md?t=1559803778942) |
||||
* [对接前端](files/10100_对接前端.md?t=1559803778942) |
||||
* [自定义过滤器](files/10102_自定义过滤器.md?t=1559803778942) |
||||
* [文件上传](files/10104_文件上传.md?t=1559803778942) |
||||
* 原理分析 |
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1559633402262) |
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1559633402262) |
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1559633402262) |
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1559633402262) |
||||
* [常见问题](files/90100_常见问题.md?t=1559633402262) |
||||
* [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1559803778942) |
||||
* [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1559803778942) |
||||
* [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1559803778942) |
||||
* [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1559803778942) |
||||
* [常见问题](files/90100_常见问题.md?t=1559803778942) |
||||
|
@ -0,0 +1,44 @@ |
||||
# 监控日志 |
||||
|
||||
1.10.0开始sop-admin提供了简单的监控日志查询,方便在线排查问题。 |
||||
|
||||
- 错误日志统一在网关负责收集 |
||||
- 只收集未知类型的错误日志,开发人员主动throw的异常不收集 |
||||
- sop-admin通过网关提供的restful接口获取日志内容,然后在后台展示 |
||||
- 收集的日志存放在内存中,重启网关日志会消失 |
||||
- 只会收集20条不同的日志内容,相同内容会count+1。可设置`ApiConfig.storeErrorCapacity`属性扩大容量,默认容量20 |
||||
|
||||
第一次使用需要添加网关服务器实例,前往:`服务管理--监控日志--添加监控服务器` |
||||
|
||||
## 永久保存日志 |
||||
|
||||
默认收集的日志存放在内存中,重启网关日志会消失(见:`DefaultServiceErrorManager.java`)。如果要永久保存日志内容,需要自己实现`ServiceErrorManager`接口 |
||||
|
||||
```java |
||||
public class MyServiceErrorManager implements ServiceErrorManager { |
||||
... |
||||
} |
||||
``` |
||||
|
||||
然后在ApiConfig中配置 |
||||
|
||||
```java |
||||
@Configuration |
||||
public class ZuulConfig extends AlipayZuulConfiguration { |
||||
|
||||
static { |
||||
... |
||||
ApiConfig.getInstance().setServiceErrorManager(new MyServiceErrorManager()); |
||||
// 日志收集容量,默认20。只会收集20条不同内容的日志 |
||||
ApiConfig.getInstance().setStoreErrorCapacity(20); |
||||
... |
||||
} |
||||
|
||||
} |
||||
``` |
||||
|
||||
- 后台预览 |
||||
|
||||
![监控日志](images/10093_1.png "10093_1.png") |
||||
|
||||
![监控日志](images/10093_2.png "10093_2.png") |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,13 @@ |
||||
DROP TABLE IF EXISTS `config_common`; |
||||
|
||||
CREATE TABLE `config_common` ( |
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, |
||||
`config_group` varchar(64) NOT NULL DEFAULT '' COMMENT '配置分组', |
||||
`config_key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置key', |
||||
`content` varchar(128) NOT NULL DEFAULT '' COMMENT '内容', |
||||
`remark` varchar(128) DEFAULT NULL COMMENT '备注', |
||||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
||||
PRIMARY KEY (`id`), |
||||
UNIQUE KEY `uk_groupkey` (`config_group`,`config_key`) USING BTREE |
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='通用配置表'; |
@ -0,0 +1,169 @@ |
||||
package com.gitee.sop.adminserver.api.service; |
||||
|
||||
import com.alibaba.fastjson.JSON; |
||||
import com.alibaba.fastjson.JSONObject; |
||||
import com.gitee.easyopen.annotation.Api; |
||||
import com.gitee.easyopen.annotation.ApiService; |
||||
import com.gitee.easyopen.doc.annotation.ApiDoc; |
||||
import com.gitee.easyopen.doc.annotation.ApiDocMethod; |
||||
import com.gitee.easyopen.exception.ApiException; |
||||
import com.gitee.easyopen.verify.DefaultMd5Verifier; |
||||
import com.gitee.fastmybatis.core.query.Query; |
||||
import com.gitee.sop.adminserver.api.service.param.LogMonitorInstanceAddParam; |
||||
import com.gitee.sop.adminserver.api.service.result.LogMonitorInstanceVO; |
||||
import com.gitee.sop.adminserver.common.QueryUtil; |
||||
import com.gitee.sop.adminserver.entity.ConfigCommon; |
||||
import com.gitee.sop.adminserver.mapper.ConfigCommonMapper; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.web.client.RestTemplate; |
||||
|
||||
import javax.validation.constraints.NotNull; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.Comparator; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@ApiService |
||||
@ApiDoc("服务管理-日志监控") |
||||
@Slf4j |
||||
public class LogApi { |
||||
|
||||
public static final String LOG_MONITOR_INSTANCE = "log.monitor.instance"; |
||||
public static final String CODE_SUCCESS = "10000"; |
||||
|
||||
@Autowired |
||||
ConfigCommonMapper configCommonMapper; |
||||
|
||||
RestTemplate restTemplate = new RestTemplate(); |
||||
|
||||
@Value("${zuul.secret}") |
||||
private String secret; |
||||
|
||||
@Api(name = "monitor.log.list") |
||||
@ApiDocMethod(description = "获取监控日志") |
||||
List<LogMonitorInstanceVO> listLog() { |
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE); |
||||
List<LogMonitorInstanceVO> ret = new ArrayList<>(); |
||||
int id = 1; |
||||
for (ConfigCommon configCommon : configCommonList) { |
||||
int pid = id++; |
||||
String ipPort = configCommon.getConfigKey(); |
||||
// 父节点
|
||||
LogMonitorInstanceVO logMonitorInstanceVOParent = new LogMonitorInstanceVO(); |
||||
logMonitorInstanceVOParent.setRawId(configCommon.getId()); |
||||
logMonitorInstanceVOParent.setTreeId(pid); |
||||
logMonitorInstanceVOParent.setMonitorName(configCommon.getContent()); |
||||
ret.add(logMonitorInstanceVOParent); |
||||
try { |
||||
String logData = this.requestLogServer(ipPort, "listErrors"); |
||||
JSONObject jsonObject = JSON.parseObject(logData); |
||||
if (CODE_SUCCESS.equals(jsonObject.getString("code"))) { |
||||
int errorTotal = 0; |
||||
List<LogMonitorInstanceVO> data = JSON.parseArray(jsonObject.getString("data"), LogMonitorInstanceVO.class); |
||||
for (LogMonitorInstanceVO instanceVO : data) { |
||||
instanceVO.setTreeId(id++); |
||||
instanceVO.setParentId(pid); |
||||
errorTotal += instanceVO.getCount(); |
||||
} |
||||
ret.addAll(data); |
||||
logMonitorInstanceVOParent.setCount(errorTotal); |
||||
} |
||||
} catch (Exception e) { |
||||
log.error("获取日志信息出错", e); |
||||
throw new ApiException("获取日志信息出错"); |
||||
} |
||||
} |
||||
Collections.sort(ret, Comparator.comparing(LogMonitorInstanceVO::getCount)); |
||||
return ret; |
||||
} |
||||
|
||||
@Api(name = "monitor.log.clear") |
||||
@ApiDocMethod(description = "清空日志") |
||||
void clearLog(@NotNull(message = "id不能为空") Long id) { |
||||
ConfigCommon configCommon = configCommonMapper.getById(id); |
||||
if (configCommon == null) { |
||||
return; |
||||
} |
||||
try { |
||||
String ipPort = configCommon.getConfigKey(); |
||||
this.requestLogServer(ipPort, "clearErrors"); |
||||
} catch (Exception e) { |
||||
throw new ApiException("清除失败"); |
||||
} |
||||
} |
||||
|
||||
@Api(name = "monitor.instance.list") |
||||
@ApiDocMethod(description = "获取已添加的监控实例") |
||||
List<String> listServiceInstance() { |
||||
List<ConfigCommon> configCommonList = configCommonMapper.listByColumn("config_group", LOG_MONITOR_INSTANCE); |
||||
return configCommonList.stream() |
||||
.map(ConfigCommon::getConfigKey) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
@Api(name = "monitor.instance.add") |
||||
@ApiDocMethod(description = "添加监控实例") |
||||
void addServiceInstance(LogMonitorInstanceAddParam param) { |
||||
String ipPort = param.getIp() + ":" + param.getPort(); |
||||
this.checkInstance(ipPort); |
||||
|
||||
Query query = new Query(); |
||||
query.eq("config_group", LOG_MONITOR_INSTANCE) |
||||
.eq("config_key", ipPort); |
||||
ConfigCommon rec = configCommonMapper.getByQuery(query); |
||||
if (rec != null) { |
||||
throw new ApiException("该实例已添加"); |
||||
} |
||||
ConfigCommon configCommon = new ConfigCommon(); |
||||
configCommon.setConfigGroup(LOG_MONITOR_INSTANCE); |
||||
configCommon.setConfigKey(ipPort); |
||||
configCommon.setContent(param.getServiceId() + "(" + ipPort + ")"); |
||||
configCommonMapper.saveIgnoreNull(configCommon); |
||||
} |
||||
|
||||
private void checkInstance(String ipPort) { |
||||
try { |
||||
String json = this.requestLogServer(ipPort, "listErrors"); |
||||
JSONObject jsonObject = JSON.parseObject(json); |
||||
if (!CODE_SUCCESS.equals(jsonObject.getString("code"))) { |
||||
log.error("请求结果:{}", json); |
||||
throw new ApiException("添加失败"); |
||||
} |
||||
} catch (Exception e) { |
||||
log.error("添加失败", e); |
||||
throw new ApiException("添加失败"); |
||||
} |
||||
} |
||||
|
||||
private String requestLogServer(String ipPort, String path) throws Exception { |
||||
DefaultMd5Verifier md5Verifier = new DefaultMd5Verifier(); |
||||
Map<String, Object> params = new HashMap<>(); |
||||
params.put("time", System.currentTimeMillis()); |
||||
String sign = md5Verifier.buildSign(params, secret); |
||||
params.put("sign", sign); |
||||
String query = QueryUtil.buildQueryString(params); |
||||
String url = "http://" + ipPort + "/" + path + "?" + query; |
||||
ResponseEntity<String> entity = restTemplate.getForEntity(url, String.class); |
||||
if (entity.getStatusCode() != HttpStatus.OK) { |
||||
throw new IllegalAccessException("无权访问"); |
||||
} |
||||
return entity.getBody(); |
||||
} |
||||
|
||||
@Api(name = "monitor.instance.del") |
||||
@ApiDocMethod(description = "删除监控实例") |
||||
void delServiceInstance(@NotNull(message = "id不能为空") Long id) { |
||||
configCommonMapper.deleteById(id); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.gitee.sop.adminserver.api.service.param; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import javax.validation.constraints.NotBlank; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Data |
||||
public class LogMonitorInstanceAddParam { |
||||
@NotBlank(message = "serviceId不能为空") |
||||
private String serviceId; |
||||
|
||||
@NotBlank(message = "ip不能为空") |
||||
private String ip; |
||||
|
||||
private int port; |
||||
} |
@ -0,0 +1,22 @@ |
||||
package com.gitee.sop.adminserver.api.service.result; |
||||
|
||||
import lombok.Data; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Data |
||||
public class LogMonitorInstanceVO { |
||||
private String id; |
||||
private int treeId; |
||||
// 表主键
|
||||
private long rawId; |
||||
private String name; |
||||
private String version; |
||||
private String serviceId; |
||||
private String errorMsg; |
||||
private long count; |
||||
|
||||
private int parentId; |
||||
private String monitorName; |
||||
} |
@ -0,0 +1,28 @@ |
||||
package com.gitee.sop.adminserver.common; |
||||
|
||||
import java.io.UnsupportedEncodingException; |
||||
import java.net.URLEncoder; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public class QueryUtil { |
||||
|
||||
public static String buildQueryString(Map<String, ?> params) throws UnsupportedEncodingException { |
||||
if (params == null || params.size() == 0) { |
||||
return ""; |
||||
} |
||||
StringBuilder query = new StringBuilder(); |
||||
int i = 0; |
||||
for (Map.Entry<String, ?> entry : params.entrySet()) { |
||||
String name = entry.getKey(); |
||||
String value = String.valueOf(entry.getValue()); |
||||
if (i++ > 0) { |
||||
query.append("&"); |
||||
} |
||||
query.append(name).append("=").append(URLEncoder.encode(value, "UTF-8")); |
||||
} |
||||
return query.toString(); |
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
package com.gitee.sop.adminserver.entity; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import javax.persistence.Column; |
||||
import javax.persistence.GeneratedValue; |
||||
import javax.persistence.GenerationType; |
||||
import javax.persistence.Id; |
||||
import javax.persistence.Table; |
||||
import java.util.Date; |
||||
|
||||
|
||||
/** |
||||
* 表名:config_common |
||||
* 备注:通用配置表 |
||||
* |
||||
* @author tanghc |
||||
*/ |
||||
@Table(name = "config_common") |
||||
@Data |
||||
public class ConfigCommon { |
||||
@Id |
||||
@Column(name = "id") |
||||
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||
/** 数据库字段:id */ |
||||
private Long id; |
||||
|
||||
/** 配置分组, 数据库字段:config_group */ |
||||
private String configGroup; |
||||
|
||||
/** 配置key, 数据库字段:config_key */ |
||||
private String configKey; |
||||
|
||||
/** 内容, 数据库字段:content */ |
||||
private String content; |
||||
|
||||
/** 备注, 数据库字段:remark */ |
||||
private String remark; |
||||
|
||||
/** 数据库字段:gmt_create */ |
||||
private Date gmtCreate; |
||||
|
||||
/** 数据库字段:gmt_modified */ |
||||
private Date gmtModified; |
||||
} |
@ -0,0 +1,12 @@ |
||||
package com.gitee.sop.adminserver.mapper; |
||||
|
||||
import com.gitee.fastmybatis.core.mapper.CrudMapper; |
||||
|
||||
import com.gitee.sop.adminserver.entity.ConfigCommon; |
||||
|
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public interface ConfigCommonMapper extends CrudMapper<ConfigCommon, Long> { |
||||
} |
@ -1 +1 @@ |
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,c=t[0],i=t[1],f=t[2],l=0,s=[];l<c.length;l++)o=c[l],u[o]&&s.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);d&&d(t);while(s.length)s.shift()();return a.push.apply(a,f||[]),n()}function n(){for(var e,t=0;t<a.length;t++){for(var n=a[t],r=!0,o=1;o<n.length;o++){var c=n[o];0!==u[c]&&(r=!1)}r&&(a.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},a=[];function c(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"5ba26eff","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"0cbb7297","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,a=document.getElementsByTagName("link"),c=0;c<a.length;c++){var f=a[c],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var s=document.getElementsByTagName("style");for(c=0;c<s.length;c++){f=s[c],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=t,d.onerror=function(t){var r=t&&t.target&&t.target.src||u,a=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");a.code="CSS_CHUNK_LOAD_FAILED",a.request=r,delete o[e],d.parentNode.removeChild(d),n(a)},d.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(d)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var a=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=a);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=c(e),f=function(t){l.onerror=l.onload=null,clearTimeout(s);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,a=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");a.type=r,a.request=o,n[1](a)}u[e]=void 0}};var s=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var s=0;s<f.length;s++)t(f[s]);var d=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.bc31495f.js></script></body></html> |
||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><link rel=icon href=favicon.ico><title>SOP Admin</title><link href=static/css/chunk-elementUI.18b11d0e.css rel=stylesheet><link href=static/css/chunk-libs.3dfb7769.css rel=stylesheet><link href=static/css/app.4f0872ef.css rel=stylesheet></head><body><noscript><strong>We're sorry but SOP Admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script>(function(e){function t(t){for(var r,o,a=t[0],i=t[1],f=t[2],l=0,d=[];l<a.length;l++)o=a[l],u[o]&&d.push(u[o][0]),u[o]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);s&&s(t);while(d.length)d.shift()();return c.push.apply(c,f||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,o=1;o<n.length;o++){var a=n[o];0!==u[a]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={runtime:0},u={runtime:0},c=[];function a(e){return i.p+"static/js/"+({}[e]||e)+"."+{"chunk-19100986":"3ec98327","chunk-238a81e9":"5955f13d","chunk-29b24c82":"2b3ac7ca","chunk-2d0aa95b":"447435b3","chunk-2d221836":"574c9ab6","chunk-2d22c2e3":"46f37153","chunk-4a59cbe4":"3309de97","chunk-ea2e58a4":"f3f85b0e"}[e]+".js"}function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.e=function(e){var t=[],n={"chunk-19100986":1,"chunk-238a81e9":1,"chunk-29b24c82":1,"chunk-ea2e58a4":1};o[e]?t.push(o[e]):0!==o[e]&&n[e]&&t.push(o[e]=new Promise(function(t,n){for(var r="static/css/"+({}[e]||e)+"."+{"chunk-19100986":"a43114f3","chunk-238a81e9":"e8e2beee","chunk-29b24c82":"857979fb","chunk-2d0aa95b":"31d6cfe0","chunk-2d221836":"31d6cfe0","chunk-2d22c2e3":"31d6cfe0","chunk-4a59cbe4":"31d6cfe0","chunk-ea2e58a4":"d10599db"}[e]+".css",u=i.p+r,c=document.getElementsByTagName("link"),a=0;a<c.length;a++){var f=c[a],l=f.getAttribute("data-href")||f.getAttribute("href");if("stylesheet"===f.rel&&(l===r||l===u))return t()}var d=document.getElementsByTagName("style");for(a=0;a<d.length;a++){f=d[a],l=f.getAttribute("data-href");if(l===r||l===u)return t()}var s=document.createElement("link");s.rel="stylesheet",s.type="text/css",s.onload=t,s.onerror=function(t){var r=t&&t.target&&t.target.src||u,c=new Error("Loading CSS chunk "+e+" failed.\n("+r+")");c.code="CSS_CHUNK_LOAD_FAILED",c.request=r,delete o[e],s.parentNode.removeChild(s),n(c)},s.href=u;var h=document.getElementsByTagName("head")[0];h.appendChild(s)}).then(function(){o[e]=0}));var r=u[e];if(0!==r)if(r)t.push(r[2]);else{var c=new Promise(function(t,n){r=u[e]=[t,n]});t.push(r[2]=c);var f,l=document.createElement("script");l.charset="utf-8",l.timeout=120,i.nc&&l.setAttribute("nonce",i.nc),l.src=a(e),f=function(t){l.onerror=l.onload=null,clearTimeout(d);var n=u[e];if(0!==n){if(n){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src,c=new Error("Loading chunk "+e+" failed.\n("+r+": "+o+")");c.type=r,c.request=o,n[1](c)}u[e]=void 0}};var d=setTimeout(function(){f({type:"timeout",target:l})},12e4);l.onerror=l.onload=f,document.head.appendChild(l)}return Promise.all(t)},i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i.oe=function(e){throw console.error(e),e};var f=window["webpackJsonp"]=window["webpackJsonp"]||[],l=f.push.bind(f);f.push=t,f=f.slice();for(var d=0;d<f.length;d++)t(f[d]);var s=l;n()})([]);</script><script src=static/js/chunk-elementUI.fa810e14.js></script><script src=static/js/chunk-libs.a463a5ed.js></script><script src=static/js/app.36a3cb86.js></script></body></html> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@ |
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.child;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]); |
||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d22c2e3"],{f1ac:function(t,e,a){"use strict";a.r(e);var n=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"app-container"},[a("el-form",{staticClass:"demo-form-inline",attrs:{inline:!0,model:t.searchFormData,size:"mini"}},[a("el-form-item",{attrs:{label:"serviceId"}},[a("el-input",{staticStyle:{width:"250px"},attrs:{clearable:!0,placeholder:"serviceId"},model:{value:t.searchFormData.serviceId,callback:function(e){t.$set(t.searchFormData,"serviceId",e)},expression:"searchFormData.serviceId"}})],1),t._v(" "),a("el-form-item",[a("el-button",{attrs:{type:"primary",icon:"el-icon-search"},on:{click:t.onSearchTable}},[t._v("查询")])],1)],1),t._v(" "),a("el-table",{staticStyle:{width:"100%","margin-bottom":"20px"},attrs:{data:t.tableData,border:"","row-key":"id"}},[a("el-table-column",{attrs:{prop:"name",label:"服务名称(serviceId)",width:"200"}}),t._v(" "),a("el-table-column",{attrs:{prop:"instanceId",label:"instanceId",width:"250"}}),t._v(" "),a("el-table-column",{attrs:{prop:"ipAddr",label:"IP地址",width:"150"}}),t._v(" "),a("el-table-column",{attrs:{prop:"serverPort",label:"端口号",width:"100"}}),t._v(" "),a("el-table-column",{attrs:{prop:"status",label:"服务状态",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-tag",{attrs:{type:"success"}},[t._v("已上线")]):t._e(),t._v(" "),e.row.parentId>0&&"STARTING"===e.row.status?a("el-tag",{attrs:{type:"info"}},[t._v("正在启动")]):t._e(),t._v(" "),e.row.parentId>0&&"UNKNOWN"===e.row.status?a("el-tag",[t._v("未知")]):t._e(),t._v(" "),e.row.parentId>0&&("OUT_OF_SERVICE"===e.row.status||"DOWN"===e.row.status)?a("el-tag",{attrs:{type:"danger"}},[t._v("已下线")]):t._e()]}}])}),t._v(" "),a("el-table-column",{attrs:{prop:"updateTime",label:"最后更新时间",width:"160"}}),t._v(" "),a("el-table-column",{attrs:{label:"操作",width:"100"},scopedSlots:t._u([{key:"default",fn:function(e){return[e.row.parentId>0&&"UP"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOffline(e.row)}}},[t._v("下线")]):t._e(),t._v(" "),e.row.parentId>0&&"OUT_OF_SERVICE"===e.row.status?a("el-button",{attrs:{type:"text",size:"mini"},on:{click:function(a){return t.onOnline(e.row)}}},[t._v("上线")]):t._e()]}}])})],1)],1)},r=[],i=(a("7f7f"),a("ac6a"),{data:function(){return{searchFormData:{serviceId:""},tableData:[]}},created:function(){this.loadTable()},methods:{loadTable:function(){this.post("service.instance.list",this.searchFormData,function(t){this.tableData=this.buildTreeData(t.data)})},buildTreeData:function(t){return t.forEach(function(e){var a=e.parentId;0===a||t.forEach(function(t){if(t.id===a){var n=t.children;n||(n=[]),n.push(e),t.children=n}})}),t=t.filter(function(t){return 0===t.parentId}),t},onSearchTable:function(){this.loadTable()},onOffline:function(t){this.confirm("确定要下线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.offline",a,function(){this.tip("下线成功"),e()})})},onOnline:function(t){this.confirm("确定要上线【"+t.name+"】吗?",function(e){var a={serviceId:t.name,instanceId:t.instanceId};this.post("service.instance.online",a,function(){this.tip("上线成功"),e()})})}}}),s=i,o=a("2877"),l=Object(o["a"])(s,n,r,!1,null,null,null);e["default"]=l.exports}}]); |
File diff suppressed because one or more lines are too long
@ -0,0 +1,234 @@ |
||||
<template> |
||||
<div class="app-container"> |
||||
<el-form :inline="true" :model="searchFormData" class="demo-form-inline" size="mini"> |
||||
<el-form-item> |
||||
<el-button type="primary" icon="el-icon-plus" @click="onAddServer">添加监控服务器</el-button> |
||||
</el-form-item> |
||||
</el-form> |
||||
<el-table |
||||
:data="tableData" |
||||
style="width: 100%;margin-bottom: 20px;" |
||||
border |
||||
row-key="treeId" |
||||
empty-text="请添加监控服务器" |
||||
> |
||||
<el-table-column |
||||
prop="monitorName" |
||||
label="网关实例" |
||||
width="300" |
||||
> |
||||
<template slot-scope="scope"> |
||||
<span v-if="scope.row.parentId === 0">{{ scope.row.monitorName }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
prop="serviceId" |
||||
label="serviceId" |
||||
width="200" |
||||
> |
||||
<template slot-scope="scope"> |
||||
<span v-if="scope.row.parentId > 0">{{ scope.row.serviceId }}</span> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
prop="name" |
||||
label="接口名 (版本号)" |
||||
width="200" |
||||
> |
||||
<template slot-scope="scope"> |
||||
{{ scope.row.name + (scope.row.version ? ' (' + scope.row.version + ')' : '') }} |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
prop="count" |
||||
label="出错次数" |
||||
width="100" |
||||
/> |
||||
<el-table-column |
||||
prop="errorMsg" |
||||
label="报错信息" |
||||
width="300" |
||||
> |
||||
<template v-if="scope.row.parentId > 0" slot-scope="scope"> |
||||
<div style="display: inline-block;" v-html="showErrorMsg(scope.row)"></div> <el-button type="text" size="mini" @click="onShowErrorDetail(scope.row)">详情</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
<el-table-column |
||||
label="操作" |
||||
width="180" |
||||
> |
||||
<template slot-scope="scope"> |
||||
<el-button v-if="scope.row.parentId === 0 && scope.row.children" type="text" size="mini" @click="onClearLog(scope.row)">清空日志</el-button> |
||||
<el-button v-if="scope.row.parentId === 0" type="text" size="mini" @click="onDelete(scope.row)">删除实例</el-button> |
||||
</template> |
||||
</el-table-column> |
||||
</el-table> |
||||
<!-- dialog --> |
||||
<el-dialog |
||||
title="选择服务器实例" |
||||
:visible.sync="logDialogInstanceVisible" |
||||
:close-on-click-modal="false" |
||||
> |
||||
<el-form |
||||
ref="logDialogForm" |
||||
:model="logDialogFormData" |
||||
:rules="rulesLog" |
||||
label-width="150px" |
||||
size="mini" |
||||
> |
||||
<el-form-item> |
||||
<p style="color: #878787;">只能选择网关实例,其它实例不支持</p> |
||||
</el-form-item> |
||||
<el-form-item prop="instanceData" label="服务器实例"> |
||||
<el-select v-model="logDialogFormData.instanceData" value-key="id" style="width: 400px;"> |
||||
<el-option |
||||
v-for="item in serviceData" |
||||
:key="item.id" |
||||
:label="item.name + '(' + item.ipAddr + ':' + item.serverPort + ')'" |
||||
:value="item" |
||||
:disabled="isOptionDisabled(item)" |
||||
> |
||||
<span style="float: left">{{ item.name }} <span v-if="isOptionDisabled(item)">(已添加)</span></span> |
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.ipAddr + ':' + item.serverPort }}</span> |
||||
</el-option> |
||||
</el-select> |
||||
</el-form-item> |
||||
</el-form> |
||||
<div slot="footer" class="dialog-footer"> |
||||
<el-button @click="logDialogInstanceVisible = false">取 消</el-button> |
||||
<el-button type="primary" @click="onLogDialogSave">保 存</el-button> |
||||
</div> |
||||
</el-dialog> |
||||
<el-dialog |
||||
title="错误详情" |
||||
:visible.sync="logDetailVisible" |
||||
width="60%" |
||||
> |
||||
<div style="overflow-x: auto" v-html="errorMsgDetail"></div> |
||||
<div slot="footer" class="dialog-footer"> |
||||
<el-button type="primary" @click="logDetailVisible = false">关 闭</el-button> |
||||
</div> |
||||
</el-dialog> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
searchFormData: {}, |
||||
tableData: [], |
||||
serviceData: [], |
||||
// 已经添加的实例 |
||||
addedInstanceList: [], |
||||
logDialogFormData: { |
||||
instanceData: null |
||||
}, |
||||
logDialogInstanceVisible: false, |
||||
logDetailVisible: false, |
||||
rulesLog: { |
||||
instanceData: [ |
||||
{ required: true, message: '不能为空', trigger: 'blur' } |
||||
] |
||||
}, |
||||
errorMsgDetail: '' |
||||
} |
||||
}, |
||||
created() { |
||||
this.loadServiceInstance() |
||||
this.loadTable() |
||||
}, |
||||
methods: { |
||||
loadServiceInstance: function() { |
||||
this.post('service.instance.list', {}, function(resp) { |
||||
this.serviceData = resp.data.filter(el => { |
||||
return el.instanceId && el.instanceId.length > 0 |
||||
}) |
||||
}) |
||||
this.post('monitor.instance.list', {}, function(resp) { |
||||
this.addedInstanceList = resp.data |
||||
}) |
||||
}, |
||||
loadTable: function() { |
||||
this.post('monitor.log.list', {}, function(resp) { |
||||
this.tableData = this.buildTreeData(resp.data) |
||||
}) |
||||
}, |
||||
isOptionDisabled: function(item) { |
||||
const ipPort = item.ipAddr + ':' + item.serverPort |
||||
const index = this.addedInstanceList.findIndex((value, index, arr) => { |
||||
return value === ipPort |
||||
}) |
||||
return index > -1 |
||||
}, |
||||
buildTreeData: function(data) { |
||||
data.forEach(ele => { |
||||
const parentId = ele.parentId |
||||
if (parentId === 0) { |
||||
// 是根元素 ,不做任何操作,如果是正常的for-i循环,可以直接continue. |
||||
} else { |
||||
// 如果ele是子元素的话 ,把ele扔到他的父亲的child数组中. |
||||
data.forEach(d => { |
||||
if (d.treeId === parentId) { |
||||
let childArray = d.children |
||||
if (!childArray) { |
||||
childArray = [] |
||||
} |
||||
childArray.push(ele) |
||||
d.children = childArray |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
// 去除重复元素 |
||||
data = data.filter(ele => ele.parentId === 0) |
||||
return data |
||||
}, |
||||
showErrorMsg: function(row) { |
||||
const msg = row.errorMsg.replace(/\<br\>/g, '') |
||||
return msg.substring(0, 30) + '...' |
||||
}, |
||||
onAddServer: function() { |
||||
this.logDialogInstanceVisible = true |
||||
}, |
||||
onDelete: function(row) { |
||||
this.confirm('确定要删除实例【' + row.monitorName + '】吗?', function(done) { |
||||
this.post('monitor.instance.del', { id: row.rawId }, function(resp) { |
||||
done() |
||||
this.tip('删除成功') |
||||
this.loadTable() |
||||
}) |
||||
}) |
||||
}, |
||||
onClearLog: function(row) { |
||||
this.confirm('确定要清空日志吗?', function(done) { |
||||
this.post('monitor.log.clear', { id: row.rawId }, function(resp) { |
||||
done() |
||||
this.tip('清空成功') |
||||
this.loadTable() |
||||
}) |
||||
}) |
||||
}, |
||||
onShowErrorDetail: function(row) { |
||||
this.errorMsgDetail = row.errorMsg |
||||
this.logDetailVisible = true |
||||
}, |
||||
onLogDialogSave: function() { |
||||
this.$refs['logDialogForm'].validate((valid) => { |
||||
if (valid) { |
||||
const instanceData = this.logDialogFormData.instanceData |
||||
const data = { |
||||
serviceId: instanceData.serviceId, |
||||
ip: instanceData.ipAddr, |
||||
port: instanceData.serverPort |
||||
} |
||||
this.post('monitor.instance.add', data, function(resp) { |
||||
this.logDialogInstanceVisible = false |
||||
this.loadTable() |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,15 @@ |
||||
package com.gitee.sop.gatewaycommon.bean; |
||||
|
||||
import lombok.Data; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Data |
||||
public class ErrorDefinition { |
||||
private String name; |
||||
private String version; |
||||
private String serviceId; |
||||
private String errorMsg; |
||||
|
||||
} |
@ -0,0 +1,32 @@ |
||||
package com.gitee.sop.gatewaycommon.bean; |
||||
|
||||
import lombok.Getter; |
||||
import lombok.Setter; |
||||
import lombok.ToString; |
||||
|
||||
import java.util.Objects; |
||||
|
||||
@Getter |
||||
@Setter |
||||
@ToString |
||||
public class ErrorEntity { |
||||
private String id; |
||||
private String name; |
||||
private String version; |
||||
private String serviceId; |
||||
private String errorMsg; |
||||
private long count; |
||||
|
||||
@Override |
||||
public boolean equals(Object o) { |
||||
if (this == o) return true; |
||||
if (o == null || getClass() != o.getClass()) return false; |
||||
ErrorEntity that = (ErrorEntity) o; |
||||
return id.equals(that.id); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return Objects.hash(id); |
||||
} |
||||
} |
@ -0,0 +1,51 @@ |
||||
package com.gitee.sop.gatewaycommon.manager; |
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition; |
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity; |
||||
import org.apache.commons.codec.digest.DigestUtils; |
||||
import org.springframework.beans.BeanUtils; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public class DefaultServiceErrorManager implements ServiceErrorManager { |
||||
|
||||
private static Map<String, ErrorEntity> store = new ConcurrentHashMap<>(128); |
||||
|
||||
@Override |
||||
public Collection<ErrorEntity> listAllErrors() { |
||||
return store.values(); |
||||
} |
||||
|
||||
@Override |
||||
public void saveError(ErrorDefinition errorDefinition) { |
||||
boolean hasCapacity = store.size() < ApiConfig.getInstance().getStoreErrorCapacity(); |
||||
// 这里还可以做其它事情,比如错误量到达一定数目后,自动发送邮件/微信给开发人员,方便及时获取异常情况
|
||||
String id = this.buildId(errorDefinition); |
||||
ErrorEntity errorEntity = store.get(id); |
||||
if (errorEntity == null && hasCapacity) { |
||||
errorEntity = new ErrorEntity(); |
||||
BeanUtils.copyProperties(errorDefinition, errorEntity); |
||||
errorEntity.setId(id); |
||||
store.put(id, errorEntity); |
||||
} |
||||
if (errorEntity != null) { |
||||
errorEntity.setCount(errorEntity.getCount() + 1); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void clear() { |
||||
store.clear(); |
||||
} |
||||
|
||||
protected String buildId(ErrorDefinition errorDefinition) { |
||||
return DigestUtils.md5Hex(errorDefinition.getServiceId() + errorDefinition.getErrorMsg()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,29 @@ |
||||
package com.gitee.sop.gatewaycommon.manager; |
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition; |
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
public interface ServiceErrorManager { |
||||
|
||||
/** |
||||
* 保存错误信息 |
||||
* @param errorDefinition |
||||
*/ |
||||
void saveError(ErrorDefinition errorDefinition); |
||||
|
||||
/** |
||||
* 清除日志 |
||||
*/ |
||||
void clear(); |
||||
|
||||
/** |
||||
* 获取所有错误信息 |
||||
* @return |
||||
*/ |
||||
Collection<ErrorEntity> listAllErrors(); |
||||
} |
@ -0,0 +1,13 @@ |
||||
package com.gitee.sop.gatewaycommon.result; |
||||
|
||||
import lombok.Data; |
||||
import lombok.EqualsAndHashCode; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@Data |
||||
@EqualsAndHashCode(callSuper = false) |
||||
public class JsonResult extends ApiResult { |
||||
private Object data; |
||||
} |
@ -0,0 +1,68 @@ |
||||
package com.gitee.sop.gateway.controller; |
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
||||
import com.gitee.sop.gatewaycommon.bean.ErrorEntity; |
||||
import com.gitee.sop.gatewaycommon.manager.ServiceErrorManager; |
||||
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||
import com.gitee.sop.gatewaycommon.result.ApiResult; |
||||
import com.gitee.sop.gatewaycommon.result.JsonResult; |
||||
import com.gitee.sop.gatewaycommon.util.RequestUtil; |
||||
import com.gitee.sop.gatewaycommon.validate.taobao.TaobaoSigner; |
||||
import org.springframework.beans.factory.annotation.Value; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* @author tanghc |
||||
*/ |
||||
@RestController |
||||
public class ErrorLogController { |
||||
|
||||
TaobaoSigner signer = new TaobaoSigner(); |
||||
|
||||
@Value("${zuul.secret}") |
||||
private String secret; |
||||
|
||||
@GetMapping("listErrors") |
||||
public ApiResult listErrors(HttpServletRequest request) { |
||||
try { |
||||
this.check(request); |
||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); |
||||
Collection<ErrorEntity> allErrors = serviceErrorManager.listAllErrors(); |
||||
JsonResult apiResult = new JsonResult(); |
||||
apiResult.setData(allErrors); |
||||
return apiResult; |
||||
} catch (Exception e) { |
||||
ApiResult apiResult = new ApiResult(); |
||||
apiResult.setCode("505050"); |
||||
apiResult.setMsg(e.getMessage()); |
||||
return apiResult; |
||||
} |
||||
} |
||||
|
||||
@GetMapping("clearErrors") |
||||
public ApiResult clearErrors(HttpServletRequest request) { |
||||
try { |
||||
this.check(request); |
||||
ServiceErrorManager serviceErrorManager = ApiConfig.getInstance().getServiceErrorManager(); |
||||
serviceErrorManager.clear(); |
||||
return new ApiResult(); |
||||
} catch (Exception e) { |
||||
ApiResult apiResult = new ApiResult(); |
||||
apiResult.setCode("505050"); |
||||
apiResult.setMsg(e.getMessage()); |
||||
return apiResult; |
||||
} |
||||
} |
||||
|
||||
private void check(HttpServletRequest request) { |
||||
Map<String, String> params = RequestUtil.convertRequestParamsToMap(request); |
||||
ApiParam apiParam = ApiParam.build(params); |
||||
signer.checkSign(apiParam, secret); |
||||
} |
||||
|
||||
} |
@ -1,4 +1,4 @@ |
||||
package com.gitee.sop.gateway; |
||||
package com.gitee.sop.gateway.controller; |
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
||||
import org.springframework.beans.factory.annotation.Value; |
Loading…
Reference in new issue