5 months ago

寫Spring還真的很少自爆就停止服務了,大概都是被 OS 層級終止(記憶體吃太多也是事實)
但線上服務還是加個服務監控怕真的掛了沒人知,台灣人用line比較多那再加個Line通知比較即時吧

Admin 監控端

依賴部分 就不用 Line 的 SDK 啦,因為它會攔截所有 request 但又沒處理好轉型問題,一般的 request 進來會報錯,反正 Http push message 到 Line 也很簡單

build.gradle
buildscript {
    ext {
        springBootVersion = '1.5.3.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

version = '0.0.1'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-mail')
    compile 'de.codecentric:spring-boot-admin-server:1.5.0'
    compile 'de.codecentric:spring-boot-admin-server-ui:1.5.0'
    compile 'com.squareup.okhttp3:okhttp:3.8.0'
//    compile 'com.linecorp.bot:line-bot-spring-boot:1.7.0'
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

這是 mail 配置跟 slack ,還有我們自己增加的 line 配置

application.yml
server:

  port: 9190


info:

  version: 1.0.0


spring:

  application:

    name: 'ServiceAdmin'

  mail:

    host: "mail.xxx.com"

    username: "ecs@xxx.com"

    password: "xxxxxxxxxxx"

  boot:

    admin:

      notify:

        mail:

          enabled: true

          from: 'ec@xxxxxx.com'

          to: 'sam@xxxxxx.com,joe@xxxxxx.com'

        slack:

          enabled: true

          webhook-url: 'https://hooks.slack.com/services'

        line:

          enabled: true

          channelSecret: 'xxxxxxxxxxxxxxxxx'

          channelToken: 'xxxxxxxxxxxxxxxxx'

          to: 'xxxxxxxxxxxxxxxxx'

讀取設定檔變數

LineProperties.java
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * 自訂的設定檔
 * Created by samchu on 2017/5/25.
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.boot.admin.notify.line")
@ConditionalOnProperty(prefix = "spring.boot.admin.notify.line", name = "enabled", matchIfMissing = true)
public class LineProperties {
    private boolean enabled = false;
    private String channelSecret;
    private String channelToken;
    private String to;
}

繼承 SpringBootAdmin 通知的 AbstractEventNotifier

LineNotifier.java
import com.ps.admin.config.LineProperties;
import com.ps.admin.utils.LineHttp;
import de.codecentric.boot.admin.event.ClientApplicationEvent;
import de.codecentric.boot.admin.event.ClientApplicationRegisteredEvent;
import de.codecentric.boot.admin.event.ClientApplicationStatusChangedEvent;
import de.codecentric.boot.admin.notify.AbstractEventNotifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 服務發生事件時會被通知
 * 參考 https://github.com/codecentric/spring-boot-admin/blob/master/spring-boot-admin-server/src/main/java/de/codecentric/boot/admin/notify/SlackNotifier.java
 * 模板 http://www.jianshu.com/p/6a0a1fa453c8
 * Created by samzh on 2017/4/6.
 */
@Slf4j
@Component
public class LineNotifier extends AbstractEventNotifier {
    @Autowired
    private LineProperties lineProperties;
    private static final String DEFAULT_MESSAGE = "#{application.name} (#{application.id}) is #{to.status}";
    private final SpelExpressionParser parser = new SpelExpressionParser();

    private Expression message;
    private List<String> notifyStatuses = Arrays.asList("UP", "DOWN", "OFFLINE");

    @Autowired
    private LineHttp lineHttp;

    @Override
    protected void doNotify(ClientApplicationEvent event) throws Exception {
        if (lineProperties.isEnabled() == false) {
            return;
        }
        this.message = parser.parseExpression(DEFAULT_MESSAGE, ParserContext.TEMPLATE_EXPRESSION);
        if (event instanceof ClientApplicationRegisteredEvent) {
            ClientApplicationRegisteredEvent registeredEvent = (ClientApplicationRegisteredEvent) event;
//            System.out.println(registeredEvent.getApplication());// Application [id=2a87974b, name=boot-test, managementUrl=http://SAMPC:5566, healthUrl=http://SAMPC:5566/health, serviceUrl=http://SAMPC:5566]

//            System.out.println(registeredEvent.getType());// REGISTRATION

//            System.out.println(registeredEvent.getApplication().getServiceUrl());// http://SAMPC:5566

//            System.out.println(registeredEvent.getApplication().getStatusInfo().getStatus());// UNKNOWN

        }
        if (event instanceof ClientApplicationStatusChangedEvent) {
            ClientApplicationStatusChangedEvent statusChangedEvent = (ClientApplicationStatusChangedEvent) event;
            String msg = message.getValue(event, String.class); // boot-test (2a87974b) is UP

            lineHttp.post(msg);
        }
    }
}

發 http post 給 Line

LineHttp.java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ps.admin.config.LineProperties;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Created by samchu on 2017/5/26.
 * https://devdocs.line.me/en/?shell#push-message
 */
@Slf4j
@Component
public class LineHttp {
    @Autowired
    private LineProperties lineProperties;

    public void post(String text) throws IOException {
        if (lineProperties.isEnabled() == false) {
            return;
        }
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .readTimeout(60, TimeUnit.SECONDS)
                .build();
        HashMap object = new HashMap<>();
        object.put("to", lineProperties.getTo());
        List messages = new ArrayList();
        HashMap message = new HashMap<>();
        message.put("type", "text");
        message.put("text", text);
        messages.add(message);
        object.put("messages", messages);
        MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        ObjectMapper mapper = new ObjectMapper();
        RequestBody body = RequestBody.create(JSON, mapper.writeValueAsString(object));
        Request request = new Request.Builder()
                .header("Authorization", String.format("Bearer %s", lineProperties.getChannelToken()))
                .url("https://api.line.me/v2/bot/message/push")
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                log.warn("發送失敗" + response.body().toString());
                //throw new IOException("Unexpected code " + response);

            }
            response.close();
        }
    }
}

Admin 被監控端

被監控端只需要添加此依賴

build.gradle
compile 'de.codecentric:spring-boot-admin-starter-client:1.5.0'

不果你如果有用 org.springframework.boot:spring-boot-starter-security 的話,預設 Actuator endpoints 是會被保護的,所以你被監控端必須自己告訴監控端我的 Auth 資料,監控端才能拿到更多資訊

application.yml
security:

#此配置方式為 basicAuth

  user:

    name: admin

    password: admin


spring:

  boot:

    admin:

      url: 'http://localhost:9190'

      client:

        metadata:

          user.name: ${security.user.name}

          user.password: ${security.user.password}

這樣就可以了

如果你不用配置就可以正常使用,請注意你是否正在對外裸奔

結果

← SpringBoot接收ajp about use spring data Page →
 
comments powered by Disqus