log模块配置

logback配置全解析

作者:muggle
Logback是由log4j创始人设计的另一个开源日志组件,分为三个模块:

  1. logback-core:其它两个模块的基础模块

  2. logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging

  3. logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能
    在springboot中我们通过xml配置来操作logback

springboot中logback的默认配置文件名称为logback-spring.xml,若需要指定xml名称,需在application.properties(application.yml)中配置logging.config=xxxx.xml
现在贴出一份logback的xml配置,可直接使用,懒得看的小伙伴复制粘贴到你的项目中去体验吧






    

    
    

    
        
            
                %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight([%-5level]) %logger - %msg%n
            
        
    
    
        
            
                ${log_dir}/%d{yyyy-MM-dd}-poseidon.log
            
            ${maxHistory}
        
        
            
                %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight([%-5level]) %logger - %msg%n
            
        
    

    

        
            ${log_dir}/runningTime/%d{yyyy-MM-dd}-poseidon.log
            ${maxHistory}
        
        
            %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n
        
    
    
        
            
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %logger - %msg%n
            
        
    
    
        
        
    

    
        
            
                com.mysql.cj.jdbc.Driver
                jdbc:mysql://xxx/xxxx?characterEncoding=UTF-8
                xx
                xxxx
            
        
        
    
    
    
    
    
        
    
    
        
        
        
    
    
        
        
        
        
    

我们可以看到xml中有四种节点
appender,logger,root,configuration

节点解读

configuration包含三个属性:

  1. scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  2. scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  3. debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

Logger作为日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。

Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、PostreSQL、 Oracle和其他数据库、 JMS和远程UNIX Syslog守护进程等。

root 就是最高级别logger,所有不被指定logger的日志都归root管理。

在slf4j框架下我们使用log是这样的:

 private static final Logger logger= LoggerFactory.getLogger(xxx.class);

或者

 private static final Logger logger= LoggerFactory.getLogger("xxxx");

可以理解为代码中的getLogger() 方法就是获取xml配置中的logger,如果没有配置相应的logger则为root
比如我配置了:


        
        

那我在获得一个logger时可以这样获得它:

 private static final Logger logger= LoggerFactory.getLogger("hhh");

我所输出的日志将被这个logger所管理
logger 上有三个配置 name level additivity
name就是这个logger的名称,level就是这个日志过滤的级别,低于这个级别的日志不输入到对应的appender中;additivity是否向上级logger传递打印信息,默认是true。logger中可以配置多个appender-ref,也就是可以指定多个输出地点。
而root只是特殊的logger,用法上无差别

appender节点:
appender节点是logback配置的关键,其name属性指定其名称,class属性指定实现类,对应得实现类有

ch.qos.logback.core.ConsoleAppender // 以控制台作为输出
ch.qos.logback.core.rolling.RollingFileAppender//以日志文件作为输出
ch.qos.logback.classic.db.DBAppender//以数据库作为输出
net.logstash.logback.appender.LogstashTcpSocketAppender//以logstash作为输出需要引入如下依赖:
ch.qos.logback.classic.AsyncAppender//异步输出 需要定义appender-ref

// logstash依赖

  net.logstash.logback
  logstash-logback-encoder
  4.11

所有的appender 实现ch.qos.logback.core.Appender接口或者 ch.qos.logback.core.UnsynchronizedAppenderBase接口(异步),我们也可以自定义appender来指定日志输出;

在Appender中可以定义哪些节点我们一个个来看:

第一种: ConsoleAppender
如同它的名字一样,这个Appender将日志输出到console,更准确的说是System.out 或者System.err。
它包含的参数如下:

