jsoup登录日志平台后调企业微信机器人自动发送错误日志告警

news2024/11/9 9:40:07

一、需求:错误日志Top10告警发送

二、需求分解

  1. jsoup实现登录,获取到cookie和token等用户鉴权信息
  2. 获取接口相应的key值
  3. 调用日志平台错误日志Top榜接口,查询到结果集
  4. 调用企业微信机器人发送消息接口
  5. 加上定时任务,可以实现定时发送错误日志告警的功能(后续加进定时任务里面去)

jsoup是java的爬虫框架,可以爬取网页数据,这里没有重点使用,只是做了个登录功能。后续可以专门它写一份爬虫的程序。。

  <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>
package com.smy.cbs.task;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.smy.cbs.util.RetryUtil;
import com.smy.framework.core.support.SpringTestCase;
import org.apache.commons.collections4.CollectionUtils;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Test;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import javax.annotation.Resource;
import javax.net.ssl.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author youlu
 * @ClassName HttpTestt
 * @Date 2023/11/22 17:14
 * @Version V1.0
 **/
public class HttpTestt2 extends SpringTestCase {
    @Resource
    private RetryUtil retryUtil;
    public static final String LOG_CENTER_BASE_URL = "https://logxxxxx.com/api/v1/";

    public static final String USER_NAME = "aaaaa";
    public static final String PASSWORD = "bbbbb";


    public static String X_Csrf_Token = "";
    public static String COOKIE = "";
    public static String id = "";


    public static int maxCount = Integer.MAX_VALUE;//达到阈值则告警
    public static List<String> filterContent = Lists.newArrayList();//过滤内容

    public static int SUB_CONTENT_LENGTH = 240;//截取报错内容字符串长度
    public static int SUB_TITLE_LENGTH = 20;//截取报错类型字符串长度
    public static int PERIOD_HOUR = 24*7;//24小时内的错误日志

    public static List<String> SEARCH_SYSTEM = Lists.newArrayList("cbs_core", "adv_core", "rls_core", "uts_core");//查询的系统
    //public static List<String> SEARCH_SYSTEM = Lists.newArrayList("adv_core");//查询的系统
    public static String WX_ROBOT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=05e1f4a8-aaaaaa" /*+ ",https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=87140cb7-bbbbbb"*/;
    public static final String TEMPLATE = "{\"markdown\":{\"content\":\"%s\"},\"msgtype\":\"markdown\"}";
    public static final String QUERY_TEMPLATE = "{\n" +
            "  \"app\": \"system\",\n" +
            "  \"source\": \"other\",\n" +
            "  \"query\": \"repo=\\\"smy_%s\\\" origin=\\\"*\\\" AND \\\"ERROR\\\"\\n| where level=\\\"ERROR\\\"\\n| eval err_type=arr_index(split(arr_index(split(_raw, \\\" - \\\"), 1), \\\"\\\\d+\\\"), 0)\\n| stats count() as num by err_type\\n| sort 10 by num\\n| join type=inner err_type [\\n  repo=\\\"smy_%s\\\" origin=\\\"*\\\" AND \\\"ERROR\\\"\\n  | where level=\\\"ERROR\\\"\\n  | eval err_type=arr_index(split(arr_index(split(_raw, \\\" - \\\"), 1), \\\"\\\\d+\\\"), 0)\\n  | fields + err_type, _raw\\n  | dedup err_type\\n]\\n| rename _raw as 原始日志, num as 统计, err_type as 错误类型\",\n" +
            "  \"mode\": \"smart\",\n" +
            "  \"preview\": false,\n" +
            "  \"collectSize\": -1,\n" +
            "  \"timeout\": 1000,\n" +
            "  \"sorts\": []\n" +
            "}";

