intra-martのエラー発生時にSlackに投稿する方法の紹介

開発部のTです。

今回はlogbackの機能を使って、intra-martでエラーが発生した時にSlackにエラー内容を投稿する方法を紹介します。

Slackの設定

SlackのIncoming Webhooks機能を使って、 特定のチャンネルにintra-martのエラー内容を投稿するためのWebhook URLを発行します。 (手順の紹介は省きます)

自社ではエラーが発生すると、:imp:の絵文字で投稿するように設定しています。

f:id:gsol-dev:20170123201536p:plain

Appenderの実装

Appenderによって、エラーログをSlack APIの仕様に基づいたJSON形式に変換し、Webhook URLに対してPOST送信します。 長文のエラーログをSlackに投稿できるように、attachments属性にエラーログを設定しています。

以下Appenderのソースです。

package jp.co.gsol.logback.appender;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.UnsynchronizedAppenderBase;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.arnx.jsonic.JSON;

public class SlackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private static final String PRETEXT = "【社内システム】iAPエラーログ";
    private String url;
    private String channel;
    private Layout<ILoggingEvent> layout;
    private int timeout = 10000;

    @Override
    protected void append(final ILoggingEvent event) {
        try {
            final URL apiUrl = new URL(url);
            final StringWriter w = new StringWriter();
            w.append("payload=").append(URLEncoder.encode(createAttachmentsStr(layout.doLayout(event)), "UTF-8"));
            final byte[] bytes = w.toString().getBytes("UTF-8");

            final HttpURLConnection con = (HttpURLConnection) apiUrl.openConnection();
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setConnectTimeout(timeout);
            con.setReadTimeout(timeout);
            con.setFixedLengthStreamingMode(bytes.length);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            final OutputStream os = con.getOutputStream();
            os.write(bytes);

            os.flush();
            os.close();
        } catch (IOException e) {
            addError(channel + "投稿時にエラーが発生しました", e);
        }
    }

    /**
     * ログフォーマットをJSON形式に変換
     * @param text
     * @return
     */
    private String createAttachmentsStr(final String text) {
        List<Map<String, Object>> attachments = new ArrayList<>();
        Map<String, Object> attachment = new HashMap<>();
        attachment.put("pretext", PRETEXT);
        attachment.put("text", text);
        attachments.add(attachment);
        Map<String, Object> map = new HashMap<>();
        map.put("attachments", attachments);
        return JSON.encode(map);
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(final String url) {
        this.url = url;
    }

    public String getChannel() {
        return channel;
    }

    public void setChannel(final String channel) {
        this.channel = channel;
    }

    public Layout<ILoggingEvent> getLayout() {
        return layout;
    }

    public void setLayout(final Layout<ILoggingEvent> layout) {
        this.layout = layout;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

im_logger.xmlの設定

im_logger.xmlにSlackAppenderの設定を行います。 intra-martのログ仕様書に基づいて、 ログ出力したい情報を<layout>タグに設定します。<url>タグにはSlackのWebhook URLを設定します。

以下、自社のim_logger.xmlの設定の抜粋です。

    <appender name="SLACK" class="jp.co.gsol.logback.appender.SlackAppender">
        <channel>#in-house-error-log</channel>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]
thread: [%thread]
level: [%-5level] logger: [%logger{255}]
tenant id: [%X{tenant.id}] log id: [%X{log.id}] request id: [%X{request.id}]
user type: [%X{user.type}] authenticated: [%X{authenticated}] user cd: [%X{user.cd}]
log message code: [%X{log.message.code}]    message: %msg%n
%ex{full}
            </pattern>
        </layout>
        <url>https://hooks.slack.com/services/XXXXXXX/YYYYYYY/ZZZZZZZZZZZZZZZZZZZZZ</url>
    </appender>

    <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="SLACK" />
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root>
        <level value="info" />
        <appender-ref ref="ASYNC_SLACK" />
    </root>

Slackに投稿される内容

intra-martでエラーが発生すると、Slackに↓の様な投稿が自動で行われます。

f:id:gsol-dev:20170126195917p:plain

投稿を展開するとExceptionの詳細が見れます。

f:id:gsol-dev:20170126195941p:plain

Exceptionが長すぎると、途中で省略されてしまうので、いつか修正したいです。

以上、intra-martのエラー発生時にSlackに投稿する方法の紹介でした。