当前位置: 首页 > article >正文

自定义基座实时采集uniapp日志

自定义基座实时采集uniapp日志

打测试包给远端现场(测试/客户)实际测试时也能实时看到日志了,也有代码行数显示。
收集的日志

流程设计

重写console.log方法
Websocket
Websocket
uniapp收集日志
通过插件或Native.js传输
安卓基座接收
后台服务接收保存
前端html页面渲染

uniapp收集代码

重写console方法

通过条件编译,在app使用环境重写日志打印方法

 // #ifdef APP-PLUS=function(...args){
 console.log = function (...args) {
     try {
         _this.$plugins.getUtils("consoleLog", {'level': 'log', 'args': args})
     } catch (e) {
         console.info('console.log 打印失败', e);
     }
 }
 console.error = function (...args) {
     try {
         _this.$plugins.getUtils("consoleLog", {'level': 'error', 'args': args})
     } catch (e) {
         console.info('console.error 打印失败', e);
     }
 }
 console.warn = function (...args) {
     try {
         _this.$plugins.getUtils("consoleLog", {'level': 'warn', 'args': args})
     } catch (e) {
         console.info('console.warn 打印失败', e);
     }
 }
 // #endif

发送给安卓层

/**
 * 快捷调用安卓工具类方法
 * this.$plugins.getUtils('method',{userId:'test'})
 * @param {Object} method
 * @param {Object} jsonObject
 * @param {Object} successCallback
 * @param {Object} errorCallback
 * @return {String} 原始字符串,如果是json化返回的就是一个json字符串 不是对象!!!
 */
getUtils: function(method, jsonObject, successCallback, errorCallback) {
	try {
		var success = typeof successCallback !== 'function' ? null : function(args) {
				successCallback(args);
			},
			fail = typeof errorCallback !== 'function' ? null : function(code) {
				errorCallback(code);
			};
		var callbackID = plus.bridge.callbackId(success, fail);
		return plus.bridge.exec(_BARCODE, "getUtils", [callbackID, method, jsonObject]);
	} catch (e) {
		console.error(e)
		errorCallback(e)
	}

},

//初始化方法,一般是登录后调用
_this.$plugins.getUtils("initConsoleLog", {'userId': _this.GLOBAL.$USER_INFO.user_iidd})

安卓自定义基座收集日志

跳转方法

/**
  * 工具类获取
  *
  * @param pWebview
  * @param array
  * @return
  */
 public void getUtils(IWebview pWebview, JSONArray array) {
     Log.i("getUtils", "工具类获取" + array.toString());
     String result = null;
     String CallBackID = array.optString(0);
     try {
         //方法
         String method = array.optString(1);
         JSONObject json = new JSONObject(array.optString(2));
         result = this.utilMethood(method, json, pWebview);
     } catch (Exception e) {
         e.printStackTrace();
         JSUtil.execCallback(pWebview, CallBackID, e.getMessage(), JSUtil.ERROR, false);
     }
     Log.i("getUtils", "工具类返回信息:\n" + result);
     JSUtil.execCallback(pWebview, CallBackID, result, JSUtil.OK, true);
 }

初始化日志信息方法

/**
 * WebSocket调试信息推送客户端
 */
private PushConsoleWebSocketClient pushConsoleWebSocketClient = null;

/**
 * 初始化推送
 */
public static boolean pushLogInit = false;

/**
 * 调试日志地址
 */
public static String LOG_WS_URL = "ws://127.0.0.1:5080/weblog/uniapplogv2/";

/**
 * 调试id
 */
public static String LOG_WS_USERID = null;

/**
 * 初始化日志信息
 *
 * @param params
 * @param pWebview
 * @return
 */
private String initConsoleLog(JSONObject params, IWebview pWebview) {
    LOG_WS_USERID = params.optString("userId");
    Log.i(TAG, "uniapp层初始化日志信息: " + LOG_WS_USERID);
    if (null != LOG_WS_USERID && !"".equals(LOG_WS_USERID)) {
        try {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        pushConsoleWebSocketClient = PushConsoleWebSocketClient.builder(LOG_WS_URL, "系统名称", LOG_WS_USERID);
                        pushConsoleWebSocketClient.connect();
                        pushLogInit = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();

        } catch (Exception e) {
            Log.e(TAG, "initConsoleLog: 初始化调试信息推送服务异常", e);
        }
    }
    return ResultUtil.ok("日志初始化完毕");
}

推送日志调试信息方法

/**
 * 推送日志信息到调试页面
 *
 * @param log   日志内容
 * @param level 日志等级
 */