    public static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MINUTES,
            new LinkedBlockingQueue<Runnable>(100), new CustomizableThreadFactory("Log_Center-pool-"));

    @Test
    public void LogCenterData() throws IOException {
        //1.模拟登录 jsoup
        jsoupLogin();

        for (String systemName : SEARCH_SYSTEM) {
            try {
                Thread.sleep(1000);
                //2.获取key
                String id = getKey(systemName);
                Thread.sleep(8000);
                List<Map<String, Object>> logCenterList = retryUtil.doRetry(4, () -> {
                    //3.获取查询结果
                    List<Map<String, Object>> contentList = getContentList(systemName, id);
                    if (CollectionUtils.isEmpty(contentList)) {
                        Thread.sleep(8000);
                        throw new Exception(systemName + "未查询到数据,需要重试!");
                    }
                    return contentList;
                }, systemName + "获取日志数据");
                //4.调微信发送短信
                //if ("adv_core".equals(systemName) && CollectionUtils.isNotEmpty(logCenterList)) {
                //    List<List<Map<String, Object>>> partition = Lists.partition(logCenterList, 5);
                //    partition.stream().forEach(k -> sendWxMessage(systemName, id, k));
                //} else {
                    sendWxMessage(systemName, id, logCenterList);
                //}
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }



    public static String getKey(String systemName) {
        Date endDate = new Date();
        Date startDate = DateUtil.offsetHour(endDate, -PERIOD_HOUR);
        //请求参数
        JSONObject paramJson = JSON.parseObject(String.format(QUERY_TEMPLATE, systemName, systemName));
        paramJson.put("startTime",startDate.getTime());
        paramJson.put("endTime",endDate.getTime());
        HttpResponse execute = HttpRequest.post(LOG_CENTER_BASE_URL + "jobs")
                //设置请求头(可任意加)
                .header("X-Csrf-Token", X_Csrf_Token)
                .header("Cookie", COOKIE)
                .header("Content-Type", "application/json")
                .header("Connection","keep-alive")
                //请求参数
                .body(paramJson.toJSONString())
                .timeout(40000)
                .execute();
        String body1 = execute.body();
        String id = JSON.parseObject(body1).getString("id");
        return id;
    }

    public static List<Map<String,Object>> getContentList(String systemName,String id)  {
        String url = LOG_CENTER_BASE_URL + "jobs/" + id + "/results";
        url += "?_=" + System.currentTimeMillis();
        HttpResponse execute = HttpRequest.get(url)
                //设置请求头(可任意加)
                .header("X-Csrf-Token", X_Csrf_Token)
                .header("Cookie", COOKIE)
                .header("Content-Type", "application/json")
                .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")
                .header("Sec-Ch-Ua-Platform", "Windows")
                .header("Connection","keep-alive")
                .timeout(40000)
                .execute();

        JSONArray rows = JSON.parseObject(execute.body()).getJSONArray("rows");
        System.err.println(rows.toString());
        List<Map<String, Object>> list = Lists.newArrayList();
        for (Object row : rows) {
            try {
                JSONArray jsonArray = JSON.parseArray(row.toString());
                String title = jsonArray.get(0).toString();
                Integer count = Integer.valueOf(jsonArray.get(1).toString());
                String content = jsonArray.get(2).toString();
                if (count >= maxCount) {
                    continue;
                }
                if (filterContent.stream().anyMatch(k -> content.contains(k))) {
                    continue;
                }
                int subContentLen = content.length() <= SUB_CONTENT_LENGTH ? content.length() : SUB_CONTENT_LENGTH;
                String subContent = content.substring(0, subContentLen);
                int subTitleLen = title.length() <= SUB_TITLE_LENGTH ? title.length() : SUB_TITLE_LENGTH;
                String subTitle = content.substring(0, subTitleLen);

                Map<String, Object> map = new HashMap<>();
                map.put("title", subTitle);
                map.put("count", count);
                map.put("content", subContent);
                list.add(map);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (CollectionUtils.isEmpty(list)) {
            System.err.println(systemName + ":未获取到数据,请求链接:" + url + "   token:" + X_Csrf_Token + "   cookie:" + COOKIE);
        }
        return list;
    }

    public static void sendWxMessage(String systemName, String id, List<Map<String, Object>> contentList) {
        if (CollectionUtils.isEmpty(contentList)) {
            System.err.println(systemName + ":发送内容为空,不发送机器人微信消息,id:" + id);
            return;
        }
        String periodDesc = getPeriodDesc();
        final int[] topNum = {1};
        String StringContent = contentList.stream().map(k -> {
            String title = (String) k.get("title");
            Integer count = (Integer) k.get("count");
            String content = (String) k.get("content");
            //return String.format("> top-%d:%s\n> 出现次数:%d\n> 错误详情描述: %s", topNum[0]++, title, count, content.trim());
            return String.format("> top-%d:出现次数:%d\n> 错误详情描述: %s", topNum[0]++, count, content.trim());
        }).collect(Collectors.joining("\n\n"));
        String content = String.format("%s近%s内错误日志Top10\n%s", systemName, periodDesc, StringContent);
        String sendContent = String.format(TEMPLATE, content);//

        //Map<String, String> contentMap = new HashMap<>();
        //contentMap.put("content", content);
        //Map<String, String> markdownMap = new HashMap<>();
        //markdownMap.put("markdown", JSON.toJSONString(contentMap));
        //markdownMap.put("msgtype", "markdown");
        //String sendText = JSON.toJSONString(markdownMap);//
        //System.err.println(sendText);

        Arrays.stream(WX_ROBOT_URL.split(",")).forEach(wx -> {
            //String post = HttpUtil.post(wx + "&debug=1", sendText);
            //String post2 = HttpUtil.post(wx + "&debug=1", sendContent);
            String post2 = HttpUtil.post(wx, sendContent);
            System.err.println(systemName + "发送微信情况2" + post2);
            //System.err.println(systemName + "发送微信情况" + post);
        });

    }

    private static String getPeriodDesc() {
        if (PERIOD_HOUR <= 24) {
            return PERIOD_HOUR + "小时";
        }
        BigDecimal dayBigDecimal = BigDecimal.valueOf(PERIOD_HOUR).divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
        String s = StrUtil.removeSuffix(StrUtil.removeSuffix(String.valueOf(dayBigDecimal), "00"), "0");
        String[] split = s.split("\\.");
        if (split.length == 1) {
            return split[0] + "天";
        }
        return s + "天";
    }

    /**
     * Jsoup 模拟登录 访问个人中心
     * 先构造登录请求参数,成功后获取到cookies
     * 设置request cookies,再次请求
     * @throws IOException
     */
    public static void jsoupLogin() throws IOException {
        //Jsoup加这个,避免请求https报证书问题
        trustEveryone();
        // 构造登陆参数
        Map<String,String> data = new HashMap<>();
        data.put("username", USER_NAME);
        data.put("password", PASSWORD);
        Connection.Response response = Jsoup.connect(LOG_CENTER_BASE_URL + "account/ldap/login")
                .ignoreContentType(true) // 忽略类型验证
                .ignoreHttpErrors(true)
                .followRedirects(false) // 禁止重定向
                .postDataCharset("utf-8")
                .header("Upgrade-Insecure-Requests","1")
                .header("Accept","application/json")
                .header("Content-Type","application/json")
                .header("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36")
                .requestBody(JSON.toJSONString(data))
                .method(Connection.Method.POST)
                .execute();
        response.charset("UTF-8");
        // login 中已经获取到登录成功之后的cookies
        // 构造访问个人中心的请求
        Map<String, String> cookies = response.cookies();
        cookies.forEach((k, v) -> COOKIE = k + "=" + v);
        String body = response.body();
        X_Csrf_Token = JSON.parseObject(body).getString("X-Csrf-Token");

        System.err.println("COOKIE:" + COOKIE);
        System.err.println("X_Csrf_Token:" + X_Csrf_Token);
    }

    public static void jsoupHandle(String id) throws IOException {
        String url = LOG_CENTER_BASE_URL + "jobs/" + id + "/results";
        Document document = Jsoup.connect(url)
                .header("X-Csrf-Token", X_Csrf_Token)
                .header("Cookie", COOKIE)
                .ignoreContentType(true) // 忽略类型验证
                .ignoreHttpErrors(true)
                .method(Connection.Method.GET)
                .get();
        System.err.println(url);
        String s = JSON.toJSONString(document);
        System.err.println(s);

    }


    public static void trustEveryone() {
        try {
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });

            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, new X509TrustManager[] { new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            } }, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
        } catch (Exception e) {
            // e.printStackTrace();
        }
    }

}

