Posts Log-Studio工程思考与实践
Post
Cancel

Log-Studio工程思考与实践

1.背景现状

►► 近些年随着容器化的普及应用,架构体系从微服务、云原生步步升级,工程日志不再是简简单单的纠查排错运维。更多的是承载着Logging、Tracing、Metrics、 Topology等方面的任务。经过大量的工程实践,以ELK为代表的日志处理方案几乎完美地整合了日志抓取、搜索和展示几个方面的需求,是目前业界最流行的日志处理 解决方案。在诸多工程项目应用中,在数据处理环境,主要是针对日志date、level、traceId等头部通用数据的提取,对于message部分的转换处理,通常将其作为 整段处理或者针对不同项目情况做特定的pattern进行解析,最终将数据扔给ES去进行检索。

日志收集

►► 在工程层 面上,一方面由于message部分的信息因为没有规范化的标准,日志文件中的业务日志信息更是五花八门,比如:PHP项目工程中将一个数组$array 直接输出到日志文件中,Java工程中将一个JavaBean直接toString()输出的。这些问题在应用Runtime阶段带来性能上的问题,同时给后续的智能运维体系工作带 来非常大的阻力。另一方面现体系中业务监控是基于日志进行策略处理,但日志管理与监控平台通过人工关联,随着后续项目的快速迭代发展,监控平台的规则维护难以 持续。

总结现有工程日志现状存在的弊端及潜在需求:

  • [代码实践]:日志与业务代码混编,不便于项目日志维护
  • [日志规范]:日志规范不便于后续日志接入及解析工作
  • [链路分析]:跨服务存在日志断链情况,不便于排查问题
  • [安全风险]:日志敏感字段加密未统一处理,存在风险
  • [故障排查]:依赖debug排查故障,日志级别动态调整
  • [工程运维]:基于日志报警需要人工运维,难以持续

2.目标分析

我们希望在工程层面尽可能的解决后顾之忧,为此我们需要探讨以下几个问题:

►► (1) ETL机制在日志工程方面实践问题。根据我们目前现状,随着接入项目的递增、日TPS的增长,以下问题将突显:

  • [突显问题1]:日志解析模板维护将难以持续
  • [突显问题2]:log transfer服务横向野蛮增长

►► 从机器资源投入角度出发,数据加工处理的便利性取决于数据源的质量(规范+标准),大多实践工程的普遍做法是在transfer阶段对收集的数据进行结构化处理, 为此需要付出的代价不仅仅是无效处理增加、机器资源浪费,更多的是同等量级下的高延时。从另一个角度出发,如果我们能在Extract阶段对数据源头标准化、规范 化萃取是不是可以更好的解决以上两个突显的问题?

►► (2) 日志报警与工程pipeline问题。基于日志关键字报警机制,RD手工将日志信息与报警平台进行关联操作,将存在以下问题:

  • [周期管理]:DEV阶段与OPS阶段不连续
  • [报警规则]:容易造成无效报警规则堆积
  • [报警质量]:报警泛滥导致运维成本增加

    是不是将工程日志上报到报警平台就能解决面临的问题?工程日志的最佳管理实践在Local还是Remote?

►► (3) 服务运维能力问题。服务运维能力取决于系统监控、业务监控的能力。其中日志在业务监控中起到了至关重要的作用。如何更合理的利用日志更快速、有 效的解决生产相关问题是DevOps理论中常提的开发运维一体化中不可不提的话题。总结一句:立足于工程、卓效于运维。主要体现以下:

  • [动态降级]:INFO级动态降级DEBUG级,排查疑难杂症
  • [链路拓扑]:服务间链路无缝接轨,实现链路、拓扑分析

►► (4) 日志信息安全问题。对于敏感信息的处理非常关键,尤其是金融科技行业更是重中之重。依赖RD同学在业务逻辑代码中嵌入 日志脱敏,存在两个问题:一是敏感信息管理不可控,二是工程重复代码问题。解决以上问题,我们是否可以对敏感字段建立字典,通过日志规范在output环节进行 filter处理,对于命中字典中存在的敏感字段对应的信息自动进行脱敏处理,对于脱敏处理方式可以采用业界通用的方式,如果有定制需求扩展SPI进行处理,这个方 式能行得通吗?实践过程中会遇到什么问题?

3.技术方案

3.1 Pipeline视角

日志与project pipeline

【工程维度】

►► 1. 开发阶段:工程代码过程中,RD同学通过需要完成两件事:

  • 本地工程进行日志埋点
  • VP系统进行日志输入工作

►► 2. 构建阶段:这里说的构建包括本地compile以及在发布过程中的build package,构建过程client工作:

  • 从VP系统pull对应日志信息到本地(对应:env、project、branch)
  • 工程maven plugin将对日志格式进行check

►► 3. 运行阶段:通过日志埋点、工程构建后,在服务运行过程中:

  • traceId生成、桥接
  • 日志规范化预期输出

【平台维度】

►► 1. VP负责日志规范化录入、包括日志日志具体含义、是否报警、敏感标识等,对于报警日志需要通过leader审核生成同步到MP

►► 2. MP接收到报警日志信息,将通过设置报警规则(frequency、spike、flatline等)来使规则生效并被加载到AP

►► 3. AP平台通过对ES对应的index以及加载的rule进行匹配,实现业务监控报警任务

3.2 工程视角

日志组件

