springboot日志及Maven多环境配置

项目简介:


Java framework for enterprise web applications
  • Vue
  • SpringBoot 2.1.6

[第二篇 日志的管理]
项目使用的是SpringBoot自带的Logback日志, SpringBoot的依赖

	 <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
          <version>${spring-boot.version}</version>
      </dependency>

	 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
     </dependency>

均带有坐标spring-boot-starter-logging也就是Logback的依赖;


先介绍一下Maven多环境打包,项目中使用了本地,开发,测试等多个环境,为了开发测试方便,使用了maven多环境打包插件,在各个子模块项目的pom.xml中配置了以及,打包时候-P命令后指定环境即可,如

  • mvn clean package -DskipTests -Plocal
  • mvn clean package -DskipTests -Ptest
    Common下统一管理配置文件
<profiles>
    <profile>
        <id>local</id>
        <activation>
            <!--activeByDefault为true表示,默认激活id为local的profile-->
            <activeByDefault>true</activeByDefault>
        </activation>
        <!-- properties里面可以添加自定义节点如下添加了一个env节点-->
        <properties>
            <!-- 这个节点的值可以在maven的其他地方引用,可以简单理解为定义了一个叫profileActive的变量 -->
            <profileActive>local</profileActive>
        </properties>
    </profile>
    <profile>
        <id>dev</id>
        <properties>
            <profileActive>dev</profileActive>
        </properties>
    </profile>
    <profile>
        <id>test</id>
        <properties>
            <profileActive>test</profileActive>
        </properties>
    </profile>
    <profile>
        <id>xs</id>
        <properties>
            <profileActive>xs</profileActive>
        </properties>
    </profile>
</profiles>
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <excludes>
                <!-- 打包时排除文件 -->
                <exclude>application*.yml</exclude>
            </excludes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <!--filtering 需要设置为 true,这样在include的时候,才会把
        配置文件中的@profileActive@ 这个maven`变量`替换成当前环境的对应值  -->
            <filtering>true</filtering>
            <!-- 打包时所包含得文件 -->
            <includes>
                <include>application-common.yml</include>
                <include>application-common-${profileActive}.yml</include>
                <include>application.yml</include>
                <include>application-appcommon.yml</include>
                <include>application-${profileActive}.yml</include>
            </includes>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Logback日志配置如下(以开发环境为例):
common公共模块
子模块包含common中配置
子模块引入配置
Logback_spring.xml ,使用了默认的彩色日志,在Pattern标签下可以修改日志的格式颜色等(比如将%clr改成%yellow),设置Mybatis执行的日志为Debug就可以在控制台看到sql的执行过程,想要自定义某个包下的日志级别,可以使用logger标签或者在yml配置文件中直接配置,还可以定义每日生成html日志等等,实用性不大不再贴出.

/**
 * logger标签自定义日志级别
 */
 	<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="ERROR"/>
	<logger name="org.springframework.cloud.sleuth" level="ERROR"/>
	<logger name="com.springms" level="ERROR"/>
    <logger name="zipkin" level="ERROR"/>
    <logger name="自己定义的包路径" level="ERROR"/>
/**
 * yml配置文件自定义日志级别
 */
logging:
   config: classpath:config/logback-config.xml
   level:
       com:
           xxx:
               index: debug(指定某个包下日志级别)