三、实现效果

企业微信机器狗开发者文档:群机器人配置说明 - 接口文档 - 企业微信开发者中心

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1261529.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity3D】MAX聚合广告SDK——Pangle广告接入失败总结

Pangle, App Monetization Simplified 注册 登录 创建应用 创建广告单元 将其应用ID和广告ID关联到MAX广告。 下载Pangle Unity Plugin包&#xff0c;新建一个空工程&#xff08;很重要&#xff09; Unity版本2019.4.0f1 gradle plugin 4.2.0 gradle版本6.7.1 build_tools 34.…

SpringBoot : ch08 自动配置原理

前言 在现代的Java开发中&#xff0c;Spring Boot已经成为了一个备受欢迎的框架。它以其简化开发流程、提高效率和强大的功能而闻名&#xff0c;使得开发人员能够更加专注于业务逻辑的实现而不必过多地关注配置问题。 然而&#xff0c;你是否曾经好奇过Spring Boot是如何做到…

7000字+24张图带你彻底弄懂线程池

大家好&#xff0c;我是三友。今天跟大家聊一聊无论是在工作中常用还是在面试中常问的线程池&#xff0c;通过画图的方式来彻底弄懂线程池的工作原理&#xff0c;以及在实际项目中该如何自定义适合业务的线程池。 一、什么是线程池 线程池其实是一种池化的技术的实现&#xff0…

