日志驱动开发

2025-06-07

日志驱动开发

如果我们将应用程序比作一个活的有机体,那么错误就好比一种疾病。这种“疾病”的成因可能有很多,包括特定用户的环境。这一点在 Web 平台上尤为重要。有时,原因非常复杂,而通过测试发现的错误,是一系列措施共同作用的结果。

就像人类的疾病一样,没有人能比患者更好地解释他们的症状,任何测试人员都可以比程序本身更好地判断发生了什么。

该怎么办?

为了了解正在发生的事情,我们需要了解用户在我们的应用程序中执行的操作的历史记录。

为了让我们的程序告诉我们它有问题,我们将采用logrock模块并将其链接到 ElasticSearch、LogStash 和 Kibana 进行进一步分析。

LogRock

logrock模块诞生于我们开始开发Cleverbrush产品时。这是一款用于处理矢量图形的软件。使用图形编辑器意味着大量的应用用例。我们试图节省时间和金钱,因此我们优化了一切,包括测试。用测试用例覆盖每个选项既昂贵又不合理,尤其是在不可能覆盖所有选项的情况下。

此模块可以为您的应用程序组织现代日志记录方法。我们基于这些日志来测试应用程序。在本文中,我将向您介绍如何组织日志系统以查找错误。

ElasticStack

ElasticStack

  • ElasticSearch是一个强大的全文搜索引擎。
  • LogStash是一个从各种来源收集日志的系统,也可以将日志发送到 ElasticSearch。
  • Kibana是 ElasticSearch 的 Web 界面,带有许多附加组件。

它是如何工作的?

应用程序日志系统

如果发生错误(或按需),应用程序会将日志发送到服务器,并保存到文件中。Logstash 会将数据增量保存到 ElasticSearch 数据库。用户登录 Kibana 即可查看已保存的日志。

基巴纳

上图显示的是已设置好的 Kibana。它显示了来自 ElasticSearch 的数据。这有助于你分析数据并了解发生了什么。

在本文中,我不会考虑设置 ElasticStack!

创建日志系统

例如,我们将基于 React 将日志系统集成到单页应用程序中。

步骤1.安装:

npm install logrock --save
Enter fullscreen mode Exit fullscreen mode

步骤 2. 设置 React 应用程序

我们需要用组件来包装应用程序

import { LoggerContainer } from "logrock";

<LoggerContainer>
  <App />
</LoggerContainer>
Enter fullscreen mode Exit fullscreen mode

LoggerContainer是一个对应用程序中的错误做出反应并形成堆栈的组件。

堆栈是一个对象,其中包含有关用户的操作系统、浏览器、按下的鼠标或键盘按钮的信息,当然还有操作子数组,其中记录了用户在我们的系统中执行的所有操作。

LoggerContainer有设置,请考虑其中的一些。

<LoggerContainer
  active={true|false}
  limit={20}
  onError={stack => {
    sendToServer(stack);
  }}
>
  <App />
</LoggerContainer>
Enter fullscreen mode Exit fullscreen mode
  • active启用或禁用日志记录。
  • limit设置了用户保存的最近操作数量的限制。如果用户执行了 21 个操作,则此数组中的第一个操作将被自动删除。这样,我们将保存错误发生之前的最后 20 个操作。
  • onError是发生错误时调用的回调函数。它会返回一个 Stack 对象,其中包含有关环境、用户操作等所有信息。我们需要通过这个回调函数将数据发送到 ElasticSearch 或后端,或者将其保存到文件中,以便进一步分析和监控。

日志记录

为了生成高质量的用户操作日志,我们必须用日志调用覆盖我们的代码。

logrock模块带有一个链接到LoggerContainer 的记录器。

例如,我们有一个组件:

import React, { useState } from "react";

export default function Toggle(props) {
  const [toggleState, setToggleState] = useState("off");

  function toggle() {
    setToggleState(toggleState === "off" ? "on" : "off");
  }

  return <div className={`switch ${toggleState}`} onClick={toggle} />;
}
Enter fullscreen mode Exit fullscreen mode

为了正确地用日志覆盖它,我们需要修改切换方法:

import React, { useState } from "react";
import logger from "logrock";

export default function Toggle(props) {
  const [toggleState, setToggleState] = useState("off");

  function toggle() {
    let state = toggleState === "off" ? "on" : "off";

    logger.info(`React.Toggle|Toggle component changed state ${state}`);

    setToggleState(state);
  }


  return <div className={`switch ${toggleState}`} onClick={toggle} />;
}
Enter fullscreen mode Exit fullscreen mode

我们添加了一个记录器,其中的信息分为两部分。React.Toggle向我们展示了此操作发生在 React 级别(即 Toggle 组件),然后我们得到了该操作以及到达此组件的当前状态的文字说明。这种级别的划分并非必需,但采用这种方法可以更清楚地了解代码的具体执行位置。

我们还可以使用React 16 中引入的“componentDidCatch”方法,以防发生错误。

与服务器交互

考虑以下示例。

假设我们有一个从后端收集用户数据的方法。该方法是异步的,部分逻辑隐藏在后端。如何正确地为这段代码添加日志记录?

