从架构角度来看 Java 分布式日志如何收集

首先,当我们如果作为架构师的角度去处理一件事情的时候,必须要有一些大局观。

也就是要求我们对个 Logging 的生态有完整的认识,从而来考虑分布式日志如何处理。

我们先来理解一些概念:

划分清楚 Logging 、Metrics、 Tracing

身边有很多同事会把这三件可能认识不太彻底,其实这是三件分别侧重点不同的事情,每件事都有各自的深度、边界和重叠部分。

Logging:

Logging 更加偏重的是一条一条的记录,而记录本身是离散事件,没有任何直接关系。Logging 可以在 Console、ElasticSearch、Kafka、File 等各种媒介中显示。而 Logging 的格式又可以通过各种 Logging 的实现去定义 Logging 的格式。

Tracing:

Tracing 的重心是整个处理链条的间接关系,而是需要把各种离散的 Logging 产生联系,让各种处理事件产生一定范围。

Metrics:

Metrics 度量的定义特征是它们是可聚合的。它们是在一段时间内构成单个逻辑度量,计数或直方图的原子数据,偏重于度量。

而三者的边界和重叠部分需要我们在整个分布式系统中要非常清楚,而本 chat 就围绕 Logging 和 Tracing 这两件事情展开一下。

技术 Tracing 链路跟踪、生态圈现状

Google Dapper:Dapper——Google 生产环境下的分布式跟踪系统,而紧接着就发表了论文 Google Dapper paper 。

然后就变成了所有分布式日志 Tracing 的鼻祖了,后来发展起来的 Zipkin、OpenTracing、sleuth 都是在 Google 的这篇论文作为理论基础上,不断优化发展出来的。

Zipkin:Zipkin 是分布式日志链路跟踪系统,最早由 Twitter 创造和开源,现在交由 OpenZipkin 社区管理。它可以帮助收集时间数据在 Microservice 架构需要解决延迟问题。

它管理这些数据的收集和查找。Zipkin 的设计是基于 Dapper。它也是一个完整的生态解决方案包括:collector(收集)、 storage(储存)、 search(搜索)、 Zipkin Web UI(页面控制台)。

而其也对个各种语言做了支持,我们重点关注了一下 java 的 client,看后面的表格。github : https://github.com/openzipkin;官方地址:http://zipkin.io/

OpenTracing:OpenTracinghttp://opentracing.io/通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。