private void pushLogToCache(String level, JSONArray log) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                com.alibaba.fastjson.JSONObject params = new com.alibaba.fastjson.JSONObject();
                params.put("code", "push");
                params.put("sys", pushConsoleWebSocketClient.getSys());
                params.put("userId", pushConsoleWebSocketClient.getUserId());
                params.put("level", level);
                params.put("timestamp", System.currentTimeMillis());
                params.put("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
                try {
                    params.put("log", parseUniappConsoleLog(log));
                } catch (Exception e) {
                    params.put("log", log);
                }
                pushConsoleWebSocketClient.send(params.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    thread.start();
//        executorService.submit(thread);
}
   

安装自定义基座

安卓WebSocket客户端

安卓WebSocket客户端推送负责将调试日志推送给后端

gradle依赖

//WebSocket连接
implementation 'org.java-websocket:Java-WebSocket:1.5.3'

import android.util.Log;

import com.inspur.mobilefsp.plugins.WfmPlugin;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONObject;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;

/**
 * WebSocket客户端类,用于与服务器建立WebSocket连接并处理消息。
 * 该类实现了WebSocketClient接口,并提供了连接、消息处理和错误处理的功能。
 *
 * @author 淡梦如烟
 * @date 20250211
 */
public class PushConsoleWebSocketClient extends WebSocketClient {

    /**
     * 日志标签,用于标识日志输出的来源
     */
    public final static String TAG = "PushLogClient";

    /**
     * WebSocket服务器的URL
     */
    private String url;

    /**
     * 系统名称
     */
    private String sys;

    /**
     * 用户ID
     */
    private String userId;

    /**
     * 构造函数,初始化WebSocket客户端
     *
     * @param serverUrl WebSocket服务器的URL
     * @param sys       系统名称
     * @param userId    用户ID
     * @throws URISyntaxException 如果提供的URL格式不正确
     */
    public PushConsoleWebSocketClient(String serverUrl, String urlParams, String sys, String userId) throws URISyntaxException {
        super(new URI(serverUrl + urlParams));
        this.url = serverUrl;
        this.sys = sys;
        this.userId = userId;
    }

    /**
     * 建造者生成客户端
     *
     * @param serverUrl
     * @param sys
     * @param userId
     * @return
     */
    public static PushConsoleWebSocketClient builder(String serverUrl, String sys, String userId) {
        try {
        	//自定义参数,自行实现
            JSONObject json = new JSONObject();
            json.put("code", "pushStart");
            json.put("userId", userId);
            json.put("sys", sys);
            JSONObject password = new JSONObject();
            password.put("userId", userId);
            password.put("timestamp", System.currentTimeMillis());
            //aes加密 ,自行实现或者用第三方包
            String encode = QEncodeUtil.aesEncrypt(json.toString(), "aes秘钥2");
            encode = URLEncoder.encode(encode, "UTF-8");
            //百分号不能作为参数
            encode = encode.replaceAll("%", "BaiFenHao");
            String url = serverUrl + encode;
            Log.e(TAG, "builder: websocket地址:" + url);
            PushConsoleWebSocketClient pushConsoleWebSocketClient = new PushConsoleWebSocketClient(serverUrl, encode, sys, userId);
            return pushConsoleWebSocketClient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取WebSocket服务器的URL
     *
     * @return WebSocket服务器的URL
     */
    public String getUrl() {
        return url;
    }

    /**
     * 获取系统名称
     *
     * @return 系统名称
     */
    public String getSys() {
        return sys;
    }

    /**
     * 获取用户ID
     *
     * @return 用户ID
     */
    public String getUserId() {
        return userId;
    }

    /**
     * 当WebSocket连接成功建立时调用
     *
     * @param handshake 握手信息
     */
    @Override
    public void onOpen(ServerHandshake handshake) {
        // WebSocket连接已成功建立
        // 在此执行任何必要的操作
        Log.i(TAG, "onOpen: " + handshake.getHttpStatus());
        WfmPlugin.pushLogInit = true;
    }

    /**
     * 当接收到来自服务器的消息时调用
     *
     * @param message 收到的消息内容
     */
    @Override
    public void onMessage(String message) {
        // 处理来自服务器的传入消息
        Log.i(TAG, "onMessage: " + message);
    }

    /**
     * 当WebSocket连接关闭时调用
     *
     * @param code   关闭状态码
     * @param reason 关闭原因
     * @param remote 是否由远程服务器关闭
     */
    @Override
    public void onClose(int code, String reason, boolean remote) {
        Log.e(TAG, "onClose: code[" + code + "];remote[" + remote + "];url[" + this.url + "];reason:" + reason);
        // WebSocket连接已关闭
        // 在此执行任何必要的清理操作
//        this.reconnectAfterMillis(100L);
    }

    /**
     * 重连锁
     */
    private static boolean reConnectLock = false;

    /**
     * 延迟重连
     *
     * @param millis
     */
    public void reconnectAfterMillis(Long millis) {
        try {
            if (reConnectLock) {
                return;
            }
            reConnectLock = true;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 尝试在5秒后重新连接
                        Thread.sleep(millis);
                        reconnect();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        reConnectLock = false;
                    }
                }
            }).start();
        } catch (Exception e) {
            e.printStackTrace();
            reConnectLock = false;
        } finally {
        }

    }

    /**
     * 当WebSocket连接期间发生错误时调用
     *
     * @param ex 发生的异常
     */
    @Override
    public void onError(Exception ex) {
        Log.e(TAG, "onError: ", ex);
        // 处理WebSocket连接期间发生的任何错误
//        this.reconnectAfterMillis(5000L);
    }
}

