从零搭建微服务项目Base(第5章——SpringBoot项目LogBack日志配置+Feign使用)

前言:

本章主要在原有项目上添加了日志配置**,对SpringBoot默认的logback的配置进行了自定义修改,并详细阐述了xml文件配置要点(只对日志配置感兴趣的小伙伴可选择直接跳到第三节),** 并使用Feign 代替原有RestTemplate完成微服务间调用,以及通过修改Feign的日志输出介绍了Feign配置的修改。

本章代码基于第4章项目,前置源码可在第4章博客下载,博客链接如下:

从零搭建微服务项目(第4章------Nacos环境隔离和配置拉取)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145532798?sharetype=blogdetail&sharerId=145532798&sharerefer=PC&sharesource=wlf2030&spm=1011.2480.3001.8118**简要介绍前置项目流程:**order-service以及user-service两服务分别连接order-db以及user-db两数据库,order-db中仅有user-id,user-info存在user-db中,为提供完整order-info,order-service通过nacos发现user-service服务地址并使用RestTemplate调用其端口拿取user-info结合从order-db中拿取的信息返回给前端。

本项目源码链接如下:

wlf728050719/SpringCloudBase5https://github.com/wlf728050719/SpringCloudBase5以及本专栏会持续更新微服务项目,每一章的项目都会基于前一章项目进行功能的完善,欢迎小伙伴们关注!同时如果只是对单章感兴趣也不用从头看,只需下载前一章项目即可,每一章都会有前置项目准备部分,跟着操作就能实现上一章的最终效果,当然如果是一直跟着做可以直接跳过这一部分。


一、前置项目准备

1.从github下载前一章的项目解压,重命名为Base5打开。

2.重命名模块为Base5

3.父工程pom.xml中<name>改成Base5,

4.选择环境为dev,并重新加载maven

5.启动nacos(安装和启动见第三章)

6.进入nacos网页 配置管理->配置列表确认有这些yaml文件

(如果不是一直跟着专栏做自然是没有的,需要看第四章的环境隔离和配置拉取,记得把父工程pom文件中namespace的值与nacos中命名空间生成的保持一致)

7.配置数据源,更换两服务的resources下yml文件的数据库配置,数据库sql见第一章数据库准备部分。

.测试数据库连接 属性->点击数据源->测试连接->输入用户名密码

8.添加运行配置 服务->加号->运行配置类型->spring boot

启动服务

测试order-service

测试user-service


二、Feign基本使用

1.order-service的pom中添加feign依赖(建议使用下面release版本)

版本不对,可能会在启动order-service时出现如下报错,即找不到org.springframework.cloud.openfeign.FeignClientFactory的Bean

2.order-service的application开启feign客户端注解,并删除掉原有RestTemplate的bean

替换后内容如下:

java 复制代码
package cn.bit.orderservice;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;


@SpringBootApplication
@MapperScan("cn.bit.orderservice.mapper")
@EnableFeignClients
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

3.创建client接口

接口内容如下:能够看出feign即通过把url封装为接口方便调用,可以理解为每个FeignClient即对应一个微服务,但通过封装让业务看起来像是服务的内部调用。

java 复制代码
package cn.bit.orderservice.client;

import cn.bit.orderservice.bean.dto.UserBaseInfoDTO;
import cn.bit.orderservice.bean.vo.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/user/baseInfo/{id}")
    R<UserBaseInfoDTO> getUserBaseInfo(@PathVariable("id") Integer id);
}

4.更改order-service的serviceImpl实现

原有调用方式如下:(通过url调用服务)

替换后内容如下:(通过定义好的feignclient调用服务)

java 复制代码
package cn.bit.orderservice.service.impl;

import cn.bit.orderservice.bean.dto.UserBaseInfoDTO;
import cn.bit.orderservice.bean.po.OrderPO;
import cn.bit.orderservice.bean.vo.OrderInfoVO;
import cn.bit.orderservice.client.UserClient;
import cn.bit.orderservice.mapper.OrderMapper;
import cn.bit.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;
    @Override
    public OrderInfoVO getOrderInfoById(int orderId) {
        OrderPO orderPO = orderMapper.getOrderPOById(orderId);
        if (orderPO == null)
            return null;

        OrderInfoVO orderInfoVO = new OrderInfoVO();
        orderInfoVO.setOrderId(orderPO.getId());
        orderInfoVO.setAmount(orderPO.getAmount());
        orderInfoVO.setCreateTime(orderPO.getCreateTime());
        orderInfoVO.setGetLocation(orderPO.getGetLocation());

        try {
            // 获取买家信息
            UserBaseInfoDTO userBaseInfo1 = userClient.getUserBaseInfo(orderPO.getBuyerId()).getData();
            orderInfoVO.setBuyerName(userBaseInfo1.getUsername());

            // 获取卖家信息
            UserBaseInfoDTO userBaseInfo2 = userClient.getUserBaseInfo(orderPO.getSellerId()).getData();
            orderInfoVO.setSellerName(userBaseInfo2.getUsername());
        } catch (Exception e) {
            System.out.println("user-service connect error");
            e.printStackTrace();
        }
        return orderInfoVO;
    }
}

