Online education - instructor management interface development, Swagger2 introduction, unified log processing, 07 unified exception processing

Posted by AlexRodr on Sat, 18 Dec 2021 06:18:37 +0100

Development of online education lecturer management interface

01 - instructor management module configuration and generation code

1, Instructor management module configuration

1. Create a configuration file in the service edu module under service

Create the file application. In the resources directory properties

# Service port
server.port=8001
# service name
spring.application.name=service-edu

# Environment settings: dev, test, prod
spring.profiles.active=dev

# mysql database connection
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

#mybatis log
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

Or create the file application. In the resources directory yml

#### application.yml
spring:
  application:
    name: service-edu
profiles:
    active: dev


####  application-dev.yml
server:
  port: 8001
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/atguigu/service/*/mapper/*.xml
global-config:
    db-config:
      logic-delete-value: 1
logic-not-delete-value: 0
spring:
datasource:
    type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8
username: root
password: root
hikari:
      connection-test-query: SELECT 1
connection-timeout: 60000
idle-timeout: 500000
max-lifetime: 540000
maximum-pool-size: 12
minimum-idle: 10
pool-name: GuliHikariPool
jackson:
    date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8

2. Create MP code generator

Create the package com. In the test/java directory atguigu. Eduservice, create a code generator: codegenerator java

public class getCode {

    @Test
    public void main1() {

        // 1. Create code generator
        AutoGenerator mpg = new AutoGenerator();

        // 2. Global configuration
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        System.out.println(projectPath);
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("atguigu");
        gc.setOpen(false); //Open Explorer after build
        gc.setFileOverride(false); //Whether the file is overwritten during regeneration
        /*
         * mp Generate service layer code. The first letter of the default interface name is I
         * UcenterService
         * */
        gc.setServiceName("%sService");	//Remove the initial I of the Service interface
        gc.setIdType(IdType.ID_WORKER); //Primary key policy
        gc.setDateType(DateType.ONLY_DATE);//Defines the date type in the generated entity class
        gc.setSwagger2(true);//Turn on Swagger2 mode

        mpg.setGlobalConfig(gc);

        // 3. Data source configuration
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4. Package configuration
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("serviceedu"); //Module name
        pc.setParent("com.atguigu");
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5. Policy configuration
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("edu_teacher");
        strategy.setNaming(NamingStrategy.underline_to_camel);//Naming policy for mapping database tables to entities
        strategy.setTablePrefix(pc.getModuleName() + "_"); //Remove table prefix when generating entities

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//Naming policy for mapping database table fields to entities
        strategy.setEntityLombokModel(true); // lombok model @ accessories (chain = true) setter chain operation

        strategy.setRestControllerStyle(true); //restful api style controller
        strategy.setControllerMappingHyphenStyle(true); //Hump to hyphen in url

        mpg.setStrategy(strategy);

        // 6. Execute
        mpg.execute();
    }
}

2, Write background management api interface

1. Write controller code

@Autowired
private TeacherService teacherService;

@GetMapping
public List<Teacher> list(){
    return teacherService.list(null);
}

2. Create SpringBoot configuration class

Create the config package under the edu package and create mybatisplusconfig java

package com.guli.edu.config;

@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.eduservice.mapper")
public class MyBatisPlusConfig {
    
}

3. Configure SQL execution performance analysis plug-in

/**
	 * SQL Execute performance analysis plug-in
	 * It is not recommended to use the development environment online. maxTime refers to the maximum sql execution time
	 */
@Bean
@Profile({"dev","test"})// Set dev test environment on
public PerformanceInterceptor performanceInterceptor() {
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(1000);//ms. if the ms set here is exceeded, sql will not be executed
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}

4. Create a SpringBoot startup class

Create startup class eduapplication Java, pay attention to the creation location of the startup class