Property Name Type Description
encoder Encoder 通常在其pattern里指定日志格式 如: %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight([%-5level]) %logger - %msg%n表示 日期格式 日志级别(高亮)logger的名称 logger的message
target String 指定输出目标。可选值:System.out 或 System.err。默认值:System.out
withJansi boolean 是否支持ANSI color codes(类似linux中的shell脚本的输出字符串颜色控制代码)。默认为false。如果设置为true。例如:[31m 代表将前景色设置成红色。在windows中,需要提供"org.fusesource.jansi:jansi:1.9",而在linux,mac os x中默认支持。

第二种: FileAppender
将日志输出到文件当中,目标文件取决于file属性。是否追加输出,取决于append属性。

Property Name Type Description
append boolean 是否以追加方式输出。默认为true。
encoder Encoder See OutputStreamAppender properties.
file String 指定文件名。注意在windows当中,反斜杠 \ 需要转义,或直接使用 / 也可以。例如 c:/temp/test.logor 或 c:\temp\test.log 都可以。没有默认值,如果上层目录不存在,FileAppender会自动创建。
prudent boolean 是否工作在谨慎模式下。在谨慎模式下,FileAppender将会安全写入日志到指定文件,即时在不同的虚拟机jvm中有另一个相同的FileAppender实例。默认值:fales;设置为true,意味着append会被自动设置成true。prudent依赖于文件排它锁。实验表明,使用文件锁,会增加3倍的日志写入消耗。比如说,当prudent模式为off,写入一条日志到文件只要10毫秒,但是prudent为真,则会接近30毫秒。prudent 模式实际上是将I/O请求序列化,因此在I/O数量较大,比如说100次/s或更多的时候,带来的延迟也会显而易见,所以应该避免。在networked file system(远程文件系统)中,这种消耗将会更大,可能导致死锁。

第三个: RollingFileAppender

RollingFileAppender继承自FileAppender,提供日志目标文件自动切换的功能。例如可以用日期作为日志分割的条件。
RollingFileAppender有两个重要属性,RollingPolicy负责怎么切换日志,TriggeringPolicy负责何时切换。为了使RollingFileAppender起作用,这两个属性必须设置,但是如果RollingPolicy的实现类同样实现了TriggeringPolicy接口,则也可以只设置RollingPolicy这个属性。
下面是它的参数:

Property Name Type Description
file String 指定文件名。注意在windows当中,反斜杠 \ 需要转义,或直接使用 / 也可以。例如 c:/temp/test.logor 或 c:\temp\test.log 都可以。没有默认值,如果上层目录不存在,FileAppender会自动创建。
append boolean 是否以追加方式输出。默认为true。
encoder Encoder See OutputStreamAppender properties.
rollingPolicy RollingPolicy 当发生日志切换时,RollingFileAppender的切换行为。例如日志文件名的修改
triggeringPolicy TriggeringPolicy 决定什么时候发生日志切换,例如日期,日志文件大小到达一定值
prudent boolean FixedWindowRollingPolicy 不支持prudent模式。TimeBasedRollingPolicy 支持prudent模式,但是需要满足一下两条约束:在prudent模式中,日志文件的压缩是不被允许,不被支持的。不能设置file属性。

第四个:SocketAppender及SSLSocketAppender(未尝试过)

到目前为止我们讲的appender都只能将日志输出到本地资源。与之相对的,SocketAppender就是被设计用来输出日志到远程实例中的。SocketAppender输出日志采用明文方式,SSLSocketAppender则采用加密方式传输日志。
被序列化的日志事件的类型是 LoggingEventVO 继承ILoggingEvent接口。远程日志记录并非是侵入式的。在反序列化接收后,日志事件就可以好像在本地生成的日志一样处理了。多个SockerAppender可以向同一台日志服务器发送日志。SocketAppender并不需要关联一个Layout,因为它只是发送序列化的日志事件给远程日志服务器。SocketAppender的发送操作是基于TCP协议的。因此如果远程服务器是可到达的,则日志会被其处理,如果远程服务器宕机或不可到达,那么日志将会被丢弃。等到远程服务器复活,日志发送将会透明的重新开始。这种透明式的重连,是通过一个“连接“线程周期性的尝试连接远程服务器实现的。
Logging events会由TCP协议实现自动缓冲。这意味着,如果网络速度比日志请求产生速度快,则网络速度并不会影响应用。但如果网络速度过慢,则网络速度则会变成限制,在极端情况下,如果远程日志服务器不可到达,则会导致应用最终阻塞。不过,如果服务器可到达,但是服务器宕机了,这种情况,应用不会阻塞,而只是丢失一些日志事件而已。
需要注意的是,即使SocketAppender没有被logger链接,它也不会被gc回收,因为他在connector thread中任然存在引用。一个connector thread 只有在网络不可达的情况下,才会退出。为了防止这个垃圾回收的问题,我们应该显示声明关闭SocketAppender。长久存活并创建/销毁大量的SocketAppender实例的应用,更应该注意这个问题。不过大多数应用可以忽略这个问题。如果JVM在SocketAppender关闭之前将其退出,又或者是被垃圾回收,这样子可能导致丢失一些还未被传输,在管道中等待的日志数据。为了防止避免日志丢失,经常可靠的办法就是调用SocketAppender的close方法,或者调用LoggerContext的stop方法,在退出应用之前。

下面我们来看看SocketAppender的属性:

Property Name Type Description
includeCallerData boolean 是否包含调用者的信息如果为true,则以下日志输出的 ?:? 会替换成调用者的文件名跟行号,为false,则为问号。2019-01-06 17:37:30,968 DEBUG [Thread-0] [?:?] chapters.appenders.socket.SocketClient2 - Hi
port int 端口号
reconnectionDelay Duration 重连延时,如果设置成“10 seconds”,就会在连接u武器失败后,等待10秒,再连接。默认值:“30 seconds”。如果设置成0,则关闭重连功能。
queueSize int 设置缓冲日志数,如果设置成0,日志发送是同步的,如果设置成大于0的值,会将日志放入队列,队列长度到达指定值,在统一发送。可以加大服务吞吐量。
eventDelayLimit Duration 设置日志超时丢弃时间。当设置“10 seconds”类似的值,如果日志队列已满,而服务器长时间来不及接收,当滞留时间超过10 seconds,日志就会被丢弃。默认值: 100 milliseconds
remoteHost String 远程日志服务器的IP
ssl SSLConfiguration 只在SSLSocketAppender包含该属性节点。提供SSL配置,详情见 Using SSL.

标准的Logback Classic包含四个可供使用的Receiver用来接收来自SocketAppender的logging evnets。

第五个: SMTPAppender

SMTPAppender 可以将logging event存放在一个或多个固定大小的缓冲区中,然后在用户指定的event到来之时,将适当的大小的logging event以邮件方式发送给运维人员。
详细属性如下:

Property Name Type Description
smtpHost String SMTP server的地址,必需指定。如网易的SMTP服务器地址是: smtp.163.com
smtpPort int SMTP server的端口地址。默认值:25
to String 指定发送到那个邮箱,可设置多个属性,指定多个目的邮箱
from String 指定发件人名称。如果设置成“muggle ”,则邮件发件人将会是“muggle [email protected]
subject String 指定emial的标题,它需要满足PatternLayout中的格式要求。如果设置成“Log: %logger - %msg”,就案例来讲,则发送邮件时,标题为“Log: com.foo.Bar - Hello World ”。 默认值:"%logger{20} - %m".
discriminator Discriminator 通过Discriminator, SMTPAppender可以根据Discriminator的返回值,将到来的logging event分发到不同的缓冲区中。默认情况下,总是返回相同的值来达到使用一个缓冲区的目的。
evaluator IEvaluator 指定触发日志发送的条件。通过指定EventEvaluator接口的实现类。默认情况下SMTPAppeender使用的是OnErrorEvaluator,表示当发送ERROR或更高级别的日志请求时,发送邮件。Logback提供了几个evaluators:OnErrorEvaluator、OnMarkerEvaluator、JaninoEventEvaluator、GEventEvaluator(功能强大)
cyclicBufferTracker CyclicBufferTracker 指定一个cyclicBufferTracker跟踪cyclic buffer。它是基于discriminator的实现的。如果你不指定,默认会创建一个CyclicBufferTracker ,默认设置cyclic buffer大小为256。你也可以手动指定使用默认的CyclicBufferTracker,并且通过属性修改默认的缓冲区接收多少条logging event。
username String 发送邮件账号,默认为null
password String 发送邮件密码,默认为null
STARTTLS boolean 如果设置为true,appender会尝试使用STARTTLS命令,如果服务端支持,则会将明文连接转换成加密连接。需要注意的是,与日志服务器连接一开始是未加密的。默认值:false
SSL boolean 如果设置为true,appender将会使用SSL连接到日志服务器。 默认值:false
charsetEncoding String 指定邮件信息的编码格式 默认值:UTF-8
localhost String 如果smtpHost没有正确配置,比如说不是完整的地址。这时候就需要localhost这个属性提供服务器的完整路径(如同java中的完全限定名 ),详情参考com.sun.mail.smtp 中的mail.smtp.localhost属性
asynchronousSending boolean 这个属性决定email的发送是否是异步。默认:true,异步发送但是在某些情况下,需要以同步方式发送错误日志的邮件给管理人员,防止不能及时维护应用。
includeCallerData boolean 默认:false 指定是否包含callerData在日志中
sessionViaJNDI boolean SMTPAppender依赖javax.mail.Session来发送邮件。默认情况下,sessionViaJNDI为false。javax.mail.Session实例的创建依赖于SMTPAppender本身的配置信息。如果设置为true,则Session的创建时通过JNDI获取引用。这样做的好处可以让你的代码复用更好,让配置更简洁。需要注意的是,如果使用JNDI获取Session对象,需要保证移除mail.jar以及activation.jar这两个jar包
jndiLocation String 如果sessionViaJNDI设置为true,则jndiLocation指定JNDI的资源名,默认值为:"java:comp/env/mail/Session"

SMTPAppender只保留最近的256条logging events 在循环缓冲区中,当缓冲区慢,就会开始丢弃最老的logging event。因此不管什么时候,SMTPAppender一封邮件最多传递256条日志事件。SMTPAppender依赖于JavaMail API。而JavaMail API又依赖于IOC框架(依赖注入)。

第六个:DBAppender

DBAppender 可以将日志事件插入到3张数据表中。它们分别是logging_event,logging_event_property,logging_event_exception。这三张数据表必须在DBAppender工作之前存在。它们的sql脚本可以在 logback-classic/src/main/java/ch/qos/logback/classic/db/script folder 这个目录下找到。这个脚本对大部分SQL数据库都是有效的,除了少部分,少数语法有差异需要调整。
下面是logback与常见数据库的支持信息:

RDBMS tested version(s) tested JDBC driver version(s) supports getGeneratedKeys() method is a dialect provided by logback
DB2 untested untested unknown NO
H2 -- - unknown YES
HSQL -- - NO YES
Microsoft SQL Server -- -- YES YES
MySQL 5.7 YES YES
PostgreSQL -- -- NO YES
Oracle -- -- YES YES
SQLLite -- - unknown YES
Sybase -- - unknown YES

下面给出三张表的sql语句:

BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;

BEGIN;
CREATE TABLE logging_event
  (
    timestmp         BIGINT NOT NULL,
    formatted_message  TEXT NOT NULL,
    logger_name       VARCHAR(254) NOT NULL,
    level_string      VARCHAR(254) NOT NULL,
    thread_name       VARCHAR(254),
    reference_flag    SMALLINT,
    arg0              VARCHAR(254),
    arg1              VARCHAR(254),
    arg2              VARCHAR(254),
    arg3              VARCHAR(254),
    caller_filename   VARCHAR(254) NOT NULL,
    caller_class      VARCHAR(254) NOT NULL,
    caller_method     VARCHAR(254) NOT NULL,
    caller_line       CHAR(4) NOT NULL,
    event_id          BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
  );
COMMIT;


BEGIN;
CREATE TABLE logging_event_property
  (
    event_id       BIGINT NOT NULL,
    mapped_key        VARCHAR(254) NOT NULL,
    mapped_value      TEXT,
    PRIMARY KEY(event_id, mapped_key),
    FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  );
COMMIT;


BEGIN;
CREATE TABLE logging_event_exception
  (
    event_id         BIGINT NOT NULL,
    i                SMALLINT NOT NULL,
    trace_line       VARCHAR(254) NOT NULL,
    PRIMARY KEY(event_id, i),
    FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  );
COMMIT;

第七个: AsyncAppender

AsyncAppender记录ILoggingEvents的方式是异步的。它仅仅相当于一个event分配器,因此需要配合其他appender才能有所作为。

需要注意的是:AsyncAppender将event缓存在 BlockingQueue ,一个由AsyncAppender创建的工作线程,会一直从这个队列的头部获取events,然后将它们分配给与AsyncAppender唯一关联的Appender中。默认情况下,如果这个队列80%已经被占满,则AsyncAppender会丢弃等级为 TRACE,DEBUG,INFO这三个等级的日志事件。
在应用关闭或重新部署的时候,AsyncAppender一定要被关闭,目的是为了停止,回收再利用worker thread,和刷新缓冲队列中logging events。那如果关闭AsyncAppender呢?可以通过关闭LoggerContext来关闭所有appender,当然也包括AsyncAppender了。AsyncAppender会在maxFlushTime属性设置的时间内等待Worker thread刷新全部日志event。如果你发现缓冲的event在关闭LoggerContext的时候被丢弃,这时候你就也许需要增加等待的时间。将maxFlushTime设置成0,就是AsyncAppender一直等待直到工作线程将所有被缓冲的events全部刷新出去才执行才结束。
根据JVM退出的模式,工作线程worker thread处理被缓冲的events的工作是可以被中断的,这样就导致了剩余未处理的events被搁浅。这种现象通常的原因是当LoggerContext没有完全关闭,或者当JVM终止那些非典型的控制流(不明觉厉)。为了避免工作线程的因为这些情况而发生中断,一个shutdown hook(关闭钩子)可以被插入到JVM运行的时候,这个钩子的作用是在JVM开始shutdown刚开始的时候执行关闭 LoggerContext的任务。

下面是AsyncAppender的属性表

Property Name Type Description
queueSize int 设置blocking queue的最大容量,默认是256条events
discardingThreshold int 默认,当blocking queue被占用80%以上,AsyncAppender就会丢弃level为 TRACE,DEBUG,INFO的日志事件,如果要保留所有等级的日志,需要设置成0
includeCallerData boolean 提取CallerData代价比较昂贵,为了提高性能,caller data默认不提供。只有一些获取代价较低的数据,如线程名称,MDC值才会被保留。如果设置为true,就会包含caller data
maxFlushTime int 设置最大等待刷新事件,单位为miliseconds(毫秒)。当LoggerContext关闭的时候,AsyncAppender会在这个时间内等待工作线程完成events的flush工作,超时未处理的events将会被抛弃。
neverBlock boolean 默认为false,如果队列被填满,为了处理所有日志,就会阻塞的应用。如果为true,为了不阻塞你的应用,也会选择抛弃一些message。

默认情况下,event queue最大的容量是256。如果队列被填充满那么就会阻塞你的应用,直到队列能够容纳新的logging event。所以当AsyncAppender工作在队列满的情况下,可以称作伪同步。
在以下四种情况下容易导致AsyncAppender伪同步状态的出现:

  1. 应用中存在大量线程
  2. 每秒产生大量的logging events
  3. 每一个logging event都存在大量的数据
  4. 子appender中存在很高的延迟

为了避免伪同步的出现,提高queueSizes普遍有效,但是就消耗了应用的可用内存。

下面列出一些 appender配置示例:



  
    myapp.log
    
      %logger{35} - %msg%n
    
  

  
    
  

  
    
  




  
    
      
        com.mysql.jdbc.Driver
        jdbc:mysql://${serverName}:${port}/${dbName}
        ${user}
        ${password}
      
    
  

  
    
  



  
    smtp.gmail.com
    465
    true
    [email protected]
    YOUR_GMAIL_PASSWORD

    EMAIL-DESTINATION
    ANOTHER_EMAIL_DESTINATION 
    [email protected]
    TESTING: %logger{20} - %m
    
      %date %-5level %logger{35} - %message%n
    
  

  
    
  



  
    smtp.gmail.com
    587
    true
    [email protected]
    YOUR_GMAIL_xPASSWORD

    EMAIL-DESTINATION
    ANOTHER_EMAIL_DESTINATION 
    [email protected]
    TESTING: %logger{20} - %m
    
      %date %-5level %logger - %message%n
    
  

  
    
  


SimpleSocketServer需要两个命令行参数,port 和 configFile路径。(该方法待验证)
java ch.qos.logback.classic.net.SimpleSocketServer 6000 \ src/main/java/chapters/appenders/socket/server1.xml

客户端的SocketAppender的简单配置例子:


  
    192.168.0.101
    8888
    10000
    true
  

  
    
  



在服务端使用SimpleSSLSocketServer
java -Djavax.net.ssl.keyStore=src/main/java/chapters/appenders/socket/ssl/keystore.jks \ -Djavax.net.ssl.keyStorePassword=changeit \ ch.qos.logback.classic.net.SimpleSSLSocketServer 6000 \ src/main/java/chapters/appenders/socket/ssl/server.xml

SSLSocketAppender配置


  
    ${host}
    ${port}
    10000
    
      
        ${truststore}
        ${password}
      
    
  

  
    
  





  
    testFile.log
    true
    
    
      %-4relative [%thread] %-5level %logger{35} - %msg%n
    
  

  
    
  

参考:https://blog.csdn.net/tianyaleixiaowu/article/details/73327752

下面基于logback配置做一个请求日志的的封装

功能:记录每次请求的参数和用户ID存入数据库或者elk
问题:javaee规范中request输入输出流都只能被读取一次,所以如果用过滤器或者拦截器读取request中的流都会导致后面的controller无法接受到数据。
所以我们要用原生的aop获得请求参数,切点为controller,这就很好的避开了以上问题。

package com.muggle.poseidon.core.aspect;

import com.muggle.poseidon.manager.UserInfoManager;
import com.muggle.poseidon.utils.RequestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;

/**
 * @program: hiram_erp
 * @description: 日志信息切面
 * @author: muggle
 * @create: 2019-02-21
 **/
@Aspect
@Component
public class LogMessageAspect {

    private final static Logger logger = LoggerFactory.getLogger("requestLog");
//    private final static Logger timeLog = LoggerFactory.getLogger(LogMessageAspect.class);
    private static final ThreadLocal threadLocal = new ThreadLocal<>();
    @Pointcut("execution(public * com.hiram.erp.controller.*.*(..))")
    public void webLog() {}

    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
//        System.out.println("sssssssssssssssssssssssssssssssssssssssssssssssssssss");
       /* // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 打印请求相关参数
        // 打印请求 url
        // 请求id
        Long userId=null;
        if (user!=null){
            userId=user.getUserInfo().getUserId();
        }
        logger.info("URL : {}, 登录id: {} ,HTTP Method: {},ip :{},Request Args : {}", request.getRequestURL().toString(),userId, request.getMethod(),request.getRemoteAddr());
*/    }

    /**
     * 在切点之后织入
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter(JoinPoint joinPoint) throws Throwable {



    }

    /**
     * 环绕
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        // 打印出参
//        logger.info("Response Args  : {},", JSONObject.toJSONString(result),new Date());
        // 执行耗时
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        HttpServletResponse response = attributes.getResponse();
        String requestURL = request.getRequestURL().toString();
        if (requestURL.contains("/sys/log_info/")){
            return result;
        }
        // 打印请求相关参数
        // 打印请求 url
        // 请求id
        String userId = UserInfoManager.getUserId();

        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String remoteAddr = RequestUtils.getIpAddr(request);
        Object[] args = joinPoint.getArgs();
//        List objects=new ArrayList<>();
        StringBuilder stringBuilder = new StringBuilder();
        for (int i=0;i 
 

对于数据库存储,如果我们希望log存在另外一个数据库中不存在项目里的数据库中,并且可以通过持久化框架查询数据库内信息。我们则可以配置多数据源,如果将日志放在同一个数据库中则直接配置appender就行了,很方便。
多数据源配置mybatis版:

其原理是配置多个sessionfactory,然后根据不同的mapperscan来区分不同mapper对应的数据库

以druid连接池为例

application.yml

log:
  datasource:
    druid:
      url: ${mysql_url}/log?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
      username:
      password:
      driver-class-name: com.mysql.cj.jdbc.Driver
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      filters: stat,wall
      initialSize: 5
      maxActive: 20
      maxPoolPreparedStatementPerConnectionSize: 20
      maxWait: 60000
      minIdle: 5
      poolPreparedStatements: true
      testOnBorrow: false
      testOnReturn: false
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1


spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: ${mysql_url}/hiram_erp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
      username:
      password:
      driver-class-name: com.mysql.cj.jdbc.Driver
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      filters: stat,wall
      initialSize: 5
      maxActive: 20
      maxPoolPreparedStatementPerConnectionSize: 20
      maxWait: 60000
      minIdle: 5
      poolPreparedStatements: true
      testOnBorrow: false
      testOnReturn: false
      testWhileIdle: true
      timeBetweenEvictionRunsMillis: 60000
      validationQuery: SELECT 1

@Configuration
// 主数据库配置 指定mapper位置
@MapperScan(basePackages = {"com.muggle.poseidon.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate")
public class ManySourceDBConfig {

    @Bean(name = "dataSource")
    // 读取application的配置信息
   @ConfigurationProperties(prefix = "spring.datasource.druid")
   // 最高优先级,表示系统默认使用该配置
    @Primary
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();

        List filterList = new ArrayList<>();

        filterList.add(wallFilter());

        druidDataSource.setProxyFilters(filterList);

        return druidDataSource;
    }

    @Bean(name = "sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(this.dataSource());

        Properties props = new Properties();
        props.setProperty("localCacheScope", "true");
        props.setProperty("lazyLoadingEnabled", "true");
        props.setProperty("aggressiveLazyLoading", "false");
        props.setProperty("jdbcTypeForNull", "NULL");
        sqlSessionFactoryBean.setConfigurationProperties(props);
        sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
        //pageHelper
        Properties properties = new Properties();
        properties.setProperty("reasonable", "true");
        properties.setProperty("supportMethodsArguments", "true");
        properties.setProperty("params", "count=countSql");
        properties.setProperty("pageSizeZero", "true");
        PageInterceptor interceptor = new PageInterceptor();
        interceptor.setProperties(properties);
        sqlSessionFactoryBean.setPlugins(new Interceptor[]{interceptor});
        sqlSessionFactoryBean.setTypeAliasesPackage("com.muggle.poseidon.model");
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:/mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = "transactionManager")
    @Primary
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(this.dataSource());
    }

    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public ServletRegistrationBean statViewServlet() {
        ServletRegistrationBean druid = new ServletRegistrationBean();
        druid.setServlet(new StatViewServlet());
        druid.setUrlMappings(Collections.singletonList("/druid/*"));
        Map params = new HashMap<>();
        params.put("loginUsername", "");
        params.put("loginPassword", "");
        druid.setInitParameters(params);
        return druid;
    }

    @Bean
    public FilterRegistrationBean webStatFilter() {
        FilterRegistrationBean fitler = new FilterRegistrationBean();
        fitler.setFilter(new WebStatFilter());
        fitler.setUrlPatterns(Collections.singletonList("/*"));
        fitler.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return fitler;
    }

    @Bean
    public WallFilter wallFilter() {

        WallFilter wallFilter = new WallFilter();

        wallFilter.setConfig(wallConfig());

        return wallFilter;

    }

    @Bean
    public WallConfig wallConfig() {

        WallConfig config = new WallConfig();

        config.setMultiStatementAllow(true);//允许一次执行多条语句

        config.setNoneBaseStatementAllow(true);//允许非基本语句的其他语句

        return config;

    }

    @Bean
    public ProcessEngineConfiguration processEngineConfiguration() {
        ProcessEngineConfiguration pec = StandaloneProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
        pec.setDataSource(dataSource());
        //如果表不存在,自动创建表
        pec.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
        //属性asyncExecutorActivate定义为true,工作流引擎在启动时就建立启动async executor线程池
        pec.setAsyncExecutorActivate(false);
        return pec;
    }


    @Bean
    public ProcessEngine processEngine() {
        return processEngineConfiguration().buildProcessEngine();
    }

}

log数据库配置




/**
 * @program:
 * @description:
 * @author: muggle
 * @create: 2019-02-23
 **/
@Configuration
// 注意确保主配置无法扫描到这个包
@MapperScan(basePackages = "com.muggle.poseidon.logmapper", sqlSessionTemplateRef  = "test1SqlSessionTemplate")

public class LogDBConfig  {
    @Bean(name = "test1DataSource")
    @ConfigurationProperties(prefix = "log.datasource.druid")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();

        List filterList = new ArrayList<>();

        filterList.add(wallFilter());

        druidDataSource.setProxyFilters(filterList);

        return druidDataSource;
    }

    @Bean(name = "test1SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new
        // mapper位置,不要和主配置的mapper放到一起
         PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/log/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "test1TransactionManager")
    public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "test1SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean
    public WallFilter wallFilter() {

        WallFilter wallFilter = new WallFilter();

        wallFilter.setConfig(wallConfig());

        return wallFilter;

    }
    @Bean
    public WallConfig wallConfig() {

        WallConfig config = new WallConfig();

        config.setMultiStatementAllow(true);//允许一次执行多条语句

        config.setNoneBaseStatementAllow(true);//允许非基本语句的其他语句

        return config;

    }
}