控制台gbk乱码

引用IntelliJ IDEA中 统一设置编码为utf-8或GBK-CSDN博客 特别注意file coding 的文件path和java的编码格式 配置

9.增删改操作

目录 一、插入操作 1、为表的所有字段插入数据 2、为表的指定字段插入数据 3、同时插入多条记录 4、将查询结果插入表中&#xff1a; 二、更新操作 三、删除操作 四、练习题 一、插入操作 在使用数据库之前&#xff0c;数据库中必须要有数据&#xff0c;MYSQL中使INSE…

ESP32-Web-Server编程-HTML 基础

ESP32-Web-Server编程-HTML 基础 概述 HTML(HyperText Markup Language) 是用来描述网页的一种语言。其相关内容存储在前端代码的 .html 文件中。 当浏览器向 web 服务器请求网页时&#xff0c;一个 HTML 文件被发送给浏览器&#xff0c;浏览器解释该文件的内容&#xff0c;…

【重磅】:Spring Initializer 已经不支持Java8,也就是SpringBoot2.x项目初始化

Spring Initializer 已经不支持Java8 问题描述解决方案升级java版本更换IDEA内置的Spring Initializer中 Server URL的镜像地址 问题描述 我们可以看到在IDEA内置的Spring Initializer中 Java版本选择模块已经不支持1.8了&#xff0c;同样的&#xff0c;官网也不再支持了 解决…

YOLOv8 onnx 文件推理多线程加速视频流

运行环境&#xff1a; MacOS&#xff1a;14.0Python 3.9Pytorch2.1onnx 运行时 模型文件&#xff1a; https://wwxd.lanzouu.com/iBqiA1g49pbc 密码:f40v 下载 best.apk后将后缀名修改为 onnx 即可模型在英伟达 T4GPU 使用 coco128 训练了 200 轮如遇下载不了可私信获取 代码…

Ubuntu18.04安装Ipopt-3.12.8流程

本文主要介绍在Ubuntu18.04中安装Ipopt库的流程&#xff0c;及过程报错的解决方法&#xff0c;已经有很多关于Ipopt安装的博客&#xff0c;但经过我的测试&#xff0c;很多都失效了&#xff0c;经过探索&#xff0c;找到可流畅的安装Ipopt的方法&#xff0c;总结成本篇博客。 …

《尚品甄选》:后台系统——权限管理之菜单管理,递归实现树形结构查询(debug一遍)

