目录
基础概念
划分清楚 Logging 、Metrics、 Tracing
技术 Tracing 链路跟踪、生态圈现状
技术 Logging 本身,生态圈现状
技术选型 比较
实战
第一个问题:所有请求的日志明细
第二个问题: 将 Logging 收集到 ELK
第三个问题:我们在我们的每个请求 Header 上加上 traceId
第四个问题:Tracing 处理
本文转载自张振华老师分享的的「从架构角度来看 Java 分布式日志如何收集」,仅供大家参考学习。
首先,当我们如果作为架构师的角度去处理一件事情的时候,必须要有一些大局观。
也就是要求我们对个 Logging 的生态有完整的认识,从而来考虑分布式日志如何处理。
我们先来理解一些概念:
身边有很多同事会把这三件可能认识不太彻底,其实这是三件分别侧重点不同的事情,每件事都有各自的深度、边界和重叠部分。
而三者的边界和重叠部分需要我们在整个分布式系统中要非常清楚,而本文 就围绕 Logging 和 Tracing 这两件事情展开一下。
Zipkin 的开源库
Tracing 整体负责干的事情有:
上面我们了解整个 Tracing 的技术栈,我们再来看下关于 Logging 的技术栈。
1>. Spring Logging:Java Util Logging、SLF4J、Log4J、Log4J2和Logback 这些都是老生常谈的问题了,默认 Spring Logging 内部采用的是 Commons Logging。 但是当我们引用相应的其它 Logging 的实现和相应的 Logging 文件的时候就会自动切换 Logging 的实现,并做到兼容。我们唯一需要注意的是:SpringProfile 的支持,如下:
自定义日志实现:
2>. ELK:Spring Logging 紧紧是负责单机日志输出,而分布式不得不请出 ELK。 ElasticSearch 负责作为我们的 logs 的储存和查询,其数据可以提供给 Jaeger 使用可以给 Kibana 使用。 而 Kibana 负责做各种基于 logs 的 chat 图和查看详细的 Logging 的日志记录的详情。Logstash 不用多说了,负责给我们收集日志,包括网关层,业务层等。
3>. Sentry:也是一个重量级选手。负责解决我们系统中的 error 日志和 error 日志警告。 Sentry 就是来帮我们解决这个问题的,它是一款精致的 Django 应用,目的在于帮助开发人员从散落在多个不同服务器上毫无头绪的日志文件里发掘活跃的异常,继而找到潜在的臭虫。 Sentry 是一个日志平台, 它分为客户端和服务端,客户端(目前客户端有 Python、PHP、C#、Ruby 等多种语言)就嵌入在你的应用程序中间,程序出现异常就向服务端发送消息,服务端将消息记录到数据库中并提供一个 Web 界面方便查看。 Sentry 还有有很多亮点,比如敏感信息过滤, release 版本跟踪,关键字查找,受影响用户统计,权限管理等(部分可能需要我们通过代码提供内容)可以通过 Sentry 进行问题分配与跟踪。 Sentry 的 plugin 模块还可以集成大量的第三方工具如: slack , jira 。 对我们来说最大的便利就是利用日志进行错误发现和排查的效率变高了。
重要的有以下三点:
当我们了解了我们需要知道的技术点之后,接下去就是针对我们公司具体业务现状进行选型,以我们公司为例,可能不止一个 Java 团队,还有 Ruby,node.js 等其它语言的开发团队。
好多其它技术选型都是基于 cncf 的,如:k8s、docker、permissions 等,所以我们就一如既往的还选择了 CNCF 的技术体系及 OpenTracing。
其实如果要去真实比较的话,差别也不是特别大,并且都做到了相互的兼容。而 Jaeger VS Zipkin server 选择了 Jaeger,因其启动简单与 Java 解耦。
Java 语言体系采用 Spring 的 Sleuth,这样我们可以省很多事情,并且也是很成熟的解决方案,而 Spring Cloud 生态也非常成熟。
生产的日志要求
生产的日志实现
1. 我们利用
import org.springframework.web.filter.CommonsRequestLoggingFilter;
来打印我们的所有的请求的日志配置如下:
//我们只需要将此类在配置文件中加载即可。里面可以设置Logging里面是否打印header 、request payload、query String 、client信息等。唯一的缺点就是没有办法打印responseBody。
@Bean
@ConditionalOnMissingBean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(true);
loggingFilter.setIncludeQueryString(true);
loggingFilter.setIncludePayload(true);
loggingFilter.setIncludeHeaders(true); return loggingFilter;
}
//源码和原理其实非常简单,做个filter做logging debug即可。public class CommonsRequestLoggingFilter extends AbstractRequestLoggingFilter { @Override
protected boolean shouldLog(HttpServletRequest request) { return logger.isDebugEnabled();
} /**
* Writes a log message before the request is processed.
*/
@Override
protected void beforeRequest(HttpServletRequest request, String message) {
logger.debug(message);
} /**
* Writes a log message after the request is processed.
*/
@Override
protected void afterRequest(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,fals
2. 针对没有 responseBody 的问题,我们可以自定义一个拦截器,和 CommonsRequestLoggingFilter 做差不多的事情即可。这里需要注意的是需要用到:
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
来做参数的输出和 response 的 io 的输出。但是切记很多东西不需要重复写给大家看一个关键代码:
此处我们采用的是 Docker 容器,直接将日志输出到控制台,用 logstash 直接收集 Docker 的日志给 ElasticSearch 在 kibana 显示。如下图所示:
我们只需要 search trackID 即可。
或者以 logback 为例,添加 logstash appender。关键代码如下:
${LOG_FILE}.json
${LOG_FILE}.json.%d{yyyy-MM-dd}.gz
7
UTC
{
"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"
}
//从上下文中取到traceId,然后丢到返回的header里面
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String traceId = ThreadContext.get("traceId");
chain.doFilter(request, response);
((HttpServletResponse)response).setHeader("TraceId", traceId);
}
1. 有了上面的理论基础,就是就看看 spring cloud sleuth 怎么支持 OpenTracing 和生成 tracId 和 span,及其将 log 吐给 jaeger。
注:
原文发布于GitChat精品课(CSDN_Tech)
原文发表时间:2018-06-01