5.启动服务后测试

关闭user-service,无法获取用户信息且控制台有异常输出。


三、日志配置

为体现feign配置需要引入日志输出,之前的控制台的彩色输出是因为Spring Cloud默认日志实现是LogBack,其自动引入了对应的依赖,并有一套默认的配置。

如果需要自行配置日志格式,操作如下:

在order-service的resources目录下创建 logback-spring.xml文件,内容如下:

(本节最后有更完善的配置xml,本xm仅用于了解日志配置相关知识)

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/order-service.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/order-service.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

    <logger name="cn.bit.orderservice" level="debug" additivity="false">
        <appender-ref ref="CONSOLE"/>
    </logger>
</configuration>

其中LOG_PATTERN属性定义一种日志输出的格式

**(1)**appender name=console指定控制台输出采用哪种格式,此处采用上述定义的格式。

**(2)**appender name=file 指定日志文件记录的目录以及输出的格式。

**(3)**appender的name的命名随意,只要对应即可,关键实现是其后面的class属性决定了功能

**(4)**root level=info指定了根日志级别是info级别,除了所有被指定设置的logger其他所有日志级别在info及以上的都会执行console以及file,即控制台打印加日志文件记录。

(5) logger通过name指定cn.bit.orderservice包下日志级别为debug即以上的日志语句会打印在控制台。且设置其不会传播到父logger,其父logger为根,因此debug日志只会被控制台输出而不会被记录在日志文件中。

日志严重等级从高到低如下

**OFF:**关闭所有日志记录。

**FATAL:**记录严重错误事件,这些事件可能导致程序中断。

**ERROR:**记录错误事件,但不会导致程序中断。

**WARN:**记录潜在有害的情况。

**INFO:**记录一般信息,用于描述程序运行过程中的关键事件。

**DEBUG:**记录详细的调试信息,用于诊断问题。

**TRACE:**记录最详细的调试信息,用于跟踪程序执行过程。

### 验证开始(该部分可自行选择跳过)

1.在order-service上添加上述xml文件,user-service不添加即使用默认配置

2.在order-controller的接口添加一句log的各种输出;会自动添加@Slf4j注解

3.启动服务,并调用一次接口,能够看到user-service的输出为彩色,order-service的输出为白色。说明每个模块用不同的配置**(按照项目目前结构需要每个模块单独写log配置文件,后续有网关模块即可统一配置)**

4.根目录有生成的logs目录和对应日志文件,验证上述**(2)。**

5.对比控制台输出和日志文件末尾,只有控制台记录了debug,info,warning,验证上述**(4)(5)。**

这时候肯定就有小伙伴有疑问,既然cn.bit.orderservice包已经被设定了只输出不记录为什么log文件里还有那么多记录。这时就会发现我着重加粗的是日志语句,不是从这个包输出的日志信息被单独配置了,而是这个包定义的日志语句被单独配置。这么说似乎还是有点抽象,下面来用案例说明。

用ctrl+左键跳转到这个HikariDataSource类,这是导入的类,不是项目编写的。

其构造函数中有logger的输出语句,也对应了上述的控制台输出

这时后在logback的配置文件为该类单独配置,即这个日志什么也不干。

然后删除刚才生成的log目录,再重新启动调用接口。前后对比就会发现上面的日志在控制台和文件中都不见了。

我个人的理解是,在编译时,编译器根据配置文件中特殊配置的logger,找到对应的类,扫描其所有logger,然后或面向切面或代理模式对这些logger进行功能增强,其他未特殊配置的类的logger就是默认的功能。不过只是个人理解,具体实现还得扒源码。

