不得不承认,微软的Teams聊天软件借(chao)鉴(xi)了很多Slack的理念,凭借其庞大的用户群体以及产品生态优势,使Teams在短时间内得以快速发展和推广。与其他消息工具类似,Teams也提供了webhook,使用它可以很容易地拓展Teams的功能,使其不仅限于一个聊天软件,更可以使其变成一个消息门户。今天我们就实现一个logback appender,使Teams可以显示应用程序的报警通知。
Webhook
在Teams中Connectors是用来与外部应用程序沟通的重要媒介,而Incoming webhooks就是一种特殊的Connector, 它本质上就是一个唯一的URL,可以通过向这个URL来post一组JSON信息,来达到与Teams沟通协作的目的。关于Teams中Connector与webhook的更多介绍,可以参考这里。
在开始向Teams发送日志之前,我们应该准备好webhook,创建webhook很简单,大概就是下面的几个步骤,
- 首先在希望展示log信息的聊天群组中新建一个channel;
- 配置这个channel的Connectors,添加一个Incoming Webhook;
- 可以给这个webhook配置一个独特的头像,也可以使用默认;
- 拷贝生成的webhook URL备用。
关于创建webhook的详细步骤,可以参考这里。
Message Card
发送至Teams的JSON结构要遵守office-365-connector-card的约束,这里我们只采用Cards中相对简单的Message Card来作为消息的载体,其JSON结构类似于,
1 | { |
借助于Lombok与Jackson,MessageCard 的实现非常简单,
1 |
|
Http客户端
前面已经讲到,Teams中的消息是来自于POST到webhook的MessageCard的JSON,所以挑选一个合适的http客户端就非常的重要,我们希望这个http客户端能够具备以下特性:
- 高性能(不解释);
- 少依赖(我们自然不希望一个logback appender还有一大堆的依赖);
- 全特性(至少能够支持配置代理等特性,毕竟发送日志的应用不一定能够直接请求到webhook);
按照以上的要求,这里我们选择OkHttp,以上3点都可以满足。下面的代码段展示如何根据配置参数构建一个http client,支持以下属性:
- 连接超时;
- 读写超时;
- 代理和代理凭据;
1 | private final OkHttpClient okHttpClient; |
以上代码中的 connectTimeout、writeTimeout、readTimeout、proxyHost、proxyPort、proxyUsername、proxyPassword 均来自于logback的配置文件。
消息内容的结构与样式
在Teams中显示系统异常信息,其主要目的还是为了提醒或者是报警,而不是用Teams来替代系统日志。当出现异常时,我们可以通过Teams的信息快速了解到报错的摘要,进而判断紧急程度,如果需要采取进一步的分析,仍然需要通过其他技术手段来查看详细日志,例如日志文件或者ELK。无论从业务需求或是性能需求看,发送到Teams的消息应该尽可能的简单高效,一目了然。
基于以上目的,我们用下面的结构与样式来表现一个消息的内容,
title
- prefix(可选), 标题前缀会自动被[]包围,用来展示Server或者Appliation的名称,当然也可以直接指定环境profiel,例如staging、prod等;
- title body, 出错日志的message部分,能够非常直观的了解到报错的消息内容,例如
log.error("error occurred when processing request", e)
, 那么title body就是 error occurred when processing request ;
body
- logger name, 输出这个日志的logger,一般是一个class的全名;
- formatted message, 相当于Exception的名称和Error Message,假设抛出异常的代码是
new IllegalStateException("OPS! You are not so good...")
, 那么输出的消息就是 java.lang.IllegalStateException: OPS! You are not so good…; - exceptoin stack trace, 由于异常的stack trace往往都很长,这里我们会加以限制,最多打印N行,默认N=5,超出部分用 … 来代替;
基于以上定义,如果抛出一个
new IllegalStateException("OPS! You are not so good...")
异常且被log.error("error occurred when processing request", e)
捕获,在Teams中显示的消息格式大概就是酱紫,
logback中,消息来源于 ILoggingEvent ,通过这个对象,我们可以获取到上面title与body所需的所有信息,由于 ILoggingEvent 是一个interface,我们需要把它cast成具体的实现类从而获得更多的属性,详细代码如下,
1 | private MessageCard buildMessage(ILoggingEvent event) { |
限制与缺点
虽说Teams为我们提供了webhook的方式,但这种其终究还是一个企业级聊天平台,而非一个集中日志系统,而Connctors本身,也有一定的QPS限制,并不能及时处理海量的日志或报警,试想一下我们的系统采用大规模的集群部署或者容器化部署,在短时间内可能会产生大量的错误报警,这个时候对于Teams Connector的冲击还是非常大的。
Connector的QPS限制可以参考这里。
所以在某些场景下,直接使用logback appender向Teams发送消息并不是一个非常好的选择,可以适当考虑采用ELK等集中式日志管理平台来收集日志,在出发某些特定条件,再由日志平台向Teams转发消息。