@SpringBootApplication
public class EduApplication {

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

5. Run startup class

visit http://localhost:8001/eduservice/teacher
Get json data

6. Unified returned json time format

By default, json time format has time zone and is world standard time, which is eight hours away from our time
In application Set in properties

#Returns the global time format of json
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

3, Instructor logical deletion function

1. EduTeacherController add delete method

@DeleteMapping("{id}")
public boolean removeById(@PathVariable String id){
    return teacherService.removeById(id);
}

2. Configure logical deletion plug-ins

Configuration in MyBatisPlusConfig

/**
      * Logical delete plug-in
      */
@Bean
public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
}

3. Test deletion using postman


Test result: is in database_ The deleted field is modified to 1

4, Cross domain configuration

1. What is cross domain

When a browser requests the resources of another domain name from the web page of one domain name, the domain name, port and protocol are all cross domain. In the development of front-end and back-end separation, we need to consider the problem of ajax cross domain.

Here we can solve this problem from the server

2. Disposition

Add annotation on Controller class

@CrossOrigin //Cross domain

02 - configure Swagger2 to generate API interface documents

1, Swagger2 introduction

In the front end and back-end separation development mode, api documents are the best way to communicate.
Swagger is a normative and complete framework for generating, describing, invoking, and visualizing RESTful Web services.
Timeliness (timely and accurately notify relevant front and rear end developers after interface changes)
Standardization (and ensure the standardization of the interface, such as interface address, request mode, parameters, response format and error information)
Consistency (the interface information is consistent, and there will be no differences due to the inconsistent document versions obtained by the developers)
Testability (test directly on the interface document to facilitate business understanding)

1, Swagger2 introduction

In the front end and back-end separation development mode, api documents are the best way to communicate.
Swagger is a normative and complete framework for generating, describing, invoking, and visualizing RESTful Web services.
Timeliness (timely and accurately notify relevant front and rear end developers after interface changes)
Standardization (and ensure the standardization of the interface, such as interface address, request mode, parameters, response format and error information)
Consistency (the interface information is consistent, and there will be no differences due to the inconsistent document versions obtained by the developers)
Testability (test directly on the interface document to facilitate business understanding)

2. Introduce related dependencies in common

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--lombok Used to simplify entity classes: need to install lombok plug-in unit-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided </scope>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <scope>provided </scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <scope>provided </scope>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- spring2.X integrate redis what is needed common-pool2
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.0</version>
        </dependency>-->
    </dependencies>

3. Create a sub module service base under common

3. In the module service base, create the configuration class of swagger

Create package com atguigu. servicebase. Config, create the class SwaggerConfig

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();

    }
    
    private ApiInfo webApiInfo(){

        return new ApiInfoBuilder()
                .title("website-Course Center API file")
                .description("This document describes the microservice interface definition of the course center")
                .version("1.0")
                .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com"))
                .build();
    }
}

4. Introduce service base into service module

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>service-base</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

5. Add annotations on the service edu startup class for testing

6. API model

You can add some custom settings, such as:
Define sample data