►► Log-Studio解决方案根据各自现状及未来规划可采用Local模式和Platform模式。Local模式支持轻量化接入,快速实现工程日志标准接入但缺乏管理手段。 Remote模式在实现公司级工程日志标准化的同时兼备有效的管理手段。以下将从功能角度概述Log-Studio相关。

►► 1. log-meteor基于snowflake算法实现分布式场景下生成唯一ID,通过ZeroFill方案有效避免伪共享问题,同时采用双RingBuffer机制提升性能。

►► 2. log-parser将日志埋点与日志信息关联format解析,可自行扩展SPI实现自定义解析,兼容sf4j\log4j等常用日志组件。

►► 3. log-bridge对于常用RPC框架、MQ组件以及http等相关通信协议进行traceId埋点及接收,保障服务间链路可追踪,同时在工程实践中RD无感知。

3.3 重点解析

3.3.1 log-parser

►► log-parser主要实现日志标准化、规范化输出,日志与代码解耦,日志报警与日志级别解耦,关键字段脱敏等,提升工程及管理效率。

日志组件-parser

为了低侵入接入,log-parser基于sl4j\log4j进行代理增强,保障与原日志不冲突,可随意切换。主要实现LoggerFactory.getLogger方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class FLoggerFactory {

        private static <T> T getLogger(String name) {

                Object logger = loggerMap.get(name);

                if (logger == null) {

                    synchronized (name.intern()) {

                        logger = loggerMap.get(name);

                        if (logger == null) {

                            Logger target = LoggerFactory.getLogger(name);

                            logger = new LoggerHandler(target).getFLogger();

                            loggerMap.put(name, logger);
                        }
                    }
                }

                return (T)logger;
            }
}

具体的proxy由LoggerHandler进行初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LoggerHandler implements InvocationHandler {

    private final Logger target;

    private final FLogger proxy;

    /**
     * Construct instance and build logger proxy.
     *
     * @param target target
     */
    public LoggerHandler(Logger target) {

        this.target = target;

        Class<?>[] targetInterfaces = target.getClass().getInterfaces();

        Class<?>[] proxyInterfaces = new Class<?>[target.getClass().getInterfaces().length + 1];

        System.arraycopy(targetInterfaces, 0, proxyInterfaces, 0, targetInterfaces.length);

        proxyInterfaces[proxyInterfaces.length - 1] = FLogger.class;

        this.proxy = (FLogger)Proxy.newProxyInstance(target.getClass().getClassLoader(), proxyInterfaces, this);
    }
}

这里对于local模式下log.xml中日志信息或者Remote模式下日志信息,通过log-parser-client进行load到cache中,Cache采用WeakHashMap。

以local模式下log.xml为例,日志格式信息如下:

1
2
3
<xml>
    <message id="app001" text="desc:biz log param, billId:{0}, cid:{1}“ skey="cid" alarm="N" level="INFO" />
</xml>

3.3.2 log-bridge

►► log-bridge通过一个全局的traceId将分布在各服务节点的请求串联,这里包括但不仅限于spanId、Pid等拓扑信息。以下以Dubbo\Http为例:

  • dubbo框架,通过dubbo的SPI机制来实现日志埋点,基于调用拦截扩展(filter)来实现,服务调用方通过在reference标签或consumer标签中引用,服务提供方 通过service或provider标签中引用

    1
    2
    3
    4
    5
    6
    7
    8
    
      <!-- 消费方调用过程拦截 -->
      <dubbo:reference filter="xxx"/>
      <!-- 消费方调用过程缺省拦截器,将拦截所有的reference -->
      <dubbo:consumer filter="xxx"/>
      <!-- 提供方调用过程拦截 -->
      <dubbo:service filter="xxx"/>
      <!-- 提供方调用过程缺省拦截器,将拦截所有的service -->
      <dubbo:provider filter="xxx"/>
    
  • 通过在httpClient发送时在header头里加入traceId与SpanId,在http接收时获取traceId和SpanId,再生成本系统对应的SpanId即可。需要对httpClient 包进行封装,其他系统需要引用封装后的log-trace-bridge-rpc-http模块。可以实现org.apache.http.HttpRequestInterceptor和org.apache.http.HttpResponseInterceptor 接口在消息传入传出时对消息头进行填充。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      public interface HttpRequestInterceptor {
    
       void process(HttpRequest request, HttpContext context) throws HttpException, IOException;
    
      }
    
      public interface HttpResponseInterceptor {
    
       void process(HttpResponse response, HttpContext context) throws HttpException, IOException;
    
      }
    

使用HttpClientBuilder提供的addRequestInerceptorFirst和addResponseInterceptorLast将HttpRequestInterceptor和HttpResponseInterceptor 对象加入到拦截器中:

◐ addRequestInerceptorFirst:将Interceptor加到执行列表的头部 ◑ addResponseInterceptorLast:将Interceptor加到执行列表的尾部

4.总结

►►各大互联网公司针对工程效率工作纷纷构建虚拟化平台,以另一条捷径积极辅助业务工程推进,将标准规范统一落实在工程实践、将智捷运维穿插在平台体系。为避 免各工程“烟囱式”个性化建设导致运维成本高、能力沉淀弱等问题,本文从从工程层面、平台层面、运维层面进行分析寻找最优日志标准化、链路化、管理化解决方案。 目前,笔者所在团队已经从非核心项目进行工程层面实践,实现日志与代码隔离、标准化与规范化输出为后续Tracing、Metrics、Topology打下工程基础!

This post is licensed under CC BY 4.0 by the author.

如何拿到目标offer?

浅谈合格的技术面试官