dot 快速的未来即将在您的城市举行活动。

加入我们在 Redis 发布会上

如何使用 Redis 构建应用程序来增强日志分析

随着您的 IT 基础设施规模的增长和复杂性的增加,您可能被迫花费更多精力来确保一切都能得到妥善管理。 

自云计算兴起以来,这一点变得尤为重要,因为我们现在拥有大量分布在不同位置的技术信息。  

但是,进行全面的日志分析可能会很繁琐、费时且*令人痛苦地无聊*。 

摆脱这种负担的一个简单而有效的方法是使用旨在简化日志监控的软件或应用程序…… 这就是 Alexis Gardin 在他的应用程序 Logub 中所做的事情。 

与许多其他 SaaS 解决方案不同,Logub 可以本地执行这些操作,并且是开源的。通过按照以下步骤,您将能够创建一个应用程序,该应用程序将为您收集、探索和分析应用程序日志。  

此应用程序的核心是依赖于 RediSearch 的能力,它能够以无与伦比的效率跨不同位置探索和分析日志。让我们来看看 Alexis 如何将这个应用程序整合在一起。 

但在深入研究之前,我们应该告诉您,我们还在 Redis Launchpad 上提供了一系列激动人心的应用程序供您查看。 

https://www.youtube.com/embed/6ZqfboCjLtk
  1. 您将构建什么?
  2. 您将需要什么?
  3. 架构
  4. 入门
  5. 数据存储方式
  6. 数据查询方式
  7. 搜索栏如何工作?
  8. 如何将 Logub 集成到您的项目中?
  9. 结论

1. 您将构建什么?

您将构建一个由 Redis 提供支持的特殊应用程序,用于收集、分析和探索日志应用程序。在下面的步骤中,我们将逐步介绍构建此应用程序所需的一切,以及所需的组件。 

即使您是 Redis 新手,我们也会向您展示如何掌握这个强大的数据库。您准备好开始了吗?

好的,让我们直接开始吧。 

2. 您将需要什么?

  • Fluentd用作跨平台的开源数据收集软件,用于处理日志。 
  • Fluent 的 Redis 输出插件: fluent-plugin-redis 是一个用于将输出发送到 redis.RediSearch 的 Fluent 插件。
  • Docker用作开发人员将应用程序打包到容器的平台。 
  • Docker Compose用于定义和运行多容器 Docker 应用程序。

3. 架构

  • Fluentd 从各个目标收集来自每个应用程序的所有日志
  • Fluentd 然后格式化每个日志并将它们发送到 Redis 以进行存储
  • 数据使用 Fluentd Redis 插件存储,这是一个用于将输出发送到 redis 的 Fluent 插件。
  • Logub 后端允许 RediSearch 对用户定义的字段进行全文搜索和索引。 

4. 入门

在演示中,DEMO 应用程序将在 Logub 中发布日志。您将能够与这个 DEMO 应用程序交互以生成您的日志。然后,您将能够在 Logub 中请求它们

先决条件 

  • Docker 
  • Docker Compose 

首先,确保您的系统上已打开以下端口:8080、8081、3000 和 6379。

步骤 1:克隆存储库

git clone https://github.com/redis-developer/logub

步骤 2. 启动 DEMO 应用程序

  • 转到 /demo 文件夹 (cd ./demo)
  • 使用给定命令启动 docker-compose
docker-compose up -d

启动 Logub 演示

% docker-compose ps
NAME                      COMMAND                  SERVICE             STATUS              PORTS
demo_fluentd_1            "tini -- /bin/entryp…"   fluentd             running             5140/tcp, 0.0.0.0:24224->24224/tcp, :::24224->24224/tcp, 0.0.0.0:24224->24224/udp, :::24224->24224/udp
demo_logub-controller_1   "java -XX:+UnlockExp…"   logub-controller    running             0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
demo_logub-generator_1    "java -XX:+UnlockExp…"   logub-generator     running             0.0.0.0:8081->8081/tcp, :::8081->8081/tcp
demo_logub-ui_1           "docker-entrypoint.s…"   logub-ui            running             0.0.0.0:3000->3000/tcp, :::3000->3000/tcp
demo_redis_1              "redis-server /usr/l…"   redis               running             0.0.0.0:6379->6379/tcp, :::6379->6379/tcp

转到 localhost:3000 以浏览日志。 

您可能需要大约一分钟的时间才能看到日志出现。您可以通过右侧的边栏过滤日志在页面上查看日志。或者,您可以通过顶部的搜索栏按过滤器或全文查询搜索日志。 

单击日志时,将显示详细信息,并且您将可以选择索引业务属性。这些业务属性以后可用作过滤器。 

接下来,转到 localhost:3000/demo 访问游乐场并添加自定义日志。此演示页面将允许您: 

  • 在演示假应用程序中创建虚拟用户,并在日志中看到它们。
  • 在系统中发布您的日志。

如果您返回主页,您可以尝试搜索您刚刚生成的日志。