@ApiModelProperty(value = "Creation time", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;

@ApiModelProperty(value = "Update time", example = "2019-01-01 8:00:00")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;

5. Define interface description and parameter description

Defined on class: @ Api
Defined on method: @ ApiOperation
Defined on parameter: @ ApiParam

@Api(description="Instructor management")
@RestController
@RequestMapping("/admin/edu/teacher")
public class TeacherAdminController {

	@Autowired
	private TeacherService teacherService;

	@ApiOperation(value = "List of all instructors")
	@GetMapping
	public List<Teacher> list(){
		return teacherService.list(null);
	}

	@ApiOperation(value = "according to ID Delete instructor")
	@DeleteMapping("{id}")
	public boolean removeById(
			@ApiParam(name = "id", value = "lecturer ID", required = true)
			@PathVariable String id){
		return teacherService.removeById(id);
	}
}

03 - unified return result object

1, Unified return data format

In the project, we will package the response into json return. Generally, we will unify the data format of all interfaces to make the data operation of the front end (IOS, Android, web) more consistent and easy.
Generally, the unified return data format has no fixed format, as long as it can clearly describe the returned data status and the specific data to be returned. However, it usually includes status code, return message and data
For example, the basic data format required by our system is as follows:

List:

{
  "success": true,
  "code": 20000,
  "message": "success",
  "data": {
    "items": [
      {
        "id": "1",
        "name": "Lau Andy",
        "intro": "Graduated from the Mathematics Department of Normal University, he loves education and has taught mathematical thinking for more than 6 years"
      }
    ]
  }
}

Pagination:

{
  "success": true,
  "code": 20000,
  "message": "success",
  "data": {
    "total": 17,
    "rows": [
      {
        "id": "1",
        "name": "Lau Andy",
        "intro": "Graduated from the Mathematics Department of Normal University, he loves education and has taught mathematical thinking for more than 6 years"
      }
    ]
  }
}

No data returned:

{
  "success": true,
  "code": 20000,
  "message": "success",
  "data": {}
}

Failed:

{
  "success": false,
  "code": 20001,
  "message": "fail",
  "data": {}
}

Therefore, we define uniform results

{
  "success": Boolean, //Is the response successful
  "code": number, //Response code
  "message": character string, //Return message
  "data": HashMap //Return data in key value pairs
}

2, Create a unified result return class

1. Create a sub module common utils under the common module

2. Create interface definition return code

Create package com atguigu. Commonutils, create interface resultcode java

package com.atguigu.commonutils;

public interface ResultCode {

    public static Integer SUCCESS = 20000;

    public static Integer ERROR = 20001;
}

4. Create result class

Create class R.java

@Data
public class R {
    @ApiModelProperty(value = "Is it successful")
    private Boolean success;

    @ApiModelProperty(value = "Return code")
    private Integer code;

    @ApiModelProperty(value = "Return message")
    private String message;

    @ApiModelProperty(value = "Return data")
    private Map<String, Object> data = new HashMap<String, Object>();

    private R(){}

    public static R ok(){
        R r = new R();
        r.setSuccess(true);
        r.setCode(ResultCode.SUCCESS);
        r.setMessage("success");
        return r;
    }

    public static R error(){
        R r = new R();
        r.setSuccess(false);
        r.setCode(ResultCode.ERROR);
        r.setMessage("fail");
        return r;
    }

    public R success(Boolean success){
        this.setSuccess(success);
        return this;
    }

    public R message(String message){
        this.setMessage(message);
        return this;
    }

    public R code(Integer code){
        this.setCode(code);
        return this;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

    public R data(Map<String, Object> map){
        this.setData(map);
        return this;
    }
}

2, Unified use of returned results

1. Add dependency in service module

<dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>common_utils</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

2. Modify the returned results in the Controller

list

@ApiOperation(value = "List of all instructors")
@GetMapping
public R list(){
    List<Teacher> list = teacherService.list(null);
    return R.ok().data("items", list);
}

delete

@ApiOperation(value = "according to ID Delete instructor")
@DeleteMapping("{id}")
public R removeById(
    @ApiParam(name = "id", value = "lecturer ID", required = true)
    @PathVariable String id){
    teacherService.removeById(id);
    return R.ok();
}

04 - development of paging and conditional query interface

1, Pagination

1. Configuring paging plug-ins in MyBatisPlusConfig

/**
 * Paging plug-in
 */
@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}

2. Paging Controller method

Add paging method in TeacherAdminController

@ApiOperation(value = "Pagination instructor list")
@GetMapping("{page}/{limit}")
public R pageList(
    @ApiParam(name = "page", value = "Current page number", required = true)
    @PathVariable Long page,

    @ApiParam(name = "limit", value = "Records per page", required = true)
    @PathVariable Long limit){

    Page<Teacher> pageParam = new Page<>(page, limit);

    teacherService.page(pageParam, null);
    List<Teacher> records = pageParam.getRecords();
    long total = pageParam.getTotal();

    return  R.ok().data("total", total).data("rows", records);
}

This is more appropriate

    @ApiOperation(value = "Paging query instructor list")
    @GetMapping("getTeacherPage/{current}/{limit}")
    public R getTeacherPage(@PathVariable Long current,
                            @PathVariable Long limit){
        Page<EduTeacher> page = new Page<>(current,limit);
        teacherService.page(page,null);
        List<EduTeacher> records = page.getRecords();
        long total = page.getTotal();
        //1. Save to MAP
//        Map<String,Object> map = new HashMap<>();
//        map.put("list",records);
//        map.put("total",total);
//        return R.ok().data(map);
        //2. Direct splicing
        return R.ok().data("list",records).data("total",total);

    }

3. Testing in Swagger

2, Condition query

According to lecturer name, lecturer Title level and lecturer residence time gmt_create query

1. Create query object

Create com guli. edu. Query package, create teacherquery Java query object

package com.guli.edu.query;

@ApiModel(value = "Teacher Query object", description = "Instructor query object encapsulation")
@Data
public class TeacherQuery implements Serializable {

	private static final long serialVersionUID = 1L;

	@ApiModelProperty(value = "Teacher name,Fuzzy query")
	private String name;

	@ApiModelProperty(value = "Title 1 senior lecturer 2 chief lecturer")
	private Integer level;
    
	@ApiModelProperty(value = "Query start time", example = "2019-01-01 10:10:10")
	private String begin;//Note that the String type is used here, and the data passed from the front end does not need type conversion

	@ApiModelProperty(value = "Query end time", example = "2019-12-01 10:10:10")
	private String end;
}

2,service

Interface

package com.guli.edu.service;
public interface TeacherService extends IService<Teacher> {

	void pageQuery(Page<Teacher> pageParam, TeacherQuery teacherQuery);
}

realization

package com.guli.edu.service.impl;

@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {

	@Override
	public void pageQuery(Page<Teacher> pageParam, TeacherQuery teacherQuery) {

		QueryWrapper<Teacher> queryWrapper = new QueryWrapper<>();
		queryWrapper.orderByAsc("sort");

		if (teacherQuery == null){
			baseMapper.selectPage(pageParam, queryWrapper);
			return;
		}

		String name = teacherQuery.getName();
		Integer level = teacherQuery.getLevel();
		String begin = teacherQuery.getBegin();
		String end = teacherQuery.getEnd();

		if (!StringUtils.isEmpty(name)) {
			queryWrapper.like("name", name);
		}

		if (!StringUtils.isEmpty(level) ) {
			queryWrapper.eq("level", level);
		}

		if (!StringUtils.isEmpty(begin)) {
			queryWrapper.ge("gmt_create", begin);
		}

		if (!StringUtils.isEmpty(end)) {
			queryWrapper.le("gmt_create", end);
		}

		baseMapper.selectPage(pageParam, queryWrapper);
	}
}

3,controller

Modify pageList method in TeacherAdminController:
Add the parameter teacherquery, which is not required

@ApiOperation(value = "Pagination instructor list")
@GetMapping("{page}/{limit}")
public R pageQuery(
    @ApiParam(name = "page", value = "Current page number", required = true)
    @PathVariable Long page,

    @ApiParam(name = "limit", value = "Records per page", required = true)
    @PathVariable Long limit,

    @ApiParam(name = "teacherQuery", value = "Query object", required = false)
    TeacherQuery teacherQuery){

    Page<Teacher> pageParam = new Page<>(page, limit);

    teacherService.pageQuery(pageParam, teacherQuery);
    List<Teacher> records = pageParam.getRecords();
    long total = pageParam.getTotal();

    return  R.ok().data("total", total).data("rows", records);
}

4. Testing in Swagger

05 - add and modify instructor interface development

1, Auto fill package

1. Add in the service base module

Create a package handler and create an autofill class MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("gmtCreate", new Date(), metaObject);
        this.setFieldValByName("gmtModified", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("gmtModified", new Date(), metaObject);

    }
}

