NaiveMonitor: 快速实现数据监控和实时报警功能。

使用要求

Maven 配置

    <dependency>
        <groupId>com.heimuheimu</groupId>
        <artifactId>naivemonitor</artifactId>
        <version>1.0</version>
    </dependency>

数据监控 DEMO

场景:对用户注册操作进行数据监控(注意:示例代码仅为说明如何使用 ExecutionMonitor。)

@Controller
public class UserRegisterController { //用户注册使用的 Controller,数据监控可根据自身需求放在任意层级,例如 Service 层、Dao 层。
    
    private final ExecutionMonitor monitor = NaiveExecutionMonitorFactory.get("UserRegister"); //获取用户注册操作执行信息监控器
    
    @Autowired
    private UserService userService;
    
    @RequestMapping(value = "/register")
    @ResponseBody
    public boolean register(@RequestParam("username") String username, @RequestParam("password") String password) {
        long startTime = System.nanoTime();
        try {
            if (userService.isExist(username)) { //如果用户名已存在
                monitor.onError(-1); //对用户名已存在的错误进行监控
                return false;
            }
            userService.add(username, password); //创建用户
            return true;
        } catch (Execution e) {
            monitor.onError(-2); //对执行过程中出现预期外异常进行监控
            return false;
        } finally{
            monitor.onExecuted(startTime); //对每一个注册操作进行监控
        }
    }
}

实现用户注册操作 Falcon 监控数据采集器:

public class UserRegisterDataCollector extends AbstractExecutionDataCollector {
    
    @Override
    protected Map<Integer, String> getErrorMetricSuffixMap() {
        Map<Integer, String> errorMetricSuffixMap = new HashMap<>();
        errorMetricSuffixMap.put(-1, "_duplicate_username"); //对用户名已存在的错误次数进行统计
        errorMetricSuffixMap.put(-2, "_exec_error"); //对执行过程中出现预期外的异常次数进行统计
        return errorMetricSuffixMap;
    }

    @Override
    protected String getCollectorName() {
        return "register"; //采集器名称,表明为用户注册操作
    }

    @Override
    protected List<ExecutionMonitor> getExecutionMonitorList() {
        return Collections.singletonList(NaiveExecutionMonitorFactory.get("UserRegister"));
    }

    @Override
    protected String getModuleName() {
        return "bookstore"; //用户注册操作所在的项目名称
    } 

    @Override
    public int getPeriod() {
        return 30; //监控数据推送周期为 30 秒
    }
} 

在 Spring 中配置 Falcon 数据推送:

    <bean id="falconReporter" class="com.heimuheimu.naivemonitor.falcon.FalconReporter" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="http://127.0.0.1:1988/v1/push" /> <!-- Falcon 监控数据推送地址-->
        <constructor-arg index="1">
            <list>
                <bean class="com.heimuheimu.naivemonitor.demo.falcon.UserRegisterDataCollector" />
            </lit>
        </constructor-arg>
    </bean>

完成以上工作后,在 Falcon 系统中可以找到以下数据项:

实时报警 DEMO

在 Spring 中配置 NaiveServiceAlarm

    <!-- 报警消息通知器列表配置 -->
    <util:list id="notifierList">
        <bean class="com.heimuheimu.naivemonitor.alarm.support.DingTalkServiceAlarmMessageNotifier">
            <constructor-arg index="0" value="https://oapi.dingtalk.com/robot/send?access_token={your_access_token}" /> <!-- 钉钉消息推送 URL 地址 -->
            <constructor-arg index="1" value="" /> <!-- Http proxy 地址,允许为空 -->
            <constructor-arg index="2" value="5000" /> <!-- 发送超时时间,毫秒 -->
            <constructor-arg index="3" value="" /> <!-- 服务不可用时显示的图片地址,允许为空 -->
            <constructor-arg index="4" value="" /> <!-- 服务恢复时显示的图片地址,允许为空 -->
        </bean>
    </util:list>
    
    <bean id="naiveServiceAlarm" class="com.heimuheimu.naivemonitor.alarm.NaiveServiceAlarm">
        <constructor-arg index="0" ref="notifierList"/>
    </bean>