/**
 * @Description Logback_spring.xml
 */
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty scope="context" name="LOG_PATH" source="custom-log.path" defaultValue="/home/log"/>
    <springProperty scope="context" name="LOG_FILENAME" source="custom-log.filename" defaultValue="log.log"/>
    <springProperty scope="context" name="ROOT_LEVEL" source="custom-log.root.level" defaultValue="info"/>
    <springProperty scope="context" name="MYBATIS_LEVEL" source="custom-log.mybatis.level" defaultValue="info"/>
    <springProperty scope="context" name="MYBATIS_DAO_PATH" source="custom-log.mybatis.dao-path" defaultValue="com.talkweb.edu.*.dao"/>

    <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"/>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr(%-80.80logger{79}){cyan} %clr(:){faint} %m%n%wEx</pattern>
        </encoder>
    </appender>

    <!--文件输出的格式设置 -->
    <appender name="FILE"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 日志日常打印文件 -->
        <file>${LOG_PATH}/${LOG_FILENAME}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>
                ${LOG_PATH}/${LOG_FILENAME}-%d{yyyy-MM-dd}.%i.log
            </fileNamePattern>
            <!-- 如果按天来回滚,则最大保存时间为60天,60天之前的都将被清理掉 -->
            <maxHistory>5</maxHistory>
            <!-- 日志总保存量为10GB -->
            <totalSizeCap>10GB</totalSizeCap>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!--文件达到 最大128MB时会被压缩和切割 -->
                <maxFileSize>128MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 文件输出的日志 的格式 -->
        <encoder>
            <pattern>
                %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - [%file:%line] - %msg%n
            </pattern>
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <!-- Safely log to the same file from multiple JVMs. Degrades performance! -->
        <prudent>false</prudent>
    </appender>

    <!--myibatis log configure -->
    <logger name="${MYBATIS_DAO_PATH}" level="${MYBATIS_LEVEL}"/>
    <!-- 日志输出级别 -->
    <root level="${ROOT_LEVEL}">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
    </root>
</configuration>

日志配置文件已述完,项目中日志用的是Aop注解日志日志,定义了一个日志的切面SystemLogAspect ,使用注解@SystemControllerLog和@SystemServiceLog可以完成控制层和服务层基本访问日志,其中还包含了对异常的处理,在服务层类上加上@Slf4j按需使用log.info(“日志信息”).

@Aspect
@Component
public class SystemLogAspect {
	// 本地异常日志记录对象
	private static final ServerLogger logger = new ServerLogger(SystemLogAspect.class);

	// Service层切点
	@Pointcut("@annotation(com.xxx.log.SystemServiceLog)")
	public void serviceAspect() {
	}

	// Controller层切点
	@Pointcut("@annotation(com.xxx.log.SystemControllerLog)")
	public void controllerAspect() {

	}