2. Add auto fill annotation to entity class

2, controller method definition

1. Add

	@ApiOperation(value = "New lecturer")
	@PostMapping
	public R save(
			@ApiParam(name = "teacher", value = "Lecturer object", required = true)
			@RequestBody Teacher teacher){

		teacherService.save(teacher);
		return R.ok();
	}

	

2. Query by id

@ApiOperation(value = "according to ID Query lecturer")
	@GetMapping("{id}")
	public R getById(
			@ApiParam(name = "id", value = "lecturer ID", required = true)
			@PathVariable String id){

		Teacher teacher = teacherService.getById(id);
		return R.ok().data("item", teacher);
	}

3. Modify according to id

@ApiOperation(value = "according to ID Modify instructor")
@PutMapping("{id}")
public R updateById(
    @ApiParam(name = "id", value = "lecturer ID", required = true)
    @PathVariable String id,

    @ApiParam(name = "teacher", value = "Lecturer object", required = true)
    @RequestBody Teacher teacher){

    teacher.setId(id);
    teacherService.updateById(teacher);
    return R.ok();
}

//Don't change the id

07 unified exception handling

1, What is unified exception handling

1. Manufacturing exception

Divide by 0

int a = 10/0;

2. What is unified exception handling

If we want the exception result to be displayed as a unified return result object and the exception information of the system to be handled uniformly, we need unified exception handling

