Procházet zdrojové kódy

集成日志框架并打印controller日志

lijie před 3 roky
rodič
revize
adaafa8210

+ 5 - 0
dmp-server/pom.xml

@@ -28,5 +28,10 @@
             <artifactId>spring-cloud-commons</artifactId>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.persagy</groupId>
+            <artifactId>integrated-log-spring-boot-starter</artifactId>
+            <version>1.0.0</version>
+        </dependency>
     </dependencies>
 </project>

+ 166 - 0
dmp-server/src/main/java/com/persagy/aspects/RequestLogAspect.java

@@ -0,0 +1,166 @@
+package com.persagy.aspects;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import com.persagy.dmp.common.context.AppContext;
+import com.persagy.dmp.common.helper.SpringHelper;
+import com.persagy.dmp.common.model.response.CommonResult;
+import com.persagy.utils.JacksonMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.*;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author: yaoll
+ * @date: 2020-09-22
+ * @verison: 1.0
+ */
+@Slf4j
+@Aspect
+@Component
+@EnableAspectJAutoProxy
+public class RequestLogAspect {
+
+	private static final ThreadLocal<LogData> dataStorage = new ThreadLocal<>();
+
+	@Pointcut("execution(public * com.persagy.dmp..*.controller.*.*(..))")
+	//@Pointcut("execution(* *(..))")
+	public void controllerPoint() {
+	}
+
+	@Before("controllerPoint()")
+	public void doBefore(JoinPoint joinPoint) {
+		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+		if (null == attributes) {
+			log.warn("获取请求参数失败!!");
+			return;
+		}
+		HttpServletRequest request = attributes.getRequest();
+		LogData data = initFlag(request);
+		dataStorage.set(data);
+		log.info("{} REQUEST URL      : {} {} {} {}", data.intFlag, data.method, data.url, data.remoteIp, data.remotePort);
+		log.info("{} REQUEST ARGS     : {} ", data.intFlag, JacksonMapper.toSimpleJson(prepare(joinPoint)));
+	}
+
+	public static List<Object> prepare(JoinPoint joinPoint) {
+		Object[] args = joinPoint.getArgs();
+		List<Object> list = new LinkedList<>();
+		if (args != null && args.length > 0) {
+			for (Object arg : args) {
+				if (arg instanceof ServletRequest) {
+					continue;
+				}
+				if (arg instanceof ServletResponse) {
+					continue;
+				}
+				if (arg instanceof MultipartFile) {
+					continue;
+				}
+				list.add(arg);
+			}
+		}
+		return list;
+	}
+
+	@AfterReturning(returning = "ret", pointcut = "controllerPoint()")
+	public void doAfterReturning(Object ret) {
+		LogData data = dataStorage.get();
+		if (ret != null) {
+			if (ret instanceof CommonResult) {
+				log.info("{} REQUEST DURATION : {} {} {}", data.intFlag, System.currentTimeMillis() - data.timestamp, ((CommonResult) ret).getResult(), data.url);
+			} else {
+				log.info("{} REQUEST DURATION : {} {} {}", data.intFlag, System.currentTimeMillis() - data.timestamp, ret.getClass().getName(), data.url);
+			}
+		} else {
+			log.info("{} REQUEST DURATION : {} {} {}", data.intFlag, System.currentTimeMillis() - data.timestamp, "null", data.url);
+		}
+		dataStorage.remove();
+	}
+
+	@AfterThrowing(throwing = "ex", pointcut = "controllerPoint()")
+	public void doAfterThrowing(Throwable ex) {
+		LogData data = dataStorage.get();
+		log.error("{} REQUEST EXCEPTION: {} {} {}", data.intFlag, System.currentTimeMillis() - data.timestamp, data.url, ex);
+		dataStorage.remove();
+	}
+
+	private static LogData initFlag(HttpServletRequest request) {
+		LogData data = new LogData();
+		// 请求IP
+		data.remoteIp = getIpAddr();
+		// 请求端口
+		data.remotePort = request.getRemotePort();
+		data.timestamp = System.currentTimeMillis();
+		data.method = request.getMethod();
+		String requestURI = request.getRequestURI();
+		String queryString = request.getQueryString();
+		data.url = requestURI + (queryString == null ? "" : "?" + queryString);
+		data.intFlag = IdUtil.fastSimpleUUID();
+		dataStorage.set(data);
+		return data;
+	}
+
+	/**
+	 * Description:获取ip地址
+	 *
+	 * @return String
+	 * @author lijie
+	 * @since 2018年10月22日: 下午4:57:26
+	 * Update By lijie 2018年10月22日: 下午4:57:26
+	 */
+	public static String getIpAddr() {
+		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+		String ip = request.getHeader("x-forwarded-for");
+		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+			ip = request.getHeader("Proxy-Client-IP");
+		}
+		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+			ip = request.getHeader("WL-Proxy-Client-IP");
+		}
+		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+			ip = request.getRemoteAddr();
+			if (ip.equals("127.0.0.1")) {
+				// 根据网卡取本机配置的IP
+				try {
+					ip = InetAddress.getLocalHost().getHostAddress();
+				} catch (UnknownHostException e) {
+					log.error("获取本机IP信息失败:", e);
+				}
+			}
+		}
+		// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+		if (ip != null && ip.length() > 15) {
+			if (ip.indexOf(",") > 0) {
+				ip = ip.substring(0, ip.indexOf(","));
+			}
+		}
+		return ip;
+	}
+	/***
+	 * Description: 日志数据实体
+	 * @author : lijie
+	 * @date :2021/8/18 10:36
+	 * Update By lijie 2021/8/18 10:36
+	 */
+	private static class LogData {
+		private String intFlag;
+		private Long timestamp;
+		private String url;
+		private String method;
+		private String remoteIp;
+		private Integer remotePort;
+	}
+}

