整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          高亮顯示系統日志應該用什么命令

          關注本頭條號,每天堅持更新原創干貨技術文章。

          如需學習視頻,請在微信搜索公眾號“智傳網優”直接開始自助視頻學習

          1. 前言

          本文主要介紹如果高效地使用工具高亮顯示某些輸出結果和關鍵字。

          您可以使用正則表達式和特定規則格式突出顯示文本。分層規則格式易于理解,但功能非常強大。該注釋將從標準輸入讀取每一行,每行根據FILE中定義的規則突出顯示,然后打印到標準輸出(屏幕)。

          高效地使用工具高亮顯示某些輸出結果和關鍵字

          remark命令一般很少人知道它可以把系統日志文件關鍵內容高亮標注顏色并打印到屏幕上,例如像ping,traceroute等等命令的輸出結果。一般情況下,我們需要自己寫正則表達式定義哪些關鍵結果應該高亮。有了remark命令后,你可能不太需要自己手動編寫規則了。

          2. 安裝`remark`命令

          Debian / Ubuntu Linux 執行以下命令安裝remark:

          [root@zcwyou ~]# cd /tmp
          [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup_0.10.0-1_amd64.deb
          [root@zcwyou ~]# sudo dpkg -i regex-markup_0.10.0-1_amd64.deb
          

          RHEL / CentOS / Fedora Linux 64用戶,執行以下命令安裝remark命令:

          [root@zcwyou ~]# cd /tmp
          [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup-0.10.0-1.x86_64.rpm
          [root@zcwyou ~]# rpm -ivh regex-markup-0.10.0-1.x86_64.rpm
          

          或者你從源代碼編譯它

          [root@zcwyou ~]# cd /tmp
          [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup-0.10.0.tar.gz
          [root@zcwyou ~]# tar -xvf regex-markup-0.10.0.tar.gz
          [root@zcwyou ~]# cd regex-markup-0.10.
          [root@zcwyou ~]# ./configure
          [root@zcwyou ~]# make
          [root@zcwyou ~]# sudo make install
          

          安裝remark命令自帶高亮語法

          3. `remark`語法

          command1 | remark /path/to/config

          command2 arg1 arg2 | remark /path/to/config

          高亮顯示ping的執行結果

          [root@zcwyou ~]# ping -c 4 www.linuxrumen.com | remark /usr/share/regex-markup/ping
          

          你可以創建一個bash shell 功能和它入到~/.bashrc file的配置文件中

          [root@zcwyou ~]# ping() { /bin/ping $@ | remark /usr/share/regex-markup/ping; }
          

          remark-pin 的規則文件放在/usr/share/regex-markup/ping。它由樣式和宏定義以及匹配語句組成。匹配語句的順序很重要,因為它們是從上到下執行的。樣式和宏在使用之前需要定義。規則文件的語法類似于編程語言(如C和Java)的縮寫,并且使用空格并不重要:

          [root@zcwyou ~]# cat /usr/share/regex-markup/ping
          

          它的文件內容一般是這樣的

          # Rules to highlight the output of ping(8)
          include "common"
          # Special: Color all lines that don't match any of the rules below red
          /.*/ red
          /^PING ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\) ([0-9]+)\(([0-9]+)\) bytes of data\.$/ {
           default
           1 blue
           2 green
           3,4 red
           break # this is merely to skip the matches below
          }
          /^PING ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\): ([0-9]+) data bytes$/ {
           default
           1 blue
           2 green
           3 red
           break # this is merely to skip the matches below
          }
          /^([0-9]+) bytes from ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\): icmp_seq=([0-9]+) ttl=([0-9]+) time=(.*)$/ {
           default
           1,4,5,6 red
           2 blue
           3 green
           break
          }
          /^([0-9]+) bytes from ([-.a-zA-Z0-9]+): icmp_seq=([0-9]+) ttl=([0-9]+) time=(.*)$/ {
           default
           1,3,4,5 red
           2 green
           break
          }
          /^--- ([-.a-zA-Z0-9]+) ping statistics ---$/ {
           default
           1 blue
           break
          }
          /^([0-9]+) packets transmitted, ([0-9]+) packets received, ([0-9]+)% packet loss$/ {
           default
           1,2,3 red
           break
          }
          /^([0-9]+) packets transmitted, ([0-9]+) received, ([0-9]+)% packet loss, time ([0-9]+ms)$/ {
           default
           1,2,3,4 red
           break
          }
          /^round-trip min\/avg\/max = ([.0-9]+)\/([.0-9]+)\/(.*)$/ {
           default
           1,2,3 red
           break
          }
          /^rtt min\/avg\/max\/mdev = ([.0-9]+)\/([.0-9]+)\/([.0-9]+)\/(.*)$/ {
           default
           1,2,3,4 red
           break
          }
          

          默認的樣式定義文件放在/usr/share/regex-markup/common

          試試跟traceroute結合

          [root@zcwyou ~]# traceroute www.linuxrumen.com | remark /usr/local/share/regex-markup/traceroute
          

          執行以下命令把Linux系統日志高亮顯示

          [root@zcwyou ~]# grep something /var/log/syslog | remark /usr/share/regex-markup/syslog
          [root@zcwyou ~]# tail -f /var/log/syslog | remark /usr/share/regex-markup/syslog
          

          使用remark命令高亮顯示diff結果

          先使用diff命令對比file1和file2,然后重定向到remark,由remark高亮顯示結果

          [root@zcwyou ~]# diff file1 file2 | remark /usr/share/regex-markup/diff
          

          使用remark命令高亮顯示mark結果

          [root@zcwyou ~]# cd /path/to/build
          [root@zcwyou ~]# make | remark /usr/share/regex-markup/make
          

          4. 如何定制高亮規則?

          如果你有這樣的需求,請參考remark命令手冊:

          [root@zcwyou ~]# man remark
          

          如何定制高亮規則

          本文已同步至博客站,尊重原創,轉載時請在正文中附帶以下鏈接:

          https://www.linuxrumen.com/cyml/1201.html

          點擊了解更多,快速查看更多的技術文章列表。

          日志是我們運維用來分析問題和處理問題的重要維護依據,但是Nginx的日志文件是非常龐大的,而且以文本格式顯示導致Nginx的日志直接分析非常困難。有時候為了初步處理或緊急處理問題,我們也對直接打開Nginx的日志文件進行分析,這里我們就介紹如何對Nginx的默認日志格式進行分析和含義的解讀。


          Nginx配置文件中的配置項

          在安裝好Nginx后系統會默認給你開通access.log訪問日志功能,這個配置文件在etc/nginx的文件夾下,打開配置文件后如下面是按行進行的默認格式配置。

          log_format main '
          $remote_addr -
          $remote_user
          [$time_local]
          "$request" ''
          $status
          $body_bytes_sent "
          $http_referer" ''
          "$http_user_agent"
          "$http_x_forwarded_for"
          ';
          

          Nginx日志的配置項目:access_log /var/log/nginx/access.log main;


          Nginx日志范例分析

          日志寫入文本文件是一行行的,這里我們把一行日志,按3個字段進行分行進行分析。

          61.135.165.19 - 這里是訪問者的IP地址

          - 這里是客戶名稱,但一般獲取不到。

          [17/Nov/2018:03:39:32 +0800] 這里是訪問的時間

          "GET /ask/0901/images/a13.jpg HTTP/1.0" 這個是這次記錄的訪問對象

          200 返回的狀態碼

          19916 這條記錄訪問對象的大小

          "http://xx.xx.xx/ask/0901/index.htm?jh=haerbinlvyou-YD&gjc=7riyoudongbei&e_keywordid=%7Bkeywordid%7D" 這個是從什么入口訪問的也就是來源。

          "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C;Baiduspider-ads)AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" 這個是客戶端瀏覽器的一些參數

          "10.205.160.32" 這里是代理的地址,一般是沒有的。


          Nginx日志中變量對應的字段

          remote_addr 遠程請求IP地址

          remote_user 客戶端用戶的名稱,沒有時用-符合代替

          time_local 本地時間戳

          request 請求具體URI文件,例如網頁中的JPG圖片。

          status http請求狀態

          body_bytes_sent 請求文件大小

          http_refer 網頁url跳轉來源,源地址,可以偽造。例如一個HTML網頁然后調用圖片URI資源。

          http_user_agent 用戶終端瀏覽器UserAgent的相關信息

          http_x_forwarded_for是HTTP的請求端真實的IP,只有在通過了HTTP 代理或者負載均衡服務器時才會添加該項

          其他非默認的選項

          host 請求host地址

          request_time 整個請求的總時間

          upstream_addr 后臺提供服務的地址(即轉發處理的目標地址)

          upstream_reponse_time請求時,upstream的響應時間

          upstream_status upstream狀態

          nginx日志切割

          通過如下方式達到日志切割:

          # vi logcron.sh
          log_dir="/data/logs/nginx"
          date_dir=`date +%Y%m%d`
          /bin/mkdir -p ${log_dir}/${date_dir} > /dev/null 2>&1
          /bin/mv ${log_dir}/access.log ${log_dir}/${date_dir}/access.log
          

          這里只需要定義一個cron,在每天晚上23:59:50執行這個腳本就可以了。


          篇幅有限,關于nginx的access.log訪問日志方面就介紹到這了,后面會分享更多關于devops和DBA方面內容,感興趣的朋友可以關注下!

           前言

            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錯誤級別

            詳情請戳官方文檔:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging

            本文主要記錄Logback日志輸出到文件以及實時輸出到web頁面

            輸出到文件

            我們創建SpringBoot項目時,spring-boot-starter已經包含了spring-boot-starter-logging,不需要再進行引入依賴

            標準日志格式

          2014-03-05 10:57:51.112  INFO 45469 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.52
          2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
          2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1358 ms
          2014-03-05 10:57:51.698  INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
          2014-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.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 = LoggerFactory.getLogger(this.getClass());
          
                  @Value("${user.home}")
                  private String userName;
          
                  /**
                   * 端口
                   */
                  @Value("${server.port}")
                  private String port;
          /**
                   * 啟動成功
                   */
                  @Bean
                  public ApplicationRunner applicationRunner() {
                      return applicationArguments -> {
                          try {
                              InetAddress ia = InetAddress.getLocalHost();
                              //獲取本機內網IP
                              log.info("啟動成功:" + "http://" + ia.getHostAddress() + ":" + port + "/");
                              log.info("${user.home} :" + userName);
                          } catch (UnknownHostException ex) {
                              ex.printStackTrace();
                          }
                      };
                  }
              }

            方法2 使用lombok的@Slf4j,幫我們創建Logger對象,效果與方法1一樣

              /**
               * 配置內部類
               */
              @Controller
              @Configuration
              class Config {
                  /**
                   * 獲取日志對象,構造函數傳入當前類,查找日志方便定位
                   */
                  private final Logger log = LoggerFactory.getLogger(this.getClass());
          
                  @Value("${user.home}")
                  private String userName;
          
                  /**
                   * 端口
                   */
                  @Value("${server.port}")
                  private String port;
          /**
                   * 啟動成功
                   */
                  @Bean
                  public ApplicationRunner applicationRunner() {
                      return applicationArguments -> {
                          try {
                              InetAddress ia = InetAddress.getLocalHost();
                              //獲取本機內網IP
                              log.info("啟動成功:" + "http://" + ia.getHostAddress() + ":" + port + "/");
                              log.info("${user.home} :" + userName);
                          } catch (UnknownHostException ex) {
                              ex.printStackTrace();
                          }
                      };
                  }
              }



            簡單配置

            如果不需要進行復雜的日志配置,則在配置文件中進行簡單的日志配置即可,默認情況下,SpringBoot日志只記錄到控制臺,不寫日志文件。如果希望在控制臺輸出之外編寫日志文件,則需要進行配置

            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擴展,可以幫助進行高級配置。您可以在您的logback-spring.xml配置文件中使用這些擴展。如果需要比較復雜的配置,建議使用擴展配置的方式

            PS:SpringBoot推薦我們使用帶-spring后綴的 logback-spring.xml 擴展配置,因為默認的的logback.xml標準配置,Spring無法完全控制日志初始化。(spring擴展對springProfile節點的支持)

            以下是項目常見的完整logback-spring.xml,SpringBoot默認掃描classpath下面的logback.xml、logback-spring.xml,所以不需要再指定spring.logging.config,當然,你指定也沒有問題

          logging:
            config: classpath:logback-spring.xml


          <?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/logback/defaults.xml"/>
              <!--配置控制臺(Console)-->
              <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
          
              <!--配置日志文件(File)-->
              <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                  <!--設置策略-->
                  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                      <!--日志文件路徑:這里%d{yyyyMMdd}表示按天分類日志-->
                      <FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/${APP_NAME}.log</FileNamePattern>
                      <!--日志保留天數-->
                      <MaxHistory>15</MaxHistory>
                  </rollingPolicy>
                  <!--設置格式-->
                  <encoder class="ch.qos.logback.classic.encoder.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>
                  <!--日志文件最大的大小-->
                  <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                      <MaxFileSize>100MB</MaxFileSize>
                  </triggeringPolicy>
              </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="com.netflix"/>
                  <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.qos.logback.core.rolling.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,請戳:SpringBoot系列——WebSocket)

          package cn.huanzi.qch.springbootlogback;
          
          
          import lombok.extern.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;
          import java.text.SimpleDateFormat;
          import java.util.Arrays;
          import java.util.Date;
          import java.util.Map;
          import java.util.concurrent.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.put(session.getId(), session);
                  lengthMap.put(session.getId(), 1);//默認從第一行開始
          
                  //獲取日志信息
                  new Thread(() -> {
                      log.info("LoggingWebSocketServer 任務開始");
                      boolean first = true;
                      while (sessionMap.get(session.getId()) != null) {
                          BufferedReader reader = null;
                          try {
                              //日志文件路徑,獲取最新的
                              String filePath = System.getProperty("user.home") + "/log/" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/"+applicationName+".log";
          
                              //字符流
                              reader = new BufferedReader(new FileReader(filePath));
                              Object[] lines = reader.lines().toArray();
          
                              //只取從上次之后產生的日志
                              Object[] copyOfRange = Arrays.copyOfRange(lines, lengthMap.get(session.getId()), lines.length);
          
                              //對日志進行著色,更加美觀  PS:注意,這里要根據日志生成規則來操作
                              for (int i = 0; i < copyOfRange.length; i++) {
                                  String line = (String) copyOfRange[i];
                                  //先轉義
                                  line = line.replaceAll("&", "&")
                                          .replaceAll("<", "<")
                                          .replaceAll(">", ">")
                                          .replaceAll("\"", """);
          
                                  //處理等級
                                  line = line.replace("DEBUG", "<span style='color: blue;'>DEBUG</span>");
                                  line = line.replace("INFO", "<span style='color: green;'>INFO</span>");
                                  line = line.replace("WARN", "<span style='color: orange;'>WARN</span>");
                                  line = line.replace("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.put(session.getId(), lines.length);
          
                              //第一次如果太大,截取最新的200行就夠了,避免傳輸的數據太大
                              if(first && copyOfRange.length > 200){
                                  copyOfRange = Arrays.copyOfRange(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) {
                  //從集合中刪除
                  sessionMap.remove(session.getId());
                  lengthMap.remove(session.getId());
              }
          
              /**
               * 發生錯誤時調用
               */
              @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 {
                      session.getBasicRemote().sendText(message);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }


            HTML頁面

            頁面收到數據就追加到div中,為了方便新增了幾個功能:

            清屏,清空div內容

            滾動至底部、將div的滾動條滑到最下面

            開啟/關閉自動滾動,div新增內容后自動將滾動條滑到最下面,點一下開啟,再點關閉,默認關閉

            PS:引入公用部分,就是一些jquery等常用靜態資源

          <!DOCTYPE>
          <!--解決idea thymeleaf 表達式模板報紅波浪線-->
          <!--suppress ALL -->
          <html xmlns:th="http://www.thymeleaf.org">
          <head>
              <meta charset="UTF-8">
              <title>IMS實時日志</title>
          
              <!-- 引入公用部分 -->
              <script th:replace="head::static"></script>
          
          </head>
          <body>
          <!-- 標題 -->
          <h1 style="text-align: center;">IMS實時日志</h1>
          
          <!-- 顯示區 -->
          <div id="loggingText" contenteditable="true"
               style="width:100%;height: 600px;background-color: ghostwhite; overflow: auto;"></div>
          
          <!-- 操作欄 -->
          <div style="text-align: center;">
              <button onclick="$('#loggingText').text('')" style="color: green; height: 35px;">清屏</button>
              <button onclick="$('#loggingText').animate({scrollTop:$('#loggingText')[0].scrollHeight});"
                      style="color: green; height: 35px;">滾動至底部
              </button>
              <button onclick="if(window.loggingAutoBottom){$(this).text('開啟自動滾動');}else{$(this).text('關閉自動滾動');};window.loggingAutoBottom = !window.loggingAutoBottom"
                      style="color: green; height: 35px; ">開啟自動滾動
              </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 {
                  console.error("不支持WebSocket");
              }
          
              //連接發生錯誤的回調方法
              websocket.onerror = function (e) {
                  console.error("WebSocket連接發生錯誤");
              };
          
              //連接成功建立的回調方法
              websocket.onopen = function () {
                  console.log("WebSocket連接成功")
              };
          
              //接收到消息的回調方法
              websocket.onmessage = function (event) {
                  //追加
                  if (event.data) {
          
                      //日志內容
                      let $loggingText = $("#loggingText");
                      $loggingText.append(event.data);
          
                      //是否開啟自動底部
                      if (window.loggingAutoBottom) {
                          //滾動條自動到最底部
                          $loggingText.scrollTop($loggingText[0].scrollHeight);
                      }
                  }
              }
          
              //連接關閉的回調方法
              websocket.onclose = function () {
                  console.log("WebSocket連接關閉")
              };
          </script>
          </html>


            效果展示


            后記

            有了日志記錄,我們以后寫代碼時就要注意了,應使用下面的正確示例

          //錯誤示例,這樣寫只會輸出到控制臺,不會輸出到日志中
          System.out.println("XXX");
          e.printStackTrace();
          
          //正確示例,既輸出到控制臺,又輸出到日志
          log.info("XXX");
          log.error("XXX報錯",e);


            SpringBoot日志暫時先記錄到這里,點擊官網了解更多:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging


            補充

            2019-07-03補充:我們之前只對日志等級關鍵字進行著色,還是覺得不夠,因此又新增了類名著色跟HTML轉義

            主要修改:

            效果:



            2019-08-12補充:我發現有時候顯示的時候,換行不太準確,我們原先是在行末追加<br/>,但有時候讀取出來的一行記錄是自動換行后的數據,頁面顯示效果很丑



            因此我改成用正則([\d+][\d+][\d+][\d+]-[\d+][\d+]-[\d+][\d+] [\d+][\d+]:[\d+][\d+]:[\d+][\d+])去匹配日期,然后再對應的起始下標插入<br/>,從而達到與控制臺輸出類似的效果

            匹配、插入結果


            頁面效果


            異步輸出日志

            異步輸出日志的方式很簡單,添加一個基于異步寫日志的appender,并指向原先配置的appender即可

              <!-- 將文件輸出設置成異步輸出 -->
              <appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
                  <!-- 不丟失日志.默認的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日志 -->
                  <discardingThreshold>0</discardingThreshold>
                  <!-- 更改默認的隊列的深度,該值會影響性能.默認值為256 -->
                  <queueSize>256</queueSize>
                  <!-- 添加附加的appender,最多只能添加一個 -->
                  <appender-ref ref="FILE"/>
              </appender>
          
              <!-- 將控制臺輸出設置成異步輸出 -->
              <appender name="ASYNC-CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
                  <!-- 不丟失日志.默認的,如果隊列的80%已滿,則會丟棄TRACT、DEBUG、INFO級別的日志 -->
                  <discardingThreshold>0</discardingThreshold>
                  <!-- 更改默認的隊列的深度,該值會影響性能.默認值為256 -->
                  <queueSize>256</queueSize>
                  <!-- 添加附加的appender,最多只能添加一個 -->
                  <appender-ref ref="CONSOLE"/>
              </appender>

            原理很簡單,主線程將日志扔到阻塞隊列中,然后IO操作日志寫入文件是通過新起一個線程去完成的


            2020-05-26補充

            e.printStackTrace();會打出詳細異常,異常名稱,出錯位置,便于調試用,但直接調用會輸出到std.err,并沒有輸出到日志文件中,因此需要先輸出到流中再轉成字符串

            封裝工具類

          import java.io.IOException;
          import java.io.PrintWriter;
          import java.io.StringWriter;
          
          /**
           * 捕獲報錯日志處理工具類
           */
          public class ErrorUtil {
          
              /**
               * Exception出錯的棧信息轉成字符串
               * 用于打印到日志中
               */
              public static String errorInfoToString(Throwable e) {
                  StringWriter sw = null;
                  PrintWriter pw = null;
                  try {
                      sw = new StringWriter();
                      pw = new PrintWriter(sw);
                      // 將出錯的棧信息輸出到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();
              }
          }

            也可以使用騷操作簡化代碼

              public static String errorInfoToString(Throwable e) {
                  //try-with-resource語法糖 處理機制
                  try(StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw)){
                      e.printStackTrace(pw);
                      pw.flush();
                      sw.flush();
                      return sw.toString();
                  }catch (Exception ignored){
                      throw new RuntimeException(ignored.getMessage(),ignored);
                  }
              }


            使用

          try {
                      //省略其他代碼
          } catch (Throwable e) {
                      //之前的操作,輸出控制臺
                      e.printStackTrace();
                      
                      //輸出到日志文件中
                      log.error(ErrorUtil.errorInfoToString(e));
          }


            配置訪問日志

            2020-08-04更新

            SpringBoot默認使用內置Tomcat,那么我們如何配置Tomcat的Access Logging呢?

            詳情可查看官方文檔:

            SpringBoot配置介紹:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-configure-accesslogs

            Apache Tomcat配置介紹:https://tomcat.apache.org/tomcat-8.5-doc/config/valve.html#Access_Logging


            SpringBoot對Access Log的默認配置

          server.tomcat.accesslog.buffered=true # Whether to buffer output such that it is flushed only periodically.
          server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be absolute or relative to the Tomcat base dir.
          server.tomcat.accesslog.enabled=false # Enable access log.
          server.tomcat.accesslog.file-date-format=.yyyy-MM-dd # Date format to place in the log file name.
          server.tomcat.accesslog.pattern=common # Format pattern for access logs.
          server.tomcat.accesslog.prefix=access_log # Log file name prefix.
          server.tomcat.accesslog.rename-on-rotate=false # Whether to defer inclusion of the date stamp in the file name until rotate time.
          server.tomcat.accesslog.request-attributes-enabled=false # Set request attributes for the IP address, Hostname, protocol, and port used for the request.
          server.tomcat.accesslog.rotate=true # Whether to enable access log rotation.
          server.tomcat.accesslog.suffix=.log # Log file name suffix.


            日志格式說明(摘自上方Apache Tomcat配置介紹官方文檔)

          Values for the pattern attribute are made up of literal text strings, combined with pattern identifiers prefixed by the "%" character to cause replacement by the corresponding variable value from the current request and response. The following pattern codes are supported:

          • %a - Remote IP address
          • %A - Local IP address
          • %b - Bytes sent, excluding HTTP headers, or '-' if zero
          • %B - Bytes sent, excluding HTTP headers
          • %h - Remote host name (or IP address if enableLookups for the connector is false)
          • %H - Request protocol
          • %l - Remote logical username from identd (always returns '-')
          • %m - Request method (GET, POST, etc.)
          • %p - Local port on which this request was received. See also %{xxx}p below.
          • %q - Query string (prepended with a '?' if it exists)
          • %r - First line of the request (method and request URI)
          • %s - HTTP status code of the response
          • %S - User session ID
          • %t - Date and time, in Common Log Format
          • %u - Remote user that was authenticated (if any), else '-'
          • %U - Requested URL path
          • %v - Local server name
          • %D - Time taken to process the request in millis. Note: In httpd %D is microseconds. Behaviour will be aligned to httpd in Tomcat 10 onwards.
          • %T - Time taken to process the request, in seconds. Note: This value has millisecond resolution whereas in httpd it has second resolution. Behaviour will be align to httpd in Tomcat 10 onwards.
          • %F - Time taken to commit the response, in millis
          • %I - Current request thread name (can compare later with stacktraces)
          • %X - Connection status when response is completed:X = Connection aborted before the response completed.+ = Connection may be kept alive after the response is sent.- = Connection will be closed after the response is sent.

          There is also support to write information incoming or outgoing headers, cookies, session or request attributes and special timestamp formats. It is modeled after the Apache HTTP Server log configuration syntax. Each of them can be used multiple times with different xxx keys:

          • %{xxx}i write value of incoming header with name xxx
          • %{xxx}o write value of outgoing header with name xxx
          • %{xxx}c write value of cookie with name xxx
          • %{xxx}r write value of ServletRequest attribute with name xxx
          • %{xxx}s write value of HttpSession attribute with name xxx
          • %{xxx}p write local (server) port (xxx==local) or remote (client) port (xxx=remote)
          • %{xxx}t write timestamp at the end of the request formatted using the enhanced SimpleDateFormat pattern xxx

          All formats supported by SimpleDateFormat are allowed in %{xxx}t. In addition the following extensions have been added:

          • sec - number of seconds since the epoch
          • msec - number of milliseconds since the epoch
          • msec_frac - millisecond fraction

          These formats cannot be mixed with SimpleDateFormat formats in the same format token.

          Furthermore one can define whether to log the timestamp for the request start time or the response finish time:

          • begin or prefix begin: chooses the request start time
          • end or prefix end: chooses the response finish time

          By adding multiple %{xxx}t tokens to the pattern, one can also log both timestamps.

          The shorthand pattern pattern="common" corresponds to the Common Log Format defined by '%h %l %u %t "%r" %s %b'.

          The shorthand pattern pattern="combined" appends the values of the Referer and User-Agent headers, each in double quotes, to the common pattern.

          When Tomcat is operating behind a reverse proxy, the client information logged by the Access Log Valve may represent the reverse proxy, the browser or some combination of the two depending on the configuration of Tomcat and the reverse proxy. For Tomcat configuration options see Proxies Support and the Proxy How-To. For reverse proxies that use mod_jk, see the generic proxy documentation. For other reverse proxies, consult their documentation.


            我們只需要配置以下幾個簡單配置即可

          #開啟內置Tomcat請求日志 access.log
          server.tomcat.accesslog.enabled=true
          #日志格式
          server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b
          #日志輸出目錄
          server.tomcat.accesslog.directory=${user.home}/log/accesslog/${spring.application.name}
          #日志文件名
          server.tomcat.accesslog.prefix=access_log
          server.tomcat.accesslog.file-date-format=_yyyy-MM-dd
          server.tomcat.accesslog.suffix=.log


            效果


            如何接口統計QPS?

            如上圖中,logging接口,我們只要統計同一秒中,logging接口的請求次數即是該接口的QPS


            代碼開源

            代碼已經開源、托管到我的GitHub、碼云:

            GitHub:https://github.com/huanzi-qch/springBoot

            碼云:https://gitee.com/huanzi-qch/springBoot


          版權聲明

          作者:huanzi-qch

          出處:https://www.cnblogs.com/huanzi-qch

          若標題中有“轉載”字樣,則本文版權歸原作者所有。若無轉載字樣,本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利.


          主站蜘蛛池模板: 香蕉一区二区三区观| 久久国产精品视频一区| 亚洲国产精品一区二区第四页| 国产在线精品一区二区| 免费播放一区二区三区| 国产精品无码不卡一区二区三区 | 国产精品va无码一区二区| 97久久精品无码一区二区| 熟女大屁股白浆一区二区| 北岛玲在线一区二区| 精品欧洲av无码一区二区三区| 中文字幕精品无码一区二区三区 | 无码人妻精品一区二区三区东京热 | 日本伊人精品一区二区三区| 红杏亚洲影院一区二区三区| 国产一区二区精品久久岳√| 欧美亚洲精品一区二区| 韩国福利影视一区二区三区| 久久亚洲日韩精品一区二区三区| 久久精品一区二区三区不卡| 熟女少妇丰满一区二区| 乱人伦一区二区三区| 亚洲AⅤ视频一区二区三区| 动漫精品一区二区三区3d| 一区在线观看视频| www一区二区三区| 精品国产一区AV天美传媒| 亚洲一区二区三区亚瑟| 久久伊人精品一区二区三区| 人妻体内射精一区二区三区| 日韩好片一区二区在线看| 国产大秀视频在线一区二区| 国产一区在线mmai| 正在播放国产一区| 国产不卡视频一区二区三区| 亚洲日韩精品一区二区三区无码| 日韩电影一区二区| 中文字幕av一区| 一区二区三区国模大胆| 一区二区三区国产| 无码夜色一区二区三区|