SpringBoot系列——Logback⽇志,输出到⽂件以及实时输出到web页⾯
前⾔
SpringBoot对所有内部⽇志使⽤通⽤⽇志记录,但保留底层⽇志实现。为Java Util Logging、Log4J2和Logback提供了默认配置。在不同的情况下,⽇志记录器都预先配置为使⽤控制台输出,同时还提供可选的⽂件输出。默认情况下,SpringBoot使⽤Logback进⾏⽇志记录。
⽇志级别有(从⾼到低):FATAL(致命),ERROR(错误),WARN(警告),INFO(信息),DEBUG(调试),TRACE(跟踪)或者 OFF(关闭),默认的⽇志配置在消息写⼊时将消息回显到控制台。默认情况下,将记录错误级别、警告级别和信息级别的消息。
PS:Logback does not have a FATAL level. It is mapped to ERROR Logback没有FATAL致命级别。它被映射到ERROR错误级别
详情请戳官⽅⽂档:
本⽂主要记录Logback⽇志输出到⽂件以及实时输出到web页⾯
输出到⽂件
我们创建SpringBoot项⽬时,spring-boot-starter已经包含了spring-boot-starter-logging,不需要再进⾏引⼊依赖
标准⽇志格式
2014-03-05 10:57:51.112 INFO 45469 --- [ main] org.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52
2014-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2014-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.t.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms
2014-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/]
2014-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.bedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
Date and Time: Millisecond precision and easily sortable. ⽇期和时间:毫秒精度,易于排序。
Log Level: ERROR, WARN, INFO, DEBUG, or TRACE. ⽇志级别:错误、警告、信息、调试或跟踪。
Process ID. 进程ID。
A --- separator to distinguish the start of actual log messages. 分隔符,⽤于区分实际⽇志消息的开始。
Thread name: Enclosed in square brackets (may be truncated for console output). 线程名称:括在⽅括号中(可能会被截断以⽤于控制台输出)。
Logger name: This is usually the source class name (often abbreviated). ⽇志程序名称:这通常是源类名称(通常缩写)。
The log message. ⽇志消息。
如何打印⽇志?
⽅法1
/**
* 配置内部类
*/
@Controller
@Configuration
class Config {
/**
* 获取⽇志对象,构造函数传⼊当前类,查⽇志⽅便定位
*/
private final Logger log = Class());
@Value("${user.home}")
private String userName;
/
**
* 端⼝
*/
@Value("${server.port}")
private String port;
/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
try {
InetAddress ia = LocalHost();
//获取本机内⽹IP
log.info("启动成功:" + "" + ia.getHostAddress() + ":" + port + "/");
log.info("${user.home} :" + userName);
} catch (UnknownHostException ex) {
ex.printStackTrace();
}
};
}
}
⽅法2 使⽤lombok的@Slf4j,帮我们创建Logger对象,效果与⽅法1⼀样
/**
* 配置内部类
*/
@Slf4j
@Controller
@Configuration
class Config {
@Value("${user.home}")
private String userName;
/**
* 端⼝
*/
@Value("${server.port}")
private String port;/**
* 启动成功
*/
@Bean
public ApplicationRunner applicationRunner() {
return applicationArguments -> {
try {
InetAddress ia = LocalHost();
session如何设置和读取/
/获取本机内⽹IP
log.info("启动成功:" + "" + ia.getHostAddress() + ":" + port + "/");
log.info("${user.home} :" + userName);
} catch (UnknownHostException ex) {
ex.printStackTrace();
}
};
}
}
简单配置
如果不需要进⾏复杂的⽇志配置,则在配置⽂件中进⾏简单的⽇志配置即可,默认情况下,SpringBo
ot⽇志只记录到控制台,不写⽇志⽂件。如果希望在控制台输出之外编写⽇志⽂件,则需要进⾏配置
logging:
path: /Users/Administrator/Desktop/杂七杂⼋/ims #⽇志⽂件路径
file: ims.log #⽇志⽂件名称
level:
root: info #⽇志级别 root表⽰所有包,也可以单独配置具体包 fatal error warn info debug trace off
重新启动项⽬
打开ims.log
扩展配置
Spring Boot包含许多Logback扩展,可以帮助进⾏⾼级配置。您可以在您的l配置⽂件中使⽤这些扩展。如果需要⽐较复杂的配置,建议使⽤扩展配置的⽅式
PS:SpringBoot推荐我们使⽤带-spring后缀的 l 扩展配置,因为默认的的l标准配置,Spring⽆法完全控制⽇志初始化。(spring扩展对springProfile节点的⽀持)
以下是项⽬常见的完整l,SpringBoot默认扫描classpath下⾯的l、l,所以不需要再指定fig,当然,你指定也没有问题
logging:
config: l
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--⽇志⽂件主⽬录:这⾥${user.home}为当前服务器⽤户主⽬录-->
<property name="LOG_HOME" value="${user.home}/log"/>
<!--⽇志⽂件名称:这⾥spring.application.name表⽰⼯程名称-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<!--默认配置-->
<include resource="org/springframework/boot/logging/l"/>
<!--配置控制台(Console)-->
<include resource="org/springframework/boot/logging/l"/>
<!--配置⽇志⽂件(File)-->
<appender name="FILE" class="ch.olling.RollingFileAppender">
<!--设置策略-->
<rollingPolicy class="ch.olling.TimeBasedRollingPolicy">
<!--⽇志⽂件路径:这⾥%d{yyyyMMdd}表⽰按天分类⽇志-->
<FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/${APP_NAME}.%i.log</FileNamePattern>
<!--⽇志保留天数-->
<MaxHistory>15</MaxHistory>
<MaxFileSise>10MB</MaxFileSise>
</rollingPolicy>
<!--设置格式-->
<encoder class="ch.qos.der.PatternLayoutEncoder">
<!--格式化输出:%d表⽰⽇期,%thread表⽰线程名,%-5level:级别从左显⽰5个字符宽度%msg:⽇志消息,%n是换⾏符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 或者使⽤默认配置 -->
<!--<pattern>${FILE_LOG_PATTERN}</pattern>-->
<charset>utf8</charset>
</encoder>
</appender>
<!-- 多环境配置按照active profile选择分⽀ -->
<springProfile name="dev">
<!--root节点全局⽇志级别,⽤来指定最基础的⽇志输出级别-->
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="CONSOLE"/>
</root>
<!-- ⼦节点向上级传递局部⽇志级别-->
<logger level="WARN" name="org.springframework"/>
<logger level="WARN" name="comflix"/>
<logger level="DEBUG" name="org.hibernate.SQL"/>
</springProfile>
<springProfile name="prod">
</springProfile>
</configuration>
启动项⽬,去到${user.home}当前服务器⽤户主⽬录,⽇志按⽇期进⾏产⽣,如果项⽬产⽣的⽇志⽂件⽐较⼤,还可以按照⼩时进⾏.log⽂件的⽣成
2021-02-24更新:
如果需要按⽇志级别分别输出到对应的⽇志⽂件,在appender标签新增filter标签进⾏指定
<!-- 时间滚动输出 level为【debug / info / warn / error】⽇志 -->
<appender name="【DEBUG / INFO / WARN / ERROR】_FILE" class="ch.olling.RollingFileAppender">
<!-- 忽略其他配置 -->
<!-- 此⽇志⽂件只记录【debug / info / warn / error】级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>【debug / info / warn / error】</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
输出到Web页⾯
我们已经有⽇志⽂件.log了,为什么还要这个功能呢?(滑稽脸)为了偷懒!
当我们把项⽬部署到Linux服务器,当你想看⽇志⽂件,还得打开xshell连接,定位到log⽂件夹,⿇烦;如果我们把⽇志输出到Web页⾯,当做超级管理员或者测试账号下⾯的⼀个功能,点击就开始实时获取⽣成的⽇志并输出在Web页⾯,是不是爽很多呢?
PS:这个功能可得⼩⼼使⽤,因为⽇志会暴露很多信息
LoggingWSServer
使⽤WebSocket实现实时获取,建⽴WebSocket连接后创建⼀个线程任务,每秒读取⼀次最新的⽇志⽂件,第⼀次只取后⾯200⾏,后⾯取相⽐上次新增的⾏,为了在页⾯上更加⽅便的阅读⽇志,对⽇志级别单词进⾏着⾊(PS:如何创建springboot的websocket,请戳:)
package cn.huanzi.qch.springbootlogback;
slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thymeleaf.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import urrent.ConcurrentHashMap;
/**
* WebSocket获取实时⽇志并输出到Web页⾯
*/
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/logging", configurator = MyEndpointConfigure.class)
public class LoggingWSServer {
@Value("${spring.application.name}")
private String applicationName;
/**
* 连接集合
*/
private static Map<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();
private static Map<String, Integer> lengthMap = new ConcurrentHashMap<String, Integer>();
/**
* 连接建⽴成功调⽤的⽅法
*/
@OnOpen
public void onOpen(Session session) {
//添加到集合中
sessionMap.Id(), session);
lengthMap.Id(), 1);//默认从第⼀⾏开始
//获取⽇志信息
new Thread(() -> {
log.info("LoggingWebSocketServer 任务开始");
boolean first = true;
while ((Id()) != null) {
BufferedReader reader = null;
try {
//⽇志⽂件路径,获取最新的
String filePath = Property("user.home") + "/log/" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/"+applicationName+".log";
//字符流
reader = new BufferedReader(new FileReader(filePath));
Object[] lines = reader.lines().toArray();
//只取从上次之后产⽣的⽇志
Object[] copyOfRange = pyOfRange(lines, (Id()), lines.length);
//对⽇志进⾏着⾊,更加美观 PS:注意,这⾥要根据⽇志⽣成规则来操作
for (int i = 0; i < copyOfRange.length; i++) {
String line = (String) copyOfRange[i];
//先转义
line = placeAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """);
//处理等级
line = place("DEBUG", "<span style='color: blue;'>DEBUG</span>");
line = place("INFO", "<span style='color: green;'>INFO</span>");
line = place("WARN", "<span style='color: orange;'>WARN</span>");
line = place("ERROR", "<span style='color: red;'>ERROR</span>");
//处理类名
String[] split = line.split("]");
if (split.length >= 2) {
String[] split1 = split[1].split("-");
if (split1.length >= 2) {
line = split[0] + "]" + "<span style='color: #298a8a;'>" + split1[0] + "</span>" + "-" + split1[1];
}
}
copyOfRange[i] = line;
}
/
/存储最新⼀⾏开始
lengthMap.Id(), lines.length);
//第⼀次如果太⼤,截取最新的200⾏就够了,避免传输的数据太⼤
if(first && copyOfRange.length > 200){
copyOfRange = pyOfRange(copyOfRange, copyOfRange.length - 200, copyOfRange.length);
first = false;
}
String result = StringUtils.join(copyOfRange, "<br/>");
//发送
send(session, result);
//休眠⼀秒
Thread.sleep(1000);
} catch (Exception e) {
//捕获但不处理
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException ignored) {
}
}
}
log.info("LoggingWebSocketServer 任务结束");
}).start();
}
/**
* 连接关闭调⽤的⽅法
*/
@OnClose
public void onClose(Session session) {
//从集合中删除
}
/**
* 发⽣错误时调⽤
*/
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
/**
* 服务器接收到客户端消息时调⽤的⽅法
*/
@OnMessage
public void onMessage(String message, Session session) {
}
/**
* 封装⼀个send⽅法,发送消息到前端
*/
private void send(Session session, String message) {
try {
} catch (Exception e) {
e.printStackTrace();
}
}
}
HTML页⾯
页⾯收到数据就追加到div中,为了⽅便新增了⼏个功能:
清屏,清空div内容
滚动⾄底部、将div的滚动条滑到最下⾯
开启/关闭⾃动滚动,div新增内容后⾃动将滚动条滑到最下⾯,点⼀下开启,再点关闭,默认关闭
PS:引⼊公⽤部分,就是⼀些jquery等常⽤静态资源
<!DOCTYPE>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="">
<head>
<meta charset="UTF-8">
<title>IMS实时⽇志</title>
<!-- 引⼊公⽤部分 -->
<script th:replace="head::static"></script>
</head>
<body>
<!-- 标题 -->
<h1 >IMS实时⽇志</h1>
<!-- 显⽰区 -->
<div id="loggingText" contenteditable="true"
></div>
<!-- 操作栏 -->
<div >
<button onclick="$('#loggingText').text('')" >清屏</button>
<button onclick="$('#loggingText').animate({scrollTop:$('#loggingText')[0].scrollHeight});"
>滚动⾄底部
</button>
<button onclick="if(window.loggingAutoBottom){$(this).text('开启⾃动滚动');}else{$(this).text('关闭⾃动滚动');};window.loggingAutoBottom = !window.loggingAutoBottom" >开启⾃动滚动
</button>
</div>
</body>
<script th:inline="javascript">
//websocket对象
let websocket = null;
//判断当前浏览器是否⽀持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:10086/websocket/logging");
} else {
<("不⽀持WebSocket");
}
//连接发⽣错误的回调⽅法
<("WebSocket连接发⽣错误");
};
//连接成功建⽴的回调⽅法
console.log("WebSocket连接成功")
};
//接收到消息的回调⽅法
/
/追加
if (event.data) {
//⽇志内容
let $loggingText = $("#loggingText");
$loggingText.append(event.data);
//是否开启⾃动底部
if (window.loggingAutoBottom) {
//滚动条⾃动到最底部
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论