NodeJS日志记录(Electron项目)

2021-07-07 17:56:46 浏览数 (1)

普通日志收集

主进程

log4js

下载依赖

代码语言:javascript复制
npm install log4js --save

工具类

代码语言:javascript复制
const log4js = require('log4js');
log4js.configure({
    appenders: {
        myLogFile: {
            type: "dateFile",
            filename: './logs/mylog',//您要写入日志文件的路径及文件名前缀
            pattern: "yyyy-MM-dd-hh.log",//(可选,默认为.yyyy-MM-dd) - 文件名后缀。格式:.yyyy-MM-dd-hh:mm:ss.log
            alwaysIncludePattern: true,//(默认为false) - 将模式包含在当前日志文件的名称以及备份中
            compress: false,//(默认为false) - 在滚动期间压缩备份文件(备份文件将具有.gz扩展名)
            encoding: 'utf-8',//default "utf-8",文件的编码
            maxLogSize: 1024 * 1024 // 1M 文件最大存储空间,当文件内容超过文件存储空间会自动生成一个文件xxx.log.1的序列自增长的文件
        },
        myLogConsole: {
            type: 'console'
        }
    },
    categories: {
        default: {
            appenders: ['myLogFile', 'myLogConsole'],
            level: log4js.levels.ALL
        },
        myLogFile: {
            appenders: ['myLogFile'],
            level: log4js.levels.ALL
        },
        myLogConsole: {
            appenders: ['myLogConsole'],
            level: log4js.levels.ALL
        }
    }
});
let log = log4js.getLogger('myLogFile');
module.exports.logger = log;

开发时在Console窗口中显示日志

代码语言:javascript复制
log4js.getLogger('myLogConsole');

正式部署后改为

代码语言:javascript复制
log4js.getLogger('myLogFile');

使用

代码语言:javascript复制
const logger = require('./assets/js/MyLog').logger;
logger.info("app init");

logger对象暴露到全局(main.js)

代码语言:javascript复制
global.zlog = logger;

注意

使用Electron和Webpack结合的时候,建议日志在main.js中引用,页面中通过ipc方式进行调用。 工具类MyLog.js我是配置在assets/js/MyLog.js。 package.json中配置build下的files配置。

package.json

代码语言:javascript复制
{
  "name": "xhlive",
  "productName": "我的博客",
  "version": "1.3.6",
  "description": "",
  "main": "main.js",
  "build": {
    "asar": true,
    "files": [
      "build/*",
      "main.js",
      "*.html",
      "image",
      "assets",
      "app.ico",
      "node_modules/**/*"
    ],
    "appId": "cn.psvmc.myblog",
    "win": {
      "icon": "app.ico",
      "target": [
        "zip"
      ]
    },
    "nsis": {
      "oneClick": false,
      "allowElevation": true,
      "allowToChangeInstallationDirectory": true,
      "installerIcon": "app.ico",
      "uninstallerIcon": "app.ico",
      "installerHeaderIcon": "app.ico",
      "createDesktopShortcut": true,
      "createStartMenuShortcut": true,
      "license": "LICENSE.txt"
    },
    "extraResources": [
    ],
    "mac": {
      "hardenedRuntime": false
    }
  },
  "scripts": {
    "start": "webpack --mode development && cross-env ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
    "webpack": "webpack --mode development",
    "dist": "webpack --mode development && electron-builder --win --ia32",
    "dist_dir": "webpack --mode development && electron-builder --dir --win --ia32"
  },
  "devDependencies": {
  },
  "dependencies": {
  }
}

Electron-log(推荐)

Electron-log日志记录工具

首先我们安装依赖:

代码语言:javascript复制
npm i electron-log --save

在项目里面引入依赖项:

代码语言:javascript复制
const zlog = require('electron-log');
let filepath = path.join(app.getPath("documents"), 'xhlive/logs/');
let nowdate = new Date();
let nowdate_str = nowdate.getFullYear()   "_"   (nowdate.getMonth()   1)   "_"   nowdate.getDate()   "_"   nowdate.getHours();
let filename = "mylog_"   nowdate_str   ".log";
zlog.transports.file.resolvePath = () => path.join(filepath, filename);
zlog.transports.file.level = true;
zlog.transports.console.level = false;
global.zlog = zlog;

然后在我们的主线程加入以下代码:

代码语言:javascript复制
zlog.info('这是个提示日志');
zlog.warn('这是个警告日志');
zlog.error('这是个错误日志');

electron-log supports the following log levels:

代码语言:javascript复制
error, warn, info, verbose, debug, silly

以上代码通过不同级别记录日志,默认情况下会在控制台打印出和保存到本地文件,

日志默认保存在app.getPath('userData')目录下的log.log文件中,

这个时候你会发现日志的时间和日志级别,日志内容都记录下来了,有这些信息我们就可以更好的跟踪bug等信息了。当然这个依赖不止这些功能:

我们可以设置log路径和文件名:

代码语言:javascript复制
zlog.transports.file.resolvePath = () => path.join(filepath, filename);

我们可以格式化日志内容:

代码语言:javascript复制
zlog.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}] {text}'

也可以通过log.transports.file.levellog.transports.console.level来分别设置日志输出目标和日志输出等级。

如果想禁止Console中输出可以设置对应项为false

代码语言:javascript复制
zlog.transports.file.level = true;
zlog.transports.console.level = false;

虽然有了这些日志信息,但是都在不同用户的电脑上,我们可以开发一个程序,在适当的时候把用户日志传送到web服务器上去,这样让我们更好的跟踪!