实时报警示例代码:

public class NaiveServiceAlarmTester {
    
    @Autowired
    private NaiveServiceAlarm naiveServiceAlarm;
    
    //服务不可用报警信息发送 Demo
    public void testSendCrashMessage() { 
        ServiceContext serviceContext = new ServiceContext();
        serviceContext.setHost(MonitorUtil.getLocalHostName()); //设置本机地址信息
        serviceContext.setProject("bookstore"); //设置当前项目名称
        serviceContext.setName("Memcached"); //设置服务名称
        serviceContext.setRemoteHost("127.0.0.1:11211"); //提供服务的远程主机地址信息,如果为本地服务,不需要进行此设置
        
        naiveServiceAlarm.onCrashed(serviceContext); //进行服务不可用报警通知
    }
    
    //服务恢复信息发送 Demo
    public void testSendRecoverMessage() { 
        ServiceContext serviceContext = new ServiceContext();
        serviceContext.setHost(MonitorUtil.getLocalHostName()); //设置本机地址信息
        serviceContext.setProject("bookstore"); //设置当前项目名称
        serviceContext.setName("Memcached"); //设置服务名称
        serviceContext.setRemoteHost("127.0.0.1:11211"); //提供服务的远程主机地址信息,如果为本地服务,不需要进行此设置
        
        naiveServiceAlarm.onRecovered(serviceContext); //进行服务不可用报警通知
    
    }
}

定制报警通知器

NaiveServiceAlarm 允许设置多个报警通知器,在一个报警通知器失败时,会继续选择其它可用的报警通知器进行通知。 由于钉钉机器人的发送频率有限制,为保证报警消息到达,建议增加一个短信报警通知器(不建议使用邮件等非实时通知器)。

使用 NaiveMonitor 框架提供的 NaiveHttpPost 类,可以方便的实现一个基于 Http Post 请求的短信报警通知器:

/**
 * 短信报警通知器。
 */
public class SmsServiceAlarmMessageNotifier implements ServiceAlarmMessageNotifier {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceAlarmMessageNotifier.class);
    
    /**
     * 短信服务 URL 地址。
     */
    private final String url;
    
    public SmsServiceAlarmMessageNotifier(String url) {
        this.url = url;
    }
    
    @Override
    public boolean send(ServiceAlarmMessage serviceAlarmMessage) {
        if (url != null && !url.isEmpty()) {
            long startTime = System.currentTimeMillis();
            HashMap<String, Object> params = new HashMap<>(); //组装短信服务需要的参数,应根据短信服务接口定义进行设置
            params.put("phoneNums", "13700000000,13811111111"); //设置短信接收人手机号码
            params.put("message", serviceAlarmMessage.toText()); //设置短信内容
            try {
                NaiveHttpPost post = new NaiveHttpPost(url, 5000); //Post 请求的超时时间设置为 5 秒
                String responseText = post.doPost(NaiveHttpPost.getPostBody(params)); //发起 Post 请求
                if ("true".equals(responseText)) { //根据返回内容判断请求是否成功,应根据短信服务接口返回值定义进行判断
                    LOGGER.info("Send ServiceAlarmMessage to sms success. Cost: `{} ms`. ServiceAlarmMessage: `{}`. Url: `{}`.",
                            System.currentTimeMillis() - startTime, serviceAlarmMessage, url);
                } else {
                    LOGGER.error("Send ServiceAlarmMessage to sms failed. Cost: `{} ms`. ResponseText: `{}`. ServiceAlarmMessage: `{}`. Url: `{}`.",
                            System.currentTimeMillis() - startTime, responseText, serviceAlarmMessage, url);
                }
                return isSuccess;
            } catch (Exception e) {
                LOGGER.error("Send ServiceAlarmMessage to sms failed. Cost: `" + (System.currentTimeMillis() - startTime)
                        + " ms`. ServiceAlarmMessage: `" + serviceAlarmMessage + "`. Url: `" + url + "`.", e);
                return false;
            }
        } else {
            LOGGER.warn("SmsServiceAlarmMessageNotifier is inactive. ServiceAlarmMessage: `{}`.", serviceAlarmMessage);
            return false;
        }
    }
}