注意:容器中日志生成与 Logub 中显示之间存在大约 1 分钟的延迟。这种延迟是由收集、格式化和将日志导入数据库的过程造成的。 

5. 工作原理?

数据存储方式

数据使用 Fluentd Redis 插件 存储。它使用 HSET 存储每个日志。例如:HSET level DEBUG message “Hello World” thread main

数据查询方式

public class LogubLog {
  @Builder.Default
  private String id = UUID.randomUUID().toString();

  @Builder.Default
  private String index = "principal";

  @NonNull
  private SystemProperties systemProperties;

  @Builder.Default
  private Map<String, Object> businessProperties = Collections.emptyMap();

  @Builder.Default
  private Optional<String> message = Optional.empty();

  @Builder.Default
  private Instant timestamp = Instant.now();

  @Builder.Default
  private Optional<String> service = Optional.empty();

  @Builder.Default
  private Optional<String> logger = Optional.empty();

  @Builder.Default
  private Optional<String> thread = Optional.empty();

  @Builder.Default
  private Optional<String> source = Optional.empty();

  @Builder.Default
  private LogLevel level = UNKNOWN;

}

public class SystemProperties {
  @Builder.Default
  Optional<String> imageName = Optional.empty();

  @Builder.Default
  Optional<String> containerName= Optional.empty();

  @Builder.Default
  Optional<String> containerId= Optional.empty();

  @Builder.Default
  Optional<String> env= Optional.empty();

  @Builder.Default
  Optional<String> host= Optional.empty();

}

这是您将用于操作日志并从 Redis 中检索日志的对象。执行此操作将允许 Logub UI 显示日志。为了在日志中执行复杂的查询,您可以使用 RediSearch。 

由于您能够动态更改 RediSearch 架构,因此可以使用“列表”数据结构来跟踪已索引的架构。 

public class LogSearch {

 @Builder.Default
 private List<LogubFieldSearch> texts = emptyList();

 @Builder.Default
 private List<LogubFieldSearch> systemProperties = emptyList();

 @Builder.Default
 private List<LogubFieldSearch> businessProperties = emptyList();

 @Builder.Default
 private List<LogubFieldSearch> basicProperties = emptyList();

 @Builder.Default
 private List<LogubFieldSearch> levels = Collections.emptyList();

 @Builder.Default
 private int limit = 25;

 @Builder.Default
 private int offset = 0;

 @Builder.Default
 private Optional<LogubSort> sort = Optional.empty();

 @Builder.Default
 private Instant beginAt = Instant.now().minus(15, ChronoUnit.MINUTES);

 @Builder.Default
 private Instant endAt = Instant.now();


 @SneakyThrows
 public QueryBuilder toQuery() {
  var query = new QueryBuilder();
  var businessPrefix = "businessProperties.";
  var systemPropertiesPrefix = "systemProperties.";

  for (LogubFieldSearch properties : businessProperties) {
    query.append(QueryBuilders.tag(businessPrefix + properties.getName(), properties.getValues(),
    properties.isNegation()));
  }
  for (LogubFieldSearch properties : systemProperties) {
    query.append(QueryBuilders
    .tag(systemPropertiesPrefix + properties.getName(), properties.getValues(),
    properties.isNegation()));
  }
  for (LogubFieldSearch properties : basicProperties) {
    query.append(QueryBuilders
    .tag(properties.getName(), properties.getValues(),
    properties.isNegation()));
  }

  if (!levels.isEmpty()) {
    for (LogubFieldSearch level : levels) {
      var onError = !level.getValues().stream().allMatch(v -> Arrays.stream(LogLevel.values())
      .anyMatch(enumLevel -> enumLevel.name().equalsIgnoreCase(v)));
      if(onError){
        log.error("bad payload for levels {}", level);
        throw new IllegalArgumentException("bad payload for level");
      }
    query.append(QueryBuilders.tag("level",level.getValues(), level.isNegation()));
    }
  }
  for (LogubFieldSearch text : texts) {
    if(!text.getType().equals(LogubFieldType.FullText)){
      log.warn("type {} not handle for text search", text.getType());
    }
    for (String value : text.getValues()) {
      query.append(QueryBuilders.text("message", value, text.isNegation()));
    }
  }

  return query;
 }


}

上面的命令将允许您根据用户输入创建纯文本 RediSearch 查询。在 RediSearch 库的顶部构建了一系列小的 QueryBuilders。它是 Logub UI 将发送的相同命令,以在您的日志中执行全面而有效的搜索。 

步骤 2:如何使用搜索栏

您可以按标签或全文搜索进行搜索。以下是一些您可以用作模型的示例,如果您遇到困难。 

  • env:dev Ut ea vero voluptate* 将搜索 dev 环境中所有以 Ut ea vero voluptate 开头的消息的日志
  • -env:prod Ut ea vero voluptate* 将搜索除 prod 之外的所有环境中所有以 Ut ea vero voluptate 开头的消息的日志
  • originRequest:France originRequest:USA 将搜索所有在 originRequest 字段中具有设置为法国或美国的 value 的日志
  • “dog” “cat” 将搜索所有包含“dog” 和“cat” 的日志
  • -“dog” “cat” 将搜索所有不包含“dog” 但包含“cat” 的日志