首先,由于我们有一个客户端应用程序,所有发送到服务器的请求都会在一个用户会话中传递,而无需重新加载页面。为了将客户端上的操作与服务器上的操作关联起来,我们必须创建一个全局的SessionID,并将其添加到每个发送到服务器的请求的标头中。在服务器上,我们可以使用任何能够覆盖我们逻辑的记录器,就像前端的示例一样,如果发生错误,则将这些数据连同附加的 sessionID 一起发送到 ElasticSearch,再发送到后端平台。

步骤1.在客户端生成SessionID :

window.SESSION_ID = `sessionid-${Math.random().toString(36).substr(3, 9)}`;
Enter fullscreen mode Exit fullscreen mode

第 2 步。请求。

我们需要为所有向服务器的请求设置SessionID 。如果我们使用库来处理请求,那么只需为所有请求声明一个 SessionID 即可轻松实现这一点。

let fetch = axios.create({...});

fetch.defaults.headers.common.sessionId = window.SESSION_ID;
Enter fullscreen mode Exit fullscreen mode

步骤3.将SessionID连接到日志堆栈。

LoggerContainer有一个特殊SessionID字段

<LoggerContainer
  active={true | false}
  sessionID={window.SESSION_ID}
  limit={20}
  onError={stack => {
    sendToServer(stack);
  }}
>
  <App />
</LoggerContainer>
Enter fullscreen mode Exit fullscreen mode

步骤4.与后端交互。

请求(在客户端)将如下所示:

logger.info(`store.getData|User is ready for loading... User ID is ${id}`);

getData('/api/v1/user', { id })
  .then(userData => {
    logger.info(`store.getData|User have already loaded. User count is ${JSON.stringify(userData)}`);
  })
  .catch(err => {
    logger.error(`store.getData|User loaded fail ${err.message}`);
  });
Enter fullscreen mode Exit fullscreen mode

工作原理:

我们在客户端请求之前写入日志。从代码中可以看到,现在将开始从服务器下载数据。我们已将SessionID附加到请求中。如果后端日志被此SessionID覆盖,并且请求失败,那么我们就能知道后端发生了什么。

因此,我们监控应用程序的整个周期,不仅在客户端,而且在服务器上。

质量保证工程师

与 QA 工程师合作值得对流程进行单独描述。

由于我们是一家初创公司,所以我们没有正式的要求,有时并非所有事情都合乎逻辑。

如果测试人员不理解该行为,则至少需要考虑这种情况。此外,测试人员通常无法重复相同的情况两次。因为导致错误行为的步骤可能很多且不简单。此外,并非所有错误都会导致严重后果,例如异常。其中一些错误只能改变应用程序的行为,但不会被系统解释为错误。为此,在准备阶段,您可以在应用程序标题中添加一个按钮,以强制发送日志。测试人员发现出现问题后,点击该按钮,并将包含操作的堆栈发送到ElasticSearch

BSOD按钮

如果发生严重错误,我们必须阻止界面,以便测试人员不会进一步点击并卡住。

为了这些目的,我们显示了蓝屏死机。

蓝屏死机

我们在上方看到此严重错误的堆栈文本,下方是之前的操作。我们还获取了错误 ID,测试人员只需选择它并将其附加到工单即可。之后,可以通过此 ID 在 Kibana 中轻松找到此错误。

为了这些目的,LoggerContainer具有以下属性:

<LoggerContainer
  active={true | false}
  limit={20}
  bsodActive={true}
  bsod={BSOD}
  onError={stack => {
    sendToServer(stack);
  }}
>
  <App />
</LoggerContainer>
Enter fullscreen mode Exit fullscreen mode
  • bsodActive启用/禁用 BSOD(禁用 BSOD 适用于生产代码)
  • bsod是 React 组件。默认情况下,它看起来像上面的截图。

要在 UI LoggerContainer 中显示按钮,我们可以使用钩子:

const { getStackData, triggerError } = useLoggerApi();

triggerError(getStackData());
Enter fullscreen mode Exit fullscreen mode

用户交互

有些日志对用户有用。要输出,用户需要使用 stdout 方法:

<LoggerContainer
  active={true | false}
  limit={20}
  bsodActive={true}
  bsod={BSOD}
  onError={stack => {
    sendToServer(stack);
  }}
  stdout={(level, message, important) => {
    console[level](message);

    if (important) {
      alert(message);
    }
  }}
>
  <App />
</LoggerContainer>
Enter fullscreen mode Exit fullscreen mode
  • stdout是负责打印消息的方法。

为了使该消息变得“重要”,只需将 true 作为第二个参数传递给记录器即可。这样,我们可以在弹出窗口中向用户显示此消息,例如,如果数据加载失败,我们可以显示一条错误消息。

logger.log('Something was wrong', true);
Enter fullscreen mode Exit fullscreen mode

技巧和窍门

  • 记录应用程序,包括生产中的应用程序,因为没有测试人员能比真实用户更好地发现瓶颈。

  • 不要忘记在许可协议中提及日志的收集。

  • 请勿记录密码、银行详细信息和其他个人信息!

  • 日志冗余也是不好的,应尽可能使信息清晰。

结论

当你发布一款应用时,它的生命才刚刚开始。你需要对你的产品负责,收集反馈,监控日志,并不断改进。

文章来源:https://dev.to/alexsergey/log-driven-development-3jmf
PREV
10 项 Java 开发人员急需的技能
NEXT
Vue.js 中的组件之间共享数据