转载

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

在日常线上运行的应用中如果报了异常,怎么在第一时间收到报错告警并且查看报错日志呢?周五就因为缺乏报错日志,导致构建服务受影响超过了 2 小时,虽然不至于「死人」,但是也确实让我意识到日志的重要性!后来临时在相关的地方加上日志,通过错误信息很快定位了到了原因,原来是数据库被运维人员做了新特性的改动,而我们服务还未适配好。想想当时的场景,还真是有点紧张~囧

今天就学习一下利用 Sentry 作为日志收集系统,结合之前学习的log4j2 日志输出,主动收集异常日志。

Sentry

我们先来 Sentry 是怎么介绍自己的:

Open-source error tracking that helps developers monitor and fix crashes in real time. Iterate continuously. Boost efficiency. Improve user experience

大体意思就是:Sentry 是一款开源的错误跟踪系统,可帮助开发人员实时监控和修复崩溃。它是不断迭代的,提高效率,改善用户体验。Sentry 本身的文档也记载的比较全面,强烈安利,一般问题可以通过阅读 Doc 学习或者 论坛咨询 。

OK,下面我们就开始通过 Docker 安装 Sentry。

PS:如果你的 Docker 环境还未配置好,可以阅读我之前的总结: Linux——CentOS 安装 Docker 教程

前提

在 Sentry Installation 页面 ,官方做了总结,Sentry 需要和几个服务有交互:

  • PostgreSQL :Docker image postgres:9.5 ,这里明确版本是 9.5,我觉得非常棒!这保证了在未来部署的可重现,避免了不同版本差异造成部署失败的问题。
  • Redis
    • 官方说如果你使用的 Ubuntu < 15.04,推荐安装 chirs-lea/redis-server
    • 文档推荐的是 Docker image redis:3.2-alpine .

硬件要求,具体的可以阅读 官方文档 ,我这里仅仅是家里测试使用,因此无所谓。

下面的步骤其实是官方文档的翻译,但是,跟着走了一遍之后发现,服务并不能正常运行起来,但是也没啥坏处,主要步骤其实就和这个差不多,可能是某些细节漏了,因此,你可以大体浏览一下步骤。如果不想浪费时间,可以直接跳到下面的 「脚本一键安装 Sentry」小节。

创建容器

需要 Docker 版本 1.10+

克隆仓库 getsentry/onpremise 。这个仓库是定制化构建你自己 Sentry 镜像的基础:

cd /data
git clone https://github.com/getsentry/onpremise.git
cd onpremise

仓库里的 sentry.conf.py and config.yml 可供你 配置 Sentry 所用,建议先浏览一下这个配置文档。

现在开始构建我们定制好的镜像。如果你构建的镜像需要推送到你本地的镜像仓中,那么可以用如下方式先定义好镜像名再构建:

REPOSITORY=registry.michael.com/sentry make build push

我本地没有镜像仓,只需要用如下方式构建即可:

make build

运行依赖的服务

Redis:

docker run /
  --detach /
  --name sentry-redis /
  redis:3.2-alpine

PostgreSQL:

docker run /
  --detach /
  --name sentry-postgres /
  --env POSTGRES_PASSWORD=secret /
  --env POSTGRES_USER=sentry /
  postgres:9.5

Outbound Email:

docker run /
  --detach /
  --name sentry-smtp /
  tianon/exim4

运行 Sentry 服务

${REPOSITORY} 只的是你刚刚定义的镜像名,如果没有,默认是 sentry-onpremise

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

为了测试镜像是 OK 的,可以运行如下命令:

docker run /
  --rm ${REPOSITORY} /
  --help

现在可以生成一个 secret-key 值:

docker run /
  --rm ${REPOSITORY} /
  config generate-secret-key

生成的值可以在 config.yml 中设置给 system.secret-key ,或者通过环境变量。如果是设置在 config.yml 文件中,这时候你必须重新构建你的镜像。

运行的基本命令举例如下:

docker run /
  --detach /
  --link sentry-redis:redis /
  --link sentry-postgres:postgres /
  --link sentry-smtp:smtp /
  --env SENTRY_SECRET_KEY='<secret-key>' /
  ${REPOSITORY} /
  <command>

官方文档里有一行小字,说明的是后面的文档,不会特地的写上 –link 去表示链接容器,但是这些都是必须的!同时,${REPOSITORY} 会被引用为 sentry-onpremise。

运行 Web 服务