+ 222 - 0
dmp-server/src/main/java/com/persagy/utils/JacksonMapper.java

@@ -0,0 +1,222 @@
+package com.persagy.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+/**
+ * @author: yaoll
+ * @date: 2020-09-04
+ * @verison: 1.0
+ */
+@Slf4j
+public class JacksonMapper {
+
+	public static final ObjectMapper nonEmptyMapper;
+	public static final ObjectMapper nonDefaultMapper;
+	public static final ObjectMapper nonEmptyFormatMapper;
+
+	static {
+		nonEmptyMapper = new ObjectMapper();
+		nonEmptyMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+		nonEmptyMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+		nonEmptyMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+
+		nonDefaultMapper = new ObjectMapper();
+		nonDefaultMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
+		nonDefaultMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+		nonDefaultMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+
+		nonEmptyFormatMapper = new ObjectMapper();
+		nonEmptyFormatMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+		nonEmptyFormatMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+		nonEmptyFormatMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
+		nonEmptyFormatMapper.enable(SerializationFeature.INDENT_OUTPUT);
+	}
+
+	private static boolean isEmpty(final CharSequence cs) {
+		return cs == null || cs.length() == 0;
+	}
+
+	public static <T> T toObject(String jsonString, Class<T> clazz) {
+		if (!isEmpty(jsonString)) {
+			try {
+				return nonEmptyMapper.readValue(jsonString, clazz);
+			} catch (IOException e) {
+				log.warn("parse json string to {} error: {}", clazz.getName(), jsonString, e);
+			}
+		}
+		return null;
+	}
+
+	public static <T> T toObject(String jsonString, TypeReference<T> typeRef) {
+		if (!isEmpty(jsonString)) {
+			try {
+				return nonEmptyMapper.readValue(jsonString, typeRef);
+			} catch (IOException e) {
+				log.warn("parse json string to {} error: {}", typeRef.toString(), jsonString, e);
+			}
+		}
+		return null;
+	}
+
+	public static <T> T toObject(String jsonString, JavaType javaType) {
+		if (!isEmpty(jsonString)) {
+			try {
+				return (T) nonEmptyMapper.readValue(jsonString, javaType);
+			} catch (IOException e) {
+				log.warn("parse json string to {} error: {}", javaType.toString(), jsonString, e);
+			}
+		}
+		return null;
+	}
+
+	public static <T> T toObject(String jsonString, Class cla, Class... clb) {
+		if (!isEmpty(jsonString)) {
+			try {
+				JavaType javaType = nonEmptyMapper.getTypeFactory().constructParametricType(cla, clb);
+				return (T) nonEmptyMapper.readValue(jsonString, javaType);
+			} catch (IOException e) {
+				log.warn("parse json string to {} error: {}", cla.getName(), jsonString, e);
+			}
+		}
+		return null;
+	}
+
+	public static JavaType constructParametricType(Class<?> parametrized, Class<?>... parameterClasses) {
+		return nonEmptyMapper.getTypeFactory().constructParametricType(parametrized, parameterClasses);
+	}
+
+	public static JavaType constructParametricType(Class<?> rawType, JavaType... parameterTypes) {
+		return nonEmptyMapper.getTypeFactory().constructParametricType(rawType, parameterTypes);
+	}
+
+	public static String toSimpleJson(Object object) {
+		try {
+			return nonEmptyMapper.writeValueAsString(object);
+		} catch (JsonProcessingException e) {
+			log.error("write to json string error:" + object, e);
+		}
+		return null;
+	}
+
+	public static String toFormatJson(Object object) {
+		try {
+			return nonEmptyFormatMapper.writeValueAsString(object);
+		} catch (JsonProcessingException e) {
+			log.error("write to json string error:" + object, e);
+		}
+		return null;
+	}
+
+	public static final String getString(ObjectNode obj, String key) {
+		return getString(obj, key, null);
+	}
+
+	public static final String getString(ObjectNode obj, String key, String defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		return obj.get(key).asText();
+	}
+
+	public static final Integer getInteger(ObjectNode obj, String key) {
+		return getInteger(obj, key, null);
+	}
+
+	public static final Integer getInteger(ObjectNode obj, String key, Integer defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		JsonNode jsonNode = obj.get(key);
+
+		return Integer.valueOf(jsonNode.asText());
+	}
+
+	public static final Double getDouble(ObjectNode obj, String key) {
+		return getDouble(obj, key, null);
+	}
+
+	public static final Double getDouble(ObjectNode obj, String key, Double defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		return Double.valueOf(obj.get(key).asText());
+	}
+
+	public static final Boolean getBoolean(ObjectNode obj, String key) {
+		return getBoolean(obj, key, null);
+	}
+
+	public static final Boolean getBoolean(ObjectNode obj, String key, Boolean defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		return Boolean.valueOf(obj.get(key).toString());
+	}
+
+	public static final ObjectNode getObject(ObjectNode obj, String key) {
+		return getObject(obj, key, null);
+	}
+
+	public static final ObjectNode getObject(ObjectNode obj, String key, ObjectNode defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		JsonNode jsonNode = obj.get(key);
+		JsonNodeType nodeType = jsonNode.getNodeType();
+		if (nodeType == JsonNodeType.STRING) {
+			return toObject(jsonNode.asText(), ObjectNode.class);
+		} else if (nodeType == JsonNodeType.OBJECT) {
+			return (ObjectNode) jsonNode;
+		}
+		return defaultValue;
+	}
+
+
+	public static final ArrayNode getArray(ObjectNode obj, String key) {
+		return getArray(obj, key, null);
+	}
+
+	public static final ArrayNode getArray(ObjectNode obj, String key, ArrayNode defaultValue) {
+		if (obj == null) {
+			return defaultValue;
+		}
+		if (!obj.has(key)) {
+			return defaultValue;
+		}
+		JsonNode jsonNode = obj.get(key);
+		JsonNodeType nodeType = jsonNode.getNodeType();
+		if (nodeType == JsonNodeType.STRING) {
+			return toObject(jsonNode.asText(), ArrayNode.class);
+		} else if (nodeType == JsonNodeType.ARRAY) {
+			return (ArrayNode) jsonNode;
+		}
+		return defaultValue;
+	}
+
+}