后台代码

springboot接受日志和推送日志

package com.faker.weblog.websocket;

import cn.hutool.core.net.URLDecoder;
import com.alibaba.fastjson2.JSONObject;
import com.faker.weblog.model.dto.PushUniappLogDto;
import com.faker.weblog.util.Toolkit;
import com.faker.weblog.wrapper.WrapMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Lazy
@Component
@Slf4j
@ServerEndpoint("/uniapplogv2/{id}")
@Api(value = "websocket日志接受和推送uniapp日志工具")
public class UniappLogWebHandleV2 {
    @ApiModelProperty(value = "客户端id")
    private String id;

    @ApiModelProperty(value = "是否初始化", example = "true")
    private boolean initialized = false;

    @ApiModelProperty(value = "是否接受日志消息", example = "true")
    private boolean isPullLogs = false;

    @ApiModelProperty(value = "系统名称", example = "fakerSys")
    private String sys;

    @ApiModelProperty(value = "用户id", example = "test")
    private String userId;

    /**
     * 日志列表
     */
    private static ConcurrentHashMap<String, ConcurrentHashMap<String, List<String>>> logListMap = new ConcurrentHashMap<>();

    /**
     * 获取日志列表
     *
     * @return
     */
    public ConcurrentHashMap<String, ConcurrentHashMap<String, List<String>>> getlogListMap() {
        return logListMap;
    }

    /**
     * 清理日志列表
     */
    public static void cleanLogListMap() {
        logListMap.clear();
    }

    /**
     * concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。
     */
    private static ConcurrentHashMap<String, UniappLogWebHandleV2> webSocketMap = new ConcurrentHashMap<String, UniappLogWebHandleV2>();

    /**
     * websocket的session
     */
    private Session session;

    /**
     * 获取session
     *
     * @return
     */
    public Session getSession() {
        return this.session;
    }

    /**
     * 新的WebSocket请求开启
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("id") String id) {
        log.info("新的WebSocket请求开启:" + id);
        try {
            String decode = id.replaceAll("BaiFenHao", "%");
            decode = URLDecoder.decode(decode, Charset.forName("UTF-8"));
            String aesJson = com.faker.dba.util.QEncodeUtil.aesDecrypt(decode, "aes秘钥2");
            JSONObject jsonObject = JSONObject.parseObject(aesJson);
            String userId = jsonObject.getString("userId");
            String password = jsonObject.getString("password");
            String sign = jsonObject.getString("sign");
            if (jsonObject.get("isPullLogs") != null) {
                this.isPullLogs = jsonObject.getBoolean("isPullLogs");
            }
            this.sys = jsonObject.getString("sys");
            this.userId = userId;
            this.session = session;
			
			//鉴权方法,自行实现
            this.validate(userId, sign, password);

            this.id = id;
            webSocketMap.put(id, this);

            String code = jsonObject.getString("code");
            if ("pushStart".equalsIgnoreCase(code)) {
            	//app推送方法
                if (thisLististMap == null) {
                    thisLististMap = new ConcurrentHashMap<>();
                    logListMap.put(this.sys, thisLististMap);
                }
                List<String> logList = thisLististMap.get(this.userId);
                if (logList == null) {
                    logList = new ArrayList<>();
                    thisLististMap.put(this.userId, logList);
                }
            } else if ("webStart".equalsIgnoreCase(code)) {
                //pc端查看日志方法
                this.isPullLogs = true;
                this.sys = jsonObject.getString("watchSys");
                this.userId = jsonObject.getString("watchUserId");

                ConcurrentHashMap<String, List<String>> thisLististMap = logListMap.get(this.sys);
                if (thisLististMap != null) {
                    List<String> logList = thisLististMap.get(this.userId);
                    if (logList != null) {
                        for (String log : logList) {
                            try {
                                session.getBasicRemote().sendText(log);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("鉴权错误:" + id, e);
        }
    }


    /**
     * WebSocket 请求关闭
     */
    @OnClose
    public void onClose() {
        // 从set中删除
        log.info("WebSocket请求关闭:" + id);
        webSocketMap.remove(id);
    }