	private String project = "index";

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作
	 * 
	 * @param joinPoint
	 *            切点
	 */
	@Before("controllerAspect()")
	public void doBefore(JoinPoint joinPoint) {
		try {
			if (null == RequestContextHolder.getRequestAttributes()) {
				return;
			}
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
					.getRequest();

			String parentLogId = request.getHeader(LoggerConstants.HTTP_HEAD_PARENT_LOG_ID);
			
			String tenant_org_id = request.getHeader(Constants.HTTP_HEAD_TENANT_ORG_ID);

			// 获取用户请求方法的参数并序列化为JSON格式字符串
			String params = "";
			if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
				for (int i = 0; i < joinPoint.getArgs().length; i++) {
					params += GsonUtil.toJson(joinPoint.getArgs()[i]) + ";";
				}
			}
			String logId = UUID.randomUUID().toString();
			// 读取session中的用户
			// 请求的IP
			String ip = request.getRemoteAddr();
			SystemLog log = new SystemLog();
			log.setLogId(logId);
			log.setLogClass(joinPoint.getTarget().getClass().getName());
			log.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
			log.setQuestIp(ip);
			log.setLogType(LoggerConstants.CONTROLL_LOG_TYPE);
			log.setDescription(getControllerMethodDescription(joinPoint));
			log.setCreatetime(new Date());
			log.setProject(project);
			log.setQuestParam(params);
			if (StringUtils.isNotBlank(parentLogId)) {
				log.setParentLogId(parentLogId);
			}
			String logJson = GsonUtil.toJson(log);

			// 存放到本地线程,以便下级请求获取到改日志ID
			ThreadLocalHelper.put(LoggerConstants.HTTP_HEAD_PARENT_LOG_ID, logId);
			
			// 本地线程,存放租户对应的单位Id
			ThreadLocalHelper.put(Constants.HTTP_HEAD_TENANT_ORG_ID,tenant_org_id);

			logger.info(logJson);
		} catch (Exception e) {
			// 记录本地异常日志
			logger.error(e.toString(), e);
		}
	}

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作
	 * 
	 * @param proceedingJoinPoint
	 *            切点
	 */
	@Around("controllerAspect()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		Object result = null;
		try {
			result = proceedingJoinPoint.proceed();
		} catch (Exception e) {
			Response<String> resultDTO = new Response<String>();
			MessageResult messageResult = new MessageResult();
			if (e instanceof ParamException) {
				ParamException errorCodeException = (ParamException) e;
				messageResult.setResultCode(errorCodeException.getCode());
				messageResult.setResultMsg(errorCodeException.getMessage());
				messageResult.setErrorParam(errorCodeException.getParaName());
			} else if (e instanceof MicroServiceException) {
				messageResult.setResultCode(IStateCode.SQL_ERORR);
				messageResult.setResultMsg(LoggerConstants.DB_EXCEPTION_ERROR);
			} else {
				messageResult.setResultCode(IStateCode.SYSTEM_ERORR);
				messageResult.setResultMsg(LoggerConstants.SYSTEM_ERROR);
			}
			resultDTO.setServerResult(messageResult);
			logger.error(e.getMessage(),e);
			String params = "";
			if (proceedingJoinPoint.getArgs() != null && proceedingJoinPoint.getArgs().length > 0) {
				for (int i = 0; i < proceedingJoinPoint.getArgs().length; i++) {
					params += GsonUtil.toJson(proceedingJoinPoint.getArgs()[i]) + ";";
				}
			}
			/* ==========记录本地异常日志========== */
			logger.error("异常方法:{" + proceedingJoinPoint.getTarget().getClass().getName() +"." + proceedingJoinPoint.getSignature().getName()
					+ "}异常信息:{" + e.getMessage() + "}参数:{" + params + "}", e);

			return resultDTO;
		}
		return result;
    }

	/**
	 * 异常通知 用于拦截service层记录异常日志
	 * 
	 * @param joinPoint
	 * @param e
	 * @throws Throwable 
	 */
	@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) throws Throwable {
		// 获取用户请求方法的参数并序列化为JSON格式字符串
		String params = "";
		if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
			for (int i = 0; i < joinPoint.getArgs().length; i++) {
				params += GsonUtil.toJson(joinPoint.getArgs()[i]) + ";";
			}
		}
		try {

			if (null == RequestContextHolder.getRequestAttributes()) {
				return;
			}
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
					.getRequest();
			// 获取请求ip
			String ip = request.getRemoteAddr();
			Object parentLogIdStr = ThreadLocalHelper.getValue(LoggerConstants.HTTP_HEAD_PARENT_LOG_ID);
			String parentLogId = null == parentLogIdStr ? null : (String) parentLogIdStr;

			String logId = UUID.randomUUID().toString();

			SystemLog log = new SystemLog();
			log.setLogId(logId);
			log.setLogClass(e.getClass().getName());
			log.setExcepMessage(e.getMessage());
			log.setMethod(joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()");
			log.setQuestIp(ip);
			log.setQuestParam(params);
			log.setDescription(getServiceMthodDescription(joinPoint));
			log.setLogType(LoggerConstants.SERVICE_LOG_TYPE);
			log.setCreatetime(new Date());
			log.setProject(project);
			if (StringUtils.isNotBlank(parentLogId)) {
				log.setParentLogId(parentLogId);
			}
			String logJson = GsonUtil.toJson(log);

			logger.error(logJson);
		} catch (Exception ex) {
			// 记录本地异常日志
			logger.error(ex.getMessage(), ex);
		}
		/* ==========记录本地异常日志========== */
		logger.error("异常方法:{" + joinPoint.getTarget().getClass().getName() +"." +joinPoint.getSignature().getName()
				+ "}异常信息:{" + e.getMessage() + "}参数:{" + params + "}", e);

		throw e;
	}

	/**
	 * 获取注解中对方法的描述信息 用于service层注解
	 * 
	 * @param joinPoint
	 *            切点
	 * @return 方法描述
	 * @throws Exception
	 */
	public static String getServiceMthodDescription(JoinPoint joinPoint) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String description = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length && null != method.getAnnotation(SystemServiceLog.class)) {
					description = method.getAnnotation(SystemServiceLog.class).description();
					break;
				}
			}
		}
		return description;
	}

	/**
	 * 获取注解中对方法的描述信息 用于Controller层注解
	 * 
	 * @param joinPoint
	 *            切点
	 * @return 方法描述
	 * @throws Exception
	 */
	public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String description = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length && null != method.getAnnotation(SystemControllerLog.class)) {
					description = method.getAnnotation(SystemControllerLog.class).description();
					break;
				}
			}
		}
		return description;
	}
}