+ 112 - 0
dmp-server/src/main/resources/logback-spring.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <contextName>${APP_NAME}</contextName>
+    <springProperty name="APP_NAME" scope="context" source="spring.application.name"/>
+    <springProperty name="LOG_FILE" scope="context" source="logging.file" defaultValue="../logs/${APP_NAME}"/>
+    <springProperty name="LOG_POINT_FILE" scope="context" source="logging.file" defaultValue="../logs/point"/>
+    <springProperty name="LOG_AUDIT_FILE" scope="context" source="logging.file" defaultValue="../logs/audit"/>
+    <springProperty name="LOG_MAXFILESIZE" scope="context" source="logback.filesize" defaultValue="50MB"/>
+    <springProperty name="LOG_TOTAL_SIZE_CAP" scope="context" source="logback.total.size.cap" defaultValue="2GB"/>
+    <springProperty name="LOG_FILEMAXDAY" scope="context" source="logback.filemaxday" defaultValue="7"/>
+    <springProperty name="ServerIP" scope="context" source="spring.cloud.client.ip-address" defaultValue="0.0.0.0"/>
+    <springProperty name="ServerPort" scope="context" source="server.port" defaultValue="0000"/>
+
+    <!-- 彩色日志 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
+    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
+    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
+
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="[${APP_NAME}:${ServerIP}:${ServerPort}] %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%level){blue} %clr(${PID}){magenta} %clr([%X{traceId}]){yellow} %clr([%thread]){orange} %clr(%logger){cyan} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />
+    <property name="CONSOLE_LOG_PATTERN_NO_COLOR" value="[${APP_NAME}:${ServerIP}:${ServerPort}] %d{yyyy-MM-dd HH:mm:ss.SSS} %level ${PID} [%X{traceId}] [%thread] %logger %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" />
+
+    <!-- 控制台日志 -->
+    <appender name="StdoutAppender" class="ch.qos.logback.core.ConsoleAppender">
+        <withJansi>true</withJansi>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+    <!-- 按照每天生成常规日志文件 -->
+    <appender name="FileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_FILE}/${APP_NAME}.log</file>
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN_NO_COLOR}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 基于时间的分包策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <!--保留时间,单位:天-->
+            <maxHistory>${LOG_FILEMAXDAY}</maxHistory>
+            <!-- 日志总保存量为2GB -->
+            <totalSizeCap>${LOG_TOTAL_SIZE_CAP}</totalSizeCap>
+            <maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
+        </rollingPolicy>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <appender name="point_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_POINT_FILE}/${APP_NAME}.point.log</file>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}|${APP_NAME}|%msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 基于时间的分包策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE}/${APP_NAME}.point.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <!--保留时间,单位:天-->
+            <maxHistory>${LOG_FILEMAXDAY}</maxHistory>
+            <!-- 日志总保存量为2GB -->
+            <totalSizeCap>${LOG_TOTAL_SIZE_CAP}</totalSizeCap>
+            <maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
+        </rollingPolicy>
+    </appender>
+
+    <appender name="audit_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_AUDIT_FILE}/${APP_NAME}.audit.log</file>
+        <encoder>
+            <pattern>%msg%n</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 基于时间的分包策略 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_FILE}/${APP_NAME}.audit.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <!--保留时间,单位:天-->
+            <maxHistory>${LOG_FILEMAXDAY}</maxHistory>
+            <!-- 日志总保存量为2GB -->
+            <totalSizeCap>${LOG_TOTAL_SIZE_CAP}</totalSizeCap>
+            <maxFileSize>${LOG_MAXFILESIZE}</maxFileSize>
+        </rollingPolicy>
+    </appender>
+
+    <appender name="point_log_async" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="point_log"/>
+    </appender>
+    <appender name="file_async" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="FileAppender"/>
+    </appender>
+    <appender name="audit_log_async" class="ch.qos.logback.classic.AsyncAppender">
+        <discardingThreshold>0</discardingThreshold>
+        <appender-ref ref="audit_log"/>
+    </appender>
+    <logger name="com.persagy.log.monitor" level="debug" addtivity="false">
+        <appender-ref ref="point_log_async" />
+    </logger>
+    <logger name="com.persagy.log.service.impl.LoggerAuditServiceImpl" level="debug" addtivity="false">
+        <appender-ref ref="audit_log_async" />
+    </logger>
+
+    <root level="INFO">
+        <appender-ref ref="StdoutAppender"/>
+        <appender-ref ref="file_async"/>
+        <appender-ref ref="audit_log_async"/>
+    </root>
+</configuration>