多数据源jpa版

package com.muggle.poseidon.config;


import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    /**
     * 扫描spring.datasource.primary开头的配置信息
     *
     * @return 数据源配置信息
     */
    @Primary
    @Bean(name = "primaryDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 获取主库数据源对象
     *
     * @param properties 注入名为primaryDataSourceProperties的bean
     * @return 数据源对象
     */
    @Primary
    @Bean(name = "primaryDataSource")
    public DataSource dataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    /**
     * 该方法仅在需要使用JdbcTemplate对象时选用
     *
     * @param dataSource 注入名为primaryDataSource的bean
     * @return 数据源JdbcTemplate对象
     */
    @Primary
    @Bean(name = "primaryJdbcTemplate")
    public JdbcTemplate jdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }




}

package com.muggle.poseidon.config;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Map;


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        // repository包名
        basePackages = "com.muggle.poseidon.repos",
        // 实体管理bean名称
        entityManagerFactoryRef = "primaryEntityManagerFactory",
        // 事务管理bean名称
        transactionManagerRef = "primaryTransactionManager"
)
public class MainDataBaseConfig {

    /**
     * 扫描spring.jpa.primary开头的配置信息
     *
     * @return jpa配置信息
     */
    @Primary
    @Bean(name = "primaryJpaProperties")
    @ConfigurationProperties(prefix = "spring.jpa")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }

    /**
     * 获取主库实体管理工厂对象
     *
     * @param primaryDataSource 注入名为primaryDataSource的数据源
     * @param jpaProperties     注入名为primaryJpaProperties的jpa配置信息
     * @param builder           注入EntityManagerFactoryBuilder
     * @return 实体管理工厂对象
     */
    @Primary
    @Bean(name = "primaryEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("primaryDataSource") DataSource primaryDataSource
            , @Qualifier("primaryJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) {
        return builder
                // 设置数据源
                .dataSource(primaryDataSource)
                // 设置jpa配置
                .properties(jpaProperties.getProperties())
                // 设置hibernate配置
                .properties(jpaProperties.getHibernateProperties(new HibernateSettings()))
                // 设置实体包名
                .packages("com.muggle.poseidon.model")
                // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }

    /**
     * 获取实体管理对象
     *
     * @param factory 注入名为primaryEntityManagerFactory的bean
     * @return 实体管理对象
     */
    @Primary
    @Bean(name = "primaryEntityManager")
    public EntityManager entityManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) {
        return factory.createEntityManager();
    }

    /**
     * 获取主库事务管理对象
     *
     * @param factory 注入名为primaryEntityManagerFactory的bean
     * @return 事务管理对象
     */
    @Primary
    @Bean(name = "primaryTransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }
}

```java
package com.muggle.poseidon.core.config;


import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        // repository包名
        basePackages = "com.muggle.poseidon.logrep",
        // 实体管理bean名称
        entityManagerFactoryRef = "secondEntityManagerFactory",
        // 事务管理bean名称
        transactionManagerRef = "secondTransactionManager"
)
public class LogDataBaseConfig {

    /**
     * 扫描spring.jpa.second开头的配置信息
     *
     * @return jpa配置信息
     */
    @Bean(name = "secondJpaProperties")
    @ConfigurationProperties(prefix = "spring.aa")
    public JpaProperties jpaProperties() {
        return new JpaProperties();
    }

    /**
     * 获取从库实体管理工厂对象
     *
     * @param secondDataSource 注入名为secondDataSource的数据源
     * @param jpaProperties    注入名为secondJpaProperties的jpa配置信息
     * @param builder          注入EntityManagerFactoryBuilder
     * @return 实体管理工厂对象
     */
    @Bean(name = "secondEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("secondDataSource") DataSource secondDataSource
            , @Qualifier("secondJpaProperties") JpaProperties jpaProperties, EntityManagerFactoryBuilder builder) {
        return builder
                // 设置数据源
                .dataSource(secondDataSource)
                // 设置jpa配置
                .properties(jpaProperties.getProperties())
                // 设置hibernate配置
                .properties(jpaProperties.getHibernateProperties(new HibernateSettings()))
                // 设置实体包名
                .packages("com.muggle.poseidon.entity")
                // 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源
                .persistenceUnit("secondPersistenceUnit")
                .build();
    }

    /**
     * 获取实体管理对象
     *
     * @param factory 注入名为secondEntityManagerFactory的bean
     * @return 实体管理对象
     */
    @Bean(name = "secondEntityManager")
    public EntityManager entityManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) {
        return factory.createEntityManager();
    }

    /**
     * 获取从库事务管理对象
     *
     * @param factory 注入名为secondEntityManagerFactory的bean
     * @return 事务管理对象
     */
    @Bean(name = "secondTransactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }
}


package com.muggle.poseidon.core.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class LogDataConfig {
    /**
     * 扫描spring.datasource.second开头的配置信息
     *
     * @return 数据源配置信息
     */
    @Bean(name = "secondDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.ss")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 获取从库数据源对象
     *
     * @param properties 注入名为secondDataSourceProperties的beanf
     * @return 数据源对象
     */
    @Bean(name = "secondDataSource")
    public DataSource dataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties properties) {
        return properties.initializeDataSourceBuilder().build();
    }

    /**
     * 该方法仅在需要使用JdbcTemplate对象时选用
     *
     * @param dataSource 注入名为secondDataSource的bean
     * @return 数据源JdbcTemplate对象
     */
    @Bean(name = "secondJdbcTemplate")
    public JdbcTemplate jdbcTemplate(@Qualifier("secondDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

application.properties

server.port=8080
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.datasource.driverClassName = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://119.23.75.58:3306/poseidon?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
spring.datasource.username =
spring.datasource.password =
spring.datasource.max-active=20
spring.datasource.max-idle=8
spring.datasource.min-idle=8
spring.datasource.initial-size=10

spring.jpa.database=mysql
spring.jpa.show-sql = true
#配置方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect

spring.ss.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url = jdbc:mysql://localhost:3306/test
spring.ss.driverClassName = com.mysql.cj.jdbc.Driver
spring.ss.url = jdbc:mysql://zzzzz/log?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true
spring.ss.username =
spring.ss.password =
spring.ss.max-active=20
spring.ss.max-idle=8
spring.ss.min-idle=8
spring.ss.initial-size=10


spring.aa.database=mysql
spring.aa.show-sql = true
#配置方言
spring.aa.database-platform=org.hibernate.dialect.MySQL5Dialect

以数据库作为输出配置就算完成了,接下来整合elk系统到我们日志系统中:

先整合logstash

logstash安装和配置:
https://www.elastic.co/cn/downloads/logstash 选择zip包下载

解压,进入bin目录 创建logstash.conf 并配置:

input {
    tcp {
    ##host:port就是上面appender中的 destination,这里其实把logstash作为服务,开启9250端口接收logback发出的消息
    host => "127.0.0.1"
    port => 9100
    mode => "server"
    tags => ["tags"]
    codec => json_lines
    }
}
output {
    stdout { codec => rubydebug }
    #输出到es
    #elasticsearch { hosts => "127.0.0.1:9200" }
        #输出到一个文件中
    file {
       path => "D:\logs\test.log"
       codec => line
    }
}

我这里先配置输出到文件,后面再修改,创建文件:D:\logs\test.log

启动:

打开cmd(不要使用powershell),进入bin:

D:\exe\logstash-6.6.1\logstash-6.6.1\bin>logstash -f logstash.conf

然后在我们的项目中进行相应的配置:
按这个来:https://github.com/logstash/logstash-logback-encoder

加入pom并指定logback版本:


1.2.3



  net.logstash.logback
  logstash-logback-encoder
  5.3
  
        
  
    ch.qos.logback
    logback-classic
    1.2.3
  

配置apppender和logger


    127.0.0.1:9100
    true

    
        true
    
  
  
      
  

测试:

RestController
@RequestMapping("/public/log")
public class LogTestController {
    private static final Logger log = LoggerFactory.getLogger("logstash");
    @Autowired
    LoggingEventRepository repository;

    @GetMapping("/")
    public String test(){
        log.info("sssssssssssssss");
        Iterable all = repository.findAll();

        return "sss";

    }
}

访问接口,logstash打印信息:

[2019-03-09T11:32:56,358][INFO ][logstash.outputs.file    ] Opening file {:path=>"D:/logs/test.log"}
{
                  "host" => "www.xmind.net",
                 "level" => "INFO",
     "caller_class_name" => "com.muggle.poseidon.controller.LogTestController",
            "@timestamp" => 2019-03-09T03:33:03.413Z,
           "logger_name" => "logstash",
              "@version" => "1",
           "thread_name" => "http-nio-8080-exec-9",
               "message" => "sssssssssssssss",
    "caller_line_number" => 22,
                  "port" => 58368,
           "level_value" => 20000,
      "caller_file_name" => "LogTestController.java",
                  "tags" => [
        [0] "tags"
    ],
    "caller_method_name" => "test"
}

test.log输出了文件:

2019-03-09T03:33:03.413Z www.xmind.net sssssssssssssss

接下来只要把输出路径换成ES就可以了,这属于logstash和es的整合,这里先不讲解;重新回归到我们的请求模块:

我希望我的模块,对每次请求都能记录下来(请求日志),并将记录存到数据库或者ES,同时我要对所有接口都进行一个幂等性的保障;保障接口的幂等性有多种方法,比较简单的是数据库做唯一索引或者加拦截器,我这里加了一个拦截器来保障接口幂等和拦截前端数据的重复提交(关于接口幂等性在其他文档中介绍):

@Slf4j
public class RequestLockInterceptor implements HandlerInterceptor {
    RedisLock redisTool;
    private int expireTime;

    public RequestLockInterceptor(int expireTime, RedislockImpl redisTool) {
        this.expireTime = expireTime;
        this.redisTool = redisTool;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if("post".equalsIgnoreCase(request.getMethod())){
            String token = request.getParameter("request_key");
            if (token==null||"".equals(token)){
                log.error("请求非法");
//            throw new PoseidonException("请求太频繁",PoseidonProperties.TOO_NUMBER_REQUEST);
                response.setContentType("application/json;charset=UTF-8");
                PrintWriter writer = response.getWriter();
                writer.write("{\"code\":\"5001\",\"msg\":\"请求非法\"}");
                writer.close();
                return false;
            }
            String ipAddr = RequestUtils.getIpAddr(request);
            String lockKey = request.getRequestURI() + "_"  + "_" + token;
            boolean lock = redisTool.lock(lockKey, ipAddr, expireTime);
            if (!lock) {//
                log.error("拦截表单重复提交");
//            throw new PoseidonException("请求太频繁",PoseidonProperties.TOO_NUMBER_REQUEST);
                response.setContentType("application/json;charset=UTF-8");
                PrintWriter writer = response.getWriter();
                writer.write("{\"code\":\"5001\",\"msg\":\"请求太频繁\"}");
                writer.close();
                return false;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//        String requestURI = request.getRequestURI();
//        String lockKey = request.getRequestURI() + "_" + RequestUtils.getIpAddr(request);
//        redisTool.unlock(lockKey,getIpAddr(request));
    }


}

项目使用了redis锁(redis锁原理和使用在其他文档中介绍)

对于系统异常,如果是业务的异常,正常处理,如果是系统发生的异常比如空指针,数据库异常等我希望系统能马上通知,以便排查问题,所以我配置邮件异常通知(关于springboot邮件配置其他文档介绍):


@RestControllerAdvice
@Slf4j
public class RestExceptionHandlerController {
    @Autowired
    EmailService emailService;
    @Value("${admin.email}")
    private String adminEmail;

    @ExceptionHandler(value = {PoseidonException.class})
    public ResultBean poseidonExceptionHandler(PoseidonException e, HttpServletRequest req) {
        return new ResultBean().setMsg(e.getMsg()).setCode(e.getCode());
    }
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public ResultBean MethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest req) {
        System.out.println(e.getMessage());
        return new ResultBean().setMsg("数据未通过校验").setCode(PoseidonProperties.COMMIT_DATA_ERROR);
    }

    @ExceptionHandler(value = {Exception.class})
    public ResultBean exceptionHandler(Exception e, HttpServletRequest req) {
        log.error("系统异常:" + req.getMethod() + req.getRequestURI(), e);
        try {
//
            EmailBean emailBean = new EmailBean();
            emailBean.setRecipient(adminEmail);
            emailBean.setSubject("poseidon---系统异常");
            emailBean.setContent("系统异常:" + req.getMethod() + req.getRequestURI()+"----"+e.getMessage());
//            改良
            emailService.sendSimpleMail(emailBean);
        } finally {
            return new ResultBean().setMsg("系统异常,请联系管理员").setCode("500");
        }
    }

    @ExceptionHandler(value = {HttpRequestMethodNotSupportedException.class})
    public ResultBean notsupported(Exception e, HttpServletRequest req) {
        return new ResultBean().setMsg("不支持的请求方式").setCode(PoseidonProperties.NOT_SUPPORT_METHOD);
    }
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    public ResultBean notFoundUrl(Exception e, HttpServletRequest req) {
        return new ResultBean().setMsg("请求路径不存在").setCode("404");
    }
}

你可能感兴趣的:(log模块配置)