    /**
     * 发生异常
     */
    @OnError
    public void onErro(Throwable throwable) {
        throwable.printStackTrace();
    }


    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        log.debug("websocket来自客户端的消息:{}", message);
        JSONObject jsonObject = JSONObject.parseObject(message);
        String code = jsonObject.getString("code");
        if (this.initialized) {
            if ("push".equalsIgnoreCase(code)) {
                PushUniappLogDto params = JSONObject.parseObject(message, PushUniappLogDto.class);
                if (Toolkit.isNullOrEmpty(params.getSys())) {
                    log.warn("系统名称不能为空");
                    return;
                }
                if (Toolkit.isNullOrEmpty(params.getUserId())) {
                    log.warn("用户id不能为空");
                    return;
                }
                if (Toolkit.isNullOrEmpty(params.getLevel())) {
                    log.warn("日志等级不能为空");
                    return;
                }
                if (Toolkit.isNullOrEmpty(params.getLog()) || "[]".equals(params.getLog())) {
                    log.warn("日志信息不能为空");
                    return;
                }
                this.sendLogs(JSONObject.toJSONString(params));
            }
        } else {
            log.warn("[" + this.sys + "][" + this.userId + "]未初始化" + this.initialized);
        }
    }

    /**
     * token鉴权
     *
     * @param userId
     * @param sign
     * @param password
     * @throws IOException
     */
    public void validate(String userId, String sign, String password) throws IOException {
        if (Toolkit.isNotNull(userId) && Toolkit.isNotNull(sign)) {
            //校验userId和密码 这里简化为校验userId和时间戳的aes加密信息,校验通过初始化连接
            try {
                String aesJson = com.faker.dba.util.QEncodeUtil.aesDecrypt(sign, "aes秘钥1");
                JSONObject aesJsonObject = JSONObject.parseObject(aesJson);
                if (aesJsonObject.get("userId") == null || aesJsonObject.get("timestamp") == null) {
                    session.getBasicRemote().sendText("加密信息校验错误,已记录!" + "<br>" + aesJson + "<br>");
                    session.close();
                }
                if (userId.equals(aesJsonObject.getString("userId"))) {
                    if (aesJsonObject.getLong("timestamp") > System.currentTimeMillis() - 1000 * 60 * 5
                            || aesJsonObject.getLong("timestamp") < System.currentTimeMillis() + 1000 * 60 * 5) {
                        this.initialized = true;
                        session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.ok("签名[" + sign + "]正确,已记录!")));
                    } else {
                        session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error("签名[" + sign + "]已过期,已记录!")));
                        session.close();
                    }

                } else {
                    session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error("签名[" + sign + "]错误,已记录!")));
                    session.close();
                }
            } catch (Exception e) {
                log.error("加密信息[" + password + "]校验错误", e);
                session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error("加密信息校验错误,已记录!" + "<br>" + e.getMessage())));
                session.close();
            }
        } else if (Toolkit.isNotNull(userId) && Toolkit.isNotNull(password)) {
            //todo 校验登录密码

        } else {
            log.error("登录信息错误[" + userId + "][" + password + "][" + sign + "]");
            session.getBasicRemote().sendText(JSONObject.toJSONString(WrapMapper.error("登录信息错误,已记录!")));
            session.close();
        }
    }

    /**
     * 向客户端发送消息
     *
     * @param message
     */
    public void sendLogs(String message) {
        ConcurrentHashMap<String, List<String>> thisLististMap = logListMap.get(this.sys);
        if (thisLististMap == null) {
            thisLististMap = new ConcurrentHashMap<>();
        }
        List<String> logList = thisLististMap.get(this.userId);
        if (logList == null) {
            logList = new ArrayList<>();
        }
        logList.add(message);
        //日志暂存最新的100条
        if (logList.size() > 100) {
            logList.remove(0);
        }
        this.sendToUser(message);
    }

    /**
     * 向指定客户端发送消息
     *
     * @param message
     */
    private void sendToUser(String message) {
        for (UniappLogWebHandleV2 webSocket : webSocketMap.values()) {
            if (webSocket.isInitialized() && webSocket.isPullLogs() && webSocket.getSys().equals(this.sys) && webSocket.getUserId().equals(this.userId)) {
                log.debug("【websocket消息】广播消息, message={}", message);
                try {
                    Session session = webSocket.getSession();
                    session.getBasicRemote().sendText(message);
                } catch (Exception e) {
                    log.error("【websocket消息】广播消息, message={}", message);
                }
            }
        }
    }

    /**
     * 向所有客户端发送消息
     *
     * @param message
     */
    public void sendToAll(String message) {
        for (UniappLogWebHandleV2 webSocket : webSocketMap.values()) {
            if (!webSocket.isInitialized()) {
                continue;
            }
            log.debug("【websocket消息】广播消息, message={}", message);
            try {
                Session session = webSocket.getSession();
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                log.error("【websocket消息】广播消息, message={}", message);
            }
        }
    }

    public String getId() {
        return id;
    }

    public String getSys() {
        return sys;
    }

    public String getUserId() {
        return userId;
    }

    public boolean isInitialized() {
        return initialized;
    }

    public boolean isPullLogs() {
        return isPullLogs;
    }
}

