阅读完需:约 11 分钟
- spring boot可以适应所有日志框架,只需在类路径下包含相应的依赖来激活各种日志系统。
- spring boot底层使用slf4j + logback框架来实现日志记录,所以如果想要自定义logback配置,就无需添加相关依赖了(spring-booot-stater中已包含相关依赖)
- 在类路径下放置自定义日志配置文件(xml配置文件),spring boot就不会使用它本身的默认日志配置了
- logback中文文档——http://www.logback.cn/01%E7%AC%AC%E4%B8%80%E7%AB%A0logback%E4%BB%8B%E7%BB%8D.html

上图是spring boot官方文档的提示内容,意思是:根据您的日志记录系统,将加载相应的文件使用。即如果我们使用logback日志框架,那么可以使用logback-spring.xml、logback-spring.groovy、logback.xml、logback.groovy之一作为配置文件来加载。

自定义例子(1):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!--配置控制台日志输出格式-->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
<!--%d表示日期, %msg表示日志的信息, %n表示换行-->
%d - %msg%n
</pattern>
</layout>
</appender>
<!--配置info信息输出到一个文件-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--根据级别过滤掉匹配的日志,不输出error级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<!--匹配上ERROR级别的日志信息,则不会输出到该文件中-->
<onMatch>DENY</onMatch>
<!--匹配不上ERROR级别的日志信息,则输出到该文件中-->
<onMismatch>ACCEPT</onMismatch>
<!--这样过滤以后,该文件中只会输出info级别的日志信息-->
</filter>
<!--日志输出格式,同上-->
<encoder>
<pattern>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略,按时间每天生成一个日志文件-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径-->
<fileNamePattern>F:/log/springboot/info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<!--配置error信息输出到一个文件-->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--根据范围过滤日志,只输出error级别的日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!--日志输出格式-->
<encoder>
<pattern>
%d - %msg%n
</pattern>
</encoder>
<!--滚动策略,按时间每天生成一个日志文件-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径-->
<fileNamePattern>F:/log/springboot/error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="consoleLog"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
</root>
</configuration>
自定义例子(2):
<?xml version="1.0" encoding="utf-8" ?>
<!--
1) 根节点<configuration>,包含下面三个属性:
scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。-->
<configuration scan="true" scanPeriod="60" debug="false">
<!--控制台输出日志格式-->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%d{yyyy-MM-dd HH:mm:ss} -- %-5level -- [%thread] -- %logger{50} --- %msg %n}"/>
<!--文件输出日志格式-->
<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{yyyy-MM-dd hh:mm:ss} -- %-5level -- [%thread] -- %logger{50} --- %msg %n}"/>
<!--
Appender: 设置日志信息的去向,常用的有以下几个
1) ch.qos.logback.core.ConsoleAppender (控制台)
2) ch.qos.logback.core.rolling.RollingFileAppender (文件大小到达指定尺寸的时候产生一个新文件)
3) ch.qos.logback.core.FileAppender (文件)
-->
<!--控制台配置-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!--当环境是dev开发环境时,这部分配置才生效-->
<springProfile name="dev">
<!--日志输出格式-->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</springProfile>
<!--当环境不是dev开发环境时,这部分配置才生效-->
<springProfile name="!dev">
<!--日志输出格式-->
<encoder>
<pattern>--%logger{50} --- %msg %n</pattern>
</encoder>
</springProfile>
</appender>
<!--日志记录文件配置-->
<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--当环境是production生产环境时,这部分配置才生效
即在production生产环境上,我们才将日志信息记录到日志文件中-->
<springProfile name="production">
<!--日志文件保存路径,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建 -->-->
<file>D:\idea\logs\example-logging.log</file>
<!--基于大小和时间的轮转策略,当日志内容超出文件大小限制后,会自动生成一个文件来继续记录和重命名-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--当日志内容超出文件大小限制后,会自动生成一个文件来继续记录,文件按下面格式命名-->
<fileNamePattern>D:\idea\logs\example-logging-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--文件最大限制,默认10MB-->
<maxFileSize>10MB</maxFileSize>
<!--文件最大保存周期,默认7天-->
<maxHistory>7</maxHistory>
<!--所有归档文件总的大小限制-->
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<!--日志输出格式-->
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
</springProfile>
</appender>
<!--
用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性
name:
用来指定受此logger约束的某一个包或者具体的某一个类。
level:
用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
如果未设置此属性,那么当前logger将会继承上级的级别。
additivity:
是否向上级logger传递打印信息。默认是true。
<logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
-->
<logger name="com.cd.example.one" level="trace"/>
<logger name="com.cd.example.two" level="debug"/>
<logger name="com.cd.example.three" level="warn"/>
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性。
<root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger
使该指定的appender生效
-->
<root level="info">
<appender-ref ref="consoleAppender"/>
<appender-ref ref="fileAppender"/>
</root>
</configuration>
关于这个日志的应用:
一般而言日志只要开启就可以了基本不需要自定义但是有时候要应用时就需要自定义配置,比如:vue+springboot实现控制台日志实时推送前台
1.自定义一个日志配置,目的是将日志转化自己进行处理。
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback-demo</contextName>
<!-- <!–输出到控制台 ConsoleAppender–>-->
<!-- <appender name="consoleLog1" class="ch.qos.logback.core.ConsoleAppender">-->
<!-- <!–展示格式 layout–>-->
<!-- <layout class="ch.qos.logback.classic.PatternLayout">-->
<!-- <pattern>%d -1 %msg%n</pattern>-->
<!-- </layout>-->
<!-- </appender>-->
<!--输出到控制台 ConsoleAppender-->
<appender name="consoleLog2" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--encoder 默认配置为PatternLayoutEncoder-->
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="cn.vhrs.untils.LogFilter"> </filter>
</appender>
<!--指定最基础的日志输出级别-->
<root level="info">
<!--appender将会添加到这个loger-->
<appender-ref ref="STDOUT"/>
<appender-ref ref="consoleLog2"/>
</root>
</configuration>
2.进行日志的处理( 日志过滤器 )
@Service
public class LogFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
String exception = "";
IThrowableProxy iThrowableProxy1 = event.getThrowableProxy();
if(iThrowableProxy1!=null){
exception = "<span class='excehtext'>"+iThrowableProxy1.getClassName()+" "+iThrowableProxy1.getMessage()+"</span></br>";
for(int i=0; i<iThrowableProxy1.getStackTraceElementProxyArray().length;i++){
exception += "<span class='excetext'>"+iThrowableProxy1.getStackTraceElementProxyArray()[i].toString()+"</span></br>";
}
}
LoggerMessage loggerMessage = new LoggerMessage(
event.getMessage()
, DateFormat.getDateTimeInstance().format(new Date(event.getTimeStamp())),
event.getThreadName(),
event.getLoggerName(),
event.getLevel().levelStr,
exception,
""
);
LoggerQueue.getInstance().push(loggerMessage);
return FilterReply.ACCEPT;
}
}
3. 创建一个阻塞队列,作为日志系统输出的日志的一个临时载体
public class LoggerQueue {
//队列大小
public static final int QUEUE_MAX_SIZE = 10000;
private static LoggerQueue alarmMessageQueue = new LoggerQueue();
//阻塞队列
private BlockingQueue blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
private LoggerQueue() {
}
public static LoggerQueue getInstance() {
return alarmMessageQueue;
}
/**
* @Description: 消息入队
* @Return: boolean
* @Author: leijun
* @Date: 2019/11/26
**/
public boolean push(LoggerMessage log) {
// System.out.println("消息入队的信息===="+log);
return this.blockingQueue.add(log);//队列满了就抛出异常,不阻塞
}
/**
* @Description: 消息出队
* @Return: com.unismc.springbootudcap.powersecurity.entity.LoggerMessage
* @Author: leijun
* @Date: 2019/11/26
**/
public LoggerMessage poll() {
LoggerMessage result = null;
try {
// System.out.println("输出:"+this.blockingQueue);
result = (LoggerMessage) this.blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return result;
}
}
4. 创建一个日志实体
/**
* 日志消息实体
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
public class LoggerMessage {
private String body;
private String timestamp;
private String threadName;
private String className;
private String level;
private String exception;
private String cause;
//省略 get , set
}
5. 接下来配置WebSocket
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
SimpMessagingTemplate messagingTemplate;
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/websocket")
.setAllowedOrigins("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue","/topic");
}
@PostConstruct
public void pushLogger(){
ExecutorService executorService= Executors.newFixedThreadPool(2);
Runnable runnable=new Runnable() {
@Override
public void run() {
while (true) {
try {
LoggerMessage log = LoggerQueue.getInstance().poll();
if(log!=null){
if(messagingTemplate!=null)
{
messagingTemplate.convertAndSend("/topic/pullLogger",log);
// System.out.println(new ObjectMapper().writeValueAsString(log));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
executorService.submit(runnable);
}
}
6.在vue中去接收消息。
//建立日志连接
initlog(context) {
if (context.state.stompClient == null) {
context.res = "<div style='color: #18d035;font-size: 14px'>通道连接成功,静默等待....</div>"
// this.$refs['logContainerDiv'].append();
// 建立连接对象
let socket = new SockJS('http://localhost:8081/ws/websocket');
// 获取STOMP子协议的客户端对象
context.state.stompClient = Stomp.over(socket);
context.state.stompClient.connect({}, () => {
context.state.stompClient.subscribe('/topic/pullLogger', (event) => {
let content = JSON.parse(event.body);
// console.log(content);
let leverhtml = '';
let className = "<span style='color: #229379'>" + content.className + "</span>";
switch (content.level) {
case 'INFO':
leverhtml = "<span style='color: #90ad2b'>" + content.level + "</span>";
break;
case 'DEBUG':
leverhtml = "<span style='color: #A8C023'>" + content.level + "</span>";
break;
case 'WARN':
leverhtml = "<span style='color: #fffa1c'>" + content.level + "</span>";
break;
case 'ERROR':
leverhtml = "<span style='color: #e3270e'>" + content.level + "</span>";
break;
}
context.state.res += "<div style='color: #18d035;font-size: 14px'>" + content.timestamp + " " + leverhtml + " --- [" + content.threadName + "] " + className + " :" + content.body + "</div>"
// this.$refs['logContainerDiv'].append(content.timestamp + " " + leverhtml + " --- [" + content.threadName + "] " + className + " :" + content.body + "<br/>");
if (content.exception != "") {
context.state.res += "<div>" + content.exception + "</div>"
// this.$refs['logContainerDiv'].append();
}
if (content.cause != "") {
context.state.res += "<div>" + content.cause + "</div>"
// this.$refs['logContainerDiv'].append(content.cause);
}
// this.$refs['logContainer'].scrollTo(this.$refs['logContainerDiv'].height() - this.$refs['logContainer'].height());
},
// {
// token: "kltoen"
// }
);
});
}
}
这只是一个连接websocket的代码,我们可以将获取到的信息保存在vue中的vuex中或者localStorage中进行持久化。