OpenTracing 正在为全球的分布式追踪,提供统一的概念和数据标准。而 OpenTracing 来自大名鼎鼎的 CNCF(Cloud Native Computing Foundation, https://www.cncf.io/)。

虽然 OpenTracing 晚于 Zipkin 但是更大大佬、更大抽象,这使其很快成为业内首选。而开源团队:Zipkin、TRACER、JAEGER(Uber 出品)等等逐渐实现了 OpenTracing 的标准。

Sleuth:这个热闹的事情,怎么能少了 Spring 开源社区呢?Spring-Cloud-Sleuth 是 Spring Cloud 的组成部分之一,为 SpringCloud 应用实现了一种分布式追踪解决方案,其兼容了Zipkin、HTrace、OpenTracing。

而底层是基于 Zipkin 的 brave 做了实现。设计思路也是参考 Dapper(他们之间的关系,作者认为应该是这样的 sleuth 通过 brave 默认输出到 Zipkin,由 Zipkin 决定是否是 OpenTracing 格式的输出,PS:这个欢迎大家一起讨论)。

官方地址:http://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/2.0.0.RC1/single/spring-cloud-sleuth.html

Jaeger 分布式监控系统由 Uber 设计并实现,现已捐赠给 CNCF 基金会,作为分布式环境下推荐的监控系统。其实主要是作为 Tracing 的 server 端,前期先支持了 Zipkin 后来又实现了 OpenTracing 的标准。有 UI 和储存(ElasticSearch、cassandra)。和 Zipkin 的 ui 服务器端有点像。官方地址:https://www.jaegertracing.io/

重点关注一下 Zipkin 的开源库

Tracing 整体负责干的事情有:

生成 trackId, spanId。

负责 MDC 给 log。

发送给 tracingserver 端,server 负责储存和搜索。

Tracing UI 负责展示。

技术 Logging 本身,生态圈现状

上面我们了解整个 Tracing 的技术栈,我们再来看下关于 Logging 的技术栈。

Spring Logging:Java Util Logging、SLF4J、Log4J、Log4J2和Logback 这些都是老生常谈的问题了,默认 Spring Logging 内部采用的是 Commons Logging。

但是当我们引用相应的其它 Logging 的实现和相应的 Logging 文件的时候就会自动切换 Logging 的实现,并做到兼容。我们唯一需要注意的是:SpringProfile 的支持,如下:

<springProfile name="staging">

   <!-- configuration to be enabled when the "staging" profile is active -->

</springProfile>

<springProfile name="dev, staging">

   <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->

</springProfile>

<springProfile name="!production">

   <!-- configuration to be enabled when the "production" profile is not active -->

</springProfile>

自定义日志实现:

ELK:Spring Logging 紧紧是负责单机日志输出,而分布式不得不请出 ELK。

ElasticSearch 负责作为我们的 logs 的储存和查询,其数据可以提供给 Jaeger 使用可以给 Kibana 使用。

而 Kibana 负责做各种基于 logs 的 chat 图和查看详细的 Logging 的日志记录的详情。Logstash 不用多说了,负责给我们收集日志,包括网关层,业务层等。

Sentry:也是一个重量级选手。负责解决我们系统中的 error 日志和 error 日志警告。

Sentry 就是来帮我们解决这个问题的,它是一款精致的 Django 应用,目的在于帮助开发人员从散落在多个不同服务器上毫无头绪的日志文件里发掘活跃的异常,继而找到潜在的臭虫。

Sentry 是一个日志平台, 它分为客户端和服务端,客户端(目前客户端有 Python、PHP、C#、Ruby 等多种语言)就嵌入在你的应用程序中间,程序出现异常就向服务端发送消息,服务端将消息记录到数据库中并提供一个 Web 界面方便查看。

Sentry 还有有很多亮点,比如敏感信息过滤, release 版本跟踪,关键字查找,受影响用户统计,权限管理等(部分可能需要我们通过代码提供内容)可以通过 Sentry 进行问题分配与跟踪。

Sentry 的 plugin 模块还可以集成大量的第三方工具如: slack , jira 。

对我们来说最大的便利就是利用日志进行错误发现和排查的效率变高了。

重要的有一下三点:

及时提醒

报警的及时性:不需要自己再去额外集成报警系统,一旦产生了 issue 便以邮件通知到项目组的每个成员。

问题关联信息的聚合

每个问题不仅有一个整体直观的描绘,聚合的日志信息省略了人工从海量日志中寻找线索,免除大量无关信息的干扰。

丰富的上下文

Sentry 不仅丰富还规范了上下文的内容,也让我们意识到更多的有效内容,提高日志的质量。

技术选型 VS

当我们了解了我们需要知道的技术点之后,接下去就是针对我们公司具体业务现状进行选型,以我们公司为例,可能不止一个 Java 团队,还有 Ruby,node.js 等其它语言的开发团队。

好多其它技术选型都是基于 cncf 的,如:k8s、docker、permissions 等,所以我们就一如既往的还选择了 CNCF 的技术体系及 OpenTracing。

其实如果要去真实比较的话,差别也不是特别大,并且都做到了相互的兼容。而 Jaeger VS Zipkin server 选择了 Jaeger,因其启动简单与 Java 解耦。

Java 语言体系采用 Spring 的 Sleuth,这样我们可以省很多事情,并且也是很成熟的解决方案,而 Spring Cloud 生态也非常成熟。

实战

生产的日志要求

每个请求的参数是什么,输出结果是什么,debug 可以选择自由开启。

每个请求的链路要串起来。

error 独立收集上下文是什么,及时警告,各个环境分开。

生产的日志实现

第一个问题:所有请求的日志明细

1. 我们利用

importorg.springframework.web.filter.CommonsRequestLoggingFilter;

来打印我们的所有的请求的日志配置如下:

//我们只需要将此类在配置文件中加载即可。里面可以设置Logging里面是否打印header 、request payload、query String 、client信息等。唯一的缺点就是没有办法打印responseBody。@Bean@ConditionalOnMissingBeanpublicCommonsRequestLoggingFilterrequestLoggingFilter(){        CommonsRequestLoggingFilter loggingFilter =newCommonsRequestLoggingFilter();        loggingFilter.setIncludeClientInfo(true);        loggingFilter.setIncludeQueryString(true);        loggingFilter.setIncludePayload(true);        loggingFilter.setIncludeHeaders(true);returnloggingFilter;    }

//源码和原理其实非常简单,做个filter做logging debug即可。publicclassCommonsRequestLoggingFilterextendsAbstractRequestLoggingFilter{@OverrideprotectedbooleanshouldLog(HttpServletRequest request){returnlogger.isDebugEnabled();    }/**

    * Writes a log message before the request is processed.

    */@OverrideprotectedvoidbeforeRequest(HttpServletRequest request, String message){        logger.debug(message);    }/**

    * Writes a log message after the request is processed.

    */@OverrideprotectedvoidafterRequest(HttpServletRequest request, String message){        logger.debug(message);    }}

日志输出的格式如下:

[36667] 2018-05-19 20:22:06.185 - [notification-api,93bb291ab411e41a,93bb291ab411e41a,false] - DEBUG [http-nio-8080-exec-1] org.springframework.web.filter.CommonsRequestLoggingFilter.log - Before request [uri=/hello;client=127.0.0.1;headers={host=[127.0.0.1:8080], connection=[keep-alive], accept=[*/*], user-agent=[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36], referer=[http://127.0.0.1:8080/swagger-ui.html], accept-encoding=[gzip, deflate, br], accept-language=[en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7], cookie=[OUTFOX_SEARCH_USER_ID_NCOO=1602949848.9012377; gsScrollPos-73=]}]

[36667] 2018-05-19 20:22:06.434 - [notification-api,93bb291ab411e41a,93bb291ab411e41a,false] - DEBUG [http-nio-8080-exec-1] org.springframework.web.filter.CommonsRequestLoggingFilter.log - After request [uri=/hello;client=127.0.0.1;headers={host=[127.0.0.1:8080], connection=[keep-alive], accept=[*/*], user-agent=[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36], referer=[http://127.0.0.1:8080/swagger-ui.html], accept-encoding=[gzip, deflate, br], accept-language=[en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7], cookie=[OUTFOX_SEARCH_USER_ID_NCOO=1602949848.9012377; gsScrollPos-73=]}]

2. 针对没有 responseBody 的问题,我们可以自定义一个拦截器,和 CommonsRequestLoggingFilter 做差不多的事情即可。这里需要注意的是需要用到:

importorg.springframework.web.util.ContentCachingRequestWrapper;

importorg.springframework.web.util.ContentCachingResponseWrapper;

来做参数的输出和 response 的 io 的输出。但是切记很多东西不需要重复写给大家看一个关键代码:

第二个问题: 将 Logging 收集到 ELK

此处我们采用的是 Docker 容器,直接将日志输出到控制台,用 logstash 直接收集 Docker 的日志给 ElasticSearch 在 kibana 显示。如下图所示:

我们只需要 search trackID 即可。

或者以 logback 为例,添加 logstash appender。关键代码如下:

            <!-- Appender to log to file in a JSON format -->

            <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">

            <file>${LOG_FILE}.json</file>

            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>

            <maxHistory>7</maxHistory>

           </rollingPolicy>

          <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">

            <providers>

           <timestamp>

               <timeZone>UTC</timeZone>

           </timestamp>

              <pattern>

               <pattern>

                   {

                   "severity": "%level",

                   "service": "${springAppName:-}",

                   "trace": "%X{X-B3-TraceId:-}",

                   "span": "%X{X-B3-SpanId:-}",

                   "parent": "%X{X-B3-ParentSpanId:-}",

                   "exportable": "%X{X-Span-Export:-}",

                   "pid": "${PID:-}",

                   "thread": "%thread",

                   "class": "%logger{40}",

                   "rest": "%message"

                   }

               </pattern>

           </pattern>

       </providers>

   </encoder>

</appender>

第三个问题:我们在我们的每个请求 Header 上加上 traceId

//从上下文中取到traceId,然后丢到返回的header里面@OverridepublicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)throwsIOException, ServletException{        String traceId = ThreadContext.get("traceId");            chain.doFilter(request, response);        ((HttpServletResponse)response).setHeader("TraceId", traceId);    }

第四个问题:Tracing 处理

1. 有了上面的理论基础,就是就看看 spring cloud sleuth 怎么支持 OpenTracing 和生成 tracId 和 span,及其将 log 吐给 jaeger。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容