html页面渲染日志

const id = JSON.stringify({
    code: 'webStart',
    userId: userToken.userId,
    sign: userToken.token,
    isPullLogs: true,
    watchSys: $('#sys').val(),
    watchUserId: $('#userId').val()
})
//aes加密
const aes = aesEncrypt(id)
//替换百分号
const url = encodeURIComponent(aes).replaceAll('%', 'BaiFenHao')
console.log('[信息]传输协议秘钥', id, aes, url)
// 指定websocket路径
var wsUrl = 'ws://' + location.host + '/weblog/uniapplogv2/' + url;

try {
    if (null != websocket && undefined != websocket) {
        websocket.close();
    }
} catch (e) {
    console.warn(e)
}
websocket = new WebSocket(wsUrl);
websocket.onmessage = function (event) {
    // 接收服务端的实时日志并添加到HTML页面中
    $("#log-container div").append(showColorLog(event.data));
    $("#log-container div").append('<br/>')
    if (localStorage.autoJump == '是') {
        // 滚动条滚动到最低部
        $("#log-container").scrollTop($("#log-container div").height() - $("#log-container").height());
    }
};
websocket.onopen = function (event) {
    reloadLock = false;
}

websocket.onerror = function (error) {
    console.log('onerror', error)
    // $("#log-container div").append('<br/><br/>连接已断开...    5秒后尝试重新连接........ <br/><br/>');
    // setTimeout(reloadWebSocket(), 5000)
}
websocket.onclose = function (event) {
    console.log('onclose', event)
    $("#log-container div").append('<br/><br/>连接已关闭...    5秒后尝试重新连接........ <br/><br/>');
    setTimeout(reloadWebSocket, 5000)
}

总结

给远程调试提供方便,websocket推送消耗较少,也是有序推送,完善好重连机制比post提交更方便查看。


http://www.kler.cn/a/547201.html

相关文章:

  • Langchain对管道操作符|的重构实现链式流程
  • 一文讲清前端热更新
  • 【NLP 21、实践 ③ 全切分函数切分句子】
  • SQLite数据库中查询性能优化及索引创建的原则总结
  • IoTDB 断电后无法启动 DataNode,日志提示 Meet error while starting up
  • Java面试宝典:说下Spring Bean的生命周期?
  • 贪心算法与动态规划的区别
  • SSM课设-学生选课系统
  • 模型报错infeasible,如何查看冲突约束
  • 万字长文破解 AI 图片生成算法-Stable diffusion
  • 基于单片机的智能奶茶机(论文+源码+图纸)
  • 机器学习-1:线性回归
  • 【Sceneform-EQR】实现3D场景背景颜色的定制化(背景融合的方式、Filament材质定制)
  • 【Android开发】安卓手机APP拍照并使用机器学习进行OCR文字识别(完整工程资料源码)
  • 【第3章:卷积神经网络(CNN)——3.6 CNN的高级特性与优化策略】
  • 计算机网络面试题库
  • 使用数据库sqlite 筛选人脸信息
  • Oracle查看执行计划
  • 项目中菜单按照层级展示sql
  • SpringCloud面试题----微服务下为什需要链路追踪系统