@Target({ElementType.PARAMETER, ElementType.METHOD})   
@Retention(RetentionPolicy.RUNTIME)   
@Documented
public @interface SystemServiceLog
{
    /**
     * 描述信息
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [违例类型] [违例说明]
     * @see [类、类#方法、类#成员]
     */
    String description()  default "";
}
@Target({ElementType.PARAMETER, ElementType.METHOD})    
@Retention(RetentionPolicy.RUNTIME)    
@Documented  
public @interface SystemControllerLog
{
    /**
     * 描述信息
     * <功能详细描述>
     * @return [参数说明]
     * 
     * @return String [返回类型说明]
     * @exception throws [违例类型] [违例说明]
     * @see [类、类#方法、类#成员]
     */
    String description()  default "";
    
}

此外还有一个aop切点日志可以计算Controller的毫秒损耗

@Aspect
@Component
@Slf4j
public class TkControllerRequestAdvice {

    private static final ThreadLocal<Long> timeThreadLocal = new ThreadLocal<Long>();

    /**
     * 定义切点
     */
    @Pointcut("execution(public * com.xxx.*.controller..*.*(..))")
    public void log() {
    }

    /**
     * 处理请求前处理
     *
     * @param joinPoint 连接点
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        String client = request.getRemoteAddr();
        String method = request.getMethod();
        String requestURI = request.getRequestURI();
        String token = request.getHeader("token");

        long currentTimeMillis = System.currentTimeMillis();
        timeThreadLocal.set(currentTimeMillis);

        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            String parameterName = parameterNames[i];
            Object parameterValue = args[i];
            if (parameterValue instanceof MultipartFile) {
                Map<String, Object> f = getFileParam((MultipartFile) parameterValue);
                params.put(parameterName, f);
                continue;
            }
            params.put(parameterName, parameterValue);
        }

        StringBuffer sb = new StringBuffer();
        sb.append("Request_").append(currentTimeMillis).append(" ");
        sb.append("<").append(client).append(">");
        sb.append(" ").append(method).append(" ");
        sb.append("\"").append(requestURI).append("\"");
        sb.append(" token:").append(token).append(" ");
        sb.append("class_method : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

        log.info(sb.toString());
    }

    /**
     * 处理请求后返回
     *
     * @param obj 返回值
     */
    @AfterReturning(pointcut = "log()", returning = "obj")
    public void afterReturning(Object obj) {
        log.info("Response_" + timeThreadLocal.get() + " => " + JsonUtil.toJson(obj));
        log.info("耗时(毫秒) : " + (System.currentTimeMillis() - timeThreadLocal.get()));
    }

    private Map<String, Object> getFileParam(MultipartFile file) {
        Map<String, Object> params = new HashMap<>();
        params.put("文件名", file.getOriginalFilename());
        params.put("文件类型", file.getContentType());
        params.put("文件大小", file.getSize());
        return params;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值