clojure实战——日志处理

时间:2023-01-10 22:52:33

clojure实战——日志处理

1. 关于日志的一些想法

日志对于开发人员来说,是定位、分析软件故障时的重要依据;对于运维人员来说,是了解软件运行状态、系统状态的重要途径;对于业务需求方来说,是获取统计业务相关数据的重要来源。由此观之,开发人员在记录日志时,不仅需要考虑开发时定位软件异常、方便调试等需要,还要考虑来自运维、需求方这三方面的需求。另外,为了方便日后进行日志分析、统计,需要好好设计日志的记录格式、内容。
从使用目的来看,日志可分为运行日志和业务日志。前者其实也包含后者,都可以用于分析软件运行状态、定位故障,但业务日志主要为需求方统计业务结果所用,因而特意提取出来,便于日后分析。
但是要注意,业务日志不能只考虑当前需求方的需求,即业务日志不是单纯为满足当前统计需求而记的。业务日志是一个数据源,记录了所有业务相关日志,使用者通过日志分析来得到自己想要的数据。理想的情况下,即使没有统计需求,业务日志所记录下来的数据应该尽可能的满足以后分析和统计的需要。

2. 构建日志系统项目demo

(1)使用到的库

日志输出的格式,应该方便目前日志分析系统的使用,因为目前使用的是elk日志分析系统,所以日志输出格式为logstash json格式,日志记录使用logback。在project文件中加入以下几个库,每个库的具体介绍可在github上自行搜索查阅:

:dependencies [
             ;; clojure日志工具库
             [org.clojure/tools.logging "0.3.1"]
             ;; logback日志
             [ch.qos.logback/logback-classic "1.1.2"]
             ;; logstash所需格式
             [net.logstash.logback/logstash-logback-encoder "3.3-utf-8"]
             ;; 将clojure的map格式转为json格式
             [cheshire "5.3.1"]]

(2)程序中的应用

可建立一个独立的单元封装本地日志相关操作:

(ns local-log
    (:require [clojure.tools.logging :as log]
              [cheshire.core :as json]))
(defn info
    "json格式输出业务日志信息" 
    [map-data] 
    (log/info (json/generate-string map-data)))

(defn debug 
    "输出debug日志" 
    [map-data] 
    (when (log/enabled? :debug)
        (log/debug map-data))) 

(defn error 
 "输出error日志" 
 [map-data] 
 (when (log/enabled? :error)
    (log/error map-data)))

在需要输出日志的单元中,引入该单元:

(ns log-test
    (:require [local-log :as log]))

(defn test-debug []
    (log/debug {:method "test-debug" :msg "test-debug run."})

(defn test-info []
    (log/info {:method "test-info" :msg "test-info run."})

(defn test-error []
    (log/error {:method "test-error" :msg "test-error run."})

(defn -main []
    (test-debug)
    (test-info)
    (test-error))

(3)logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-10contextName %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

<!-- 按照每天生成日志文件 -->
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 拦截debug以上级别的日志-->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>debug</level>
    </filter>
    <file>logs/test-log.log</file>
    <!--根据日期滚定记录,过期日志后面添加日期后缀-->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/test-log.log.%d{yyyy-MM-dd}</fileNamePattern>
        <!-- 日志文件保留天数-->
        <maxHistory>180</maxHistory>
    </rollingPolicy>
    <!-- debug日志无需交于日志分析工具,这里使用普通的输出格式-->
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

<!-- 按照每天生成日志文件 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 拦截info级别的日志,只有info级别的日志会被记录-->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>info</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <file>logs/test-log-info.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/test-log-info.log.%d{yyyy-MM-dd}</fileNamePattern>
        <!-- 日志文件保留天数-->
        <maxHistory>180</maxHistory>
    </rollingPolicy>
    <!-- 这里使用将logbback日志格式转成logstash json格式的类-->
    <encoder class="net.logstash.logback.encoder.LogstashEncoder" />
</appender>

<!-- 生成err日志文件 -->
<appender name="ERR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 拦截warn以上级别的日志-->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>warn</level>
    </filter>
    <file>logs/test-log-error.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/test-log-error.log.%d{yyyy-MM-dd}</fileNamePattern>
        <!-- 日志文件保留天数-->
        <maxHistory>180</maxHistory>
    </rollingPolicy>
    <!-- 这里使用普通的输出格式-->
    <encoder>
        <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>
<!--通过修改level="info"来决定输出地日志级别-->
<logger name="local-log" level="info" additivity="false">
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="LOG_FILE" />
</logger>

<root level="debug">
    <appender-ref ref="LOG_FILE" />
</root>

上面的配置,将info业务日志独立成一个文件,并将输出格式转为elk的logtash所需的json格式,方便日志分析和统计。

运行测试文件:

lein run -m test-log/-main

可看到不同级别的日志将记录于不同文件中。

3. 碎碎念

大数据时代,数据分析愈加重要,用户行为分析就是从大量的日志文件中分析出来的。也许现阶段的需求很小,并不需要大量的日志内容,但是将来的需求是未知的,因此业务日志应当内容详尽,格式统一清晰。为方便输出当前的统计需求而在程序中做复杂的逻辑处理,是非常不合理的(如不要为了统计总的用户数而添加一个userCount变量)。日志应当尽可能记录元数据,然后再通过日志分析系统进行统计。
以前写代码时,经常会使用prn来打印信息,调试代码,但这些代码在发布时是需要删除的,所以很是麻烦,也显得忒不专业^ ^。可以使用log/debug的方式,并通过判断debug开关是否开启来打印debug输出。