これは、なにをしたくて書いたもの?
Sentryへのログ送信に合わせて、アラートメールを送ってみようかなと。
以前にLogbackを使って、Sentryにログイベントを送信するところはやりました。
Sentry JavaのLogbackインテグレーションを試す - CLOVER🍀
こちらをもとにして、アラートメール送信をやってみます。
Sentryのインストール
こちらに沿って、Sentryをローカルインストールします。
GitHub - getsentry/onpremise: Sentry On-Premise setup
$ git clone https://github.com/getsentry/onpremise.git $ cd onpremise $ ./install.sh
今回インストールしたSentryのバージョンは、9.1.2です。
この時、ユーザーを作成することができるので、以下の情報で作成。
「user001@example.com / password」
また、メールサーバーにはMailCatcher 0.7.0を使用し、192.168.0.2で動作しているものとします。
これに合わせて、docker-compose.ymlを以下のように修正します。
x-defaults: &defaults restart: unless-stopped build: context: . args: SENTRY_IMAGE: ${SENTRY_IMAGE} depends_on: - redis - postgres - memcached - smtp env_file: .env environment: SENTRY_MEMCACHED_HOST: memcached SENTRY_REDIS_HOST: redis SENTRY_POSTGRES_HOST: postgres SENTRY_EMAIL_HOST: 192.168.0.2 volumes: - sentry-data:/var/lib/sentry/files
メールサーバーの接続先だけ変更すればOKです。
起動。
$ docker-compose up
ところで、ドキュメントを読んでいるとconfig.ymlで設定しそうな雰囲気もあったのですが、今回こちらは設定がうまく反映されません
でした…。
############### # Mail Server # ############### mail.backend: 'smtp' # Use dummy if you want to disable email entirely mail.host: '192.168.0.2' mail.port: 1025 # mail.username: '' # mail.password: '' # mail.use-tls: false # The email address to send on behalf of mail.from: 'sentry-admin@localhost.localdomain'
とりあえず、Sentryにアクセスしてみます。「http://localhost:9000」にすると、こんな感じの画面が表示されるのでログイン。
メール送信に関する続きの設定を求められるので、ここで設定します。メールサーバーもここで設定できていいと
思うんですけどね?
なお、MailCatcherのSMTPポートは、1025です。
Sentryのプロジェクトを作成します。
こちらを利用する、サンプルコードを書いていきましょう。
サンプルプロジェクト
サンプルプロジェクトは、前回のエントリを少し変えた形で。
Maven依存関係。
<dependency> <groupId>io.sentry</groupId> <artifactId>sentry-logback</artifactId> <version>1.7.27</version> </dependency>
リクエストパスに応じて、各ログレベルのイベントを発生させるようなサンプルアプリケーション。ログメッセージは、QueryStringで
指定できるようにしました。
src/main/java/org/littlewings/sentry/logback/Server.java
package org.littlewings.sentry.logback; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Optional; import com.sun.net.httpserver.HttpServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; public class Server { static Logger logger = LoggerFactory.getLogger(Server.class); public static void main(String... args) throws IOException { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 10); server.createContext("/", exchange -> { MDC.put("app", "java-app"); String path = exchange.getRequestURI().getPath(); String query = exchange.getRequestURI().getQuery(); Map<String, String> queryValues = new HashMap<>(); if (query != null) { for (String kvPair : query.split("&")) { String[] kv = kvPair.split("="); queryValues.put(kv[0], kv[1]); } } Optional<String> logMessage = Optional.ofNullable(queryValues.get("m")); logger.info("{} access", path); byte[] body; switch (path) { case "/info": infoLogging(logMessage); body = "OK!!".getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); break; case "/warn": warnLogging(logMessage); body = "WARN!!".getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(500, body.length); exchange.getResponseBody().write(body); break; case "/error": errorLogging(logMessage); body = "ERROR!!".getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(500, body.length); exchange.getResponseBody().write(body); break; case "/exception": String em = Optional.ofNullable(queryValues.get("em")).orElse("Oops!!"); exceptionLogging(em, logMessage); body = em.getBytes(StandardCharsets.UTF_8); exchange.sendResponseHeaders(500, body.length); exchange.getResponseBody().write(body); break; default: exchange.sendResponseHeaders(404, 0); break; } MDC.clear(); }); server.start(); } private static void infoLogging(Optional<String> logMessage) { logger.info(logMessage.orElse("INFOログだよ")); } private static void warnLogging(Optional<String> logMessage) { logger.warn(logMessage.orElse("WARNログです")); } private static void errorLogging(Optional<String> logMessage) { logger.error(logMessage.orElse("エラーですよ")); } private static void exceptionLogging(String exceptionMessage, Optional<String> logMessage) { logger.error(logMessage.orElse("例外だよ"), new RuntimeException(exceptionMessage)); } }
Logbackの設定。ここに、Sentryの設定も入ります。 src/main/resources/logback.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] [%X{app}] %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="Sentry" class="io.sentry.logback.SentryAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <root level="info"> <appender-ref ref="STDOUT"/> <appender-ref ref="Sentry"/> </root> </configuration>
これで、起動。作成したプロジェクトのDSNを指定してください。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.sentry.logback.Server -Dsentry.dsn=http://c2cdf4bc618c4b8181d9c0e1e15be772@localhost:9000/2
とりあえず、WARNレベルのログイベントを発生させてみます。
$ curl localhost:8080/warn?m=warn0001 WARN!!
これで、Sentryにログイベントが送信され、
メールも通知されます。
これは、Sentryでデフォルトでプロジェクト内の全メンバーに通知が行われるようになっているからです。
ちなみに、この状態で再度イベントを発生させても、通知が来なかったりします。
$ curl localhost:8080/warn?m=warn0002 WARN!! $ curl localhost:8080/warn?m=warn0003 WARN!! $ curl localhost:8080/error?m=error0001 ERROR!!
ちなみに、イベントをクローズ後に再度発生させたりすると、メールが来ますよ。
これは、デフォルトのアラートルールがプロジェクトで最初のイベントを通知するようになっているからですね。
これを変更するには、カスタムアラートルールを作ることになるみたいです。
Creating Custom Alert Notifications - Docs
Routing Alert Notifications - Docs
あとは、メール配信のタイミングを調整したりすると良いかもしれません。
ややハンパですが、今回はここまでで。