├── .gitignore ├── LICENSE ├── README.md ├── easy-log-core ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── easycode8 │ │ └── easylog │ │ └── core │ │ ├── LogDefinition.java │ │ ├── LogHolder.java │ │ ├── LogInfo.java │ │ ├── LogStopWatch.java │ │ ├── adapter │ │ ├── ControllerLogAttributeMapping.java │ │ ├── LogAttributeMappingAdapter.java │ │ └── ServiceLogAttributeMapping.java │ │ ├── annotation │ │ ├── EasyLog.java │ │ ├── EasyLogConfiguration.java │ │ ├── EasyLogProperties.java │ │ ├── EnableEasyLog.java │ │ └── Tag.java │ │ ├── aop │ │ ├── BeanFactoryLogAttributeSourceAdvisor.java │ │ ├── LogStaticMethodMatcherPointcutAdvisor.java │ │ └── interceptor │ │ │ ├── AbstractCacheLogAttributeSource.java │ │ │ ├── AnnotationLogAttributeSource.java │ │ │ ├── DefaultLogAttribute.java │ │ │ ├── LogAspectSupport.java │ │ │ ├── LogAttribute.java │ │ │ ├── LogAttributeSource.java │ │ │ ├── LogAttributeSourcePointcut.java │ │ │ ├── LogMethodInterceptor.java │ │ │ └── TimingLogAttributeSourcePointcut.java │ │ ├── cache │ │ ├── LogAttributeCache.java │ │ ├── LogAttributeCacheConfiguration.java │ │ ├── LogAttributeMemoryCache.java │ │ └── LogAttributeRedisCache.java │ │ ├── constants │ │ └── HandleMode.java │ │ ├── handler │ │ ├── DefaultLogHandler.java │ │ └── LogDataHandler.java │ │ ├── monitor │ │ └── EasyLogApplicationInfoPrinter.java │ │ ├── provider │ │ ├── OperatorProvider.java │ │ └── SessionOperatorProvider.java │ │ ├── trace │ │ ├── LogTracer.java │ │ └── NoneLogTracer.java │ │ └── util │ │ ├── LogUtils.java │ │ └── SpringSpelUtils.java │ └── resources │ └── easy-log │ └── db │ └── liquibase │ └── changelog-1.0.xml ├── easy-log-data-mybatis-plus ├── pom.xml ├── readme.md └── src │ └── main │ ├── java │ └── com │ │ └── easycode8 │ │ └── easylog │ │ └── mybatis │ │ ├── CompareResult.java │ │ ├── adapter │ │ ├── MybatisAdapter.java │ │ ├── MybatisLogAttributeMappingAdapter.java │ │ ├── MybatisLogAttributeMappingConfiguration.java │ │ └── MybatisPlusAdapter.java │ │ ├── autoconfigure │ │ ├── EasyLogMybatisPlusProperties.java │ │ └── MybatisDataLogAutoConfiguration.java │ │ ├── handler │ │ ├── DataSnapshotHandler.java │ │ └── MybatisPlusDataSnapshotHandler.java │ │ ├── interceptor │ │ └── DataSnapshotInterceptor.java │ │ └── util │ │ ├── CamelCaseUtils.java │ │ ├── GenericTypeUtils.java │ │ ├── MybatisUtils.java │ │ └── SqlUtils.java │ └── resources │ └── META-INF │ └── spring.factories ├── easy-log-spring-boot-starter ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── easycode8 │ │ └── easylog │ │ └── autoconfigure │ │ ├── EasyLogAutoConfiguration.java │ │ ├── LogAttributeSourceConfiguration.java │ │ └── source │ │ ├── OpenApi3LogAttributeSource.java │ │ └── SwaggerLogAttributeSource.java │ └── resources │ └── META-INF │ └── spring.factories ├── easy-log-trace ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── easycode8 │ │ └── easylog │ │ └── trace │ │ ├── DefaultLogTracer.java │ │ ├── LogTraceConfiguration.java │ │ ├── ZipkinLogTracer.java │ │ ├── autoconfigure │ │ ├── EasyLogTraceAutoConfiguration.java │ │ └── EasyLogTraceEnvironmentPostProcessor.java │ │ └── filter │ │ ├── EasyLogTraceFilter.java │ │ └── MDCConstants.java │ └── resources │ └── META-INF │ └── spring.factories ├── easy-log-web ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── easycode8 │ │ └── easylog │ │ └── web │ │ ├── autoconfigure │ │ ├── EasyLogWebAutoConfiguration.java │ │ ├── EasyLogWebProperties.java │ │ └── EasyLogWebSocketAutoConfiguration.java │ │ ├── controller │ │ └── EasyLogController.java │ │ ├── filter │ │ ├── LogFilter.java │ │ └── SecurityBasicAuthFilter.java │ │ └── model │ │ ├── PageInfo.java │ │ ├── param │ │ └── LogAttributeParam.java │ │ └── vo │ │ └── LogAttributeVO.java │ └── resources │ ├── META-INF │ ├── resources │ │ ├── easy-log-ui.html │ │ └── webjars │ │ │ └── easy-log │ │ │ ├── axios │ │ │ └── axios.min.js │ │ │ ├── easylog.js │ │ │ ├── iview │ │ │ ├── iview.min.js │ │ │ └── style │ │ │ │ ├── fonts │ │ │ │ ├── ionicons.svg │ │ │ │ ├── ionicons.ttf │ │ │ │ └── ionicons.woff │ │ │ │ └── iview.css │ │ │ ├── iview@3.5.4 │ │ │ └── dist │ │ │ │ ├── iview.min.js │ │ │ │ └── styles │ │ │ │ ├── fonts │ │ │ │ └── ionicons.woff2 │ │ │ │ └── iview.css │ │ │ ├── jquery-2.0.2.min.js │ │ │ ├── lodash.min.js │ │ │ ├── sockjs-client │ │ │ └── 1.1.4 │ │ │ │ └── sockjs.min.js │ │ │ ├── stomp.js │ │ │ └── 2.3.3 │ │ │ │ └── stomp.min.js │ │ │ ├── vue │ │ │ └── vue.js │ │ │ ├── vue@2.6.14 │ │ │ └── dist │ │ │ │ └── vue.js │ │ │ ├── xterm-addon-fit@0.6.0 │ │ │ └── lib │ │ │ │ └── xterm-addon-fit.js │ │ │ └── xterm@4.18.0 │ │ │ ├── css │ │ │ └── xterm.css │ │ │ └── lib │ │ │ └── xterm.js │ └── spring.factories │ └── templates │ └── easy-log.html └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | 26 | .idea 27 | /target/ 28 | .flattened-pom.xml 29 | *.iml 30 | -------------------------------------------------------------------------------- /easy-log-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-log 7 | io.github.easycode8 8 | ${revision} 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | easy-log-core 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-configuration-processor 20 | true 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | true 27 | 28 | 29 | 30 | com.alibaba 31 | fastjson 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-data-redis 38 | true 39 | 40 | 41 | 42 | 43 | commons-collections 44 | commons-collections 45 | ${commons-collections.version} 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/LogDefinition.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core; 2 | 3 | import java.io.Serializable; 4 | 5 | public interface LogDefinition extends Serializable { 6 | Integer STATUS_UN_INIT = 0; 7 | Integer STATUS_INIT = 1; 8 | Integer STATUS_BEFORE = 2; 9 | Integer STATUS_FINISH = 3; 10 | 11 | String TYPE_WEB = "web"; 12 | String TYPE_SERVICE = "service"; 13 | String TYPE_DAO = "dao"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/LogHolder.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core; 2 | 3 | import org.springframework.core.NamedThreadLocal; 4 | 5 | import java.util.ArrayDeque; 6 | import java.util.Deque; 7 | 8 | public class LogHolder { 9 | /** 10 | * 为什么不能用String 而要使用队列(这里作为栈实现)来存储 11 | * 因为每次一个方法做多aop增强时候。都会在进入方法前为当前线程绑定当前日志信息。结束时候清除信息 12 | * 如果是使用同一个变量,那么aop增强方法内部还又带有AOP增加的方法,那么进入时候就会覆盖外出方法,同时内部方法aop结束时候,日志信息被清除 13 | * 外部线程拿到的日志信息则会出现空指针 14 | */ 15 | private static final ThreadLocal> LOG_DEQUE = new NamedThreadLocal>("LOG_DEQUE") { 16 | @Override 17 | protected Deque initialValue() { 18 | return new ArrayDeque<>(); 19 | } 20 | }; 21 | 22 | 23 | /** 24 | * 入栈 25 | */ 26 | public static void push(LogInfo info) { 27 | LOG_DEQUE.get().push(info); 28 | } 29 | 30 | /** 31 | * 获取栈顶元素 32 | * @return 33 | */ 34 | public static LogInfo peek() { 35 | return LOG_DEQUE.get().peek(); 36 | 37 | } 38 | 39 | 40 | 41 | /** 42 | * 移除栈顶数据源,如果是最后元素,则清空线程数据 43 | */ 44 | public static void poll() { 45 | Deque deque = LOG_DEQUE.get(); 46 | deque.poll(); 47 | if (deque.isEmpty()) { 48 | LOG_DEQUE.remove(); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/LogInfo.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core; 2 | 3 | 4 | import java.util.Date; 5 | import java.util.UUID; 6 | 7 | public class LogInfo implements LogDefinition { 8 | 9 | 10 | /** 日志主键*/ 11 | // @TableId(type = IdType.UUID) 12 | private String logId; 13 | 14 | /** 日志类型*/ 15 | private String type; 16 | 17 | /** 日志标题*/ 18 | private String title; 19 | 20 | /** 日志摘要*/ 21 | private String description; 22 | 23 | /** 请求IP*/ 24 | private String ip; 25 | 26 | /** URI*/ 27 | private String requestUri; 28 | 29 | /** 请求方式*/ 30 | private String method; 31 | 32 | /** 提交参数*/ 33 | private String params; 34 | 35 | /** 异常*/ 36 | private String exception; 37 | 38 | /** 操作时间*/ 39 | private Date operateDate; 40 | 41 | /** 请求时长*/ 42 | private Long timeout; 43 | 44 | /** 操作人*/ 45 | private String operator; 46 | 47 | /** 链路追踪ID(和requestId等效) 遵从OpenTracing规范使用traceId命名*/ 48 | private String traceId; 49 | 50 | /** 历史数据*/ 51 | private String dataSnapshot; 52 | 53 | /** 日志状态*/ 54 | private Integer status = 0; 55 | 56 | /** 日志标签用于扩展业务自定义属性*/ 57 | private String tags; 58 | 59 | public String getLogId() { 60 | return logId; 61 | } 62 | 63 | public void setLogId(String logId) { 64 | this.logId = logId; 65 | } 66 | 67 | public String getType() { 68 | return type; 69 | } 70 | 71 | public void setType(String type) { 72 | this.type = type; 73 | } 74 | 75 | public String getTitle() { 76 | return title; 77 | } 78 | 79 | public void setTitle(String title) { 80 | this.title = title; 81 | } 82 | 83 | public String getDescription() { 84 | return description; 85 | } 86 | 87 | public void setDescription(String description) { 88 | this.description = description; 89 | } 90 | 91 | public String getIp() { 92 | return ip; 93 | } 94 | 95 | public void setIp(String ip) { 96 | this.ip = ip; 97 | } 98 | 99 | public String getRequestUri() { 100 | return requestUri; 101 | } 102 | 103 | public void setRequestUri(String requestUri) { 104 | this.requestUri = requestUri; 105 | } 106 | 107 | public String getMethod() { 108 | return method; 109 | } 110 | 111 | public void setMethod(String method) { 112 | this.method = method; 113 | } 114 | 115 | public String getParams() { 116 | return params; 117 | } 118 | 119 | public void setParams(String params) { 120 | this.params = params; 121 | } 122 | 123 | public String getException() { 124 | return exception; 125 | } 126 | 127 | public void setException(String exception) { 128 | this.exception = exception; 129 | } 130 | 131 | public Date getOperateDate() { 132 | return operateDate; 133 | } 134 | 135 | public void setOperateDate(Date operateDate) { 136 | this.operateDate = operateDate; 137 | } 138 | 139 | public Long getTimeout() { 140 | return timeout; 141 | } 142 | 143 | public void setTimeout(Long timeout) { 144 | this.timeout = timeout; 145 | } 146 | 147 | public String getOperator() { 148 | return operator; 149 | } 150 | 151 | public void setOperator(String operator) { 152 | this.operator = operator; 153 | } 154 | 155 | public String getTraceId() { 156 | return traceId; 157 | } 158 | 159 | public void setTraceId(String traceId) { 160 | this.traceId = traceId; 161 | } 162 | 163 | public String getDataSnapshot() { 164 | return dataSnapshot; 165 | } 166 | 167 | public void setDataSnapshot(String dataSnapshot) { 168 | this.dataSnapshot = dataSnapshot; 169 | } 170 | 171 | public Integer getStatus() { 172 | return status; 173 | } 174 | 175 | public void setStatus(Integer status) { 176 | this.status = status; 177 | } 178 | 179 | public String getTags() { 180 | return tags; 181 | } 182 | 183 | public void setTags(String tags) { 184 | this.tags = tags; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/LogStopWatch.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.helpers.MessageFormatter; 5 | import org.springframework.util.StopWatch; 6 | 7 | public class LogStopWatch { 8 | // private static final ThreadLocal STOP_WATCH = new NamedThreadLocal<>("STOP_WATCH"); 9 | 10 | 11 | private Logger logger; 12 | private StopWatch stopWatch; 13 | 14 | 15 | 16 | public LogStopWatch(Logger logger, String stopWatchName) { 17 | this.logger = logger; 18 | this.stopWatch = new StopWatch(stopWatchName); 19 | } 20 | 21 | 22 | public LogStopWatch stop() { 23 | if (logger.isDebugEnabled()) { 24 | stopWatch.stop(); 25 | } 26 | 27 | return this; 28 | } 29 | 30 | public void start(String message, String... variable) { 31 | if (logger.isDebugEnabled()) { 32 | stopWatch.start(MessageFormatter.arrayFormat(message, variable).getMessage()); 33 | } 34 | 35 | } 36 | 37 | public void start(String taskName) { 38 | if (logger.isDebugEnabled()) { 39 | stopWatch.start(taskName); 40 | } 41 | 42 | } 43 | 44 | 45 | public void showDetail() { 46 | // for (StopWatch.TaskInfo taskInfo : stopWatch.getTaskInfo()) { 47 | // stopWatch.getLastTaskInfo().getTimeMillis(); 48 | // System.out.println("任务:" + taskInfo.getTaskName() + " timeout:" + taskInfo.getTimeNanos()); 49 | // } 50 | if (logger.isDebugEnabled()) { 51 | logger.debug(stopWatch.prettyPrint()); 52 | } 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/adapter/ControllerLogAttributeMapping.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.adapter; 2 | 3 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 4 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 5 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 6 | import com.easycode8.easylog.core.util.LogUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | * 从controller bean日志属性提取 15 | */ 16 | public class ControllerLogAttributeMapping implements LogAttributeMappingAdapter { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAttributeMapping.class); 19 | private final EasyLogProperties easyLogProperties; 20 | 21 | public ControllerLogAttributeMapping(EasyLogProperties easyLogProperties) { 22 | LOGGER.info("[easy-log]启动controller bean日志增强"); 23 | this.easyLogProperties = easyLogProperties; 24 | } 25 | 26 | @Override 27 | public LogAttribute getLogAttribute(Method method, Class targetClass) { 28 | if (easyLogProperties.getScanController().getEnabled() && isControllerPublicMethod(method, targetClass)) { 29 | String title = LogUtils.createDefaultTitle(method, targetClass); 30 | 31 | return DefaultLogAttribute.builder() 32 | .title(title) 33 | .async(easyLogProperties.getAsync()) 34 | .build(); 35 | } 36 | return null; 37 | } 38 | 39 | 40 | private boolean isControllerPublicMethod(Method method, Class targetClass) { 41 | // 如果方法不是controller自己的而是继承来的则忽略 42 | if (!method.getDeclaringClass().equals(targetClass)) { 43 | return false; 44 | } 45 | // return (targetClass.getAnnotation(Controller.class) != null && (method.getAnnotation(ResponseBody.class) != null || method.getReturnType() == ResponseEntity.class)) 46 | // || 47 | // (targetClass.getAnnotation(RestController.class) != null && !Modifier.isStatic(method.getModifiers()) 48 | // && Modifier.isPublic(method.getModifiers())); 49 | 50 | // 判断是否是Controller的接口方法 51 | return method.isAnnotationPresent(RequestMapping.class) || 52 | method.isAnnotationPresent(GetMapping.class) || 53 | method.isAnnotationPresent(PostMapping.class) || 54 | method.isAnnotationPresent(PutMapping.class) || 55 | method.isAnnotationPresent(DeleteMapping.class); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/adapter/LogAttributeMappingAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.adapter; 2 | 3 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | public interface LogAttributeMappingAdapter { 8 | 9 | /** 预留排序接口,用于多个日志属性适配器命中多个接口增强时候定义优先级,*/ 10 | default int order() { 11 | return 10; 12 | } 13 | 14 | LogAttribute getLogAttribute(Method method, Class targetClass); 15 | } 16 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/adapter/ServiceLogAttributeMapping.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.adapter; 2 | 3 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 4 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 5 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 6 | import com.easycode8.easylog.core.util.LogUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Modifier; 13 | 14 | /** 15 | * 从service bean日志属性提取 16 | */ 17 | public class ServiceLogAttributeMapping implements LogAttributeMappingAdapter{ 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(ServiceLogAttributeMapping.class); 20 | private final EasyLogProperties easyLogProperties; 21 | 22 | public ServiceLogAttributeMapping(EasyLogProperties easyLogProperties) { 23 | LOGGER.info("[easy-log]启动service bean日志增强"); 24 | this.easyLogProperties = easyLogProperties; 25 | } 26 | 27 | @Override 28 | public LogAttribute getLogAttribute(Method method, Class targetClass) { 29 | // 如果找不到EasyLog 检查是否开启server-debug模式 30 | if (isServicePublicMethod(method, targetClass)) { 31 | String title = LogUtils.createDefaultTitle(method, targetClass); 32 | 33 | return DefaultLogAttribute.builder() 34 | .title(title) 35 | .async(easyLogProperties.getAsync()) 36 | .build(); 37 | } 38 | return null; 39 | } 40 | 41 | 42 | private boolean isServicePublicMethod(Method method, Class targetClass) { 43 | // 如果方法来自于Object对象忽略处理 44 | if (method.getDeclaringClass().equals(Object.class)) { 45 | return false; 46 | } 47 | return easyLogProperties.getScanService().getEnabled() && targetClass.getAnnotation(Service.class) != null 48 | && !Modifier.isStatic(method.getModifiers()) 49 | && Modifier.isPublic(method.getModifiers()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/annotation/EasyLog.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.annotation; 2 | 3 | import com.easycode8.easylog.core.constants.HandleMode; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target({ElementType.METHOD}) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Documented 10 | public @interface EasyLog { 11 | int MODE_DEFAULT = 0; 12 | int MODE_ASYNC = 1; 13 | int MODE_SYNC = 2; 14 | 15 | /** 日志标题 title别名*/ 16 | String value() default ""; 17 | /** 日志标题*/ 18 | String title() default ""; 19 | /** 日志模板 使用spring的spel表达式 用于从请求参数中提取日志描述*/ 20 | String template() default ""; 21 | /** 日志处理器*/ 22 | String handler() default ""; 23 | /** 日志操作人 使用spring的spel表达式 用于从请求参数中提取日志操作人*/ 24 | String operator() default ""; 25 | /** 处理日志的模式 GLOBAL:使用全局默认值 ASYNC:使用异步 SYNC:使用同步*/ 26 | HandleMode handleMode() default HandleMode.GLOBAL; 27 | /** 日志自定义标签用于记录扩展的业务值*/ 28 | Tag[] tags() default {@Tag(key = "", value = "")}; 29 | 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/annotation/EasyLogConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.annotation; 2 | 3 | 4 | import com.easycode8.easylog.core.handler.DefaultLogHandler; 5 | import com.easycode8.easylog.core.handler.LogDataHandler; 6 | import com.easycode8.easylog.core.adapter.LogAttributeMappingAdapter; 7 | import com.easycode8.easylog.core.aop.BeanFactoryLogAttributeSourceAdvisor; 8 | import com.easycode8.easylog.core.aop.interceptor.AnnotationLogAttributeSource; 9 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 10 | import com.easycode8.easylog.core.aop.interceptor.LogMethodInterceptor; 11 | import com.easycode8.easylog.core.cache.LogAttributeCache; 12 | import com.easycode8.easylog.core.cache.LogAttributeCacheConfiguration; 13 | import com.easycode8.easylog.core.monitor.EasyLogApplicationInfoPrinter; 14 | import com.easycode8.easylog.core.provider.OperatorProvider; 15 | import com.easycode8.easylog.core.provider.SessionOperatorProvider; 16 | import com.easycode8.easylog.core.trace.LogTracer; 17 | import com.easycode8.easylog.core.trace.NoneLogTracer; 18 | import org.springframework.aop.Advisor; 19 | import org.springframework.beans.factory.ObjectProvider; 20 | import org.springframework.beans.factory.annotation.Qualifier; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.context.annotation.Bean; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.context.annotation.Import; 26 | import org.springframework.core.task.TaskDecorator; 27 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 28 | 29 | import java.util.List; 30 | import java.util.concurrent.ThreadPoolExecutor; 31 | 32 | @Configuration 33 | @EnableConfigurationProperties(EasyLogProperties.class) 34 | public class EasyLogConfiguration { 35 | @Bean 36 | public Advisor easyLogStaticMethodMatcherPointcutAdvisor(LogAttributeSource logAttributeSource, LogMethodInterceptor logMethodInterceptor, EasyLogProperties easyLogProperties) { 37 | //Advisor 是 Spring AOP 对 Advice 和 Pointcut 的抽象,可以理解为“执行通知者”,一个 Pointcut (一般对应方法)和用于“增强”它的 Advice 共同组成这个方法的一个 Advisor 38 | BeanFactoryLogAttributeSourceAdvisor advisor = new BeanFactoryLogAttributeSourceAdvisor(); 39 | advisor.setLogAttributeSource(logAttributeSource); 40 | advisor.setAdvice(logMethodInterceptor); 41 | //设置自定义优先级值越小优先级越高 42 | if (easyLogProperties.getAspectOrder() != null) { 43 | advisor.setOrder(easyLogProperties.getAspectOrder()); 44 | } 45 | return advisor; 46 | } 47 | 48 | @Bean 49 | public LogMethodInterceptor easyLogMethodInterceptor(LogAttributeSource logAttributeSource, 50 | @Qualifier("easyLogThreadPoolTaskExecutor") ThreadPoolTaskExecutor taskExecutor, 51 | @Qualifier("easyLogDataHandler") LogDataHandler logDataHandler, 52 | ObjectProvider operatorProvider, 53 | ObjectProvider tracerObjectProvider) { 54 | LogMethodInterceptor logMethodInterceptor = new LogMethodInterceptor(); 55 | logMethodInterceptor.setLogAttributeSource(logAttributeSource); 56 | logMethodInterceptor.setThreadPoolTaskExecutor(taskExecutor); 57 | logMethodInterceptor.setLogDataHandler(logDataHandler); 58 | logMethodInterceptor.setOperatorProvider(operatorProvider.getIfAvailable()); 59 | LogTracer logTracer = tracerObjectProvider.getIfAvailable(); 60 | if (logTracer == null) { 61 | logTracer = new NoneLogTracer(); 62 | } 63 | logMethodInterceptor.setLogTracer(logTracer); 64 | return logMethodInterceptor; 65 | } 66 | 67 | @Bean 68 | @ConditionalOnMissingBean 69 | public LogAttributeSource logAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties, List mappingAdapters) { 70 | return new AnnotationLogAttributeSource(logAttributeCache, easyLogProperties, mappingAdapters); 71 | } 72 | 73 | /**按优先级选择日志属性缓存实现 redis>local*/ 74 | @Configuration 75 | @ConditionalOnMissingBean(LogAttributeCache.class) 76 | @Import({LogAttributeCacheConfiguration.RedisCacheConfiguration.class, LogAttributeCacheConfiguration.LocalCacheConfiguration.class}) 77 | public static class ChooseLogAttributeCacheConfiguration { 78 | 79 | } 80 | 81 | 82 | 83 | @Bean 84 | @ConditionalOnMissingBean(name = "easyLogDataHandler") 85 | public LogDataHandler easyLogDataHandler() { 86 | return new DefaultLogHandler(); 87 | } 88 | 89 | @Bean 90 | @ConditionalOnMissingBean 91 | public OperatorProvider easyLogOperatorProvider(EasyLogProperties easyLogProperties) { 92 | return new SessionOperatorProvider(easyLogProperties); 93 | } 94 | 95 | /** 96 | * 日志处理异步执行线程池 97 | * @param easyLogProperties 98 | * @param objectProvider 99 | * @return 100 | */ 101 | @Bean 102 | @ConditionalOnMissingBean(name = "easyLogThreadPoolTaskExecutor") 103 | public ThreadPoolTaskExecutor easyLogThreadPoolTaskExecutor(EasyLogProperties easyLogProperties, ObjectProvider objectProvider) { 104 | EasyLogProperties.Task async = easyLogProperties.getTask(); 105 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 106 | // 核心线程数定义了最小可以同时运行的线程数量 107 | // 1. cpu密集型CorePoolSize = CPU核心数+1 108 | // 2. IO密集型CorePoolSize = CPU核心数 * 2 109 | taskExecutor.setCorePoolSize(async.getCorePoolSize()); 110 | // 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数 111 | taskExecutor.setMaxPoolSize(async.getMaxPoolSize()); 112 | // 任务队列,被提交但尚未被执行的任务 113 | taskExecutor.setQueueCapacity(async.getQueueCapacity()); 114 | // 当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁 115 | taskExecutor.setKeepAliveSeconds(async.getKeepAliveSeconds()); 116 | taskExecutor.setThreadNamePrefix(async.getThreadNamePrefix()); 117 | //拒绝策略,当队列满了并且工作线程数量大于线程池的最大线程数时,提供拒绝策略 118 | taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 119 | taskExecutor.setTaskDecorator(objectProvider.getIfAvailable()); 120 | taskExecutor.initialize(); 121 | return taskExecutor; 122 | } 123 | 124 | // 打印应用监控信息 125 | @Bean 126 | public EasyLogApplicationInfoPrinter easyLogApplicationInfoPrinter() { 127 | return new EasyLogApplicationInfoPrinter(); 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/annotation/EasyLogProperties.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.annotation; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | @ConfigurationProperties(prefix = "spring.easy-log") 9 | public class EasyLogProperties { 10 | 11 | /** 是否启用easy-log 默认true*/ 12 | private Boolean enabled = true; 13 | 14 | /** 15 | * 获取操作人信息的表达式 支持从session或请求头获取 默认为空示例: 16 | *
  • spring.easy-log.operator=session.account.username:通过session获取属性为account中对象的username字段作为操作人
  • 17 | *
  • spring.easy-log.operator=header.x-username:代表从请求头中获取x-username作为操作人信息
  • 18 | */ 19 | private String operator = ""; 20 | 21 | /**是否开启默认异步记录日志 默认false*/ 22 | private Boolean async = false; 23 | 24 | /**日志aop生效的包范围,默认为空,尝试增强所有bean,建议填写项目基础包名,减少无用的aop判断,支持多个*/ 25 | private Set scanPackages = new HashSet<>(); 26 | 27 | // spring顺序默认是Ordered.LOWEST_PRECEDENCE(Integer.MAX_VALUE)优先级最低,如果两个aspect顺序一样,则使用bean注册的顺序 28 | /**切面的顺序 值越小越先执行Integer.MIN_VALUE 优先级最高, 设置为0 比一般spring 默认最低优先级高*/ 29 | private Integer aspectOrder = 0; 30 | 31 | private Task task = new Task(); 32 | 33 | private ScanOpenApi scanOpenApi; 34 | 35 | private ScanSwagger scanSwagger; 36 | 37 | private ScanService scanService; 38 | 39 | private ScanController scanController; 40 | 41 | private Cache cache = new Cache(); 42 | 43 | 44 | public Boolean getEnabled() { 45 | return enabled; 46 | } 47 | 48 | public void setEnabled(Boolean enabled) { 49 | this.enabled = enabled; 50 | } 51 | 52 | public String getOperator() { 53 | return operator; 54 | } 55 | 56 | public void setOperator(String operator) { 57 | this.operator = operator; 58 | } 59 | 60 | public ScanService getScanService() { 61 | return scanService; 62 | } 63 | 64 | public void setScanService(ScanService scanService) { 65 | this.scanService = scanService; 66 | } 67 | 68 | public ScanController getScanController() { 69 | return scanController; 70 | } 71 | 72 | public void setScanController(ScanController scanController) { 73 | this.scanController = scanController; 74 | } 75 | 76 | public ScanOpenApi getScanOpenApi() { 77 | return scanOpenApi; 78 | } 79 | 80 | public void setScanOpenApi(ScanOpenApi scanOpenApi) { 81 | this.scanOpenApi = scanOpenApi; 82 | } 83 | 84 | public ScanSwagger getScanSwagger() { 85 | return scanSwagger; 86 | } 87 | 88 | public void setScanSwagger(ScanSwagger scanSwagger) { 89 | this.scanSwagger = scanSwagger; 90 | } 91 | 92 | public Boolean getAsync() { 93 | return async; 94 | } 95 | 96 | public void setAsync(Boolean async) { 97 | this.async = async; 98 | } 99 | 100 | public Task getTask() { 101 | return task; 102 | } 103 | 104 | public void setTask(Task task) { 105 | this.task = task; 106 | } 107 | 108 | public Integer getAspectOrder() { 109 | return aspectOrder; 110 | } 111 | 112 | public void setAspectOrder(Integer aspectOrder) { 113 | this.aspectOrder = aspectOrder; 114 | } 115 | 116 | public Cache getCache() { 117 | return cache; 118 | } 119 | 120 | public void setCache(Cache cache) { 121 | this.cache = cache; 122 | } 123 | 124 | public Set getScanPackages() { 125 | return scanPackages; 126 | } 127 | 128 | public void setScanPackages(Set scanPackages) { 129 | this.scanPackages = scanPackages; 130 | } 131 | 132 | public static class Task { 133 | 134 | 135 | /**核心线程数定义了最小可以同时运行的线程数量*/ 136 | private Integer corePoolSize = 4; 137 | /**当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数*/ 138 | private Integer maxPoolSize = 20; 139 | /**任务队列,被提交但尚未被执行的任务*/ 140 | private Integer queueCapacity = 50; 141 | /**当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁*/ 142 | private Integer keepAliveSeconds = 1800; 143 | /**线程名称前缀*/ 144 | private String threadNamePrefix = "easy-log-"; 145 | 146 | public Integer getCorePoolSize() { 147 | return corePoolSize; 148 | } 149 | 150 | public void setCorePoolSize(Integer corePoolSize) { 151 | this.corePoolSize = corePoolSize; 152 | } 153 | 154 | public Integer getMaxPoolSize() { 155 | return maxPoolSize; 156 | } 157 | 158 | public void setMaxPoolSize(Integer maxPoolSize) { 159 | this.maxPoolSize = maxPoolSize; 160 | } 161 | 162 | public Integer getQueueCapacity() { 163 | return queueCapacity; 164 | } 165 | 166 | public void setQueueCapacity(Integer queueCapacity) { 167 | this.queueCapacity = queueCapacity; 168 | } 169 | 170 | public Integer getKeepAliveSeconds() { 171 | return keepAliveSeconds; 172 | } 173 | 174 | public void setKeepAliveSeconds(Integer keepAliveSeconds) { 175 | this.keepAliveSeconds = keepAliveSeconds; 176 | } 177 | 178 | public String getThreadNamePrefix() { 179 | return threadNamePrefix; 180 | } 181 | 182 | public void setThreadNamePrefix(String threadNamePrefix) { 183 | this.threadNamePrefix = threadNamePrefix; 184 | } 185 | } 186 | 187 | public static class ScanOpenApi { 188 | /**是否开启openapi3 的@Operation注解作为日志标识*/ 189 | private Boolean enabled = false; 190 | 191 | public Boolean getEnabled() { 192 | return enabled; 193 | } 194 | 195 | public void setEnabled(Boolean enabled) { 196 | this.enabled = enabled; 197 | } 198 | } 199 | 200 | public static class ScanSwagger { 201 | /**是否开启swagger的@ApiOperation注解作为日志标识*/ 202 | private Boolean enabled = false; 203 | 204 | public Boolean getEnabled() { 205 | return enabled; 206 | } 207 | 208 | public void setEnabled(Boolean enabled) { 209 | this.enabled = enabled; 210 | } 211 | } 212 | 213 | public static class ScanService { 214 | /**是否开启扫描@Service标记的公开方法*/ 215 | private Boolean enabled = false; 216 | 217 | public Boolean getEnabled() { 218 | return enabled; 219 | } 220 | 221 | public void setEnabled(Boolean enabled) { 222 | this.enabled = enabled; 223 | } 224 | } 225 | 226 | public static class ScanController { 227 | /**是否开启扫描Controller的接口方法*/ 228 | private Boolean enabled = false; 229 | 230 | public Boolean getEnabled() { 231 | return enabled; 232 | } 233 | 234 | public void setEnabled(Boolean enabled) { 235 | this.enabled = enabled; 236 | } 237 | } 238 | 239 | public static class Cache { 240 | /**easy-log日志属性缓存前缀 默认: easy-log: */ 241 | private String keyPrefix = "easy-log:xxx-service"; 242 | /** 是否强制删除历史缓存 */ 243 | private Boolean dropFirst = false; 244 | 245 | public String getKeyPrefix() { 246 | return keyPrefix; 247 | } 248 | 249 | public void setKeyPrefix(String keyPrefix) { 250 | this.keyPrefix = keyPrefix; 251 | } 252 | 253 | 254 | public Boolean getDropFirst() { 255 | return dropFirst; 256 | } 257 | 258 | public void setDropFirst(Boolean dropFirst) { 259 | this.dropFirst = dropFirst; 260 | } 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/annotation/EnableEasyLog.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.annotation; 2 | 3 | 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.*; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | @Import(EasyLogConfiguration.class) 12 | public @interface EnableEasyLog { 13 | } 14 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/annotation/Tag.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Tag { 11 | String key(); 12 | String value(); 13 | } -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/BeanFactoryLogAttributeSourceAdvisor.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop; 2 | 3 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 4 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSourcePointcut; 5 | import com.easycode8.easylog.core.aop.interceptor.TimingLogAttributeSourcePointcut; 6 | import org.springframework.aop.Pointcut; 7 | import org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor; 8 | import org.springframework.lang.Nullable; 9 | 10 | public class BeanFactoryLogAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { 11 | @Nullable 12 | private LogAttributeSource logAttributeSource; 13 | 14 | private final LogAttributeSourcePointcut logAttributeSourcePointcut = new TimingLogAttributeSourcePointcut() { 15 | @Override 16 | protected LogAttributeSource getLogAttributeSource() { 17 | return logAttributeSource; 18 | } 19 | }; 20 | 21 | @Override 22 | public Pointcut getPointcut() { 23 | return this.logAttributeSourcePointcut; 24 | } 25 | 26 | public void setLogAttributeSource(LogAttributeSource logAttributeSource) { 27 | this.logAttributeSource = logAttributeSource; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/LogStaticMethodMatcherPointcutAdvisor.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop; 2 | 3 | import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * TODO 暂未使用 9 | */ 10 | public class LogStaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor { 11 | 12 | @Override 13 | public boolean matches(Method method, Class aClass) { 14 | return false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/AbstractCacheLogAttributeSource.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | 4 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 5 | import com.easycode8.easylog.core.cache.LogAttributeCache; 6 | import com.easycode8.easylog.core.util.LogUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Proxy; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public abstract class AbstractCacheLogAttributeSource implements LogAttributeSource { 17 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCacheLogAttributeSource.class); 18 | private final LogAttributeCache logAttributeCache; 19 | protected EasyLogProperties easyLogProperties; 20 | 21 | protected AbstractCacheLogAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties) { 22 | this.logAttributeCache = logAttributeCache; 23 | this.easyLogProperties = easyLogProperties; 24 | } 25 | 26 | 27 | @Override 28 | public LogAttribute getLogAttribute(Method method, Class targetClass) { 29 | String key; 30 | // 如果是代理对象获取代理接口的真实名称 31 | if (Proxy.isProxyClass(targetClass)) { 32 | String className = ((Class) targetClass.getGenericInterfaces()[0]).getName(); 33 | if (!this.matches(className, easyLogProperties.getScanPackages())) { 34 | return null; 35 | } 36 | key = LogUtils.createDefaultTitle(method, ((Class) targetClass.getGenericInterfaces()[0]), false); 37 | 38 | } else { 39 | if (!this.matches(targetClass.getName(), easyLogProperties.getScanPackages())) { 40 | return null; 41 | } 42 | if (targetClass.getAnnotation(SpringBootApplication.class) != null) { 43 | return null; 44 | } 45 | 46 | key = LogUtils.createDefaultTitle(method, targetClass, false); 47 | } 48 | 49 | if (!easyLogProperties.getCache().getKeyPrefix().endsWith(":")) { 50 | key = easyLogProperties.getCache().getKeyPrefix() + ":" + key; 51 | } else { 52 | key = easyLogProperties.getCache().getKeyPrefix() + key; 53 | } 54 | if (logAttributeCache.containsKey(key)) { 55 | return logAttributeCache.get(key); 56 | } 57 | LogAttribute attribute = doGetLogAttribute(method, targetClass); 58 | if (attribute != null) { 59 | logAttributeCache.put(key, attribute); 60 | } 61 | return attribute; 62 | } 63 | 64 | public abstract LogAttribute doGetLogAttribute(Method method, Class targetClass); 65 | 66 | public Map getCacheMap() { 67 | return this.logAttributeCache.getAll(); 68 | } 69 | 70 | /** 71 | * 更新日志属性缓存 72 | * 73 | * @param key 74 | * @param logAttribute 75 | */ 76 | public void updateCache(String key, LogAttribute logAttribute) { 77 | this.logAttributeCache.put(key, logAttribute); 78 | } 79 | 80 | private boolean matches(String className, Set basePackages) { 81 | if (basePackages == null || basePackages.isEmpty()) { 82 | return true; 83 | } 84 | boolean matches = basePackages.stream().anyMatch(item -> className.startsWith(item)); 85 | if (LOGGER.isTraceEnabled()) { 86 | LOGGER.trace("[ease-log] 类:{} AOP增强:{} 匹配前缀:{}", className, matches, basePackages); 87 | } 88 | return matches; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/AnnotationLogAttributeSource.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | import com.easycode8.easylog.core.adapter.LogAttributeMappingAdapter; 4 | import com.easycode8.easylog.core.annotation.EasyLog; 5 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 6 | import com.easycode8.easylog.core.annotation.Tag; 7 | import com.easycode8.easylog.core.cache.LogAttributeCache; 8 | import com.easycode8.easylog.core.util.LogUtils; 9 | import org.springframework.util.CollectionUtils; 10 | import org.springframework.util.StringUtils; 11 | 12 | import java.lang.reflect.Method; 13 | import java.util.AbstractMap; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | public class AnnotationLogAttributeSource extends AbstractCacheLogAttributeSource { 20 | 21 | 22 | private final List mappingAdapters; 23 | 24 | public AnnotationLogAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties, List mappingAdapters) { 25 | super(logAttributeCache, easyLogProperties); 26 | this.mappingAdapters = mappingAdapters; 27 | } 28 | 29 | 30 | @Override 31 | public LogAttribute doGetLogAttribute(Method method, Class targetClass) { 32 | EasyLog easyLog = method.getAnnotation(EasyLog.class); 33 | // EasyLog注解优先级最高 34 | if (easyLog != null) { 35 | String title = StringUtils.isEmpty(easyLog.title()) ? easyLog.value() : easyLog.title(); 36 | // 如果都没有定义标题使用默认标题 37 | if (StringUtils.isEmpty(title)) { 38 | title = LogUtils.createDefaultTitle(method, targetClass); 39 | } 40 | Tag[] tags = easyLog.tags(); 41 | Map tagMap = null; 42 | if (tags != null) { 43 | tagMap = Arrays.stream(tags) 44 | // 提出key为空的数据 45 | .filter(item -> StringUtils.hasText(item.key())) 46 | // .map(p -> Map.entry(p.key(), p.value())) 47 | .map(p -> new AbstractMap.SimpleEntry<>(p.key(), p.value())) 48 | // 将 Map.Entry 对象转为 Map 49 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 50 | 51 | } 52 | 53 | // 如果注解指定是否同步优先级最高,否则读取项目默认配置值 54 | boolean async = false; 55 | switch (easyLog.handleMode()) { 56 | case GLOBAL: 57 | async = easyLogProperties.getAsync(); 58 | break; 59 | case ASYNC: 60 | async = true; 61 | break; 62 | case SYNC: 63 | async = false; 64 | break; 65 | } 66 | 67 | return DefaultLogAttribute.builder() 68 | .title(title) 69 | .handler(easyLog.handler()) 70 | .template(easyLog.template()) 71 | .operator(easyLog.operator()) 72 | .async(async) 73 | .tags(tagMap) 74 | .build(); 75 | } 76 | // 从映射适配器中获取自定义的日志属性 77 | if (CollectionUtils.isEmpty(mappingAdapters)) { 78 | return null; 79 | } 80 | 81 | for (LogAttributeMappingAdapter mappingAdapter : mappingAdapters) { 82 | LogAttribute logAttribute = mappingAdapter.getLogAttribute(method, targetClass); 83 | if (logAttribute != null) { 84 | return logAttribute; 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/DefaultLogAttribute.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | 4 | import java.io.Serializable; 5 | import java.util.Map; 6 | 7 | public class DefaultLogAttribute implements LogAttribute, Serializable { 8 | /** 9 | * 日志标题 10 | */ 11 | String title; 12 | /** 13 | * 日志处理器 14 | */ 15 | String handler; 16 | /** 17 | * 日志spel模板 18 | */ 19 | String template; 20 | /** 21 | * 日志操作人 22 | */ 23 | String operator; 24 | /** 25 | * 是否异步处理日志 26 | */ 27 | Boolean async; 28 | /** 29 | * 日志标签用于使用着扩展属性 30 | */ 31 | Map tags; 32 | /** 33 | * 是否活跃:非活跃的忽略增强处理 34 | */ 35 | Boolean active = true; 36 | 37 | 38 | @Override 39 | public String title() { 40 | return this.title; 41 | } 42 | 43 | @Override 44 | public String handler() { 45 | return this.handler; 46 | } 47 | 48 | @Override 49 | public String template() { 50 | return this.template; 51 | } 52 | 53 | @Override 54 | public String operator() { 55 | return this.operator; 56 | } 57 | 58 | @Override 59 | public boolean async() { 60 | return this.async; 61 | } 62 | 63 | @Override 64 | public Map tags() { 65 | return this.tags; 66 | } 67 | 68 | @Override 69 | public Boolean active() { 70 | return this.active; 71 | } 72 | 73 | 74 | public static Builder builder() { 75 | return new Builder(); 76 | } 77 | 78 | public static final class Builder { 79 | String title; 80 | String handler; 81 | String template; 82 | String operator; 83 | boolean async; 84 | Map tags; 85 | 86 | private Builder() { 87 | } 88 | 89 | public static Builder aDefaultLogAttribute() { 90 | return new Builder(); 91 | } 92 | 93 | public Builder title(String title) { 94 | this.title = title; 95 | return this; 96 | } 97 | 98 | public Builder handler(String handler) { 99 | this.handler = handler; 100 | return this; 101 | } 102 | 103 | public Builder template(String template) { 104 | this.template = template; 105 | return this; 106 | } 107 | 108 | public Builder operator(String operator) { 109 | this.operator = operator; 110 | return this; 111 | } 112 | 113 | public Builder async(boolean async) { 114 | this.async = async; 115 | return this; 116 | } 117 | 118 | public Builder tags(Map tags) { 119 | this.tags = tags; 120 | return this; 121 | } 122 | 123 | public DefaultLogAttribute build() { 124 | DefaultLogAttribute defaultLogAttribute = new DefaultLogAttribute(); 125 | defaultLogAttribute.handler = this.handler; 126 | defaultLogAttribute.template = this.template; 127 | defaultLogAttribute.title = this.title; 128 | defaultLogAttribute.tags = this.tags; 129 | defaultLogAttribute.async = this.async; 130 | defaultLogAttribute.operator = this.operator; 131 | return defaultLogAttribute; 132 | } 133 | } 134 | 135 | public String getTitle() { 136 | return title; 137 | } 138 | 139 | public void setTitle(String title) { 140 | this.title = title; 141 | } 142 | 143 | public String getHandler() { 144 | return handler; 145 | } 146 | 147 | public void setHandler(String handler) { 148 | this.handler = handler; 149 | } 150 | 151 | public String getTemplate() { 152 | return template; 153 | } 154 | 155 | public void setTemplate(String template) { 156 | this.template = template; 157 | } 158 | 159 | public String getOperator() { 160 | return operator; 161 | } 162 | 163 | public void setOperator(String operator) { 164 | this.operator = operator; 165 | } 166 | 167 | public Boolean getAsync() { 168 | return async; 169 | } 170 | 171 | public void setAsync(Boolean async) { 172 | this.async = async; 173 | } 174 | 175 | public Map getTags() { 176 | return tags; 177 | } 178 | 179 | public void setTags(Map tags) { 180 | this.tags = tags; 181 | } 182 | 183 | public Boolean getActive() { 184 | return active; 185 | } 186 | 187 | public void setActive(Boolean active) { 188 | this.active = active; 189 | } 190 | 191 | 192 | } 193 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/LogAspectSupport.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | 4 | import com.easycode8.easylog.core.handler.LogDataHandler; 5 | import com.easycode8.easylog.core.LogHolder; 6 | import com.easycode8.easylog.core.LogInfo; 7 | import com.easycode8.easylog.core.LogStopWatch; 8 | import com.easycode8.easylog.core.provider.OperatorProvider; 9 | import com.easycode8.easylog.core.trace.LogTracer; 10 | import com.easycode8.easylog.core.util.LogUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.beans.factory.BeanFactory; 14 | import org.springframework.beans.factory.BeanFactoryAware; 15 | import org.springframework.beans.factory.InitializingBean; 16 | import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; 17 | import org.springframework.lang.Nullable; 18 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 19 | import org.springframework.util.StringUtils; 20 | 21 | import java.lang.reflect.Method; 22 | 23 | /** 24 | * BeanFactoryAware 作用是通过日志注解属性中定义bean名称去spring容器中寻找对应的处理器 25 | * InitializingBean 校验必须需要的资源 26 | */ 27 | public abstract class LogAspectSupport implements BeanFactoryAware , InitializingBean { 28 | private static final Logger LOGGER = LoggerFactory.getLogger(LogAspectSupport.class); 29 | 30 | 31 | private LogAttributeSource logAttributeSource; 32 | 33 | private LogDataHandler logDataHandler; 34 | 35 | private ThreadPoolTaskExecutor threadPoolTaskExecutor; 36 | 37 | private OperatorProvider operatorProvider; 38 | 39 | private LogTracer logTracer; 40 | 41 | @Nullable 42 | private BeanFactory beanFactory; 43 | 44 | protected Object invoke(Method method, @Nullable Class targetClass, Object[] args, 45 | Object targetObject, 46 | final InvocationCallback invocation) throws Throwable { 47 | // 获取日志属性 48 | LogAttribute logAttribute = getLogAttributeSource().getLogAttribute(method, targetClass); 49 | // 如果日志处理非活跃忽略增强处理 50 | if (!logAttribute.active()) { 51 | return invocation.proceedWithLog(); 52 | } 53 | final LogDataHandler handler = this.determineLogHandleAdapter(logAttribute); 54 | 55 | // 创建日志信息 56 | long startTime = System.currentTimeMillis(); 57 | LogInfo info = handler.init(logAttribute, method, args, targetClass); 58 | boolean isAsync = logAttribute.async(); 59 | 60 | 61 | Object returnValue = null; 62 | try { 63 | // 日志trace初始化 64 | logTracer.init(info); 65 | LogHolder.push(info); 66 | // 获取处理器bean 67 | String handlerName = handler.getClass().getSimpleName(); 68 | LogStopWatch stopWatch = new LogStopWatch(LOGGER, logAttribute.title()); 69 | stopWatch.start("{}.init() //日志初始化属性", handlerName); 70 | LogUtils.initLog(info, logAttribute, method, args, targetClass); 71 | // 设置操作人 72 | this.chooseOperatorIfEmpty(info, operatorProvider); 73 | info.setStatus(LogInfo.STATUS_INIT); 74 | 75 | // This is an around advice: Invoke the next interceptor in the chain. 76 | // This will normally result in a target object being invoked. 77 | stopWatch.stop().start("{}.before() //日志前处理", handlerName); 78 | handler.before(info, method, args, targetClass, targetObject); 79 | info.setStatus(LogInfo.STATUS_BEFORE); 80 | stopWatch.stop().start("{} //{} param:{}" , info.getMethod(), info.getTitle(), info.getParams());; 81 | 82 | logTracer.start(info); 83 | returnValue = invocation.proceedWithLog(); 84 | info.setTimeout(System.currentTimeMillis() - startTime); 85 | 86 | stopWatch.stop().start(handlerName + ".after() //日志后处理"); 87 | 88 | info.setStatus(LogInfo.STATUS_FINISH); 89 | // 如果是登录接口可能登录后才有用户信息,所以这个补充设置一次 90 | this.chooseOperatorIfEmpty(info, operatorProvider); 91 | if (isAsync) { 92 | final Object retVal = returnValue; 93 | threadPoolTaskExecutor.execute(() -> handler.after(info, method, targetClass, retVal)); 94 | } else { 95 | handler.after(info, method, targetClass, returnValue); 96 | } 97 | 98 | stopWatch.stop().showDetail(); 99 | 100 | } catch (Throwable ex) { 101 | 102 | info.setTimeout(System.currentTimeMillis() - startTime); 103 | if (LogInfo.STATUS_UN_INIT == info.getStatus()) { 104 | LOGGER.error("[easy-log] handle init {} error:{}", LogUtils.createDefaultTitle(method, targetClass), ex.getMessage(), ex); 105 | } 106 | if (LogInfo.STATUS_INIT == info.getStatus()) { 107 | LOGGER.error("[easy-log] handle before {} error:{}", LogUtils.createDefaultTitle(method, targetClass), ex.getMessage(), ex); 108 | } else if (LogInfo.STATUS_BEFORE == info.getStatus()) { // 业务执行不成功记录失败原因 109 | info.setException(ex.getMessage()); 110 | if (isAsync) { 111 | final Object retVal = returnValue; 112 | threadPoolTaskExecutor.execute(() -> handler.after(info, method, targetClass, retVal)); 113 | } else { 114 | handler.after(info, method, targetClass, returnValue); 115 | } 116 | 117 | } else if (LogInfo.STATUS_FINISH == info.getStatus()) { // 业务执行成功,但是日志后处理失败,提示错误日志。这时候业务会因为异常回滚操作 118 | LOGGER.error("[easy-log] handle after {} error:{}", LogUtils.createDefaultTitle(method, targetClass), ex.getMessage(), ex); 119 | } 120 | 121 | throw ex; 122 | } finally { 123 | LogHolder.poll(); 124 | logTracer.finish(info); 125 | } 126 | 127 | return returnValue; 128 | } 129 | 130 | protected void chooseOperatorIfEmpty(LogInfo info, OperatorProvider operatorProvider) { 131 | // 如果操作人为空,尝试从上下文获取 132 | if (operatorProvider != null && StringUtils.isEmpty(info.getOperator())) { 133 | info.setOperator(operatorProvider.currentOperator()); 134 | } 135 | 136 | } 137 | 138 | 139 | /** 140 | * 支持注解中指定 自定义日志处理器 141 | * @param logAttribute 142 | * @return 143 | */ 144 | protected LogDataHandler determineLogHandleAdapter(LogAttribute logAttribute) { 145 | if (logAttribute == null || this.beanFactory == null) { 146 | return getLogDataHandler(); 147 | } 148 | String qualifier = logAttribute.handler(); 149 | if (StringUtils.hasText(qualifier)) { 150 | // TODO 补充从缓存中获取 see org.springframework.transaction.interceptor.TransactionAspectSupport.determineQualifiedTransactionManager 151 | LogDataHandler logDataHandler = BeanFactoryAnnotationUtils.qualifiedBeanOfType( 152 | beanFactory, LogDataHandler.class, qualifier); 153 | 154 | 155 | return logDataHandler; 156 | 157 | } else { 158 | LogDataHandler defaultLogDataHandler = getLogDataHandler(); 159 | if (defaultLogDataHandler == null) { 160 | defaultLogDataHandler = this.beanFactory.getBean(LogDataHandler.class); 161 | } 162 | return defaultLogDataHandler; 163 | 164 | } 165 | 166 | } 167 | 168 | 169 | /** 170 | * Simple callback interface for proceeding with the target invocation. 171 | * Concrete interceptors/aspects adapt this to their invocation mechanism. 172 | */ 173 | @FunctionalInterface 174 | protected interface InvocationCallback { 175 | 176 | @Nullable 177 | Object proceedWithLog() throws Throwable; 178 | } 179 | 180 | @Override 181 | public void afterPropertiesSet() { 182 | if (getLogDataHandler() == null && this.beanFactory == null) { 183 | throw new IllegalStateException( 184 | "Set the 'LogDataHandler' property or make sure to run within a BeanFactory " + 185 | "containing a LogDataHandler bean!"); 186 | } 187 | if (getLogAttributeSource() == null) { 188 | throw new IllegalStateException( 189 | "Either 'LogAttributeSource' or 'LogAttribute' is required: " + 190 | "If there are no log methods, then don't use a log aspect."); 191 | } 192 | } 193 | 194 | @Override 195 | public void setBeanFactory(BeanFactory beanFactory) { 196 | this.beanFactory = beanFactory; 197 | } 198 | 199 | public LogAttributeSource getLogAttributeSource() { 200 | return logAttributeSource; 201 | } 202 | 203 | public void setLogAttributeSource(LogAttributeSource logAttributeSource) { 204 | this.logAttributeSource = logAttributeSource; 205 | } 206 | 207 | public LogDataHandler getLogDataHandler() { 208 | return logDataHandler; 209 | } 210 | 211 | public void setLogDataHandler(LogDataHandler logDataHandler) { 212 | this.logDataHandler = logDataHandler; 213 | } 214 | 215 | public void setThreadPoolTaskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) { 216 | this.threadPoolTaskExecutor = threadPoolTaskExecutor; 217 | } 218 | 219 | public void setOperatorProvider(OperatorProvider operatorProvider) { 220 | this.operatorProvider = operatorProvider; 221 | } 222 | 223 | public void setLogTracer(LogTracer logTracer) { 224 | this.logTracer = logTracer; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/LogAttribute.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | 4 | import java.util.Map; 5 | 6 | public interface LogAttribute { 7 | /** 日志标题*/ 8 | String title(); 9 | /** 日志处理器*/ 10 | String handler(); 11 | /** 日志spel模板*/ 12 | String template(); 13 | /** 日志操作人*/ 14 | String operator(); 15 | /** 是否异步处理日志*/ 16 | boolean async(); 17 | /** 日志标签用于使用着扩展属性*/ 18 | Map tags(); 19 | /**是否活跃:非活跃的忽略增强处理*/ 20 | Boolean active(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/LogAttributeSource.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | public interface LogAttributeSource { 6 | LogAttribute getLogAttribute(Method method, Class targetClass); 7 | } 8 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/LogAttributeSourcePointcut.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | import org.springframework.aop.support.StaticMethodMatcherPointcut; 4 | import org.springframework.lang.Nullable; 5 | 6 | import java.io.Serializable; 7 | import java.lang.reflect.Method; 8 | 9 | public abstract class LogAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { 10 | 11 | /** 12 | * 是否作为切点的判断--通过被增强的方法和目标类是否能够提取出日志属性。 13 | * 将是否能够提取出日志属性能力通过接口暴露出去 14 | * @param method 15 | * @param targetClass 16 | * @return 17 | */ 18 | @Override 19 | public boolean matches(Method method, Class targetClass) { 20 | LogAttributeSource source = getLogAttributeSource(); 21 | return (source == null || source.getLogAttribute(method, targetClass) != null); 22 | } 23 | 24 | @Nullable 25 | protected abstract LogAttributeSource getLogAttributeSource(); 26 | } 27 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/LogMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | import org.aopalliance.intercept.MethodInterceptor; 4 | import org.aopalliance.intercept.MethodInvocation; 5 | import org.springframework.aop.support.AopUtils; 6 | 7 | /** 8 | * MethodInterceptor org.aopalliance.aop.Advice 9 | */ 10 | public class LogMethodInterceptor extends LogAspectSupport implements MethodInterceptor { 11 | @Override 12 | public Object invoke(MethodInvocation invocation) throws Throwable { 13 | Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); 14 | return invoke(invocation.getMethod(), targetClass, invocation.getArguments(), invocation.getThis(), invocation::proceed); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/aop/interceptor/TimingLogAttributeSourcePointcut.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.aop.interceptor; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | public abstract class TimingLogAttributeSourcePointcut extends LogAttributeSourcePointcut { 7 | private static AtomicLong totalMatchesTime = new AtomicLong(0); 8 | 9 | @Override 10 | public boolean matches(Method method, Class targetClass) { 11 | long startTime = System.currentTimeMillis(); 12 | 13 | boolean result = super.matches(method, targetClass); 14 | 15 | long endTime = System.currentTimeMillis(); 16 | long elapsedTime = endTime - startTime; 17 | totalMatchesTime.addAndGet(elapsedTime); 18 | 19 | return result; 20 | } 21 | 22 | // 在项目启动后调用此方法打印总耗时 23 | public static long getTotalAopMatchesTime() { 24 | //System.out.println("Total matches() execution time: " + totalMatchesTime.get() + " ms"); 25 | return totalMatchesTime.get(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/cache/LogAttributeCache.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.cache; 2 | 3 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 4 | 5 | import java.util.Map; 6 | 7 | public interface LogAttributeCache { 8 | 9 | boolean containsKey(String key); 10 | void put(String key, LogAttribute attribute); 11 | LogAttribute get(String key); 12 | 13 | Map getAll(); 14 | } 15 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/cache/LogAttributeCacheConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.cache; 2 | 3 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Qualifier; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.data.redis.connection.RedisConnectionFactory; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.data.redis.listener.PatternTopic; 15 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 16 | 17 | public abstract class LogAttributeCacheConfiguration { 18 | private static final Logger LOGGER = LoggerFactory.getLogger(LogAttributeCacheConfiguration.class); 19 | 20 | @ConditionalOnProperty(value = "spring.easy-log.cache.redis.enabled", havingValue = "true", matchIfMissing = true) 21 | @ConditionalOnClass(RedisTemplate.class) 22 | public static class RedisCacheConfiguration { 23 | @Bean 24 | @ConditionalOnBean(RedisTemplate.class) 25 | @ConditionalOnMissingBean(LogAttributeCache.class) 26 | public LogAttributeCache logAttributeCache (@Qualifier("redisTemplate") RedisTemplate redisTemplate, EasyLogProperties easyLogProperties) { 27 | LOGGER.info("[easy-log]启动redis缓存日志属性(spring.easy-log.cache.redis.enabled 可控制关闭)"); 28 | return new LogAttributeRedisCache(redisTemplate, easyLogProperties); 29 | } 30 | 31 | 32 | // 配置redis事件监听器 33 | @Bean 34 | public RedisMessageListenerContainer redisMessageListenerContainer( 35 | RedisConnectionFactory connectionFactory, LogAttributeRedisCache messageListener) { 36 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 37 | container.setConnectionFactory(connectionFactory); 38 | // 配置监听的频道或模式 39 | container.addMessageListener(messageListener, new PatternTopic("easy-log.*")); 40 | return container; 41 | } 42 | } 43 | 44 | public static class LocalCacheConfiguration { 45 | @Bean 46 | @ConditionalOnMissingBean(LogAttributeCache.class) 47 | public LogAttributeCache logAttributeCache () { 48 | LOGGER.info("[easy-log]日志属性--使用本地内存缓存"); 49 | return new LogAttributeMemoryCache(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/cache/LogAttributeMemoryCache.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.cache; 2 | 3 | 4 | 5 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class LogAttributeMemoryCache implements LogAttributeCache{ 11 | 12 | private ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); 13 | @Override 14 | public boolean containsKey(String key) { 15 | return cacheMap.containsKey(key); 16 | } 17 | 18 | @Override 19 | public void put(String key, LogAttribute attribute) { 20 | cacheMap.put(key, attribute); 21 | } 22 | 23 | @Override 24 | public LogAttribute get(String key) { 25 | return cacheMap.get(key); 26 | } 27 | 28 | @Override 29 | public Map getAll() { 30 | return cacheMap; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/cache/LogAttributeRedisCache.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.cache; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 5 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 6 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 7 | import org.apache.commons.collections.CollectionUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.boot.ApplicationArguments; 11 | import org.springframework.boot.ApplicationRunner; 12 | import org.springframework.data.redis.connection.Message; 13 | import org.springframework.data.redis.connection.MessageListener; 14 | import org.springframework.data.redis.core.HashOperations; 15 | import org.springframework.data.redis.core.RedisTemplate; 16 | 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.stream.Collectors; 22 | 23 | public class LogAttributeRedisCache implements LogAttributeCache, MessageListener, ApplicationRunner { 24 | private final static Logger LOGGER = LoggerFactory.getLogger(LogAttributeRedisCache.class); 25 | 26 | private ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); 27 | 28 | private final RedisTemplate redisTemplate; 29 | private final EasyLogProperties easyLogProperties; 30 | 31 | private Boolean inited = false; 32 | private String hashKey; 33 | 34 | public LogAttributeRedisCache(RedisTemplate redisTemplate, EasyLogProperties easyLogProperties) { 35 | this.redisTemplate = redisTemplate; 36 | this.easyLogProperties = easyLogProperties; 37 | 38 | this.hashKey = easyLogProperties.getCache().getKeyPrefix(); 39 | } 40 | 41 | @Override 42 | public boolean containsKey(String key) { 43 | return cacheMap.containsKey(key); 44 | } 45 | 46 | @Override 47 | public void put(String key, LogAttribute attribute) { 48 | if (inited) { 49 | // 1.配置同步更新redis中配置 50 | this.pushToHash(key, attribute); 51 | 52 | // 2.通过redis发送事件,通知其他节点更新配置 53 | redisTemplate.convertAndSend("easy-log.update", (DefaultLogAttribute) attribute); 54 | } else { 55 | cacheMap.put(key, (DefaultLogAttribute) attribute); 56 | } 57 | } 58 | 59 | @Override 60 | public LogAttribute get(String key) { 61 | return cacheMap.get(key); 62 | } 63 | 64 | @Override 65 | public Map getAll() { 66 | Map resultMap = this.getAllFromHash().entrySet() 67 | .stream() 68 | .collect(Collectors.toMap( 69 | Map.Entry::getKey, 70 | entry -> entry.getValue() 71 | )); 72 | return resultMap; 73 | } 74 | 75 | @Override 76 | public void run(ApplicationArguments args) throws Exception { 77 | this.inited = true; 78 | this.initConfig(); 79 | } 80 | 81 | @Override 82 | public void onMessage(Message message, byte[] pattern) { 83 | String channel = new String(message.getChannel()); 84 | String body = new String(message.getBody()); 85 | DefaultLogAttribute defaultLogAttribute = JSON.parseObject(body, DefaultLogAttribute.class); 86 | LOGGER.info("[easy-log] cache receive redis message. channel[{}] content:{}", channel, body); 87 | cacheMap.put(defaultLogAttribute.title(), defaultLogAttribute); 88 | } 89 | 90 | private void initConfig() { 91 | 92 | 93 | Map redisCacheMap = this.getAllFromHash(); 94 | 95 | if (redisCacheMap == null || redisCacheMap.isEmpty()) { 96 | 97 | LOGGER.info("[easy-log]通过redis初始化日志属性--开始 cache:{}", this.cacheMap); 98 | this.pushAllToHash(this.cacheMap); 99 | LOGGER.info("[easy-log]通过redis初始化日志属性---成功"); 100 | } else if (easyLogProperties.getCache().getDropFirst()) { 101 | LOGGER.warn("[easy-log]使用本地配置强制初始化集群配置--开始"); 102 | this.pushAllToHash(this.cacheMap); 103 | LOGGER.warn("[easy-log]使用本地配置强制初始化集群配置--成功"); 104 | } else { 105 | LOGGER.info("[easy-log]通过redis刷新数据源配置--开始"); 106 | this.reloadLocalConfig(); 107 | LOGGER.info("[easy-log]通过redis刷新数据源配置--成功"); 108 | } 109 | } 110 | 111 | // private void pushConfig(List attributes) { 112 | // redisTemplate.opsForValue().set(easyLogProperties.getCache().getKeyPrefix() + applicationName, JSON.toJSONString(attributes)); 113 | // LOGGER.info("[easy-log] push data to redis success!"); 114 | // } 115 | 116 | protected void reloadLocalConfig() { 117 | 118 | Map redisCacheMap = this.getAllFromHash(); 119 | 120 | // 远程多出key配置 redis要删除 说明本地代码已经移除了对应的日志记录点/ 也有可能某个研发提交了代码,其他人没有更新 121 | // 这种情况避免误判,只删除redis缓存,日志记录实际上还是使用内存来判断 122 | List waitDeleteSet = (List) CollectionUtils.subtract(redisCacheMap.keySet(), new HashSet(cacheMap.keySet())); 123 | for (String key : waitDeleteSet) { 124 | LOGGER.info("[easy-log] redis remove cache key:{}", key); 125 | this.deleteFromHash(key); 126 | redisCacheMap.remove(key); 127 | } 128 | // 本地和远程都有的部分,使用远程来覆盖 129 | this.cacheMap.putAll(redisCacheMap); 130 | 131 | // 本地多出key配置 补充到 redis中 说明是新补充的日志记录点,或者其他人节点放弃记录了为来得及更新。 132 | List waitAddSet = (List) CollectionUtils.subtract(new HashSet(cacheMap.keySet()), redisCacheMap.keySet()); 133 | for (String key : waitAddSet) { 134 | LOGGER.info("[easy-log] redis push cache key:{}", key); 135 | this.pushToHash(key, cacheMap.get(key)); 136 | } 137 | } 138 | 139 | 140 | public void pushToHash(String key, Object value) { 141 | HashOperations hashOperations = redisTemplate.opsForHash(); 142 | hashOperations.put(hashKey, key, value); 143 | } 144 | 145 | public void pushAllToHash(Map map) { 146 | HashOperations hashOperations = redisTemplate.opsForHash(); 147 | hashOperations.putAll(hashKey, map); 148 | } 149 | 150 | public Object getFromHash(String key) { 151 | HashOperations hashOperations = redisTemplate.opsForHash(); 152 | return hashOperations.get(hashKey, key); 153 | } 154 | 155 | public Map getAllFromHash() { 156 | HashOperations hashOperations = redisTemplate.opsForHash(); 157 | return hashOperations.entries(hashKey); 158 | } 159 | 160 | public void deleteFromHash(String key) { 161 | HashOperations hashOperations = redisTemplate.opsForHash(); 162 | hashOperations.delete(hashKey, key); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/constants/HandleMode.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.constants; 2 | 3 | /** 4 | * 日志处理模式枚举 5 | */ 6 | public enum HandleMode { 7 | GLOBAL, 8 | ASYNC, 9 | SYNC 10 | } 11 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/handler/DefaultLogHandler.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.handler; 2 | 3 | import com.easycode8.easylog.core.LogInfo; 4 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | public class DefaultLogHandler implements LogDataHandler { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLogHandler.class); 12 | 13 | 14 | @Override 15 | public LogInfo init(LogAttribute logAttribute, Method method, Object[] args, Class targetClass) { 16 | return new LogInfo(); 17 | } 18 | 19 | @Override 20 | public void before(LogInfo info, Method method, Object[] args, Class targetClass, Object targetObject) { 21 | LOGGER.info("[easy-log][{}]--begin operator:[{}] param:{}", info.getTitle(), info.getOperator(), info.getParams()); 22 | } 23 | 24 | @Override 25 | public void after(LogInfo info, Method method, Class targetClass, Object returnValue) { 26 | if (LogInfo.STATUS_FINISH == info.getStatus()) { 27 | if (LogInfo.TYPE_DAO.equals(info.getType())) { 28 | LOGGER.info("[easy-log][{}]--end timeout:{} dataSnapshot:{}", info.getTitle(), info.getTimeout(), info.getDataSnapshot()); 29 | } else { 30 | LOGGER.info("[easy-log][{}]--end timeout:{} ", info.getTitle(), info.getTimeout()); 31 | } } else { 32 | LOGGER.warn("[easy-log][{}]--end timeout:{} exception:{}", info.getTitle(), info.getTimeout(), info.getException()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/handler/LogDataHandler.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.handler; 2 | 3 | import com.easycode8.easylog.core.LogInfo; 4 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * 日志处理器接口 10 | * @param 11 | */ 12 | public interface LogDataHandler { 13 | 14 | /** 15 | * 初始化日志对象 16 | * @param logAttribute 17 | * @param method 18 | * @param targetClass 19 | * @return 20 | */ 21 | T init(LogAttribute logAttribute, Method method, Object[] args, Class targetClass); 22 | 23 | /** 24 | * 执行方法前的日志处理 25 | * @param info 26 | * @param method 27 | * @param args 28 | * @param targetClass 29 | * @param targetObject 30 | */ 31 | void before(T info, Method method, Object[] args, Class targetClass, Object targetObject); 32 | 33 | /** 34 | * 执行方法后的日志处理 35 | * @param info 36 | * @param method 37 | * @param targetClass 38 | * @param returnValue 39 | */ 40 | void after(T info, Method method, Class targetClass, Object returnValue); 41 | } 42 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/monitor/EasyLogApplicationInfoPrinter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.monitor; 2 | 3 | 4 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 5 | import com.easycode8.easylog.core.aop.interceptor.TimingLogAttributeSourcePointcut; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.availability.AvailabilityChangeEvent; 10 | import org.springframework.boot.availability.ReadinessState; 11 | import org.springframework.context.ApplicationListener; 12 | import org.springframework.context.ConfigurableApplicationContext; 13 | import org.springframework.core.env.ConfigurableEnvironment; 14 | import org.springframework.core.env.Environment; 15 | 16 | import java.lang.management.ManagementFactory; 17 | import java.lang.management.MemoryUsage; 18 | import java.util.Optional; 19 | 20 | /** 21 | * 打印应用相关上下文信息 22 | */ 23 | public class EasyLogApplicationInfoPrinter implements ApplicationListener> { 24 | private static final Logger LOGGER = LoggerFactory.getLogger(EasyLogApplicationInfoPrinter.class); 25 | 26 | @Value("${easy-log.monitor.printServerInfo:true}") 27 | private boolean printServerInfo; 28 | 29 | 30 | private EasyLogProperties easyLogProperties; 31 | 32 | @Override 33 | public void onApplicationEvent(AvailabilityChangeEvent availabilityChangeEvent) { 34 | // ReadinessState.ACCEPTING_TRAFFIC 表示可以接受流量 35 | if (availabilityChangeEvent.getState() == ReadinessState.ACCEPTING_TRAFFIC && this.printServerInfo) { 36 | try { 37 | ConfigurableApplicationContext context = (ConfigurableApplicationContext) availabilityChangeEvent.getSource(); 38 | 39 | ConfigurableEnvironment environment = context.getEnvironment(); 40 | String serverPort = getApplicationServerPort(environment); 41 | LOGGER.info("=============================="); 42 | LOGGER.info("[easy-log] aop扫描加载耗时:{}毫秒", TimingLogAttributeSourcePointcut.getTotalAopMatchesTime()); 43 | // 获取Java虚拟机内存管理的MXBean 44 | MemoryUsage heapUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); 45 | LOGGER.info("[easy-log] JVM堆内存使用情况: 初始值={}MB, 最大值={}MB, 当前值={}MB", 46 | heapUsage.getInit() / 1024 / 1024, 47 | heapUsage.getMax() / 1024 / 1024, 48 | heapUsage.getUsed() / 1024 / 1024); 49 | 50 | LOGGER.info("[easy-log] 项目启动端口: {},项目启动耗时: {} 秒", serverPort, ManagementFactory.getRuntimeMXBean().getUptime() / 1000f); 51 | LOGGER.info("=============================="); 52 | } catch (Exception e) { 53 | // ignore 54 | } 55 | } 56 | } 57 | 58 | public static String getApplicationServerPort(Environment environment) { 59 | return Optional.ofNullable(environment.getProperty("local.server.port")) 60 | .orElse(environment.getProperty("server.port")); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/provider/OperatorProvider.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.provider; 2 | 3 | /** 4 | * 操作人提供接口 5 | */ 6 | public interface OperatorProvider { 7 | 8 | /**获取当前操作人*/ 9 | String currentOperator(); 10 | } 11 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/provider/SessionOperatorProvider.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.provider; 2 | 3 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 4 | import com.easycode8.easylog.core.util.SpringSpelUtils; 5 | import org.springframework.util.StringUtils; 6 | import org.springframework.web.context.request.RequestContextHolder; 7 | import org.springframework.web.context.request.ServletRequestAttributes; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | 11 | /** 12 | * 根据session提取操作人信息 13 | */ 14 | public class SessionOperatorProvider implements OperatorProvider{ 15 | private static final String PREFIX_SESSION = "SESSION."; 16 | private static final String PREFIX_HEADER = "HEADER."; 17 | private final EasyLogProperties easyLogProperties; 18 | 19 | 20 | public SessionOperatorProvider(EasyLogProperties easyLogProperties) { 21 | this.easyLogProperties = easyLogProperties; 22 | } 23 | 24 | @Override 25 | public String currentOperator() { 26 | String operator = ""; 27 | if (StringUtils.isEmpty(easyLogProperties.getOperator())) { 28 | return operator; 29 | } 30 | if (easyLogProperties.getOperator().startsWith(PREFIX_SESSION)) { 31 | String[] info = easyLogProperties.getOperator().replace(PREFIX_SESSION, "").split("\\.", 2); 32 | operator = (String) SpringSpelUtils.getSessionAttribute(info[0], info[1]); 33 | } 34 | 35 | if (easyLogProperties.getOperator().startsWith(PREFIX_HEADER)) { 36 | String expression = easyLogProperties.getOperator().replace(PREFIX_HEADER, ""); 37 | // 项目启动后立马执行业务处理,可能是非web请求这里做下兼容 38 | if (RequestContextHolder.getRequestAttributes() == null) { 39 | return operator; 40 | } 41 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 42 | operator = request.getHeader(expression); 43 | } 44 | return operator; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/trace/LogTracer.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.trace; 2 | 3 | 4 | import com.easycode8.easylog.core.LogInfo; 5 | 6 | public interface LogTracer { 7 | void init(LogInfo logInfo); 8 | void start(LogInfo logInfo); 9 | void finish(LogInfo logInfo); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/trace/NoneLogTracer.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.trace; 2 | 3 | import com.easycode8.easylog.core.LogInfo; 4 | 5 | public class NoneLogTracer implements LogTracer{ 6 | @Override 7 | public void init(LogInfo logInfo) { 8 | 9 | } 10 | 11 | @Override 12 | public void start(LogInfo logInfo) { 13 | 14 | } 15 | 16 | @Override 17 | public void finish(LogInfo logInfo) { 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/util/LogUtils.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.util; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.easycode8.easylog.core.LogInfo; 6 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.util.CollectionUtils; 10 | import org.springframework.util.StringUtils; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import org.springframework.web.context.request.RequestContextHolder; 13 | import org.springframework.web.context.request.ServletRequestAttributes; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import java.lang.reflect.Method; 17 | import java.util.*; 18 | import java.util.stream.Collectors; 19 | 20 | public abstract class LogUtils { 21 | 22 | 23 | public static void initLog(LogInfo info, LogAttribute logAttribute, Method method, Object[] args, Class targetClass) { 24 | info.setLogId(UUID.randomUUID().toString().replace("-", "")); 25 | info.setTitle(logAttribute.title()); 26 | // 定义了操作人spel表达式则使用尝试解析 27 | info.setOperator(SpringSpelUtils.parse(method, args, logAttribute.operator())); 28 | info.setOperateDate(new Date()); 29 | 30 | 31 | if (RequestContextHolder.getRequestAttributes() != null && targetClass.getAnnotation(Controller.class) != null || targetClass.getAnnotation(RestController.class) != null) { 32 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 33 | info.setRequestUri( request.getRequestURI() + " [" + request.getMethod() + "]"); 34 | info.setIp(request.getRemoteAddr()); 35 | if (StringUtils.isEmpty(info.getType())) { 36 | info.setType(LogInfo.TYPE_WEB); 37 | } 38 | info.setParams(LogUtils.buildRequestParams(request.getParameterMap(), args)); 39 | } else { 40 | info.setParams(LogUtils.buildRequestParams(null, args)); 41 | } 42 | 43 | if (targetClass.getAnnotation(Service.class) != null && StringUtils.isEmpty(info.getType())) { 44 | info.setType(LogInfo.TYPE_SERVICE); 45 | } 46 | // 处理tags信息 47 | if (!CollectionUtils.isEmpty(logAttribute.tags())) { 48 | JSONObject tags = new JSONObject(); 49 | for (Map.Entry item: logAttribute.tags().entrySet()) { 50 | tags.put(item.getKey(), SpringSpelUtils.parse(method, args, item.getValue())); 51 | } 52 | info.setTags(JSON.toJSONString(tags)); 53 | } 54 | 55 | 56 | info.setMethod(LogUtils.createDefaultTitle(method, targetClass)); 57 | String spelDescription = SpringSpelUtils.parse(method, args, logAttribute.template()); 58 | info.setDescription(spelDescription); 59 | 60 | } 61 | 62 | public static String buildRequestParams(Map paramMap, Object[] args) { 63 | 64 | // post/put 请求体json参数 65 | if (CollectionUtils.isEmpty(paramMap)) { 66 | List paramList = new ArrayList<>(); 67 | for (Object obj : args) { 68 | try { 69 | paramList.add(JSON.toJSONString(obj)); 70 | } catch (Throwable e) { 71 | // 不是所有对象都支持序列化比如 HttpServletRequest 72 | paramList.add("notSerializableParam"); 73 | } 74 | 75 | } 76 | return String.join(",",paramList ); 77 | // get 请求参数 78 | } else { 79 | StringBuilder params = new StringBuilder(); 80 | for (Map.Entry param : ((Map) paramMap).entrySet()) { 81 | params.append(("".equals(params.toString()) ? "" : "&") + param.getKey() + "="); 82 | String paramValue = (param.getValue() != null && param.getValue().length > 0 ? param.getValue()[0] : ""); 83 | params.append(paramValue); 84 | } 85 | return params.toString(); 86 | } 87 | 88 | } 89 | 90 | public static String createDefaultTitle(Method method, Class targetClass) { 91 | return createDefaultTitle(method, targetClass, true); 92 | } 93 | 94 | public static String createDefaultTitle(Method method, Class targetClass, boolean isSimpleName) { 95 | StringBuilder title = new StringBuilder(); 96 | if (isSimpleName) { 97 | title.append(targetClass.getSimpleName()); 98 | } else { 99 | title.append(targetClass.getName()); 100 | } 101 | title.append("." + method.getName()); 102 | if (method.getParameters() != null) { 103 | List paramNames = Arrays.stream(method.getParameters()).map(item -> item.getName()).collect(Collectors.toList()); 104 | title.append("(" + String.join(",",paramNames) + ")"); 105 | } else { 106 | title.append("()"); 107 | } 108 | return title.toString(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /easy-log-core/src/main/java/com/easycode8/easylog/core/util/SpringSpelUtils.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.core.util; 2 | 3 | import org.springframework.core.LocalVariableTableParameterNameDiscoverer; 4 | import org.springframework.expression.EvaluationContext; 5 | import org.springframework.expression.Expression; 6 | import org.springframework.expression.ExpressionParser; 7 | import org.springframework.expression.spel.standard.SpelExpressionParser; 8 | import org.springframework.expression.spel.support.StandardEvaluationContext; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.context.request.RequestContextHolder; 11 | import org.springframework.web.context.request.ServletRequestAttributes; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpSession; 15 | import java.lang.reflect.Method; 16 | 17 | 18 | public class SpringSpelUtils { 19 | 20 | /**解析spel表达式*/ 21 | private static ExpressionParser parser = new SpelExpressionParser(); 22 | /**将方法参数纳入Spring管理*/ 23 | private static LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); 24 | 25 | 26 | /** 27 | * 解析spel表达式 28 | * @param method 方法 29 | * @param args 获取参数对象数组 30 | * @param template spel表达式 31 | * @return 32 | */ 33 | public static String parse(Method method, Object[] args, String template) { 34 | if (StringUtils.isEmpty(template)) { 35 | return ""; 36 | } 37 | //获取方法参数名 38 | String[] params = discoverer.getParameterNames(method); 39 | EvaluationContext context = new StandardEvaluationContext(); 40 | for (int len = 0; len < params.length; len++) { 41 | context.setVariable(params[len], args[len]); 42 | } 43 | Expression expression = parser.parseExpression(template); 44 | String spelDescription = expression.getValue(context, String.class); 45 | return spelDescription; 46 | } 47 | 48 | 49 | /** 50 | * 使用SpEL表达式从session中提取属性信息 51 | * 52 | * @param expression SpEL表达式 53 | * @return 属性值 54 | */ 55 | public static Object getSessionAttribute(String attributeName, String expression) { 56 | if (RequestContextHolder.getRequestAttributes() == null) { 57 | return null; 58 | } 59 | HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 60 | HttpSession session = request.getSession(); 61 | // 解析表达式并获取属性值 62 | // return parser.parseExpression(expression).getValue(session.getAttribute("account")); 63 | Object attributeValue = session.getAttribute(attributeName); 64 | if (attributeValue == null) { 65 | return null; 66 | } 67 | return parser.parseExpression(expression).getValue(attributeValue); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /easy-log-core/src/main/resources/easy-log/db/liquibase/changelog-1.0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-log 7 | io.github.easycode8 8 | ${revision} 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | easy-log-data-mybatis-plus 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-configuration-processor 20 | true 21 | 22 | 23 | 24 | io.github.easycode8 25 | easy-log-core 26 | ${revision} 27 | 28 | 29 | 30 | 31 | 32 | com.baomidou 33 | mybatis-plus-boot-starter 34 | ${mybatis-plus.version} 35 | true 36 | 37 | 38 | 39 | 40 | org.apache.commons 41 | commons-lang3 42 | ${commons-lang3.version} 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/readme.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | ## 包目录说明 3 | - com.easycode8.easylog.mybatis 4 | - adapter: 存放适配处理逻辑 (mybatis和mybatis-plus的Mapper不一致需要适配 ) 5 | - handler: 处理快照数据生成逻辑 6 | - interceptor: mybatis拦截器入口,是否生成日志数据快照由handler决定 7 | 8 | 9 | ## mybatis拦截器方式实现 10 | ```java 11 | public class RecordInterceptor implements Interceptor { 12 | 13 | @Override 14 | public Object intercept(Invocation invocation) throws Throwable { 15 | Object[] args = invocation.getArgs(); 16 | MappedStatement ms = (MappedStatement) args[0]; 17 | Object parameterObject = args[1]; 18 | BoundSql boundSql = ms.getBoundSql(parameterObject); 19 | String sql = boundSql.getSql(); 20 | SqlCommandType sqlCommandType = ms.getSqlCommandType(); 21 | 22 | if (sqlCommandType == SqlCommandType.DELETE || sqlCommandType == SqlCommandType.UPDATE) { 23 | // 记录信息的逻辑 24 | String tableName = getTableNameFromSql(sql); 25 | // 获取删除或修改前的记录信息 26 | List> originalRecords = getOriginalRecords(tableName, parameterObject); 27 | // 将信息记录到日志文件或数据库中 28 | recordLog(tableName, originalRecords); 29 | } 30 | 31 | return invocation.proceed(); 32 | } 33 | 34 | private String getTableNameFromSql(String sql) { 35 | // 根据 SQL 语句获取表名 36 | // ... 37 | } 38 | 39 | private List> getOriginalRecords(String tableName, Object parameterObject) { 40 | // 获取删除或修改前的记录信息 41 | // ... 42 | } 43 | 44 | private void recordLog(String tableName, List> originalRecords) { 45 | // 将信息记录到日志文件或数据库中 46 | // ... 47 | } 48 | } 49 | 50 | ``` -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/CompareResult.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis; 2 | 3 | public class CompareResult { 4 | 5 | /** 6 | * 字段名 7 | */ 8 | private String fieldName; 9 | /** 10 | * 字段注释 11 | */ 12 | private String fieldComment; 13 | /** 14 | * 字段旧值 15 | */ 16 | private Object oldValue; 17 | /** 18 | * 字段新值 19 | */ 20 | private Object newValue; 21 | 22 | public String getFieldName() { 23 | return fieldName; 24 | } 25 | 26 | public void setFieldName(String fieldName) { 27 | this.fieldName = fieldName; 28 | } 29 | 30 | public String getFieldComment() { 31 | return fieldComment; 32 | } 33 | 34 | public void setFieldComment(String fieldComment) { 35 | this.fieldComment = fieldComment; 36 | } 37 | 38 | public Object getOldValue() { 39 | return oldValue; 40 | } 41 | 42 | public void setOldValue(Object oldValue) { 43 | this.oldValue = oldValue; 44 | } 45 | 46 | public Object getNewValue() { 47 | return newValue; 48 | } 49 | 50 | public void setNewValue(Object newValue) { 51 | this.newValue = newValue; 52 | } 53 | } -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/adapter/MybatisAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.adapter; 2 | 3 | import com.easycode8.easylog.core.handler.DefaultLogHandler; 4 | import com.easycode8.easylog.core.LogInfo; 5 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 6 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 7 | import com.easycode8.easylog.core.util.LogUtils; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | public class MybatisAdapter extends DefaultLogHandler implements MybatisLogAttributeMappingAdapter { 12 | 13 | @Override 14 | public LogAttribute getLogAttribute(Method method, Class targetClass) { 15 | 16 | if (targetClass.getGenericInterfaces() != null && targetClass.getGenericInterfaces().length == 1) { 17 | String mapperName = targetClass.getGenericInterfaces()[0].getTypeName().toLowerCase(); 18 | if (mapperName.endsWith("dao") || mapperName.endsWith("mapper") ) { 19 | 20 | String title = LogUtils.createDefaultTitle(method, (Class)targetClass.getGenericInterfaces()[0]); 21 | return DefaultLogAttribute.builder() 22 | .title(title) 23 | .handler("mybatisLogDataHandler") 24 | .build(); 25 | 26 | } 27 | 28 | } 29 | return null; 30 | 31 | } 32 | 33 | @Override 34 | public void before(LogInfo info, Method method, Object[] args, Class targetClass, Object targetObject) { 35 | info.setType(LogInfo.TYPE_DAO); 36 | super.before(info, method, args, targetClass, targetObject); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/adapter/MybatisLogAttributeMappingAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.adapter; 2 | 3 | import com.easycode8.easylog.core.adapter.LogAttributeMappingAdapter; 4 | 5 | public interface MybatisLogAttributeMappingAdapter extends LogAttributeMappingAdapter { 6 | } 7 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/adapter/MybatisLogAttributeMappingConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.adapter; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 8 | import org.springframework.context.annotation.Bean; 9 | 10 | public abstract class MybatisLogAttributeMappingConfiguration { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(MybatisLogAttributeMappingConfiguration.class); 12 | 13 | @ConditionalOnClass(BaseMapper.class) 14 | public static class MybatisPlus { 15 | @Bean 16 | @ConditionalOnMissingBean 17 | public MybatisLogAttributeMappingAdapter mybatisPlusLogDataHandler() { 18 | LOGGER.info("[easy-log]启动mybatis-plus mapper操作日志记录"); 19 | return new MybatisPlusAdapter(); 20 | } 21 | } 22 | 23 | public static class Mybatis { 24 | @Bean 25 | @ConditionalOnMissingBean 26 | public MybatisLogAttributeMappingAdapter mybatisLogDataHandler() { 27 | LOGGER.info("[easy-log]启动mybatis mapper操作日志记录"); 28 | return new MybatisAdapter(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/adapter/MybatisPlusAdapter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.adapter; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.easycode8.easylog.core.handler.DefaultLogHandler; 5 | import com.easycode8.easylog.core.LogInfo; 6 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 7 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 8 | import com.easycode8.easylog.core.util.LogUtils; 9 | import com.easycode8.easylog.mybatis.util.GenericTypeUtils; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | public class MybatisPlusAdapter extends DefaultLogHandler implements MybatisLogAttributeMappingAdapter { 14 | @Override 15 | public LogAttribute getLogAttribute(Method method, Class targetClass) { 16 | 17 | if (BaseMapper.class.isAssignableFrom(targetClass)) { 18 | // 忽略Mapper属于Object的方法, 如toString等不记录 19 | if (method.getDeclaringClass().equals(Object.class)) { 20 | return null; 21 | } 22 | 23 | // 如果是对象LogInfo及其子类忽略操作,避免日志持久化操作本身记录日志 24 | Class clazz = GenericTypeUtils.getGenericParameterType(((Class)targetClass.getGenericInterfaces()[0]), BaseMapper.class); 25 | // 非空判断防止用户继承泛型接口又不定义泛型 26 | if ( clazz != null && LogInfo.class.isAssignableFrom(clazz)) { 27 | return null; 28 | } 29 | 30 | String title = LogUtils.createDefaultTitle(method, ((Class)targetClass.getGenericInterfaces()[0])); 31 | return DefaultLogAttribute.builder() 32 | .title(title) 33 | .handler("mybatisPlusLogDataHandler") 34 | .build(); 35 | 36 | } 37 | return null; 38 | 39 | } 40 | 41 | @Override 42 | public void before(LogInfo info, Method method, Object[] args, Class targetClass, Object targetObject) { 43 | 44 | String methodName = LogUtils.createDefaultTitle(method, ((Class)targetClass.getGenericInterfaces()[0])); 45 | info.setMethod(methodName); 46 | info.setType(LogInfo.TYPE_DAO); 47 | super.before(info, method, args, targetClass, targetObject); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/autoconfigure/EasyLogMybatisPlusProperties.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.autoconfigure; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties(prefix = "spring.easy-log") 6 | public class EasyLogMybatisPlusProperties { 7 | 8 | private ScanMybatisPlus scanMybatisPlus; 9 | 10 | public static class ScanMybatisPlus { 11 | /**是否开启扫描mybatis-plus mapper接口方法(必须继承BaseMapper.class)*/ 12 | private Boolean enabled = false; 13 | 14 | public Boolean getEnabled() { 15 | return enabled; 16 | } 17 | 18 | public void setEnabled(Boolean enabled) { 19 | this.enabled = enabled; 20 | } 21 | } 22 | 23 | public ScanMybatisPlus getScanMybatisPlus() { 24 | return scanMybatisPlus; 25 | } 26 | 27 | public void setScanMybatisPlus(ScanMybatisPlus scanMybatisPlus) { 28 | this.scanMybatisPlus = scanMybatisPlus; 29 | } 30 | } -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/autoconfigure/MybatisDataLogAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.autoconfigure; 2 | 3 | import com.easycode8.easylog.mybatis.adapter.MybatisLogAttributeMappingAdapter; 4 | import com.easycode8.easylog.mybatis.adapter.MybatisLogAttributeMappingConfiguration; 5 | import com.easycode8.easylog.mybatis.handler.DataSnapshotHandler; 6 | import com.easycode8.easylog.mybatis.handler.MybatisPlusDataSnapshotHandler; 7 | import com.easycode8.easylog.mybatis.interceptor.DataSnapshotInterceptor; 8 | import org.apache.ibatis.mapping.MappedStatement; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 12 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | 18 | 19 | 20 | @ConditionalOnClass(MappedStatement.class) 21 | @Configuration 22 | @ConditionalOnProperty(name = {"spring.easy-log.enabled", "spring.easy-log.scan-mybatis-plus.enabled"}, havingValue = "true") 23 | @EnableConfigurationProperties(EasyLogMybatisPlusProperties.class) 24 | public class MybatisDataLogAutoConfiguration { 25 | 26 | @Bean 27 | public DataSnapshotInterceptor recordInterceptor(JdbcTemplate jdbcTemplate, DataSnapshotHandler dataSnapshotHandler) { 28 | return new DataSnapshotInterceptor(jdbcTemplate, dataSnapshotHandler); 29 | } 30 | 31 | @Bean 32 | @ConditionalOnMissingBean 33 | public DataSnapshotHandler mybatisPlusDataSnapshotHandler() { 34 | return new MybatisPlusDataSnapshotHandler(); 35 | } 36 | 37 | /** 38 | * mybtatis日志属性选择器支持mybatis及mybatis-plus 优先级:mybatis-plus > mybatis 39 | */ 40 | @Configuration 41 | @ConditionalOnMissingBean(MybatisLogAttributeMappingAdapter.class) 42 | @Import({MybatisLogAttributeMappingConfiguration.MybatisPlus.class, MybatisLogAttributeMappingConfiguration.Mybatis.class}) 43 | public static class ChooseMybatisLogAttributeMapping { 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/handler/DataSnapshotHandler.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.handler; 2 | 3 | import org.apache.ibatis.mapping.BoundSql; 4 | import org.apache.ibatis.mapping.MappedStatement; 5 | import org.apache.ibatis.mapping.SqlCommandType; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * 数据快照处理器 13 | */ 14 | public interface DataSnapshotHandler { 15 | 16 | boolean supports(Class mapperClass, Method method); 17 | 18 | void handle(MappedStatement mappedStatement, BoundSql boundSql, List originalRecords) throws IllegalAccessException; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/handler/MybatisPlusDataSnapshotHandler.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.easycode8.easylog.core.LogHolder; 6 | import com.easycode8.easylog.core.LogInfo; 7 | import com.easycode8.easylog.mybatis.util.MybatisUtils; 8 | import org.apache.ibatis.mapping.BoundSql; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.mapping.SqlCommandType; 11 | import org.apache.logging.log4j.util.Strings; 12 | import org.springframework.util.StringUtils; 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | public class MybatisPlusDataSnapshotHandler implements DataSnapshotHandler{ 20 | 21 | @Override 22 | public boolean supports(Class mapperClass, Method method) { 23 | LogInfo logInfo = LogHolder.peek(); 24 | // 如果没开启日志上下文,忽略数据快照处理 25 | // TODO 操作对象暴露出去让外部决定是否要执行快照记录 26 | // Class entityClass = GenericTypeUtils.getGenericParameterType(mapperClass, BaseMapper.class); 27 | return logInfo != null; 28 | } 29 | 30 | @Override 31 | public void handle(MappedStatement mappedStatement, BoundSql boundSql, List originalRecords) throws IllegalAccessException { 32 | SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); 33 | LogInfo logInfo = LogHolder.peek(); 34 | if (sqlCommandType == SqlCommandType.DELETE) { 35 | if (StringUtils.isEmpty(logInfo.getDataSnapshot())) { 36 | logInfo.setDataSnapshot("deleted:" +JSON.toJSONString(originalRecords)); 37 | } else { 38 | logInfo.setDataSnapshot(logInfo.getDataSnapshot() + ";" + "deleted:" + JSON.toJSONString(originalRecords)); 39 | } 40 | } else if (sqlCommandType == SqlCommandType.UPDATE) { 41 | // 获取mapper的实体类型 42 | Class entityClass = MybatisUtils.getEntityClassByMapper(mappedStatement, boundSql); 43 | // 获取实体对象 44 | Object entityObject = MybatisUtils.getEntityObjectByMapper(mappedStatement, boundSql); 45 | 46 | List dataSnapshot = new ArrayList<>(); 47 | for (Object entity : originalRecords) { 48 | dataSnapshot.add(JSON.toJSONString(MybatisUtils.compareTowObject(entity, entityObject))); 49 | } 50 | // LOGGER.info("[{}] data after update:{}", title, dataSnapshot); 51 | 52 | if (StringUtils.isEmpty(logInfo.getDataSnapshot())) { 53 | logInfo.setDataSnapshot("updated:" + Strings.join(dataSnapshot, ',')); 54 | } else { 55 | logInfo.setDataSnapshot(logInfo.getDataSnapshot() + ";" + "updated:" + Strings.join(dataSnapshot, ',')); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/interceptor/DataSnapshotInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.interceptor; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.easycode8.easylog.core.util.LogUtils; 6 | import com.easycode8.easylog.mybatis.handler.DataSnapshotHandler; 7 | import com.easycode8.easylog.mybatis.util.CamelCaseUtils; 8 | import com.easycode8.easylog.mybatis.util.MybatisUtils; 9 | import com.easycode8.easylog.mybatis.util.SqlUtils; 10 | import org.apache.ibatis.executor.Executor; 11 | import org.apache.ibatis.mapping.BoundSql; 12 | import org.apache.ibatis.mapping.MappedStatement; 13 | import org.apache.ibatis.mapping.SqlCommandType; 14 | import org.apache.ibatis.plugin.*; 15 | import org.apache.ibatis.session.Configuration; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.beans.BeanUtils; 19 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 20 | import org.springframework.jdbc.core.JdbcTemplate; 21 | import org.springframework.util.CollectionUtils; 22 | import org.springframework.util.StringUtils; 23 | 24 | import java.beans.PropertyDescriptor; 25 | import java.lang.reflect.Method; 26 | import java.sql.ResultSet; 27 | import java.sql.SQLException; 28 | import java.util.*; 29 | 30 | @Intercepts({@Signature( 31 | type = Executor.class, 32 | method = "update", 33 | args = {MappedStatement.class, Object.class} 34 | )}) 35 | public class DataSnapshotInterceptor implements Interceptor { 36 | private static final Logger LOGGER = LoggerFactory.getLogger(DataSnapshotInterceptor.class); 37 | 38 | private final JdbcTemplate jdbcTemplate; 39 | private final DataSnapshotHandler dataSnapshotHandler; 40 | 41 | public DataSnapshotInterceptor(JdbcTemplate jdbcTemplate, DataSnapshotHandler dataSnapshotHandler) { 42 | this.jdbcTemplate = jdbcTemplate; 43 | this.dataSnapshotHandler = dataSnapshotHandler; 44 | } 45 | 46 | @Override 47 | public Object intercept(Invocation invocation) throws Throwable { 48 | Object[] args = invocation.getArgs(); 49 | MappedStatement mappedStatement = (MappedStatement) args[0]; 50 | Object parameterObject = args[1]; 51 | BoundSql boundSql = mappedStatement.getBoundSql(parameterObject); 52 | String sqlId = mappedStatement.getId(); 53 | // 获取mapper接口及方法 54 | String mapperClassName = sqlId.substring(0, sqlId.lastIndexOf(".")); 55 | Class mapperClass = Class.forName(mapperClassName); 56 | 57 | Method method = MybatisUtils.getMethodBySqlId(sqlId); 58 | // 判断当前方法是否支持 59 | if (!dataSnapshotHandler.supports(mapperClass, method)) { 60 | return invocation.proceed(); 61 | } 62 | // 解析原始sql 63 | String title = LogUtils.createDefaultTitle(method, mapperClass); 64 | Configuration configuration = mappedStatement.getConfiguration(); 65 | 66 | SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); 67 | 68 | // 获取删除或修改前的记录信息 69 | if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.DELETE) { 70 | try { 71 | String realSql = MybatisUtils.showSql(configuration, boundSql); 72 | LOGGER.info("[easy-log][{}] sql ==> {}", title, realSql); 73 | Class entityClass = MybatisUtils.getEntityClassByMapper(mappedStatement, boundSql); 74 | List originalRecords = getOriginalRecords(sqlCommandType, realSql, entityClass); 75 | LOGGER.info("[easy-log][{}] original data <== {}", title, JSON.toJSONString(originalRecords)); 76 | dataSnapshotHandler.handle(mappedStatement, boundSql, originalRecords); 77 | } catch (Exception ex) { 78 | // 记录数据快照不要影响业务执行 79 | LOGGER.warn("[easy-log][{}] record DataSnapshot failure cause:{}", title, ex.getMessage(), ex); 80 | } 81 | 82 | } 83 | return invocation.proceed(); 84 | } 85 | 86 | /** 87 | * 根据原始sql 构造修改活着删除前的数据快照 88 | * @param sqlCommandType 89 | * @param realSql 90 | * @return 91 | */ 92 | private List getOriginalRecords(SqlCommandType sqlCommandType, String realSql, Class entityClass) { 93 | // 获取删除或修改前的记录信息 94 | String selectSql = ""; 95 | if (sqlCommandType == SqlCommandType.DELETE) { 96 | selectSql = SqlUtils.convertDeleteToSelect(realSql); 97 | List> data = jdbcTemplate.queryForList(selectSql); 98 | if (CollectionUtils.isEmpty(data)) { 99 | return new ArrayList<>(); 100 | } 101 | for (Map map : data) { 102 | Set keySet = new HashSet(map.keySet()); 103 | keySet.forEach(key->{ 104 | Object value = map.get(key); 105 | map.remove(key); 106 | LOGGER.debug(key + " => " + CamelCaseUtils.toCamelCase(key)); 107 | map.put(CamelCaseUtils.toCamelCase(key), value); 108 | }); 109 | } 110 | return data; 111 | 112 | } else if (sqlCommandType == SqlCommandType.UPDATE) { 113 | selectSql = SqlUtils.convertUpdateToSelect(realSql); 114 | LOGGER.debug("[easy-log] select original data:{}", selectSql); 115 | // sql解析失败则放弃取历史值。 116 | if (StringUtils.isEmpty(selectSql)) { 117 | return new ArrayList<>(); 118 | } 119 | 120 | return jdbcTemplate.query(selectSql, new CustomObjectRowMapper(entityClass)); 121 | } else { 122 | return new ArrayList<>(); 123 | } 124 | 125 | } 126 | 127 | @Override 128 | public Object plugin(Object target) { 129 | return Plugin.wrap(target, this); 130 | } 131 | 132 | static class CustomObjectRowMapper extends BeanPropertyRowMapper { 133 | 134 | public CustomObjectRowMapper(Class targetType) { 135 | super(targetType); 136 | this.setPrimitivesDefaultedForNullValue(true); 137 | } 138 | 139 | @Override 140 | protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException { 141 | //System.out.println(pd.getName() + " " + pd.getPropertyType() + " " + super.getColumnValue(rs, index, pd)); 142 | if (!BeanUtils.isSimpleProperty(pd.getPropertyType())) { 143 | 144 | return null; 145 | } 146 | return super.getColumnValue(rs, index, pd); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/util/CamelCaseUtils.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.util; 2 | 3 | public class CamelCaseUtils { 4 | 5 | private static final char SEPARATOR = '_'; 6 | private static final String CHARSET_NAME = "UTF-8"; 7 | 8 | /** 9 | * 驼峰命名法工具 10 | * @return 11 | * toCamelCase("hello_world") == "helloWorld" 12 | * toCapitalizeCamelCase("hello_world") == "HelloWorld" 13 | * toUnderScoreCase("helloWorld") = "hello_world" 14 | */ 15 | public static String toCamelCase(String s) { 16 | if (s == null) { 17 | return null; 18 | } 19 | 20 | s = s.toLowerCase(); 21 | 22 | StringBuilder sb = new StringBuilder(s.length()); 23 | boolean upperCase = false; 24 | for (int i = 0; i < s.length(); i++) { 25 | char c = s.charAt(i); 26 | 27 | if (c == SEPARATOR) { 28 | upperCase = true; 29 | } else if (upperCase) { 30 | sb.append(Character.toUpperCase(c)); 31 | upperCase = false; 32 | } else { 33 | sb.append(c); 34 | } 35 | } 36 | 37 | return sb.toString(); 38 | } 39 | 40 | /** 41 | * 驼峰命名法工具 42 | * @return 43 | * toCamelCase("hello_world") == "helloWorld" 44 | * toCapitalizeCamelCase("hello_world") == "HelloWorld" 45 | * toUnderScoreCase("helloWorld") = "hello_world" 46 | */ 47 | public static String toCapitalizeCamelCase(String s) { 48 | if (s == null) { 49 | return null; 50 | } 51 | s = toCamelCase(s); 52 | return s.substring(0, 1).toUpperCase() + s.substring(1); 53 | } 54 | 55 | /** 56 | * 驼峰命名法工具 57 | * @return 58 | * toCamelCase("hello_world") == "helloWorld" 59 | * toCapitalizeCamelCase("hello_world") == "HelloWorld" 60 | * toUnderScoreCase("helloWorld") = "hello_world" 61 | */ 62 | public static String toUnderScoreCase(String s) { 63 | if (s == null) { 64 | return null; 65 | } 66 | 67 | StringBuilder sb = new StringBuilder(); 68 | boolean upperCase = false; 69 | for (int i = 0; i < s.length(); i++) { 70 | char c = s.charAt(i); 71 | 72 | boolean nextUpperCase = true; 73 | 74 | if (i < (s.length() - 1)) { 75 | nextUpperCase = Character.isUpperCase(s.charAt(i + 1)); 76 | } 77 | 78 | if ((i > 0) && Character.isUpperCase(c)) { 79 | if (!upperCase || !nextUpperCase) { 80 | sb.append(SEPARATOR); 81 | } 82 | upperCase = true; 83 | } else { 84 | upperCase = false; 85 | } 86 | 87 | sb.append(Character.toLowerCase(c)); 88 | } 89 | 90 | return sb.toString(); 91 | } 92 | 93 | public static void main(String[] args) { 94 | System.out.println(CamelCaseUtils.toCamelCase("HI_TOM")); 95 | System.out.println(CamelCaseUtils.toCapitalizeCamelCase("HI_TOM")); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/util/GenericTypeUtils.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.util; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import java.lang.reflect.Type; 5 | 6 | public class GenericTypeUtils { 7 | /** 8 | * 获取指定类实现接口的泛型类型 9 | * 10 | * @param clazz 指定的类 11 | * @param interfaceClass 实现的接口的Class对象 12 | * @return 该类实现接口的泛型类型 13 | */ 14 | public static Class getGenericParameterType(Class clazz, Class interfaceClass) { 15 | Type[] interfaces = clazz.getGenericInterfaces(); 16 | for (Type type : interfaces) { 17 | if (type instanceof ParameterizedType) { 18 | ParameterizedType parameterizedType = (ParameterizedType) type; 19 | 20 | if (interfaceClass.isAssignableFrom((Class) parameterizedType.getRawType())) { 21 | Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); 22 | if (actualTypeArguments != null && actualTypeArguments.length > 0) { 23 | Type typeArgument = actualTypeArguments[0]; 24 | if (typeArgument instanceof Class) { 25 | return (Class) typeArgument; 26 | } else if (typeArgument instanceof ParameterizedType) { 27 | Type rawType = ((ParameterizedType) typeArgument).getRawType(); 28 | if (rawType instanceof Class && ((Class) rawType).isAssignableFrom(interfaceClass)) { 29 | return getGenericParameterType(clazz, (Class) rawType); 30 | } 31 | } 32 | } 33 | } else if (type instanceof Class && interfaceClass.isAssignableFrom((Class) type)) { 34 | return getGenericParameterType(clazz, (Class) type); 35 | } 36 | 37 | } else if (type instanceof Class) { //泛型接口可能存在接口继承, 需要继续往下找才能找到泛型接口 38 | return getGenericParameterType((Class) type, interfaceClass); 39 | } 40 | } 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/java/com/easycode8/easylog/mybatis/util/SqlUtils.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.mybatis.util; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class SqlUtils { 7 | public static String convertDeleteToSelect(String deleteSql) { 8 | // 定义正则表达式匹配模式,支持提取 schema 和表名 9 | // 支持标准的delete from语句 10 | //String pattern = "DELETE\\s+FROM\\s+(\\w+(?:\\.\\w+)?)\\s+WHERE\\s+(.+)"; 11 | // 支持标准的delete from语句及省略form的语句 12 | String pattern = "DELETE\\s+(?:FROM\\s+)?(\\w+(?:\\.\\w+)?)\\s+WHERE\\s+(.+)"; 13 | Pattern regex = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); 14 | Matcher matcher = regex.matcher(deleteSql); 15 | 16 | if (matcher.find()) { 17 | String fullTableName = matcher.group(1); 18 | String[] tableParts = fullTableName.split("\\."); 19 | String schema = null; 20 | String tableName = null; 21 | 22 | if (tableParts.length > 1) { 23 | schema = tableParts[0]; 24 | tableName = tableParts[1]; 25 | } else { 26 | tableName = tableParts[0]; 27 | } 28 | 29 | String whereClause = matcher.group(2); 30 | 31 | // 构建 SELECT 语句,包括 schema 和表名 32 | String selectSql = "SELECT * FROM " + getTableNameWithSchema(schema, tableName) + " WHERE " + whereClause; 33 | return selectSql; 34 | } 35 | 36 | return null; 37 | } 38 | 39 | public static String convertUpdateToSelect(String updateSql) { 40 | // 定义正则表达式匹配模式,支持提取 schema 和表名 41 | String pattern = "UPDATE\\s+(\\w+(?:\\.\\w+)?)\\s+SET(.+?)WHERE\\s+(.+)"; 42 | Pattern regex = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); 43 | Matcher matcher = regex.matcher(updateSql); 44 | 45 | if (matcher.find()) { 46 | String fullTableName = matcher.group(1); 47 | String[] tableParts = fullTableName.split("\\."); 48 | String schema = null; 49 | String tableName = null; 50 | 51 | if (tableParts.length > 1) { 52 | schema = tableParts[0]; 53 | tableName = tableParts[1]; 54 | } else { 55 | tableName = tableParts[0]; 56 | } 57 | 58 | String setClause = matcher.group(2); 59 | String whereClause = matcher.group(3); 60 | 61 | // 构建 SELECT 语句,包括 schema 和表名 62 | String selectSql = "SELECT * FROM " + getTableNameWithSchema(schema, tableName) + " WHERE " + whereClause; 63 | return selectSql; 64 | } 65 | 66 | return null; 67 | } 68 | 69 | private static String getTableNameWithSchema(String schema, String tableName) { 70 | if (schema != null) { 71 | return schema + "." + tableName; 72 | } 73 | return tableName; 74 | } 75 | 76 | public static void main(String[] args) { 77 | String deleteSql = "DELETE FROM schema_name.table_name WHERE condition"; 78 | 79 | String selectSql = convertDeleteToSelect(deleteSql); 80 | System.out.println(selectSql); 81 | 82 | String updateSql = "UPDATE schema_name.table_name SET column1 = value1, column2 = value2 WHERE condition"; 83 | 84 | String selectSql1 = convertUpdateToSelect(updateSql); 85 | System.out.println(selectSql1); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /easy-log-data-mybatis-plus/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.easycode8.easylog.mybatis.autoconfigure.MybatisDataLogAutoConfiguration -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-log 7 | io.github.easycode8 8 | ${revision} 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | easy-log-spring-boot-starter 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-configuration-processor 20 | true 21 | 22 | 23 | 24 | 25 | io.github.easycode8 26 | easy-log-core 27 | ${revision} 28 | 29 | 30 | 31 | 32 | io.github.easycode8 33 | easy-log-trace 34 | ${revision} 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | true 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-aop 47 | true 48 | 49 | 50 | 51 | 52 | io.springfox 53 | springfox-swagger2 54 | true 55 | 56 | 57 | 58 | io.swagger.core.v3 59 | swagger-annotations 60 | 2.2.8 61 | true 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/src/main/java/com/easycode8/easylog/autoconfigure/EasyLogAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.autoconfigure; 2 | 3 | 4 | import com.easycode8.easylog.core.adapter.ControllerLogAttributeMapping; 5 | import com.easycode8.easylog.core.adapter.ServiceLogAttributeMapping; 6 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 7 | import com.easycode8.easylog.core.annotation.EnableEasyLog; 8 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 9 | import com.easycode8.easylog.trace.autoconfigure.EasyLogTraceAutoConfiguration; 10 | import org.aspectj.weaver.Advice; 11 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 19 | import org.springframework.context.annotation.Import; 20 | 21 | @Configuration 22 | @EnableEasyLog 23 | @ConditionalOnClass({Advice.class, EnableAspectJAutoProxy.class})//AopAutoConfiguration 参考 24 | @AutoConfigureAfter({EasyLogTraceAutoConfiguration.class, RedisAutoConfiguration.class}) 25 | @ConditionalOnProperty(value = "spring.easy-log.enabled", havingValue = "true", matchIfMissing = true) 26 | public class EasyLogAutoConfiguration { 27 | 28 | 29 | /** 30 | * 多个属性源使用按优先级加载,覆盖core包中默认逻辑。因为swagger包 31 | * 在日志框架中不是必须得,因此使用swagger注解得属性源,不在core包中实现 32 | * 33 | * */ 34 | @Configuration(proxyBeanMethods = false) 35 | @ConditionalOnMissingBean(LogAttributeSource.class) 36 | @Import({LogAttributeSourceConfiguration.OpenApi3Source.class, LogAttributeSourceConfiguration.SwaggerSource.class, LogAttributeSourceConfiguration.EasyLogSource.class}) 37 | protected static class ChooseLogAttributeSourceConfiguration { 38 | 39 | @Bean 40 | @ConditionalOnMissingBean 41 | @ConditionalOnProperty(value = "spring.easy-log.scan-service.enabled", havingValue = "true") 42 | public ServiceLogAttributeMapping serviceLogAttributeMapping(EasyLogProperties easyLogProperties) { 43 | return new ServiceLogAttributeMapping(easyLogProperties); 44 | } 45 | 46 | @Bean 47 | @ConditionalOnMissingBean 48 | @ConditionalOnProperty(value = "spring.easy-log.scan-controller.enabled", havingValue = "true") 49 | public ControllerLogAttributeMapping controllerLogAttributeMapping(EasyLogProperties easyLogProperties) { 50 | return new ControllerLogAttributeMapping(easyLogProperties); 51 | } 52 | } 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/src/main/java/com/easycode8/easylog/autoconfigure/LogAttributeSourceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.autoconfigure; 2 | 3 | import com.easycode8.easylog.autoconfigure.source.OpenApi3LogAttributeSource; 4 | import com.easycode8.easylog.core.adapter.LogAttributeMappingAdapter; 5 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 6 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 7 | import com.easycode8.easylog.autoconfigure.source.SwaggerLogAttributeSource; 8 | import com.easycode8.easylog.core.aop.interceptor.AnnotationLogAttributeSource; 9 | import com.easycode8.easylog.core.cache.LogAttributeCache; 10 | import io.swagger.annotations.ApiOperation; 11 | import io.swagger.v3.oas.annotations.Operation; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.context.annotation.Bean; 16 | 17 | import java.util.List; 18 | 19 | abstract class LogAttributeSourceConfiguration { 20 | 21 | @ConditionalOnMissingBean(LogAttributeSource.class) 22 | @ConditionalOnClass(ApiOperation.class) 23 | @ConditionalOnProperty(value = "spring.easy-log.scan-swagger.enabled", havingValue = "true") 24 | static class SwaggerSource { 25 | 26 | @Bean 27 | public LogAttributeSource logAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties, List mappingAdapters) { 28 | return new SwaggerLogAttributeSource(logAttributeCache, new AnnotationLogAttributeSource(logAttributeCache, easyLogProperties, mappingAdapters), easyLogProperties); 29 | } 30 | 31 | } 32 | 33 | @ConditionalOnMissingBean(LogAttributeSource.class) 34 | @ConditionalOnClass(Operation.class) 35 | @ConditionalOnProperty(value = "spring.easy-log.scan-open-api.enabled", havingValue = "true") 36 | static class OpenApi3Source { 37 | 38 | @Bean 39 | public LogAttributeSource logAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties, List mappingAdapters) { 40 | return new OpenApi3LogAttributeSource(logAttributeCache, new AnnotationLogAttributeSource(logAttributeCache, easyLogProperties, mappingAdapters), easyLogProperties); 41 | } 42 | 43 | } 44 | 45 | @ConditionalOnMissingBean(LogAttributeSource.class) 46 | static class EasyLogSource { 47 | @Bean 48 | public LogAttributeSource logAttributeSource(LogAttributeCache logAttributeCache, EasyLogProperties easyLogProperties, List mappingAdapters) { 49 | return new AnnotationLogAttributeSource(logAttributeCache, easyLogProperties, mappingAdapters); 50 | } 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/src/main/java/com/easycode8/easylog/autoconfigure/source/OpenApi3LogAttributeSource.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.autoconfigure.source; 2 | 3 | 4 | import com.easycode8.easylog.core.annotation.EasyLog; 5 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 6 | import com.easycode8.easylog.core.aop.interceptor.AbstractCacheLogAttributeSource; 7 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 8 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 9 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 10 | import com.easycode8.easylog.core.cache.LogAttributeCache; 11 | 12 | import io.swagger.v3.oas.annotations.Operation; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.lang.reflect.Method; 17 | 18 | public class OpenApi3LogAttributeSource extends AbstractCacheLogAttributeSource { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(OpenApi3LogAttributeSource.class); 20 | final private LogAttributeSource logAttributeSource; 21 | 22 | 23 | public OpenApi3LogAttributeSource(LogAttributeCache logAttributeCache, LogAttributeSource logAttributeSource, EasyLogProperties easyLogProperties) { 24 | super(logAttributeCache, easyLogProperties); 25 | LOGGER.info("[easy-log]启动OpenApi3日志增强"); 26 | this.logAttributeSource = logAttributeSource; 27 | } 28 | 29 | @Override 30 | public LogAttribute doGetLogAttribute(Method method, Class targetClass) { 31 | Operation apiOperation = method.getAnnotation(Operation.class); 32 | if (apiOperation != null && method.getAnnotation(EasyLog.class) == null) { 33 | LogAttribute logAttribute = DefaultLogAttribute.builder() 34 | .title(apiOperation.summary()) 35 | //.template(apiOperation.notes()) //不适用note作为模板解析,因为可能会报错 36 | .async(easyLogProperties.getAsync()) 37 | .build(); 38 | return logAttribute; 39 | } 40 | 41 | 42 | return logAttributeSource.getLogAttribute(method, targetClass); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/src/main/java/com/easycode8/easylog/autoconfigure/source/SwaggerLogAttributeSource.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.autoconfigure.source; 2 | 3 | 4 | import com.easycode8.easylog.core.annotation.EasyLog; 5 | import com.easycode8.easylog.core.annotation.EasyLogProperties; 6 | import com.easycode8.easylog.core.aop.interceptor.AbstractCacheLogAttributeSource; 7 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 8 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 9 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 10 | import com.easycode8.easylog.core.cache.LogAttributeCache; 11 | import io.swagger.annotations.ApiOperation; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.lang.reflect.Method; 16 | 17 | public class SwaggerLogAttributeSource extends AbstractCacheLogAttributeSource { 18 | private static final Logger LOGGER = LoggerFactory.getLogger(SwaggerLogAttributeSource.class); 19 | final private LogAttributeSource logAttributeSource; 20 | 21 | 22 | public SwaggerLogAttributeSource(LogAttributeCache logAttributeCache, LogAttributeSource logAttributeSource, EasyLogProperties easyLogProperties) { 23 | super(logAttributeCache, easyLogProperties); 24 | LOGGER.info("[easy-log]启动Swagger日志增强"); 25 | this.logAttributeSource = logAttributeSource; 26 | } 27 | 28 | @Override 29 | public LogAttribute doGetLogAttribute(Method method, Class targetClass) { 30 | ApiOperation apiOperation = method.getAnnotation(ApiOperation.class); 31 | if (apiOperation != null && method.getAnnotation(EasyLog.class) == null) { 32 | LogAttribute logAttribute = DefaultLogAttribute.builder() 33 | .title(apiOperation.value()) 34 | //.template(apiOperation.notes()) //不适用note作为模板解析,因为可能会报错 35 | .async(easyLogProperties.getAsync()) 36 | .build(); 37 | return logAttribute; 38 | } 39 | 40 | 41 | return logAttributeSource.getLogAttribute(method, targetClass); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /easy-log-spring-boot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.easycode8.easylog.autoconfigure.EasyLogAutoConfiguration -------------------------------------------------------------------------------- /easy-log-trace/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-log 7 | io.github.easycode8 8 | ${revision} 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | easy-log-trace 14 | 15 | 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-configuration-processor 21 | true 22 | 23 | 24 | 25 | io.github.easycode8 26 | easy-log-core 27 | ${revision} 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | true 34 | 35 | 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-zipkin 40 | 2.2.6.RELEASE 41 | true 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/DefaultLogTracer.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace; 2 | 3 | 4 | import com.easycode8.easylog.core.LogInfo; 5 | import com.easycode8.easylog.core.trace.LogTracer; 6 | import com.easycode8.easylog.trace.filter.MDCConstants; 7 | import org.slf4j.MDC; 8 | 9 | /** 10 | * 默认的日志追踪--暂不实现 11 | */ 12 | public class DefaultLogTracer implements LogTracer { 13 | 14 | @Override 15 | public void init(LogInfo logInfo) { 16 | logInfo.setTraceId(MDC.get(MDCConstants.TRACE_ID)); 17 | } 18 | 19 | @Override 20 | public void start(LogInfo logInfo) { 21 | 22 | } 23 | 24 | @Override 25 | public void finish(LogInfo logInfo) { 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/LogTraceConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace; 2 | 3 | import brave.Tracer; 4 | import com.easycode8.easylog.core.trace.LogTracer; 5 | import com.easycode8.easylog.trace.filter.EasyLogTraceFilter; 6 | import com.easycode8.easylog.trace.filter.MDCConstants; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 11 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 13 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 14 | import org.springframework.context.annotation.Bean; 15 | 16 | public abstract class LogTraceConfiguration { 17 | private static final Logger LOGGER = LoggerFactory.getLogger(LogTraceConfiguration.class); 18 | 19 | @ConditionalOnMissingBean(LogTracer.class) 20 | @ConditionalOnProperty(value = "spring.easy-log.trace.default.enabled", havingValue = "true", matchIfMissing = true) 21 | public static class DefaultTraceConfig { 22 | 23 | @Bean 24 | public LogTracer logTracer() { 25 | LOGGER.info("[easy-log]日志链路启用默认记录:{}", MDCConstants.TRACE_ID); 26 | return new DefaultLogTracer(); 27 | } 28 | 29 | @Bean 30 | @ConditionalOnMissingBean(name = "easyLogTraceFilter") 31 | public FilterRegistrationBean easyLogTraceFilter() { 32 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 33 | registrationBean.setFilter(new EasyLogTraceFilter()); 34 | registrationBean.addUrlPatterns("/*"); 35 | registrationBean.setOrder(1); 36 | registrationBean.setName("easyLogTraceFilter[日志链路]"); 37 | return registrationBean; 38 | } 39 | } 40 | 41 | @ConditionalOnProperty( 42 | value = {"spring.sleuth.enabled", "spring.zipkin.enabled", "spring.easy-log.trace.zipkin.enabled"}, 43 | matchIfMissing = true 44 | ) 45 | @ConditionalOnClass(Tracer.class) 46 | public static class ZipKinTranceConfig { 47 | 48 | @Bean 49 | @ConditionalOnBean(Tracer.class) 50 | @ConditionalOnMissingBean 51 | public LogTracer logTracer(Tracer tracer) { 52 | LOGGER.info("[easy-log]日志链路启用zipkin记录"); 53 | return new ZipkinLogTracer(tracer); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/ZipkinLogTracer.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace; 2 | 3 | 4 | 5 | import brave.Span; 6 | import brave.Tracer; 7 | import com.easycode8.easylog.core.LogInfo; 8 | import com.easycode8.easylog.core.trace.LogTracer; 9 | import org.springframework.core.NamedThreadLocal; 10 | import org.springframework.util.StringUtils; 11 | 12 | import java.util.ArrayDeque; 13 | import java.util.Deque; 14 | 15 | /** 16 | * 基于zipkin的日志追踪器 17 | */ 18 | public class ZipkinLogTracer implements LogTracer { 19 | 20 | 21 | private static final ThreadLocal> SPAN_DEQUE = new NamedThreadLocal>("SPAN_DEQUE") { 22 | @Override 23 | protected Deque initialValue() { 24 | return new ArrayDeque<>(); 25 | } 26 | }; 27 | 28 | 29 | private final Tracer tracer; 30 | 31 | public ZipkinLogTracer(Tracer tracer) { 32 | this.tracer = tracer; 33 | } 34 | 35 | @Override 36 | public void init(LogInfo info) { 37 | 38 | Span span = this.tracer.nextSpan().kind(Span.Kind.SERVER).start(); 39 | info.setTraceId(span.context().traceIdString()); 40 | push(span); 41 | } 42 | 43 | @Override 44 | public void start(LogInfo info) { 45 | Span span = peek(); 46 | String spanName = info.getMethod(); 47 | if (!info.getTitle().equals(info.getMethod())) { 48 | spanName = spanName + "//" + info.getTitle(); 49 | } 50 | span.name(spanName); 51 | span.tag("param", info.getParams()); 52 | span.tag("title", info.getTitle()); 53 | 54 | 55 | if (StringUtils.hasText(info.getRequestUri())) { 56 | span.tag("uri", info.getRequestUri()); 57 | } 58 | 59 | if (StringUtils.hasText(info.getOperator())) { 60 | span.tag("operator", info.getOperator()); 61 | } 62 | 63 | if (StringUtils.hasText(info.getDescription())) { 64 | span.tag("description", info.getDescription()); 65 | } 66 | 67 | if (StringUtils.hasText(info.getException())) { 68 | span.tag("exception", info.getException()); 69 | } 70 | 71 | if (StringUtils.hasText(info.getTags())) { 72 | span.tag("tags", info.getTags()); 73 | } 74 | 75 | if (StringUtils.hasText(info.getIp())) { 76 | span.tag("ip", info.getIp()); 77 | } 78 | 79 | if (StringUtils.hasText(info.getException())) { 80 | span.tag("exception", info.getException()); 81 | } 82 | 83 | 84 | } 85 | 86 | @Override 87 | public void finish(LogInfo info) { 88 | Span span = peek(); 89 | span.tag("timeout", info.getTimeout().toString()); 90 | if (StringUtils.hasText(info.getDataSnapshot())) { 91 | span.tag("dataSnapshot", info.getDataSnapshot()); 92 | } 93 | span.finish(); 94 | poll(); 95 | 96 | } 97 | 98 | /** 99 | * 入栈 100 | */ 101 | public static void push(Span span) { 102 | SPAN_DEQUE.get().push(span); 103 | } 104 | 105 | /** 106 | * 获取栈顶元素 107 | * 108 | * @return 109 | */ 110 | public static Span peek() { 111 | return SPAN_DEQUE.get().peek(); 112 | 113 | } 114 | 115 | 116 | /** 117 | * 移除栈顶数据源,如果是最后元素,则清空线程数据 118 | */ 119 | public static void poll() { 120 | Deque deque = SPAN_DEQUE.get(); 121 | deque.poll(); 122 | if (deque.isEmpty()) { 123 | SPAN_DEQUE.remove(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/autoconfigure/EasyLogTraceAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace.autoconfigure; 2 | 3 | import com.easycode8.easylog.core.trace.LogTracer; 4 | import com.easycode8.easylog.trace.LogTraceConfiguration; 5 | import org.springframework.boot.autoconfigure.AutoConfigureAfter; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | 12 | @Configuration 13 | @AutoConfigureAfter(name = "org.springframework.cloud.sleuth.autoconfig.TraceAutoConfiguration") 14 | //@AutoConfigureBefore({EasyLogAutoConfiguration.class}) // 日志追踪者需要在日志核心框架执行初始化 挪到starter after等效 15 | @ConditionalOnProperty(value = {"spring.easy-log.enabled", "spring.easy-log.trace.enabled"}, havingValue = "true", matchIfMissing = true) 16 | public class EasyLogTraceAutoConfiguration { 17 | 18 | 19 | /**按优先级选择链路追踪实现 zipkin>local*/ 20 | @Configuration 21 | @ConditionalOnMissingBean(LogTracer.class) 22 | @Import({LogTraceConfiguration.ZipKinTranceConfig.class, LogTraceConfiguration.DefaultTraceConfig.class}) 23 | static class ChooseLogTraceConfiguration { 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/autoconfigure/EasyLogTraceEnvironmentPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace.autoconfigure; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.env.EnvironmentPostProcessor; 5 | import org.springframework.core.env.ConfigurableEnvironment; 6 | import org.springframework.core.env.MapPropertySource; 7 | import org.springframework.core.env.MutablePropertySources; 8 | import org.springframework.core.env.PropertySource; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * 修改日志输出格式增加traceId记录, 如果sleuth存在且开启则优先使用sleuth作为链路追踪 15 | * 16 | * @see org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor 17 | */ 18 | public class EasyLogTraceEnvironmentPostProcessor implements EnvironmentPostProcessor { 19 | 20 | private static final String PROPERTY_SOURCE_NAME = "easylog- defaultProperties"; 21 | 22 | @Override 23 | public void postProcessEnvironment(ConfigurableEnvironment environment, 24 | SpringApplication application) { 25 | 26 | Map map = new HashMap(); 27 | // This doesn't work with all logging systems but it's a useful default so you see 28 | // traces in logs without having to configure it. 29 | if (Boolean.parseBoolean(environment.getProperty("spring.easy-log.trace.enabled", "true")) && !this.existSleuthTrace(environment)) { 30 | map.put("logging.pattern.level", "%5p [${spring.application.name:},%X{X-Trace-Id:-}]"); 31 | } 32 | addOrReplace(environment.getPropertySources(), map); 33 | } 34 | 35 | private void addOrReplace(MutablePropertySources propertySources, 36 | Map map) { 37 | MapPropertySource target = null; 38 | if (propertySources.contains(PROPERTY_SOURCE_NAME)) { 39 | PropertySource source = propertySources.get(PROPERTY_SOURCE_NAME); 40 | if (source instanceof MapPropertySource) { 41 | target = (MapPropertySource) source; 42 | for (String key : map.keySet()) { 43 | if (!target.containsProperty(key)) { 44 | target.getSource().put(key, map.get(key)); 45 | } 46 | } 47 | } 48 | } 49 | if (target == null) { 50 | target = new MapPropertySource(PROPERTY_SOURCE_NAME, map); 51 | } 52 | if (!propertySources.contains(PROPERTY_SOURCE_NAME)) { 53 | propertySources.addLast(target); 54 | } 55 | } 56 | 57 | /** 58 | * sleuth的链路追踪是否生效 59 | * 60 | * @return 61 | */ 62 | private boolean existSleuthTrace(ConfigurableEnvironment environment) { 63 | boolean flag = false; 64 | // 判断另一个类是否存在 65 | try { 66 | Class.forName("org.springframework.cloud.sleuth.autoconfig.TraceEnvironmentPostProcessor"); 67 | flag = true; 68 | } catch (ClassNotFoundException e) { 69 | // 另一个类不存在,执行后续处理逻辑 70 | } 71 | // 如果TraceEnvironmentPostProcessor 存在且spring.sleuth.enabled 开着, 则使用 sleuth的日志格式 72 | return flag && Boolean.parseBoolean(environment.getProperty("spring.sleuth.enabled", "true")); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/filter/EasyLogTraceFilter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace.filter; 2 | 3 | 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.slf4j.MDC; 8 | import org.springframework.util.StringUtils; 9 | 10 | import javax.servlet.*; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.util.UUID; 15 | 16 | /** 17 | * 记录TRACE_ID 18 | */ 19 | public class EasyLogTraceFilter implements Filter { 20 | private static final Logger LOGGER = LoggerFactory.getLogger(EasyLogTraceFilter.class); 21 | 22 | @Override 23 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 24 | HttpServletRequest request = (HttpServletRequest)servletRequest; 25 | HttpServletResponse response = (HttpServletResponse) servletResponse; 26 | try { 27 | mdc(request, response); 28 | } catch (Exception e) { 29 | LOGGER.warn("record mdc logger error:{}", e.getMessage()); 30 | } 31 | 32 | try { 33 | filterChain.doFilter(servletRequest, servletResponse); 34 | } finally { 35 | MDC.clear(); 36 | } 37 | 38 | } 39 | 40 | private void mdc(HttpServletRequest request, HttpServletResponse response) { 41 | String traceId = StringUtils.isEmpty(request.getHeader(MDCConstants.TRACE_ID))? UUID.randomUUID().toString().replace("-","") :request.getHeader(MDCConstants.TRACE_ID); 42 | MDC.put(MDCConstants.TRACE_ID, traceId); 43 | // 设置响应traceId 44 | if (!StringUtils.isEmpty(traceId)) { 45 | response.setHeader(MDCConstants.TRACE_ID, traceId); 46 | } 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /easy-log-trace/src/main/java/com/easycode8/easylog/trace/filter/MDCConstants.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.trace.filter; 2 | 3 | public class MDCConstants { 4 | 5 | public static final String TRACE_ID = "X-Trace-Id"; 6 | public static final String SPAN_ID = "X-Span-Id"; 7 | 8 | private MDCConstants() { 9 | } 10 | } -------------------------------------------------------------------------------- /easy-log-trace/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.easycode8.easylog.trace.autoconfigure.EasyLogTraceAutoConfiguration 3 | 4 | 5 | # Environment Post Processor 6 | org.springframework.boot.env.EnvironmentPostProcessor=\ 7 | com.easycode8.easylog.trace.autoconfigure.EasyLogTraceEnvironmentPostProcessor 8 | -------------------------------------------------------------------------------- /easy-log-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | easy-log 7 | io.github.easycode8 8 | ${revision} 9 | ../pom.xml 10 | 11 | 4.0.0 12 | 13 | easy-log-web 14 | 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-configuration-processor 20 | true 21 | 22 | 23 | 24 | io.github.easycode8 25 | easy-log-core 26 | ${revision} 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-web 32 | true 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-websocket 40 | true 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/autoconfigure/EasyLogWebAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.autoconfigure; 2 | 3 | import com.easycode8.easylog.core.aop.interceptor.LogAttributeSource; 4 | import com.easycode8.easylog.web.controller.EasyLogController; 5 | import com.easycode8.easylog.web.filter.SecurityBasicAuthFilter; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | @ConditionalOnProperty(value = "spring.easy-log.web.enabled", havingValue = "true", matchIfMissing = true) 14 | @EnableConfigurationProperties(EasyLogWebProperties.class) 15 | public class EasyLogWebAutoConfiguration { 16 | 17 | @Bean 18 | @ConditionalOnBean(LogAttributeSource.class) 19 | public EasyLogController easyLogController() { 20 | return new EasyLogController(); 21 | } 22 | 23 | @Bean 24 | public SecurityBasicAuthFilter securityBasicAuthFilter(EasyLogWebProperties easyLogWebProperties) { 25 | return new SecurityBasicAuthFilter(easyLogWebProperties.getEnableBasicAuth(), easyLogWebProperties.getUsername(), easyLogWebProperties.getPassword()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/autoconfigure/EasyLogWebProperties.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.autoconfigure; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties(prefix = "spring.easy-log.web") 6 | public class EasyLogWebProperties { 7 | /**账号*/ 8 | private String username = "admin"; 9 | /**密码*/ 10 | private String password = "admin123"; 11 | /**是否开启web登入认证*/ 12 | private Boolean enableBasicAuth = true; 13 | 14 | public String getUsername() { 15 | return username; 16 | } 17 | 18 | public void setUsername(String username) { 19 | this.username = username; 20 | } 21 | 22 | public String getPassword() { 23 | return password; 24 | } 25 | 26 | public void setPassword(String password) { 27 | this.password = password; 28 | } 29 | 30 | public Boolean getEnableBasicAuth() { 31 | return enableBasicAuth; 32 | } 33 | 34 | public void setEnableBasicAuth(Boolean enableBasicAuth) { 35 | this.enableBasicAuth = enableBasicAuth; 36 | } 37 | } -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/autoconfigure/EasyLogWebSocketAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.autoconfigure; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import com.easycode8.easylog.web.filter.LogFilter; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.messaging.simp.SimpMessagingTemplate; 13 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 14 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 15 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 16 | 17 | @Configuration 18 | @ConditionalOnProperty(value = "spring.easy-log.web.websocket.enabled", havingValue = "true", matchIfMissing = true) 19 | @ConditionalOnClass(StompEndpointRegistry.class) 20 | @EnableWebSocketMessageBroker 21 | public class EasyLogWebSocketAutoConfiguration { 22 | private static final Logger LOGGER = LoggerFactory.getLogger(EasyLogWebSocketAutoConfiguration.class); 23 | 24 | @Bean 25 | @ConditionalOnMissingBean 26 | public WebSocketMessageBrokerConfigurer brokerConfigurer() { 27 | 28 | return new WebSocketMessageBrokerConfigurer() { 29 | /** 30 | * stomp 协议,一种格式比较简单且被广泛支持的通信协议,spring4提供了以stomp协议为基础的websocket通信实现。 31 | * spring 的websocket实现,实际上是一个简易版的消息队列(而且是主题-订阅模式的) 32 | * @param registry 33 | */ 34 | @Override 35 | public void registerStompEndpoints(StompEndpointRegistry registry) { 36 | // "/websocket",客户端需要注册这个端点进行链接, 37 | // .withSockJS()的作用是声明我们想要使用 SockJS 功能,如果WebSocket不可用的话,会使用 SockJS。 38 | registry.addEndpoint("/websocket-stomp") 39 | .setAllowedOrigins("*") 40 | .withSockJS(); 41 | 42 | 43 | } 44 | }; 45 | } 46 | 47 | @Bean 48 | @ConditionalOnClass(LoggerContext.class) 49 | public LogFilter easyLogOnlineLogFilter(SimpMessagingTemplate simpMessagingTemplate) { 50 | LOGGER.info("[easy-log]启动web在线实时日志(websocket)"); 51 | return new LogFilter(simpMessagingTemplate); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/controller/EasyLogController.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.controller; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.easycode8.easylog.core.aop.interceptor.AbstractCacheLogAttributeSource; 6 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 7 | import com.easycode8.easylog.core.aop.interceptor.LogAttribute; 8 | import com.easycode8.easylog.core.handler.LogDataHandler; 9 | import com.easycode8.easylog.web.model.PageInfo; 10 | import com.easycode8.easylog.web.model.param.LogAttributeParam; 11 | import com.easycode8.easylog.web.model.vo.LogAttributeVO; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.context.ApplicationContext; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.util.CollectionUtils; 16 | import org.springframework.util.StringUtils; 17 | import org.springframework.web.bind.annotation.GetMapping; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | 22 | import java.util.*; 23 | import java.util.stream.Collectors; 24 | 25 | 26 | @RequestMapping("/easy-log") 27 | public class EasyLogController { 28 | 29 | @Autowired 30 | private AbstractCacheLogAttributeSource logAttributeSource; 31 | 32 | 33 | @Autowired 34 | private ApplicationContext applicationContext; 35 | 36 | 37 | @GetMapping("/list") 38 | public ResponseEntity> list(LogAttributeParam param, PageInfo> pageInfo) { 39 | Map result = new HashMap<>(); 40 | if (CollectionUtils.isEmpty(logAttributeSource.getCacheMap())) { 41 | return ResponseEntity.ok(result); 42 | } 43 | List data = new ArrayList<>(); 44 | for (Map.Entry item : logAttributeSource.getCacheMap().entrySet()) { 45 | LogAttributeVO attribute = JSON.parseObject(JSON.toJSONString(item.getValue()), LogAttributeVO.class); 46 | attribute.setMethod(item.getKey()); 47 | attribute.setTemplate(StringUtils.isEmpty(attribute.template()) ? "": attribute.template()); 48 | attribute.setHandler(StringUtils.isEmpty(attribute.getHandler()) ? "easyLogDataHandler": attribute.getHandler()); 49 | data.add(attribute); 50 | } 51 | 52 | List filterList = data.stream() 53 | .filter(item -> item.getTitle().contains(param.getTitle())) 54 | .filter(item -> item.getTemplate().contains(param.getTemplate())) 55 | .filter(item -> param.getActive() == null || (param.getActive() != null && item.active().equals(param.getActive()))) 56 | 57 | .sorted(Comparator.comparing(LogAttributeVO::getMethod)).collect(Collectors.toList()); 58 | pageInfo.doPage(filterList,(item) -> item); 59 | result.put("data", pageInfo); 60 | return ResponseEntity.ok(result); 61 | } 62 | 63 | @PostMapping("update") 64 | public ResponseEntity> update(@RequestBody LogAttributeParam param) { 65 | Map result = new HashMap<>(); 66 | if (logAttributeSource.getCacheMap().containsKey(param.getMethod())) { 67 | DefaultLogAttribute logAttribute = (DefaultLogAttribute) logAttributeSource.getCacheMap().get(param.getMethod()); 68 | logAttribute.setActive(param.getActive()); 69 | logAttribute.setAsync(param.getAsync()); 70 | logAttribute.setHandler(param.getHandler()); 71 | logAttributeSource.updateCache(param.getMethod(), logAttribute); 72 | } 73 | result.put("message", "动态修改配置成功!"); 74 | return ResponseEntity.ok(result); 75 | } 76 | @GetMapping("param") 77 | public ResponseEntity> queryParam() { 78 | Map result = new HashMap<>(); 79 | Map dict = new HashMap<>(); 80 | String[] beanNames = applicationContext.getBeanNamesForType(LogDataHandler.class); 81 | dict.put("handler", Arrays.asList(beanNames)); 82 | dict.put("enabledWebSocket", applicationContext.containsBean("easyLogOnlineLogFilter")); 83 | result.put("data", dict); 84 | return ResponseEntity.ok(result); 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/filter/LogFilter.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.filter; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | import ch.qos.logback.core.ConsoleAppender; 6 | import ch.qos.logback.core.encoder.Encoder; 7 | import ch.qos.logback.core.filter.Filter; 8 | import ch.qos.logback.core.spi.FilterReply; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.messaging.simp.SimpMessagingTemplate; 12 | 13 | import javax.annotation.PostConstruct; 14 | 15 | 16 | public class LogFilter extends Filter { 17 | private static final Logger LOGGER = LoggerFactory.getLogger(LogFilter.class); 18 | 19 | private Encoder encoder; 20 | 21 | 22 | private final SimpMessagingTemplate messagingTemplate; 23 | 24 | public LogFilter(SimpMessagingTemplate messagingTemplate) { 25 | this.messagingTemplate = messagingTemplate; 26 | } 27 | 28 | 29 | @Override 30 | public FilterReply decide(ILoggingEvent event) { 31 | messagingTemplate.convertAndSend("/topic/logback", new String(encoder.encode(event))); 32 | return FilterReply.NEUTRAL; 33 | } 34 | 35 | @PostConstruct 36 | public void configureLogback() { 37 | // 获取LoggerContext 38 | LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); 39 | 40 | // 查找ConsoleAppender 41 | ConsoleAppender consoleAppender = findConsoleAppender(context); 42 | if (consoleAppender != null) { 43 | // 创建并添加自定义过滤器 44 | Filter customFilter = this; 45 | consoleAppender.addFilter(customFilter); 46 | encoder = (Encoder) consoleAppender.getEncoder(); 47 | } 48 | } 49 | 50 | 51 | private ConsoleAppender findConsoleAppender(LoggerContext context) { 52 | // 在LoggerContext中查找ConsoleAppender 53 | // 这里假设ConsoleAppender的名称为"CONSOLE"或"console" 54 | ConsoleAppender appender = (ConsoleAppender) context.getLogger(Logger.ROOT_LOGGER_NAME).getAppender("CONSOLE"); 55 | if (appender == null) { 56 | appender = (ConsoleAppender) context.getLogger(Logger.ROOT_LOGGER_NAME).getAppender("console"); 57 | } 58 | if (appender == null) { 59 | throw new IllegalStateException("websocket在线日志未发现logback console appender配置"); 60 | } 61 | return appender; 62 | } 63 | } -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/filter/SecurityBasicAuthFilter.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.easycode8.easylog.web.filter; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import javax.servlet.*; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | import java.util.Base64; 13 | import java.util.Enumeration; 14 | 15 | /** 16 | * 页面鉴权 17 | */ 18 | public class SecurityBasicAuthFilter implements Filter { 19 | private static final Logger LOGGER = LoggerFactory.getLogger(SecurityBasicAuthFilter.class); 20 | private static final String AUTH_SESSION = "DynamicDataSourceBasicAuthSession"; 21 | /*** 22 | * 是否开启basic验证,默认不开启 23 | */ 24 | private boolean enableBasicAuth = false; 25 | 26 | private String userName; 27 | 28 | private String password; 29 | 30 | @Override 31 | public void init(FilterConfig filterConfig) throws ServletException { 32 | Enumeration enumeration = filterConfig.getInitParameterNames(); 33 | //SpringMVC环境中,由此init方法初始化此Filter,SpringBoot环境中则不同 34 | if (enumeration.hasMoreElements()) { 35 | setEnableBasicAuth(Boolean.valueOf(filterConfig.getInitParameter("enableBasicAuth"))); 36 | setUserName(filterConfig.getInitParameter("userName")); 37 | setPassword(filterConfig.getInitParameter("password")); 38 | } 39 | } 40 | 41 | @Override 42 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 43 | HttpServletRequest servletRequest = (HttpServletRequest) request; 44 | HttpServletResponse httpServletResponse = (HttpServletResponse) response; 45 | //针对dynamic-datasource资源请求过滤 46 | if (enableBasicAuth) { 47 | if (servletRequest.getRequestURI().contains("easy-log-ui.html")) { 48 | //判断Session中是否存在 49 | Object swaggerSessionValue = servletRequest.getSession().getAttribute(AUTH_SESSION); 50 | if (swaggerSessionValue != null) { 51 | chain.doFilter(request, response); 52 | } else { 53 | //匹配到,判断auth 54 | //获取请求头Authorization 55 | String auth = servletRequest.getHeader("Authorization"); 56 | if (auth == null || "".equals(auth)) { 57 | writeForbiddenCode(httpServletResponse); 58 | return; 59 | } 60 | String userAndPass = decodeBase64(auth.substring(6)); 61 | String[] upArr = userAndPass.split(":"); 62 | if (upArr.length != 2) { 63 | writeForbiddenCode(httpServletResponse); 64 | } else { 65 | String iptUser = upArr[0]; 66 | String iptPass = upArr[1]; 67 | //匹配服务端用户名及密码 68 | if (iptUser.equals(userName) && iptPass.equals(password)) { 69 | servletRequest.getSession().setAttribute(AUTH_SESSION, userName); 70 | chain.doFilter(request, response); 71 | } else { 72 | writeForbiddenCode(httpServletResponse); 73 | return; 74 | } 75 | } 76 | } 77 | } else { 78 | chain.doFilter(request, response); 79 | } 80 | } else { 81 | chain.doFilter(request, response); 82 | } 83 | } 84 | 85 | @Override 86 | public void destroy() { 87 | 88 | } 89 | 90 | private void writeForbiddenCode(HttpServletResponse httpServletResponse) throws IOException { 91 | httpServletResponse.setStatus(401); 92 | httpServletResponse.setHeader("WWW-Authenticate", "Basic realm=\"input Dynamic DataSource Basic userName & password \""); 93 | httpServletResponse.getWriter().write("You do not have permission to access this resource"); 94 | } 95 | 96 | public SecurityBasicAuthFilter(boolean enableBasicAuth, String userName, String password) { 97 | this.enableBasicAuth = enableBasicAuth; 98 | this.userName = userName; 99 | this.password = password; 100 | } 101 | 102 | public SecurityBasicAuthFilter(boolean enableBasicAuth) { 103 | this.enableBasicAuth = enableBasicAuth; 104 | } 105 | 106 | public SecurityBasicAuthFilter() { 107 | } 108 | 109 | public boolean isEnableBasicAuth() { 110 | return enableBasicAuth; 111 | } 112 | 113 | public void setEnableBasicAuth(boolean enableBasicAuth) { 114 | this.enableBasicAuth = enableBasicAuth; 115 | } 116 | 117 | public String getUserName() { 118 | return userName; 119 | } 120 | 121 | public void setUserName(String userName) { 122 | this.userName = userName; 123 | } 124 | 125 | public String getPassword() { 126 | return password; 127 | } 128 | 129 | public void setPassword(String password) { 130 | this.password = password; 131 | } 132 | 133 | protected String decodeBase64(String source) { 134 | String decodeStr = null; 135 | if (source != null) { 136 | try { 137 | 138 | byte[] bytes = Base64.getDecoder().decode(source); 139 | decodeStr = new String(bytes); 140 | } catch (Exception e) { 141 | LOGGER.error(e.getMessage(), e); 142 | } 143 | } 144 | return decodeStr; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/model/PageInfo.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.model; 2 | 3 | import org.springframework.util.CollectionUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class PageInfo { 9 | /** 页码*/ 10 | private int pageIndex; 11 | /** 分页大小*/ 12 | private int pageSize; 13 | /** 总数*/ 14 | private long total; 15 | /** 分页结果数据*/ 16 | private List rows; 17 | 18 | public PageInfo() { 19 | if(null == rows ){ 20 | this.rows = new ArrayList<>(); 21 | } 22 | } 23 | 24 | 25 | public int getPageSize() { 26 | return pageSize; 27 | } 28 | 29 | public void setPageSize(int pageSize) { 30 | this.pageSize = pageSize; 31 | } 32 | 33 | public long getTotal() { 34 | return total; 35 | } 36 | 37 | public void setTotal(long total) { 38 | this.total = total; 39 | } 40 | 41 | public List getRows() { 42 | return rows; 43 | } 44 | 45 | public void setRows(List rows) { 46 | this.rows = rows; 47 | } 48 | 49 | public int getPageIndex() { 50 | return pageIndex; 51 | } 52 | 53 | public void setPageIndex(int pageIndex) { 54 | this.pageIndex = pageIndex; 55 | } 56 | 57 | 58 | /** 59 | * 执行计算内存分页及结果设值 60 | */ 61 | public PageInfo doPage(List allowedList, Callback callback) { 62 | if (CollectionUtils.isEmpty(allowedList)) { 63 | return new PageInfo<>(); 64 | } 65 | this.setTotal(allowedList.size()); 66 | int fromIndex = this.getPageIndex() < 1 ? 0 : (this.getPageIndex() - 1) * this.getPageSize(); 67 | int toIndex = fromIndex + this.getPageSize(); 68 | toIndex = toIndex > this.getTotal() ? (int) this.getTotal() : toIndex; 69 | List pageList = allowedList.subList(fromIndex, toIndex); 70 | this.setRows(callback.buildRowsData(pageList)); 71 | if (null == this.getRows()) { 72 | this.setRows(new ArrayList<>()); 73 | } 74 | return this; 75 | } 76 | public interface Callback { 77 | /** 78 | * 回调设值选择内存分页的具体业务数据 79 | * @param chooseList 80 | * @return 81 | */ 82 | List buildRowsData(List chooseList); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/model/param/LogAttributeParam.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.model.param; 2 | 3 | import java.io.Serializable; 4 | 5 | public class LogAttributeParam implements Serializable { 6 | /** 日志标题*/ 7 | private String title; 8 | /** 日志处理器*/ 9 | private String handler; 10 | /** 日志spel模板*/ 11 | private String template; 12 | /** 日志操作人*/ 13 | private String operator; 14 | /** 是否异步处理日志*/ 15 | private Boolean async; 16 | /**是否活跃:非活跃的忽略增强处理*/ 17 | Boolean active = true; 18 | /**增强的方法*/ 19 | String method; 20 | 21 | 22 | public String getTitle() { 23 | return title; 24 | } 25 | 26 | public void setTitle(String title) { 27 | this.title = title; 28 | } 29 | 30 | public String getHandler() { 31 | return handler; 32 | } 33 | 34 | public void setHandler(String handler) { 35 | this.handler = handler; 36 | } 37 | 38 | public String getTemplate() { 39 | return template; 40 | } 41 | 42 | public void setTemplate(String template) { 43 | this.template = template; 44 | } 45 | 46 | public String getOperator() { 47 | return operator; 48 | } 49 | 50 | public void setOperator(String operator) { 51 | this.operator = operator; 52 | } 53 | 54 | public Boolean getAsync() { 55 | return async; 56 | } 57 | 58 | public void setAsync(Boolean async) { 59 | this.async = async; 60 | } 61 | 62 | public Boolean getActive() { 63 | return active; 64 | } 65 | 66 | public void setActive(Boolean active) { 67 | this.active = active; 68 | } 69 | 70 | public String getMethod() { 71 | return method; 72 | } 73 | 74 | public void setMethod(String method) { 75 | this.method = method; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /easy-log-web/src/main/java/com/easycode8/easylog/web/model/vo/LogAttributeVO.java: -------------------------------------------------------------------------------- 1 | package com.easycode8.easylog.web.model.vo; 2 | 3 | import com.easycode8.easylog.core.aop.interceptor.DefaultLogAttribute; 4 | 5 | public class LogAttributeVO extends DefaultLogAttribute { 6 | String method; 7 | 8 | public String getMethod() { 9 | return method; 10 | } 11 | 12 | public void setMethod(String method) { 13 | this.method = method; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/axios/axios.min.js: -------------------------------------------------------------------------------- 1 | /* axios v0.12.0 | (c) 2016 by Matt Zabriskie */ 2 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(n){if(r[n])return r[n].exports;var o=r[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var r={};return t.m=e,t.c=r,t.p="",t(0)}([function(e,t,r){e.exports=r(1)},function(e,t,r){"use strict";function n(e){this.defaults=i.merge({},e),this.interceptors={request:new u,response:new u}}var o=r(2),i=r(3),s=r(5),u=r(14),a=r(15),c=r(16),f=r(17),p=r(9);n.prototype.request=function(e){"string"==typeof e&&(e=i.merge({url:arguments[0]},arguments[1])),e=i.merge(o,this.defaults,{method:"get"},e),e.baseURL&&!a(e.url)&&(e.url=c(e.baseURL,e.url)),e.withCredentials=e.withCredentials||this.defaults.withCredentials,e.data=p(e.data,e.headers,e.transformRequest),e.headers=i.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),i.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=[s,void 0],r=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)r=r.then(t.shift(),t.shift());return r};var d=new n(o),l=e.exports=f(n.prototype.request,d);l.request=f(n.prototype.request,d),l.Axios=n,l.defaults=d.defaults,l.interceptors=d.interceptors,l.create=function(e){return new n(e)},l.all=function(e){return Promise.all(e)},l.spread=r(18),i.forEach(["delete","get","head"],function(e){n.prototype[e]=function(t,r){return this.request(i.merge(r||{},{method:e,url:t}))},l[e]=f(n.prototype[e],d)}),i.forEach(["post","put","patch"],function(e){n.prototype[e]=function(t,r,n){return this.request(i.merge(n||{},{method:e,url:t,data:r}))},l[e]=f(n.prototype[e],d)})},function(e,t,r){"use strict";function n(e,t){!o.isUndefined(e)&&o.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}var o=r(3),i=r(4),s=/^\)\]\}',?\n/,u={"Content-Type":"application/x-www-form-urlencoded"};e.exports={transformRequest:[function(e,t){return i(t,"Content-Type"),o.isFormData(e)||o.isArrayBuffer(e)||o.isStream(e)||o.isFile(e)||o.isBlob(e)?e:o.isArrayBufferView(e)?e.buffer:o.isURLSearchParams(e)?(n(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):o.isObject(e)?(n(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e){e=e.replace(s,"");try{e=JSON.parse(e)}catch(t){}}return e}],headers:{common:{Accept:"application/json, text/plain, */*"},patch:o.merge(u),post:o.merge(u),put:o.merge(u)},timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&300>e}}},function(e,t){"use strict";function r(e){return"[object Array]"===x.call(e)}function n(e){return"[object ArrayBuffer]"===x.call(e)}function o(e){return"undefined"!=typeof FormData&&e instanceof FormData}function i(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function s(e){return"string"==typeof e}function u(e){return"number"==typeof e}function a(e){return"undefined"==typeof e}function c(e){return null!==e&&"object"==typeof e}function f(e){return"[object Date]"===x.call(e)}function p(e){return"[object File]"===x.call(e)}function d(e){return"[object Blob]"===x.call(e)}function l(e){return"[object Function]"===x.call(e)}function h(e){return c(e)&&l(e.pipe)}function m(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function y(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function w(){return"undefined"!=typeof window&&"undefined"!=typeof document&&"function"==typeof document.createElement}function g(e,t){if(null!==e&&"undefined"!=typeof e)if("object"==typeof e||r(e)||(e=[e]),r(e))for(var n=0,o=e.length;o>n;n++)t.call(null,e[n],n,e);else for(var i in e)e.hasOwnProperty(i)&&t.call(null,e[i],i,e)}function v(){function e(e,r){"object"==typeof t[r]&&"object"==typeof e?t[r]=v(t[r],e):t[r]=e}for(var t={},r=0,n=arguments.length;n>r;r++)g(arguments[r],e);return t}var x=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:n,isFormData:o,isArrayBufferView:i,isString:s,isNumber:u,isObject:c,isUndefined:a,isDate:f,isFile:p,isBlob:d,isFunction:l,isStream:h,isURLSearchParams:m,isStandardBrowserEnv:w,forEach:g,merge:v,trim:y}},function(e,t,r){"use strict";var n=r(3);e.exports=function(e,t){n.forEach(e,function(r,n){n!==t&&n.toUpperCase()===t.toUpperCase()&&(e[t]=r,delete e[n])})}},function(e,t,r){"use strict";e.exports=function(e){return new Promise(function(t,n){try{var o;"function"==typeof e.adapter?o=e.adapter:"undefined"!=typeof XMLHttpRequest?o=r(6):"undefined"!=typeof process&&(o=r(6)),"function"==typeof o&&o(t,n,e)}catch(i){n(i)}})}},function(e,t,r){"use strict";var n=r(3),o=r(7),i=r(8),s=r(9),u=r(10),a="undefined"!=typeof window&&window.btoa||r(11),c=r(12);e.exports=function(e,t,f){var p=f.data,d=f.headers;n.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||u(f.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),f.auth){var y=f.auth.username||"",w=f.auth.password||"";d.Authorization="Basic "+a(y+":"+w)}if(l.open(f.method.toUpperCase(),o(f.url,f.params,f.paramsSerializer),!0),l.timeout=f.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&0!==l.status){var r="getAllResponseHeaders"in l?i(l.getAllResponseHeaders()):null,n=f.responseType&&"text"!==f.responseType?l.response:l.responseText,o={data:s(n,r,f.transformResponse),status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:r,config:f,request:l};c(e,t,o),l=null}},l.onerror=function(){t(new Error("Network Error")),l=null},l.ontimeout=function(){var e=new Error("timeout of "+f.timeout+"ms exceeded");e.timeout=f.timeout,e.code="ECONNABORTED",t(e),l=null},n.isStandardBrowserEnv()){var g=r(13),v=f.withCredentials||u(f.url)?g.read(f.xsrfCookieName):void 0;v&&(d[f.xsrfHeaderName]=v)}if("setRequestHeader"in l&&n.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),f.withCredentials&&(l.withCredentials=!0),f.responseType)try{l.responseType=f.responseType}catch(x){if("json"!==l.responseType)throw x}f.progress&&("post"===f.method||"put"===f.method?l.upload.addEventListener("progress",f.progress):"get"===f.method&&l.addEventListener("progress",f.progress)),void 0===p&&(p=null),l.send(p)}},function(e,t,r){"use strict";function n(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=r(3);e.exports=function(e,t,r){if(!t)return e;var i;if(r)i=r(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)&&(t+="[]"),o.isArray(e)||(e=[e]),o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(n(t)+"="+n(e))}))}),i=s.join("&")}return i&&(e+=(-1===e.indexOf("?")?"?":"&")+i),e}},function(e,t,r){"use strict";var n=r(3);e.exports=function(e){var t,r,o,i={};return e?(n.forEach(e.split("\n"),function(e){o=e.indexOf(":"),t=n.trim(e.substr(0,o)).toLowerCase(),r=n.trim(e.substr(o+1)),t&&(i[t]=i[t]?i[t]+", "+r:r)}),i):i}},function(e,t,r){"use strict";var n=r(3);e.exports=function(e,t,r){return n.forEach(r,function(r){e=r(e,t)}),e}},function(e,t,r){"use strict";var n=r(3);e.exports=n.isStandardBrowserEnv()?function(){function e(e){var t=e;return r&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,r=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(r){var o=n.isString(r)?e(r):r;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function r(){this.message="String contains an invalid character"}function n(e){for(var t,n,i=String(e),s="",u=0,a=o;i.charAt(0|u)||(a="=",u%1);s+=a.charAt(63&t>>8-u%1*8)){if(n=i.charCodeAt(u+=.75),n>255)throw new r;t=t<<8|n}return s}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.prototype=new Error,r.prototype.code=5,r.prototype.name="InvalidCharacterError",e.exports=n},function(e,t){"use strict";e.exports=function(e,t,r){var n=r.config.validateStatus;r.status&&n&&!n(r.status)?t(r):e(r)}},function(e,t,r){"use strict";var n=r(3);e.exports=n.isStandardBrowserEnv()?function(){return{write:function(e,t,r,o,i,s){var u=[];u.push(e+"="+encodeURIComponent(t)),n.isNumber(r)&&u.push("expires="+new Date(r).toGMTString()),n.isString(o)&&u.push("path="+o),n.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,r){"use strict";function n(){this.handlers=[]}var o=r(3);n.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},n.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},n.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=n},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,"")}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var r=new Array(arguments.length),n=0;n') 61 | .attr("value", maybePrefix(resource.location, relativeLocation)) 62 | .text(resource.name + " (" + resource.location + ")"); 63 | $urlDropdown.append(option); 64 | }); 65 | $urlDropdown.change(); 66 | }); 67 | } 68 | 69 | function onSuccess(data) { 70 | window.swaggerUi = new SwaggerUi({ 71 | dom_id: "swagger-ui-container", 72 | validatorUrl: data.validatorUrl, 73 | supportedSubmitMethods: data.supportedSubmitMethods || ['get', 'post', 'put', 'delete', 'patch'], 74 | docExpansion: data.docExpansion || 'none', 75 | jsonEditor: JSON.parse(data.jsonEditor) || false, 76 | apisSorter: data.apisSorter || 'alpha', 77 | defaultModelRendering: data.defaultModelRendering || 'schema', 78 | showRequestHeaders: data.showRequestHeaders || true, 79 | timeout: data.requestTimeout, 80 | onComplete: function(swaggerApi, swaggerUi) { 81 | 82 | initializeSpringfox(); 83 | 84 | if (window.SwaggerTranslator) { 85 | window.SwaggerTranslator.translate(); 86 | } 87 | 88 | $('pre code').each(function(i, e) { 89 | hljs.highlightBlock(e) 90 | }); 91 | 92 | }, 93 | onFailure: function(data) { 94 | log("Unable to Load SwaggerUI"); 95 | }, 96 | }); 97 | 98 | initializeBaseUrl(); 99 | 100 | function addApiKeyAuthorization(security) { 101 | var apiKeyVehicle = security.apiKeyVehicle || 'query'; 102 | var apiKeyName = security.apiKeyName || 'api_key'; 103 | var apiKey = security.apiKey || ''; 104 | if (apiKey && apiKey.trim() != "") { 105 | var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization(apiKeyName, apiKey, apiKeyVehicle); 106 | window.swaggerUi.api.clientAuthorizations.add(apiKeyName, apiKeyAuth); 107 | log("added key " + apiKey); 108 | } 109 | } 110 | 111 | function log() { 112 | if ('console' in window) { 113 | console.log.apply(console, arguments); 114 | } 115 | } 116 | 117 | function oAuthIsDefined(security) { 118 | return security.clientId 119 | && security.appName 120 | && security.realm; 121 | } 122 | 123 | function initializeSpringfox() { 124 | var security = {}; 125 | window.springfox.securityConfig(function(data) { 126 | security = data; 127 | addApiKeyAuthorization(security); 128 | if (typeof initOAuth == "function" && oAuthIsDefined(security)) { 129 | initOAuth(security); 130 | } 131 | }); 132 | } 133 | } 134 | 135 | function onError() { 136 | baseUrl = prompt( 137 | "Unable to infer base url. This is common when using dynamic servlet registration or when" + 138 | " the API is behind an API Gateway. The base url is the root of where" + 139 | " all the swagger resources are served. For e.g. if the api is available at http://example.org/api/v2/api-docs" + 140 | " then the base url is http://example.org/api/. Please enter the location manually: ", 141 | window.location.href); 142 | window.springfox.uiConfig(onSuccess, onError); 143 | } 144 | 145 | }); 146 | 147 | -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview/style/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easycode8/easy-log/df9a281c1a55ff2bd1a9a28a511ab1bb168b795e/easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview/style/fonts/ionicons.ttf -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview/style/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easycode8/easy-log/df9a281c1a55ff2bd1a9a28a511ab1bb168b795e/easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview/style/fonts/ionicons.woff -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview@3.5.4/dist/styles/fonts/ionicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/easycode8/easy-log/df9a281c1a55ff2bd1a9a28a511ab1bb168b795e/easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/iview@3.5.4/dist/styles/fonts/ionicons.woff2 -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/stomp.js/2.3.3/stomp.min.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | /* 3 | Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0 4 | 5 | Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/) 6 | Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com) 7 | */ 8 | (function(){var t,e,n,i,r={}.hasOwnProperty,o=[].slice;t={LF:"\n",NULL:"\x00"};n=function(){var e;function n(t,e,n){this.command=t;this.headers=e!=null?e:{};this.body=n!=null?n:""}n.prototype.toString=function(){var e,i,o,s,u;e=[this.command];o=this.headers["content-length"]===false?true:false;if(o){delete this.headers["content-length"]}u=this.headers;for(i in u){if(!r.call(u,i))continue;s=u[i];e.push(""+i+":"+s)}if(this.body&&!o){e.push("content-length:"+n.sizeOfUTF8(this.body))}e.push(t.LF+this.body);return e.join(t.LF)};n.sizeOfUTF8=function(t){if(t){return encodeURI(t).match(/%..|./g).length}else{return 0}};e=function(e){var i,r,o,s,u,a,c,f,h,l,p,d,g,b,m,v,y;s=e.search(RegExp(""+t.LF+t.LF));u=e.substring(0,s).split(t.LF);o=u.shift();a={};d=function(t){return t.replace(/^\s+|\s+$/g,"")};v=u.reverse();for(g=0,m=v.length;gy;c=p<=y?++b:--b){r=e.charAt(c);if(r===t.NULL){break}i+=r}}return new n(o,a,i)};n.unmarshall=function(n){var i;return function(){var r,o,s,u;s=n.split(RegExp(""+t.NULL+t.LF+"*"));u=[];for(r=0,o=s.length;r0){u.push(e(i))}}return u}()};n.marshall=function(e,i,r){var o;o=new n(e,i,r);return o.toString()+t.NULL};return n}();e=function(){var e;function r(t){this.ws=t;this.ws.binaryType="arraybuffer";this.counter=0;this.connected=false;this.heartbeat={outgoing:1e4,incoming:1e4};this.maxWebSocketFrameSize=16*1024;this.subscriptions={}}r.prototype.debug=function(t){var e;return typeof window!=="undefined"&&window!==null?(e=window.console)!=null?e.log(t):void 0:void 0};e=function(){if(Date.now){return Date.now()}else{return(new Date).valueOf}};r.prototype._transmit=function(t,e,i){var r;r=n.marshall(t,e,i);if(typeof this.debug==="function"){this.debug(">>> "+r)}while(true){if(r.length>this.maxWebSocketFrameSize){this.ws.send(r.substring(0,this.maxWebSocketFrameSize));r=r.substring(this.maxWebSocketFrameSize);if(typeof this.debug==="function"){this.debug("remaining = "+r.length)}}else{return this.ws.send(r)}}};r.prototype._setupHeartbeat=function(n){var r,o,s,u,a,c;if((a=n.version)!==i.VERSIONS.V1_1&&a!==i.VERSIONS.V1_2){return}c=function(){var t,e,i,r;i=n["heart-beat"].split(",");r=[];for(t=0,e=i.length;t>> PING"):void 0}}(this))}if(!(this.heartbeat.incoming===0||o===0)){s=Math.max(this.heartbeat.incoming,o);if(typeof this.debug==="function"){this.debug("check PONG every "+s+"ms")}return this.ponger=i.setInterval(s,function(t){return function(){var n;n=e()-t.serverActivity;if(n>s*2){if(typeof t.debug==="function"){t.debug("did not receive server activity for the last "+n+"ms")}return t.ws.close()}}}(this))}};r.prototype._parseConnect=function(){var t,e,n,i;t=1<=arguments.length?o.call(arguments,0):[];i={};switch(t.length){case 2:i=t[0],e=t[1];break;case 3:if(t[1]instanceof Function){i=t[0],e=t[1],n=t[2]}else{i.login=t[0],i.passcode=t[1],e=t[2]}break;case 4:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3];break;default:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3],i.host=t[4]}return[i,e,n]};r.prototype.connect=function(){var r,s,u,a;r=1<=arguments.length?o.call(arguments,0):[];a=this._parseConnect.apply(this,r);u=a[0],this.connectCallback=a[1],s=a[2];if(typeof this.debug==="function"){this.debug("Opening Web Socket...")}this.ws.onmessage=function(i){return function(r){var o,u,a,c,f,h,l,p,d,g,b,m;c=typeof ArrayBuffer!=="undefined"&&r.data instanceof ArrayBuffer?(o=new Uint8Array(r.data),typeof i.debug==="function"?i.debug("--- got data length: "+o.length):void 0,function(){var t,e,n;n=[];for(t=0,e=o.length;t{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{constructor(){}activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core;if(0===e._renderService.dimensions.actualCellWidth||0===e._renderService.dimensions.actualCellHeight)return;const t=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),n=Math.max(0,parseInt(r.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),s=i-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(s/e._renderService.dimensions.actualCellHeight))}}}})(),e})()})); 2 | //# sourceMappingURL=xterm-addon-fit.js.map -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/resources/webjars/easy-log/xterm@4.18.0/css/xterm.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved. 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * @license MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /** 35 | * Default styles for xterm.js 36 | */ 37 | 38 | .xterm { 39 | position: relative; 40 | user-select: none; 41 | -ms-user-select: none; 42 | -webkit-user-select: none; 43 | } 44 | 45 | .xterm.focus, 46 | .xterm:focus { 47 | outline: none; 48 | } 49 | 50 | .xterm .xterm-helpers { 51 | position: absolute; 52 | top: 0; 53 | /** 54 | * The z-index of the helpers must be higher than the canvases in order for 55 | * IMEs to appear on top. 56 | */ 57 | z-index: 5; 58 | } 59 | 60 | .xterm .xterm-helper-textarea { 61 | padding: 0; 62 | border: 0; 63 | margin: 0; 64 | /* Move textarea out of the screen to the far left, so that the cursor is not visible */ 65 | position: absolute; 66 | opacity: 0; 67 | left: -9999em; 68 | top: 0; 69 | width: 0; 70 | height: 0; 71 | z-index: -5; 72 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 73 | white-space: nowrap; 74 | overflow: hidden; 75 | resize: none; 76 | } 77 | 78 | .xterm .composition-view { 79 | /* TODO: Composition position got messed up somewhere */ 80 | background: #000; 81 | color: #FFF; 82 | display: none; 83 | position: absolute; 84 | white-space: nowrap; 85 | z-index: 1; 86 | } 87 | 88 | .xterm .composition-view.active { 89 | display: block; 90 | } 91 | 92 | .xterm .xterm-viewport { 93 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 94 | background-color: #000; 95 | overflow-y: scroll; 96 | cursor: default; 97 | position: absolute; 98 | right: 0; 99 | left: 0; 100 | top: 0; 101 | bottom: 0; 102 | } 103 | 104 | .xterm .xterm-screen { 105 | position: relative; 106 | } 107 | 108 | .xterm .xterm-screen canvas { 109 | position: absolute; 110 | left: 0; 111 | top: 0; 112 | } 113 | 114 | .xterm .xterm-scroll-area { 115 | visibility: hidden; 116 | } 117 | 118 | .xterm-char-measure-element { 119 | display: inline-block; 120 | visibility: hidden; 121 | position: absolute; 122 | top: 0; 123 | left: -9999em; 124 | line-height: normal; 125 | } 126 | 127 | .xterm { 128 | cursor: text; 129 | } 130 | 131 | .xterm.enable-mouse-events { 132 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 133 | cursor: default; 134 | } 135 | 136 | .xterm.xterm-cursor-pointer, 137 | .xterm .xterm-cursor-pointer { 138 | cursor: pointer; 139 | } 140 | 141 | .xterm.column-select.focus { 142 | /* Column selection mode */ 143 | cursor: crosshair; 144 | } 145 | 146 | .xterm .xterm-accessibility, 147 | .xterm .xterm-message { 148 | position: absolute; 149 | left: 0; 150 | top: 0; 151 | bottom: 0; 152 | right: 0; 153 | z-index: 10; 154 | color: transparent; 155 | } 156 | 157 | .xterm .live-region { 158 | position: absolute; 159 | left: -9999px; 160 | width: 1px; 161 | height: 1px; 162 | overflow: hidden; 163 | } 164 | 165 | .xterm-dim { 166 | opacity: 0.5; 167 | } 168 | 169 | .xterm-underline { 170 | text-decoration: underline; 171 | } 172 | 173 | .xterm-strikethrough { 174 | text-decoration: line-through; 175 | } 176 | 177 | .xterm-screen .xterm-decoration-container .xterm-decoration { 178 | z-index: 6; 179 | position: absolute; 180 | } 181 | -------------------------------------------------------------------------------- /easy-log-web/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.easycode8.easylog.web.autoconfigure.EasyLogWebAutoConfiguration, \ 3 | com.easycode8.easylog.web.autoconfigure.EasyLogWebSocketAutoConfiguration 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.easycode8 8 | easy-log 9 | pom 10 | ${revision} 11 | 12 | easy-log-core 13 | easy-log-spring-boot-starter 14 | easy-log-web 15 | easy-log-data-mybatis-plus 16 | easy-log-trace 17 | 18 | 19 | 20 | 21 | 1.0.12 22 | UTF-8 23 | 8 24 | 8 25 | 26 | 3.8.1 27 | 1.2.7 28 | 3.0.1 29 | 30 | 2.3.2.RELEASE 31 | 2.2.6.RELEASE 32 | 33 | 34 | 1.2.79 35 | 36 | 2.6 37 | 3.8.1 38 | 3.2.2 39 | 5.7.19 40 | 41 | 42 | 2.10.5 43 | 1.9.6 44 | 3.25.0-GA 45 | 46 | 47 | 3.4.2 48 | 1.2.8 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-dependencies 56 | ${spring-boot.version} 57 | pom 58 | import 59 | 60 | 61 | 62 | 63 | com.alibaba 64 | fastjson 65 | ${alibaba-json.version} 66 | 67 | 68 | 69 | commons-io 70 | commons-io 71 | ${commons-io.version} 72 | 73 | 74 | 75 | 76 | cn.hutool 77 | hutool-all 78 | ${hutool-all.version} 79 | 80 | 81 | 82 | 83 | 84 | 85 | io.springfox 86 | springfox-swagger2 87 | ${springfox-swagger.version} 88 | 89 | 90 | 91 | io.springfox 92 | springfox-spring-webmvc 93 | ${springfox-swagger.version} 94 | 95 | 96 | 97 | 98 | io.springfox 99 | springfox-swagger-ui 100 | ${springfox-swagger.version} 101 | compile 102 | 103 | 104 | 105 | com.github.xiaoymin 106 | swagger-bootstrap-ui 107 | ${swagger-bootstrap-ui.version} 108 | 109 | 110 | 111 | org.javassist 112 | javassist 113 | ${javassist.version} 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-compiler-plugin 124 | ${maven-compiler-plugin.version} 125 | true 126 | 127 | 128 | true 129 | ${maven.compiler.source} 130 | ${maven.compiler.target} 131 | true 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.codehaus.mojo 139 | flatten-maven-plugin 140 | ${flatten-maven-plugin.version} 141 | 142 | true 143 | resolveCiFriendliesOnly 144 | 145 | 146 | 147 | flatten 148 | process-resources 149 | 150 | flatten 151 | 152 | 153 | 154 | flatten.clean 155 | clean 156 | 157 | clean 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | org.apache.maven.plugins 167 | maven-source-plugin 168 | ${maven-source-plugin.version} 169 | 170 | 171 | attach-sources 172 | 173 | jar 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | --------------------------------------------------------------------------------