暴露的是 9000 的端口,这样部署之后,可以通过 http://localhost:9000/ 访问。

docker run /
  --detach /
  --link sentry-redis:redis /
  --link sentry-postgres:postgres /
  --link sentry-smtp:smtp /
  --name sentry-web /
  --publish 9000:9000 /
  --env SENTRY_SECRET_KEY=${SENTRY_SECRET_KEY} /
  sentry-onpremise /
  run web

脚本一键安装 Sentry

发现了 getsentry/onpremise/install.sh 这个脚本,看懂这个脚本里的内容基本上就差不多知道安装步骤了。

读一下脚本:

  • LATEST_STABLE_SENTRY_IMAGE='sentry:9.1.2' 安装的是 sentry:9.1.2
  • docker-compose.yml 中会去创建两个卷,分别是 sentry-datasentry-postgres ,这和上面步骤是不是发现区别了,上面创建
    postgres 时,可没有创建卷;
  • cp -n .env.example "$ENV_FILE" 会将仓库下的 .env.example 文件重命名为 .env 文件,其实里面就是为了设置一个环境变量 SENTRY_SECRET_KEY
  • export SENTRY_IMAGE=$LATEST_STABLE_SENTRY_IMAGE 命名了一个环境变量,记录了镜像名
  • docker-compose build 开始构建镜像啦,其实就是类似这个命令 docker build .
  • SECRET_KEY=$(docker-compose run --rm web config generate-secret-key 2> /dev/null | tail -n1 | sed -e 's/[//&]///&/g') 运行命令获得 secret-key 值,赋值给了变量 SECRET_KEY
  • sed -i -e 's/^SENTRY_SECRET_KEY=.*$/SENTRY_SECRET_KEY='"$SECRET_KEY"'/' $ENV_FILESECRET_KEY 变量值填写到环境变量文件 .env
  • 接着就是设置数据库的步骤,这其实是就是为了在数据库中添加账号数据和其他一些数据表生成
  • 最后就是执行 cleanup 函数

执行脚本之前,我们先把之前创建的几个服务删除掉吧:

docker stop sentry-smtp sentry-postgres sentry-redis
docker rm $(docker ps -aq)

配置邮箱

通过上面的阅读,可以发现,运行脚本时,会去执行 docker build . ,这个会将配置文件 config.yml 和环境变量 .env 放入镜像的。因此,我们启用发生异常时获取邮箱告警的功能,需要现在配置文件中设置好邮箱配置。

这里我们选择在 .env 中配置好环境变量,实现邮箱的配置:

配置 config.yml :

SENTRY_SECRET_KEY=)g=lkl1)uugx236%#)mq2o34^@a&g3q85q**co*hbapm5y1*bs
# 邮箱设置
SENTRY_EMAIL_HOST=smtp.qq.com
SENTRY_EMAIL_USER=649168982@qq.com
SENTRY_SERVER_EMAIL=649168982@qq.com
# 这里指的是邮箱的授权码,而非密码
SENTRY_EMAIL_PASSWORD=xxxx
SENTRY_EMAIL_USE_TLS=true
SENTRY_EMAIL_PORT=587
  • 网上的教程,大多数是 SENTRY_EMAIL_HOST: 'smtp.exmail.qq.com' ,这个是企业邮箱,我们个人的不这么设置。
  • SENTRY_EMAIL_USERSENTRY_SERVER_EMAIL 要保持一致;

这里我设置 1 分钟,就能及时收到邮箱,但是设置 5 分钟,等了 5 分钟,也没收到,不知道是何原因:

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

经过上面的设置,可以试试邮箱发送功能是否 OK:点击左上角头像,选择 Admin -》 Mail -》 测试设置

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

部署 sentry

下面开始安装步骤:

  • sh install.sh 执行安装脚本,脚本执行完需要一点时间,运行完成之后,会退出。中间过程会让你选择是否创建账号:

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

  • 接着运行 docker-compose up -d 即可;

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

PS:为何容器的名称是都是 onpremise 开头的呢? 因为不指定名称时,会默认取目录名的 。

这时候输入刚刚创建的账号登录:

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

配置 Sentry

创建 project

那我之前的 log4j2 的 demo 作为演示,这里选择一个 Java 项目,并且,我还创建了一个叫 spring-boot 的 Team。

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

spring-boot 项目适配

官方文档-Java 给出了适用于 Java 项目的全面的适配指南,咱们使用的是 log4j2 。

引入依赖