6.把logback.xml替换成下面内容, 即console变成console1,格式变成年月日,然后日志路径改成log12345

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATTERN" value="%d{yyyy年MM月dd日 HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

    <appender name="CONSOLE1" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs12345/order-service.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/order-service.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="CONSOLE1"/>
        <appender-ref ref="FILE"/>
    </root>

    <logger name="cn.bit.orderservice" level="debug" additivity="false">
        <appender-ref ref="CONSOLE1"/>
    </logger>
    <logger name="com.zaxxer.hikari.HikariDataSource" level="info" additivity="false">
    </logger>
</configuration>

7.启动后输出,验证上述**(1)(2)(3)**。

### 验证结束

这时候会有人问,博主博主,你的日志xml讲解确实很多但还是太吃理解了,有没有更简单强势的xml推荐呢?

有的,兄弟!(bushi)

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <property name="logLevel" value="INFO"/>
    <property name="logPath" value="logs/order-service"/>
    <property name="maxHistory" value="60"/>
    <property name="queueSize" value="512"/>

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN"
              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    <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>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <!-- DEBUG 日志文件输出 -->
    <appender name="FILE_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/debug.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/debug-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>128MB</maxFileSize>
            <maxHistory>${maxHistory}</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- INFO 日志文件输出 -->
    <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/info.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/info-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>128MB</maxFileSize>
            <maxHistory>${maxHistory}</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- WARN 日志文件输出 -->
    <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/warn.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/warn-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>128MB</maxFileSize>
            <maxHistory>${maxHistory}</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- ERROR 日志文件输出 -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/error.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>128MB</maxFileSize>
            <maxHistory>${maxHistory}</maxHistory>
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%thread] %-5level [%logger{50}] - %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 异步日志记录 -->
    <appender name="ASYNC_LOG_DEBUG" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>${queueSize}</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="FILE_DEBUG"/>
    </appender>

    <appender name="ASYNC_LOG_INFO" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>${queueSize}</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="FILE_INFO"/>
    </appender>

    <appender name="ASYNC_LOG_WARN" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>${queueSize}</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="FILE_WARN"/>
    </appender>

    <appender name="ASYNC_LOG_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>${queueSize}</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="FILE_ERROR"/>
    </appender>

    <root level="${logLevel}">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ASYNC_LOG_DEBUG"/>
        <appender-ref ref="ASYNC_LOG_INFO"/>
        <appender-ref ref="ASYNC_LOG_WARN"/>
        <appender-ref ref="ASYNC_LOG_ERROR"/>
    </root>

    <logger name="cn.bit.orderservice" level="debug" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ASYNC_LOG_DEBUG"/>
        <appender-ref ref="ASYNC_LOG_INFO"/>
        <appender-ref ref="ASYNC_LOG_WARN"/>
        <appender-ref ref="ASYNC_LOG_ERROR"/>
    </logger>

</configuration>

使用时只需要注意下面两点:

全局日志等级,这里设置为info,在没有特殊指定的情况下会把info及以上等级日志输出和输出到文件中。

单独配置项目文件即cn.bit.orderservice包中的日志,这里相等于把等级下调到了debug。

这些在控制台日志文件 中就会有导入包的info及以上和项目中的debug及以上信息。

其实在nacos的yml或本地application.yml文件中也能使用logger.level对单独的包进行配置,但似乎并不能设置additivity属性阻止其传递给父logger.配置前缀如下:


四、Feign自定义配置

1.在order-service下创建配置类

类内容如下:(这里对feign的日志输出进行配置)

java 复制代码
package cn.bit.orderservice.config;

import org.springframework.context.annotation.Bean;
import feign.Logger;
public class FeignConfiguration {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
}

2.在order-service的application上启动配置(这种写法会使该模块下所有client都默认使用该配置)

或在UserClient上单独添加配置,具体如下:(因为后面会有多个client扩充,因此项目采用全局配置)

3.在前面日志配置好的情况下启动。

能够看到详细的feign信息。

换成basic

仅输出基本信息


最后:

很抱歉在第五章才加上日志配置的部分,这部分其实应该放在第0章,毕竟日志的重要性不言而喻。不过目前日志配置仍然是每个模块配置一个,实际在网关模块学习后只需要配置一个,并且能够通过使用@{project.artifactId}来使每个模块的日志输出到不同目录,目前仍然需要手动指定每个模块的输出路径。日志配置也会在后面的章节有所更改。

相关推荐
追逐时光者2 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_2 小时前
敏捷开发流程-精简版
前端·后端
苏打水com3 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
西瓜er4 小时前
JAVA:Spring Boot 集成 FFmpeg 实现多媒体处理
java·spring boot·ffmpeg
间彧4 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧4 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧4 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧4 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧5 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng6 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端