2, Unified exception handling

1. Create unified exception handler

Create a unified exception handling class globalexceptionhandler in service base java:

/**
 * Unified exception handling class
 */
@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(Exception.class)
	@ResponseBody
	public R error(Exception e){
		e.printStackTrace();
		return R.error();
	}
}

2. Testing

Return unified error results

3, Handling specific exceptions

1. Add exception handling method

GlobalExceptionHandler. Add in Java

@ExceptionHandler(ArithmeticException.class)
@ResponseBody
public R error(ArithmeticException e){
    e.printStackTrace();
    return R.error().message("Custom exception executed");
}

2. Testing

4, Custom exception

1. Create custom exception class

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GuliException extends RuntimeException {

    @ApiModelProperty(value = "Status code")
    private Integer code;

    private String msg;
    
}

2. GuliException is thrown at the required position in the business

try {
    int a = 10/0;
}catch(Exception e) {
    throw new GuliException(20001,"Custom exception occurred");
}

3. Add exception handling method

GlobalExceptionHandler. Add in Java

@ExceptionHandler(GuliException.class)
@ResponseBody
public R error(GuliException e){
    e.printStackTrace();
    return R.error().message(e.getMsg()).code(e.getCode());
}

4. Testing

08 - unified log processing

1, Log

1. Configure log level

The behavior of the Logger is hierarchical, as shown in the following table:
It is divided into: OFF, FATAL, ERROR, WARN, INFO, DEBUG and ALL
By default, the log level printed by spring boot from the console is only INFO or above. You can configure the log level

# Set log level
logging.level.root=WARN

The output of important methods can be added info
 If you want to debug the parameters of the entry method, just take a look debug

Abnormal try crash Time error 

This method can only print logs on the console

2, Logback log

spring boot uses Logback internally as the framework for log implementation.
Logback is very similar to log4j. If you are familiar with log4j, you will be handy with logback soon.
Some advantages of logback over log4j: https://blog.csdn.net/caisini_vc/article/details/48551287

1. Configure logback logs