<dependency>
    <groupId>io.sentry</groupId>
    <artifactId>sentry-log4j2</artifactId>
    <version>1.7.26</version>
</dependency>

log4j2.xml 配置修改

  • configuration 中加上 packages="org.apache.logging.log4j.core,io.sentry.log4j2"

下面示例中的 SentryAppender 表示发送 warn 级别的日志到 Sentry Server。 ConsoleAppender 仅仅表示是一个示例,表示你项目中之前使用的非 sentryappener 的例子。

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="warn" packages="org.apache.logging.log4j.core,io.sentry.log4j2">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>

        <Sentry name="Sentry" />
    </appenders>

    <loggers>
        <root level="INFO">
            <appender-ref ref="Console" />
            <!-- Note that the Sentry logging threshold is overridden to the WARN level -->
            <appender-ref ref="Sentry" level="WARN" />
        </root>
    </loggers>
</configuration>

经过测试,,因为原有项目中的 apppender 都是为了之前的作用设置的,比如控制台打印、比如输出到文件。要想将异常信息发送到 Sentry,这里的 SentryAppender 是必不可少的。别忘了 appender-ref 也要设置!

配置 DSN

配置页面 介绍了如何设置 DSN(Data Source Name)。

进入 Sentry,项目的 DSN 在项目页面-》setings-》Clinet Keys(DSN) 中可以发现:

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

配置 DSN 有好几种方式,具体的可以在页面查看,这里介绍我采用的:

resources 文件夹下,新建 sentry.properties

dsn=http://8d53042c89774e5dba599ee67c5c8804@192.168.3.43:9000/3

默认的就是 sentry.properties ,一开始我直接写在了 application.properties 中,Sentry 怎么也收不到异常日志。

代码中

代码示例:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;

public class MyClass {
    private static final Logger logger = LogManager.getLogger(MyClass.class);
    private static final Marker MARKER = MarkerManager.getMarker("myMarker");

    void logSimpleMessage() {
        // This sends a simple event to Sentry
        logger.error("This is a test");
    }

    void logWithBreadcrumbs() {
        // Record a breadcrumb that will be sent with the next event(s),
        // by default the last 100 breadcrumbs are kept.
        Sentry.record(
            new BreadcrumbBuilder().setMessage("User made an action").build()
        );

        // This sends a simple event to Sentry
        logger.error("This is a test");
    }

    void logWithTag() {
        // This sends an event with a tag named 'log4j2-Marker' to Sentry
        logger.error(MARKER, "This is a test");
    }

    void logWithExtras() {
        // MDC extras
        ThreadContext.put("extra_key", "extra_value");
        // NDC extras are sent under 'log4j2-NDC'
        ThreadContext.push("Extra_details");
        // This sends an event with extra data to Sentry
        logger.error("This is a test");
    }

    void logException() {
        try {
            unsafeMethod();
        } catch (Exception e) {
            // This sends an exception event to Sentry
            logger.error("Exception caught", e);
        }
    }

    void unsafeMethod() {
        throw new UnsupportedOperationException("You shouldn't call this!");
    }
}

可以发现,Sentry 使用的接口和之前 log4j2 是有区别的:

// 原来
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Logger log = LoggerFactory.getLogger(this.getClass());
// sentry
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private final Logger log = LogManager.getLogger(this.getClass());

实际过程中,这里是很多人忽视的,一定要仔细一点!

这时候,使用我们之前的错误接口故意打印错误日志,看看 Sentry 的捕获效果吧:

Spring Boot 实战 —— 通过 Sentry 收集 Log4j2 异常日志记录

总结

今天在之前加入的一个技术微信群里,一位同学发了一篇怎么搭建 Zabbix 的文章,被群主踢了。后来大家讨论,这种搭建的文章,普遍质量比较低,没有太多分享的意义,应该要更多的关注一些前言的知识、或者底层的基础知识。这种观点我是比较认同的,但是谁不是一步一步慢慢来的呢?

今天搭建 Sentry 的过程,对之前 docker-compose 的用法又有了进一步的认识,学习到了使用 .env 的方式设置容器内环境变量的方式,同时,也学习到了可以公用一种配置,让 docker-compose 文件内多个服务公用的方式。其实,自己的每一点的折腾,都会是后面的基石。只要持续积累,才会越走越顺!

原文  https://michael728.github.io/2019/08/11/java-spring-boot-sentry-log4j2/
正文到此结束
Loading...