SQL 执行监控

DB 跪,网站跪。NaiveMonitor 框架提供了 SqlExecutionMonitor 来监控项目中的 SQL 执行。

如果你的项目仍在使用古老的 Spring + iBatis,NaiveMonitor 框架提供了直接的解决方案,仅需要通过 Spring 配置即可进行监控:

    <!-- 用于替换 Spring 的 iBatis 辅助类 SqlMapClientTemplate 配置 -->
    <bean id="bookstoreSqlMapClientTemplate" class="com.heimuheimu.naivemonitor.ibatis.SmartSqlMapClientTemplate">
        <constructor-arg index="0" value="bookstore" /> <!-- 数据库名称 -->
        <constructor-arg index="1" ref="bookstoreSqlMapClient" /> <!-- iBatis SqlMapClient 实例 -->
        <constructor-arg index="2" value="50" /> <!-- 大于该执行时间的 SQL 语句执行将会被定义为慢查,单位:毫秒 -->
    </bean>
    
    <!-- 在监控数据采集器列表中加入 SqlExecutionDataCollector-->
    <bean id="falconReporter" class="com.heimuheimu.naivemonitor.falcon.FalconReporter" init-method="init" destroy-method="close">
        <constructor-arg index="0" value="http://127.0.0.1:1988/v1/push" /> <!-- Falcon 监控数据推送地址-->
        <constructor-arg index="1">
            <list>
                <!-- SQL 执行监控数据采集器-->
                <bean class="com.heimuheimu.naivemonitor.falcon.support.SqlExecutionDataCollector">
                    <constructor-arg index="0" value="bookstore" /> <!-- 当前项目名称 -->
                    <constructor-arg index="1" value="bookstore" /> <!-- 数据库名称 -->
                </bean>>
            </lit>
        </constructor-arg>
    </bean>

代码层无需修改,仍使用 Spring 提供的 SqlMapClientTemplate 进行访问:

@Repository
public class UserDaoImpl implements UserDao {
    
    @Resource(name = "bookstoreSqlMapClientTemplate")
    private SqlMapClientTemplate sqlMapClientTemplate;
    
    public void add(User user) {
        sqlMapClientTemplate.insert("UserDao.add", user);
    }
}

SQL 执行日志配置:

#SQL 慢查日志配置
log4j.logger.NAIVESQL_ERROR_EXECUTION_LOGGER=ERROR, NAIVESQL_ERROR_EXECUTION_LOGGER
log4j.additivity.NAIVESQL_ERROR_EXECUTION_LOGGER=false
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER.file=${log.output.directory}/naivemonitor/sql_error.log
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER.encoding=UTF-8
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER.DatePattern=_yyyy-MM-dd
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER.layout=org.apache.log4j.PatternLayout
log4j.appender.NAIVESQL_ERROR_EXECUTION_LOGGER.layout.ConversionPattern=%d{ISO8601} %-5p [%F:%L] : %m%n

#SQL 错误日志配置
log4j.logger.NAIVESQL_SLOW_EXECUTION_LOGGER=ERROR, NAIVESQL_SLOW_EXECUTION_LOGGER
log4j.additivity.NAIVESQL_SLOW_EXECUTION_LOGGER=false
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER.file=${log.output.directory}/naivemonitor/sql_slow_execution.log
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER.encoding=UTF-8
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER.DatePattern=_yyyy-MM-dd
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER.layout=org.apache.log4j.PatternLayout
log4j.appender.NAIVESQL_SLOW_EXECUTION_LOGGER.layout.ConversionPattern=%d{ISO8601} : %m%n

如果你的项目在使用其它 ORM 框架,请参考 SmartSqlMapClientTemplate 类来实现 SQL 执行监控功能。

更多信息