为了获得良好的应用程序测试体验,我们强烈建议您使用游乐场创建自己的日志,添加业务属性,然后进行一些实验,以了解它的工作原理。 

步骤 3:如何将 Logub 集成到您的项目中

您将需要以下 Docker 镜像列表来运行 Logub

Logub 日志格式

目前,Logub 只能处理一种特定的日志格式。将来,这种格式将扩展并变得更可定制。

以下是 Logub 格式

{
 "level": "....",
 "thread": "....",
 "logger": "....",
 "message": "....."
}

请注意,这些字段不是必需的。 

如果您想添加业务属性,则需要添加一个嵌套的 JSON 对象,该对象以“mdc”作为键。例如

{
 "level": "....",
 "thread": "....",
 "logger": "....",
 "message": ".....",
 "mdc":{
    "myproperties": "....",
    "anotherOne":".....",
 }
}

在 Logub 中发布日志

要在 Loghub 中浏览日志,您的容器需要使用 Docker Fluentd 日志记录驱动程序。以下是客户集成的配置示例。

# Your container(s)
  MY-CUSTOM-APP:
    image: "MY-CUSTOM-APP-IMAGE"
    logging:
      driver: "Fluentd"
      options:
        Fluentd-address: localhost:24224
        tag: MY-CUSTOM-TAG
  ## Logub Fluentd + Redis conf
  logub-controller:
    image: "logub/logub-controller:0.1"
    ports:
      - "8080:8080"
    depends_on:
      - redis
    links:
      - "Fluentd"
      - "redis"
  logub-ui:
    image: "logub/logub-ui:0.1"
    ports:
      - "3000:3000"
    depends_on:
      - redis
  Fluentd:
    image: "logub/logub-Fluentd:0.1"
    links:
      - "redis"
    ports:
      - "24224:24224"
      - "24224:24224/udp"
  redis:
    image: "redislabs/redismod"
    command: ["/usr/local/etc/redis/redis.conf","--bind","redis","--port", "6379"]
    volumes:
      - ./redis/data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
    ports:
      - "6379:6379"

步骤 4:在 Logub 中设置 Redis

RediSearch

Logub 使用 RediSearch 的功能来处理应用程序日志。当日志持久保存到 Redis 数据库时,它们会附带 3 种类型的字段

  • 系统属性:是 Docker 和 Fluentd 在发送日志时提供给我们的信息(例如环境、容器名称等)。
  • 基本属性:是日志具有的基本信息(例如时间戳、服务等)。这些属性会在 RediSearch 中自动索引。 
  • 业务属性:由 Logub 用户在特定字段中提供。用户在此必须遵守键值 (Map) 格式。借助 RediSearch 的高级功能,您可以在需要对这些“业务属性”进行研究时对其进行索引。
{
  "timestamp": "2021-05-14 11:01:11.686",
  "level": "WARN",
  "thread": "scheduling-1",
  "mdc": {
    "app": "Toughjoyfax",
    "correlationId": "521f075f-36be-4f85-957e-d1c87ad71aa8",
    "originRequest": "Tonga",
    "origin": "LoremIpsum"
  },
  "logger": "com.loghub.loggenerator.service.LoggerService",
  "message": "Doloremque dolores ut minima sed."
}

这里有一个日志示例,描述了当 Fluentd 将数据扁平化并持久化到 Redis 数据库时,我们的工具是如何工作的。Logub API 将允许用户或公司索引 mdc 对象的一个或所有字段。

在这个项目中,Tag 数据类型被广泛使用。在日志搜索中(例如客户 ID),通常会根据业务属性进行搜索。此外,我们还将TextField 数据类型用于日志消息。这允许用户在此字段中进行全文搜索。

以下是搜索过程的简化模式:

Redis

Redis 用于通过 Fluentd 以这种方式将日志存储在 Redis 的HashSet 类型中。

为了跟踪用户索引的字段,您还可以添加一个“schema”对象,该对象使用 Redis 的List 类型

结论:使用 RediSearch 穿透混乱

云等数字创新爆炸带来的一个缺点是,监控 IT 基础设施可能变得复杂。应用程序可能散布在不同的位置,这使得将所有日志拉到一个位置变得很困难。

但是,通过使用 Redis,此过程的单调性将被消除,使您可以轻松地搜索、收集和存储日志。如果您想详细了解此应用程序的开发方式,请查看 Alexis 的YouTube 视频

我们还有许多由像 Alex 这样才华横溢的程序员开发的创新应用程序,它们已发布在我们的 Launchpad 上。查看它,获得灵感,并看看使用 Redis 可以实现哪些创新。

谁构建了这个应用程序?

Alexis Gardin

Alexis 是一位创新型软件工程师,目前在 Zendoc 工作。前往他的 GitHub 页面查看他参与的其他项目。