Delete application Log configuration in properties
Install the idea color log plug-in: grep console
Create logback spring in resources xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration  scan="true" scanPeriod="10 seconds">
    <!-- The log level is from low to high TRACE < DEBUG < INFO < WARN < ERROR < FATAL,If set to WARN,Is lower than WARN No information will be output -->
    <!-- scan:When this property is set to true If the configuration file changes, it will be reloaded. The default value is true -->
    <!-- scanPeriod:Set the time interval for monitoring whether the configuration file is modified. If no time unit is given, the default unit is milliseconds. When scan by true This property takes effect when. The default interval is 1 minute. -->
    <!-- debug:When this property is set to true When, it will be printed out logback Internal log information, real-time viewing logback Running status. The default value is false.  -->

    <contextName>logback</contextName>
    <!-- name The value of is the name of the variable, value The value defined by the variable. Values defined by are inserted into the logger In context. After defining variables, you can make“ ${}"To use variables. -->
    <property name="log.path" value="D:/guli_log/edu" />

    <!-- Color log -->
    <!-- Configure format variables: CONSOLE_LOG_PATTERN Color log format -->
    <!-- magenta:Magenta -->
    <!-- boldMagenta:Coarse red-->
    <!-- cyan:Cyan -->
    <!-- white:white -->
    <!-- magenta:Magenta -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--Output to console-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <!--This log appender It is used for development. Only the lowest level is configured. The log level output from the console is log information greater than or equal to this level-->
        <!-- For example, if you configure INFO Level, even if other locations are configured DEBUG Level logs will not be output -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
            <!-- Set character set -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>


    <!--output to a file-->

    <!-- Time scrolling output level by INFO journal -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_info.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- Daily log archive path and format -->
            <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records info Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- Time scrolling output level by WARN journal -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_warn.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- Set character set here -->
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records warn Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>


    <!-- Time scrolling output level by ERROR journal -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- The path and file name of the log file being recorded -->
        <file>${log.path}/log_error.log</file>
        <!--Log file output format-->
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset> <!-- Set character set here -->
        </encoder>
        <!-- The rolling strategy of the logger, recording by date and by size -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--Log file retention days-->
            <maxHistory>15</maxHistory>
        </rollingPolicy>
        <!-- This log file only records ERROR Rank -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--
        <logger>Used to set the log printing level of a package or a specific class, and specify<appender>. 
        <logger>Only one name Properties,
        An optional level And an optional addtivity Properties.
        name:Used to specify that this logger A package or a specific class of constraints.
        level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,
              If this property is not set, the current logger Will inherit the level of the superior.
    -->
    <!--
        use mybatis When I was young, sql Statement is debug It will not print until next, and here we only configure it info,So I want to see sql Statement, there are two operations:
        First handle<root level="INFO">Change to<root level="DEBUG">This will print sql,But there will be a lot of other messages in the log
        The second is to give it alone mapper Directory configuration under DEBUG Mode, the code is as follows, which is configured in this way sql The statement will print, and others are normal DEBUG Level:
     -->
    <!--development environment :Print Console -->
    <springProfile name="dev">
        <!--You can output data in a project debug Logs, including mybatis of sql journal-->
        <logger name="com.guli" level="INFO" />

        <!--
            root Node is a required node. It is used to specify the most basic log output level. There is only one node level attribute
            level:Used to set the print level, regardless of case: TRACE, DEBUG, INFO, WARN, ERROR, ALL and OFF,The default is DEBUG
            Can contain zero or more appender Element.
        -->
        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="WARN_FILE" />
            <appender-ref ref="ERROR_FILE" />
        </root>
    </springProfile>


    <!--production environment :output to a file-->
    <springProfile name="pro">

        <root level="INFO">
            <appender-ref ref="CONSOLE" />
            <appender-ref ref="DEBUG_FILE" />
            <appender-ref ref="INFO_FILE" />
            <appender-ref ref="ERROR_FILE" />
            <appender-ref ref="WARN_FILE" />
        </root>
    </springProfile>

</configuration>

2. Output error log to file

GlobalExceptionHandler. In Java
Class

@Slf4j

Exception output statement

 log.error(e.getMessage());

3. Output log stack information to a file

Define tool classes
Create util package under Guli framework common and create exceptionutil Java tool class

package com.guli.common.util;

public class ExceptionUtil {

	public static String getMessage(Exception e) {
		StringWriter sw = null;
		PrintWriter pw = null;
		try {
			sw = new StringWriter();
			pw = new PrintWriter(sw);
			// Output the wrong stack information to printWriter
			e.printStackTrace(pw);
			pw.flush();
			sw.flush();
		} finally {
			if (sw != null) {
				try {
					sw.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
			if (pw != null) {
				pw.close();
			}
		}
		return sw.toString();
	}
}

call

log.error(ExceptionUtil.getMessage(e));

Create toString method in GuliException

@Override
public String toString() {
    return "GuliException{" +
        "message=" + this.getMessage() +
        ", code=" + code +
        '}';
}

Topics: Java Apache Spring