关于日志管理:日志管理系统多种方式总结

27次阅读

共计 5884 个字符,预计需要花费 15 分钟才能阅读完成。

  • 我的项目实际

    • 微服务架构中,二次浅封装实际
    • 分布式我的项目中,选型与依赖治理

一、背景简介

我的项目中日志的治理是根底性能之一,不同的用户和场景下对日志都有特定的需要,从而须要用不同的策略进行日志采集和治理,如果是在分布式的我的项目中,日志的体系设计更加简单。

  • 日志类型:业务操作、信息打印、申请链路;
  • 角色需要:研发端、用户端、服务级、零碎级;

用户与需要

  • 用户端:外围数据的增删改,业务操作日志;
  • 研发端:日志采集与管理策略,异样日志监控;
  • 服务级:要害日志打印,问题发现与排查;
  • 零碎级:分布式我的项目中链路生成,监控体系;

不同的场景中,须要选用不同的技术手段去实现日志采集治理,例如日志打印、操作记录、ELK 体系等,留神要防止日志治理导致程序异常中断的状况。

越是简单的零碎设计和业务场景,就越依赖日志的输入信息,在大规模的架构中,通常还会搭建独立的日志平台,提供日志数据的采集、存储、剖析等整套解决方案。

二、Slf4j 组件

1、外观模式

日志的组件恪守外观设计模式,Slf4j 作为日志体系的外观对象,定义标准日志的规范,日志能力的具体实现交由各个子模块去实现;Slf4j 明确日志对象的加载办法和性能接口,与客户端交互提供日志治理性能;

private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Impl.class) ;

通常禁止间接应用 Logback、Log4j 等具体实现组件的 API,防止组件替换带来不必要的麻烦,能够做到日志的对立保护。

2、SPI 接口

从 Slf4j 和 Logback 组件交互来看,在日志的应用过程中,根本的切入点即应用 Slf4j 的接口,辨认并加载 Logback 中的具体实现;SPI 定义的接口标准,通常作为第三方(内部)组件的实现。

上述 SPI 作为两套组件的连接点,通过源码大抵看下加载过程,追溯 LoggerFactory 的源码即可:

public final class org.slf4j.LoggerFactory {private final static void performInitialization() {bind();
    }
    private final static void bind() {
        try {StaticLoggerBinder.getSingleton();
        } catch (NoClassDefFoundError ncde) {String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            }
        }
    }
}

此处只贴出了几行示意性质的源码,在 LoggerFactory 中执行初始化绑定关联的时候,如果没有找到具体的日志实现组件,是会报告出相应的异样信息,并且采纳的是 System.err 输入谬误提醒。

三、自定义组件

1、性能封装

对于日志(或其余)罕用性能,通常会在代码工程中封装独立的代码包,作为公共依赖,对立治理和保护,对于日志的自定义封装能够参考之前的文档,这里通常波及几个外围点:

  • starter 加载:封装包配置成 starter 组件,能够被框架扫描和加载;
  • aop 切面编程:通常在相干办法上增加日志注解,即可自动记录动作;
  • annotation 注解:定义日志记录须要标记的外围参数和解决逻辑;

至于如何组装日志内容,适配业务语义,以及后续的治理流程,则依据具体场景设计相应的策略即可,比方日志怎么存储、是否实时剖析、是否异步执行等。

2、对象解析

在自定义注解中,会波及到对象解析的问题,即在注解中放入要从对象中解析的属性,并且把值拼接到日志内容中,能够加强业务日志的语义可读性。

import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class Test {public static void main(String[] args) {
        // Map 汇合
        HashMap<String,Object> infoMap = new HashMap<>() ;
        infoMap.put("info","Map 的形容") ;
        // List 汇合
        ArrayList<Object> arrayList = new ArrayList<>() ;
        arrayList.add("List-00");
        arrayList.add("List-01");
        // User 对象
        People oldUser = new People("Wang",infoMap,arrayList) ;
        People newUser = new People("LiSi",infoMap,arrayList) ;
        // 包装对象
        WrapObj wrapObj = new WrapObj("WrapObject",oldUser,newUser) ;
        // 对象属性解析
        SpelExpressionParser parser = new SpelExpressionParser();
        // objName
        Expression objNameExp = parser.parseExpression("#root.objName");
        System.out.println(objNameExp.getValue(wrapObj));
        // oldUser
        Expression oldUserExp = parser.parseExpression("#root.oldUser");
        System.out.println(oldUserExp.getValue(wrapObj));
        // newUser.userName
        Expression userNameExp = parser.parseExpression("#root.newUser.userName");
        System.out.println(userNameExp.getValue(wrapObj));
        // newUser.hashMap[info]
        Expression ageMapExp = parser.parseExpression("#root.newUser.hashMap[info]");
        System.out.println(ageMapExp.getValue(wrapObj));
        // oldUser.arrayList[1]
        Expression arr02Exp = parser.parseExpression("#root.oldUser.arrayList[1]");
        System.out.println(arr02Exp.getValue(wrapObj));
    }
}
@Data
@AllArgsConstructor
class WrapObj {
    private String objName ;
    private People oldUser ;
    private People newUser ;
}
@Data
@AllArgsConstructor
class People {
    private String userName ;
    private HashMap<String,Object> hashMap ;
    private ArrayList<Object> arrayList ;
}