渲染进程

工具类

代码语言:javascript复制
const remote = window.require("electron").remote;
const app = remote.app;
let zlog = remote.getGlobal('zlog');
const isDevelopment = !app.isPackaged;
window.zlog = zlog;

function get_log_func(m_args) {
    let temp_arr = [];
    for (let m_arg of m_args) {
        if (m_arg) {
            if (m_arg instanceof Object) {
                temp_arr.push(JSON.stringify(m_arg));
            } else {
                temp_arr.push(m_arg);
            }
        }
    }
    return temp_arr.join(" | ");
}

let logger = {
    log: function (...args) {
        if (zlog) {
            zlog.info(get_log_func(args));
        }
        if (isDevelopment) {
            console.log(...args)
        }
    },
    info: function (...args) {
        if (zlog) {
            zlog.info(get_log_func(args));
        }
        if (isDevelopment) {
            console.info(...args)
        }
    },
    warn: function (...args) {
        if (zlog) {
            zlog.warn(get_log_func(args));
        }
        if (isDevelopment) {
            console.warn(...args)
        }
    },
    error: function (...args) {
        if (zlog) {
            zlog.error(get_log_func(args));
        }
        if (isDevelopment) {
            console.error(...args)
        }
    },
}

export {logger}

页面中引用

代码语言:javascript复制
import {logger} from "./assets/js/mylog";
logger.info("登录初始化");

主进程网络日志

Electron有主进程和渲染进程,一般呢我们通过渲染进程的控制台network就可以看到程序发起的网络请求。但是使用这个方法呢会有三个问题:

  1. 无法监控主进程发起的网络请求;
  2. Electron呢可能会有多个窗口,不同的窗口发起不同的请求可能存在关联关系,就需要联合监控,就非常麻烦。
  3. 无法精确的分析某个时段的网络请求。

为了弥补这方面的不足,Electron提供了netLog模块,允许开发人员通过编程的方式记录网络请求。

代码语言:javascript复制
const { remote } = require('electron') 
let filepath = path.join(app.getPath("documents"), 'xhlive/logs/');
let nowdate = new Date();
let nowdate_str = nowdate.getFullYear()   "_"   (nowdate.getMonth()   1)   "_"   nowdate.getDate()   "_"   nowdate.getHours();
let filename = "mynet_"   nowdate_str   ".log";
await remote.netLog.startLogging(path.join(filepath, filename)); 
let ses = remote.getCurrentWebContents().session;
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.baidu.com');
xhr.onload = async () => { 
  log.info(xhr.responseText); 
  await remote.netLog.stopLogging() 
} 
xhr.send()

netLog模块是一个主进程模块,所以我们需要通过remote来使用他,

它的startLogging接收两个参数,第一个参数是日志文件记录的路径,也可以通过app.getPath('userData')来指定路径,第二个参数是一个配置对象,具体参考文档。

接着我们发起一个网络请求,得到响应后,我们通过stopLogging()来停止网络监控。

tips: 低版本的Electron可以使用以下方法:

app.commandLine.appendSwitch('log-net-log', 'net-log'),net-log为文件名称,可以自定义,文件会保存在项目根目录下,

也可以根据app.commandLine.hasSwitch('log-net-log')查看网络日志开关是否被打开,返回truefalse

崩溃日志收集

官方文档:https://www.electronjs.org/docs/api/crash-reporter#crashreporter

代码语言:javascript复制
const {crashReporter} = require('electron')

crashReporter.start({
    productName:'myblog',
    companyName:'psvmc',
    submitURL:'https://www.psvmc.cn',
    uploadToServer:false
})

console.info(crashReporter.getCrashesDirectory());

可以调用以下方法模拟崩溃

代码语言:javascript复制
process.crash()

官方说可以设置崩溃日志的目录,但是我这里设置无效

代码语言:javascript复制
let logpath = path.resolve(app.getPath("documents")   '/myblog/logs/');
console.info(logpath);
app.setPath('crashDumps', logpath)

官方说的设置uploadToServer:false,就不需要设置submitURL,但是实际测试并非如此。

另外这种方法生成的错误日志也没法通过文本文档查看,所以我就没有使用。

但是我们可以监听事件child-process-gone

https://www.electronjs.org/docs/api/app#事件-render-process-gone

代码

代码语言:javascript复制
const { app } = require('electron')

app.on('child-process-gone', (event, details) => {
    console.log(details);
})

崩毁重启

代码语言:javascript复制
import { BrowserWindow, app, dialog} from 'electron';

const mainWindow = BrowserWindow.fromId(global.mainId);
mainWindow.webContents.on('crashed', () => {
   const options = {
      type: 'error',
      title: '进程崩溃了',
      message: '这个进程已经崩溃.',
      buttons: ['重载', '退出'],
    };    
   recordCrash().then(() => {
      dialog.showMessageBox(options, (index) => {
        if (index === 0) reloadWindow(mainWindow);
        else app.quit();
      });
    }).catch((e) => {
      console.log('err', e);
    });
})

function recordCrash() { 
    return new Promise(resolve => { 
       // 崩溃日志请求成功.... 
      resolve();
    })
}
  
function reloadWindow(mainWin) {
  if (mainWin.isDestroyed()) {
    app.relaunch();
    app.exit(0);
  } else {
    BrowserWindow.getAllWindows().forEach((w) => {
      if (w.id !== mainWin.id) w.destroy();
    });
    mainWin.reload();
  }
}

0 人点赞