文章目录 一、表结构设计二、菜单管理接口2.1 查询菜单2.2 添加菜单2.3 修改菜单2.4 删除菜单 三、分配菜单3.1 查询菜单3.2 保存菜单(批量插入) 四、动态菜单五、解决bug 一、表结构设计 菜单管理就是对系统的首页中的左侧菜单进行维护。 一个用户可以担任多个角色&#xff…

解密人工智能:线性回归

导言 人工智能&#xff08;AI&#xff09;已经成为当今科技领域的热门话题&#xff0c;其应用领域涵盖了各个行业。线性回归作为人工智能中的一种关键统计学方法&#xff0c;被广泛应用于预测和决策支持系统中。本文将为您详细介绍线性回归在人工智能中的应用原理与方法&#x…

将用户的session改为分布式共享session

将用户的session改为分布式session 分布式session理解 使用分布式session的原因&#xff1a; 后台服务器是分布式的&#xff08;比如要负载均衡&#xff09;&#xff0c;在A服务器请求的的信息&#xff08;如用户登录信息&#xff09;存在A的session中&#xff0c;B服务器并不…

代码随想录算法训练营 ---第四十八天

第一题&#xff1a; 简介&#xff1a; 注&#xff1a;本题简介是我的思路&#xff0c;题解思路看下方。 动态规划五部曲&#xff1a; 1.确定dp数组的含义 //dp[i]表示 偷到第i家能偷到的最大金额 for(int i2;i<nums.size();i){if(i-3>0)dp[i] max(dp[i-2],dp[i-3])nu…

vue中的keep-alive详解与应用场景

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-keep-alive 目录 一、Keep-alive 是什么 二、使用场景 三、原理分析 四、案例实现 activa…

NB-IoT BC260Y Open CPU SDK④开发环境搭建

NB-IoT BC260Y Open CPU SDK④开发环境搭建 1、SDK包的介绍2、编程工具3、程序框架1、SDK包的介绍 (1)、SDK包的下载: 链接: (2)、文件目录介绍 文件名描述device启动文件、底层配置文档等doc存放 QuecOpen 项目相关的说明文档osFreeRTOS 相关代码out输出编译 App 和调…

06-学成在线添加课程,包含课程基本信息,营销信息,课程计划信息,师资信息

添加课程 界面原型 第一步: 用户进入课程查询列表,点击添加课程按钮,选择课程类型是直播还是录播,课程类型不同那么授课方式也不同 添加的课程和教学机构是一对一的关系 第二步: 用户选完课程形式后,点击下一步填写课程的基本信息和营销信息(两张表) 用户只要填完课程信息就…

SpringCloud--分布式事务实现

一、分布式事务 首先要明白事务是指数据库中的一组操作&#xff0c;这些操作要么全部成功执行&#xff0c;要么全部不执行&#xff0c;以保持数据的一致性和完整性。在本地事务中&#xff0c;也就是传统的单机事务&#xff0c;必须要满足原子性(Atomicity)、一致性(Consistenc…

错误:FinalShell连接CentOs连接失败

需要说明的是:这个错误不是首次连接发生的,而是多次使用后可能发生的错误 正文: 可能的原因是虚拟机的ip地址发生了变更,原因有以下几点: 最最可能的原因:1.DHCP分配变更&#xff1a; 如果虚拟机使用DHCP来获取IP地址&#xff0c;那么DHCP服务器可能会分配给虚拟机一个新的I…

java设计模式学习之【单例模式】

文章目录 引言单例模式简介定义与用途实现方式&#xff1a;饿汉式懒汉式 UML 使用场景优势与劣势单例模式在spring中的应用饿汉式实现懒汉式实现数据库连接示例代码地址 引言 单例模式是一种常用的设计模式&#xff0c;用于确保在一个程序中一个类只有一个实例&#xff0c;并且…

不小心删除了短信,如何在 Android 上恢复已删除的短信

不小心删除了文字消息在 Android 手机上使用可能会是一种令人痛苦的体验。这些消息可能包含有价值的信息、珍贵的回忆或重要的细节。幸运的是&#xff0c;您可以探索多种方法来恢复这些丢失的消息。在本文中&#xff0c;我们将深入研究可用于检索已删除短信的选项&#xff0c;并…