留神下面应用的 SpelExpressionParser 解析器,即 Spring 框架的原生 API;业务中遇到的很多问题,倡议都优先从外围依赖 (Spring+JDK) 中寻找解决形式,多花工夫相熟零碎中外围组件的全貌,对开发视线和思路会有极大的帮忙。

3、模式设计

这里看一个比较复杂的自定义日志解决思路,通过 AOP 模式识别日志注解,并解析注解中要记录的对象属性,构建相应的日志主体,最初依据注解标记的场景去适配不同的业务策略:

对于性能的通用性要求越高,在封装时内置的适配策略就要越形象,在解决简单的逻辑流程时,要长于将不同的组件搭配应用,能够分担业务撑持的压力,造成稳固牢靠的解决方案。

四、分布式链路

1、链路辨认

基于微服务实现的分布式系统,解决一个申请会通过多个子服务,如果过程中某个服务产生异样,须要定位这个异样归属的申请动作,从而更好的去判断异样起因并复现解决。

定位的动作则依赖一个外围的标识:TraceId- 轨迹 ID,即申请在各个服务流转时,会携带该申请绑定的 TraceId,这样能够辨认不同服务的哪些动作为同一个申请产生的。

通过 TraceId 和 SpanId 即可还原出申请的链路视图,再联合相干日志打印记录等动作,则能够疾速解决异样问题。在微服务体系中 Sleuth 组件提供了该能力的撑持。

链路视图的外围参数能够集成 Slf4j 组件中,这里能够参考 org.slf4j.MDC 语法,MDC 提供日志前后的参数传递映射能力,外部包装 Map 容器治理参数;在 Logback 组件中,StaticMDCBinder提供该能力的绑定,这样日志打印也能够携带链路视图的标识,做到该能力的残缺集成。

2、ELK 体系

链路视图产生的日志是十分宏大的,那这些文档类的日志如何治理和疾速查问应用同样是个关键问题,很常见的一个解决方案即 ELK 体系,当初已更新换代为 ElasticStack 产品。

  • Kibana:能够在 Elasticsearch 中应用图形和图表对数据进行可视化;
  • Elasticsearch:提供数据的存储,搜寻和剖析引擎的能力;
  • Logstash:数据处理管道,可能同时从多个起源采集、转换、推送数据;

Logstash 提供日志采集和传输能力,Elasticsearch 存储大量 JSON 格局的日志记录,Kibana 则能够视图化展示数据。

3、服务与配置

配置依赖:须要在服务中配置 Logstash 地址和端口,即日志传输地址,以及服务名称;

spring:
  application:
    name: app_serve
logstash:
  destination: 
    uri: Logstash- 地址
    port: Logstash- 端口

配置读取:Logback 组件配置中加载上述外围参数,这样在配置上下文中能够通过 name 的值应用该参数;

<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="butte_app" />
<springProperty scope="context" name="DES_URI" source="logstash.destination.uri" />
<springProperty scope="context" name="DES_PORT" source="logstash.destination.port" />

日志传输:对传输内容做相应的配置,指定 LogStash 服务配置,编码,外围参数等;

<appender name="LogStash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
    <!-- 日志传输地址 -->
    <destination>${DES_URI:-}:${DES_PORT:-}</destination>
    <!-- 日志传输编码 -->
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
        <providers>
            <timestamp>
                <timeZone>UTC</timeZone>
            </timestamp>
            <!-- 日志传输参数 -->
            <pattern>
                <pattern>
                    {
                    "severity": "%level",
                    "service": "${APP_NAME:-}",
                    "trace": "%X{X-B3-TraceId:-}",
                    "span": "%X{X-B3-SpanId:-}",
                    "exportable": "%X{X-Span-Export:-}",
                    "pid": "${PID:-}",
                    "thread": "%thread",
                    "class": "%logger{40}",
                    "rest": "%message"
                    }
                </pattern>
            </pattern>
        </providers>
    </encoder>
</appender>

输入格局:还能够通过日志的格局设定,治理日志文件或者控制台的输入内容;

<pattern>%d{yyyy-MM-dd HH:mm:ss} %contextName [%thread] %-5level %logger{100} - %msg %n</pattern>

对于 Logback 组件日志的其余配置,例如输入地位,级别,数据传输方式等,能够多参考官网文档,一直优化。

4、数据通道

再看看数据传输到 Logstash 服务后,如何再传输到 ES 的,这里也须要相应的传输配置,留神 logstash 和 ES 举荐应用雷同的版本,本案例中是 6.8.6 版本。

配置文件:logstash-butte.conf

input {
  tcp {
    host => "192.168.37.139"
    port => "5044"
    codec => "json"
  }
}

output {
  elasticsearch {hosts => ["http://localhost:9200"]
    index => "log-%{+YYYY.MM.dd}"
  }
}
  • 输出配置:指定 logstash 连贯的 host 和端口,并且指定数据格式为 json 类型;
  • 输入配置:指定日志数据输入的 ES 地址,并指定 index 索引按天的创立形式;

启动 logstash 服务

/opt/logstash-6.8.6/bin/logstash -f /opt/logstash-6.8.6/config/logstash-butte.conf

这样残缺的 ELK 日志治理链路就实现了,通过应用 Kibana 工具就能够查看日志记录,依据 TraceId 就能够找到视图链路。

五、参考源码

利用仓库:https://gitee.com/cicadasmile/butte-flyer-parent

组件封装:https://gitee.com/cicadasmile/butte-frame-parent

正文完
 0