写点什么

揭秘如何用 Monaco Editor 打造功能强大的日志查看器

作者:袋鼠云数栈
  • 2025-01-03
    浙江
  • 本文字数:3808 字

    阅读完需:约 12 分钟

揭秘如何用Monaco Editor打造功能强大的日志查看器

Monaco Editor 是一个基于浏览器的代码编辑器,由 Microsoft 开发,是 Visual Studio Code 的核心编辑器组件。为用户提供了一个功能丰富、性能优异的代码编辑环境,常用于 web 应用。 下面本文将从 Monaco Editor 的使用方法、使用逻辑作为切入点,讲述在网页中如何通过 Monaco Editor 实现日志查看器,包括实时日志和普通日志的展示、如何在 Monaco Editor 中支持 a 元素功能的相关内容。从理论到实践,涵盖了从基础概念到具体实现的全过程。


01 前言

在 Web IDE 中,控制台中展示日志是至关重要的功能。Monaco Editor 作为一个强大的代码编辑器,提供了丰富的功能和灵活的 API ,支持为内容进行“装饰”,非常适合用来构建日志展示器。如下图:除了实时日志外,还有一些需要查看历史日志的场景。如下图:

02 什么是 Monarch

Monarch 是 Monaco Editor 自带的一个语法高亮库,通过它,我们可以用类似 JSON 的语法来实现自定义语言的语法高亮功能。这里不做过多的介绍,只介绍在本文中使用到的那部分内容。

一个语言定义基本上就是描述语言的各种属性的 JSON 值,部分通用属性如下:

  • tokenizer (必填项,带状态的对象)这个定义了 tokenization 的规则。Monaco Editor 中用于定义语言语法高亮和解析的一个核心组件。它的主要功能是将输入的代码文本分解成一个个的 token,以便于编辑器能够根据这些 token 进行语法高亮、错误检查和其他编辑功能。

  • ignoreCase (可选项=false,布尔值)语言是否大小写不敏感?tokenizer(分词器)中的正则表达式使用这个属性去进行大小写(不)敏感匹配,以及 case 场景中的测试。

brackets (可选项,括号定义的数组)tokenizer 使用这个来轻松的定义大括号匹配,更多信息详见 @brackets 和 bracket 部分。每个方括号定义都是一个由 3 个元素或对象组成的数组,描述了 open 左大括号、close 右大括号和 token 令牌类。默认定义如下:


1. tokenizer

tokenizer 属性描述了如何进行词法分析,以及如何将输入转换成 token ,每个 token 都会被赋予一个 css 类名,用于在编辑器中渲染,内置的 css token 包括:

当然也可以自定义 css token,通过以下方式将自定义的 css token 注入。

一个 tokenizer 由一个描述状态的对象组成。tokenizer 的初始状态由 tokenizer 定义的第一个状态决定。这句话什么意思呢?查看下方例子,root 就是 tokenizer 定义的第一个状态,就是初始状态。同理,如果把 afterIf 和 root 两个状态调换位置,那么 afterIf 就是初始状态。

如何获取 tokenizer 定义的第一个状态呢?

在 compile 解析 setMonarchTokensProvider 传入的语言定义对象时,会将读取出来的第一个 key 作为初始状态。可能会有疑问,就一定能保证在定义对象时,写入的第一个属性,在读取时一定第一个被读出吗? 在 JavaScript 中,对象属性的顺序有一些特定的规则:

  • 整数键:如果属性名是一个整数(如 "1"、"2"等),这些属性会按照数值的升序排列。

  • 字符串键:对于非整数的字符串键,属性的顺序是按照它们被添加到对象中的顺序。

  • Symbol 键:如果属性的键是 Symbol 类型,这些属性会按照它们被添加到对象中的顺序。 因此,当使用' for...in'循环遍历对象的属性时,属性的顺序如下:

  • 首先是所有整数键,按升序排列。

  • 然后是所有字符串键,按添加顺序排列。

  • 最后是所有 Symbol 键,按添加顺序排列。 看个例子:


上述例子可以看出,“1”、“2”虽然被写在了后面,但仍然会被排序优先输出,其后才是字符串键根据添加顺序输出。所以,尽可能不要使用整数键去定义状态名。 当 tokenizer 处于某种状态时,只有那个状态的规则才能匹配。所有规则是按顺序进行匹配的,当匹配到第一个规则时,它的 action 将被用来确定 token 的类型。不会再使用后面的规则进行尝试,因此,以一种最有效的方式排列规则是很重要的。比如空格和标识符优先。 如何定义一个状态? 每个状态定义为一个用于匹配输入的规则数组,规则可以有如下形式:

  • [regex, action] {regex: regex, action: action}形式的简写。 [regex, action, next]

  • { regex: regex, action: action{ next: next} }形式的简写。



regex 是正则表达式,action 分为以下几种:

  • string { token: string } 的简写

  • [action, ..., actionN] 多个 action 组成的数组。这仅在正则表达式恰好由 N 个组(即括号部分)组成时才允许。举个例子:


  • { token: tokenClass } 这个 tokenClass 可以是内置的 css token,也可以是自定义的 token。同时,还规定了一些特殊的 token 类:

"@rematch" 备份输入并重新调用 tokenizer 。这只在状态发生变化时才有效(或者我们进入了无限的递归),所以这个通常和 next 属性一起使用。例如,当你处于特定的 tokenizer 状态,并想要在看到某些结束标记时退出,但是不想在处于该状态时使用它们,就可以使用这个。例如:

这个 language 的状态流转图是怎么样的呢?


可以看出,在定义一个状态时,应保证状态存在出口即没有定义转移到其他状态的规则),否则可能会导致死循环,不断的使用状态内的规则去匹配。 "@pop" 弹出 tokenizer 栈以返回到之前的状态。 "@push"推入当前状态,并在当前状态中继续。


$n 匹配输入的第 n 组,或者是 $0 代表这个匹配的输入。

$Sn 状态的第 n 个部分,比如,状态 @tag.foo,用 $S0 代表整个状态名(即 tag.foo ),$S1 返回 tag,$S2 返回 foo 。


03 Monaco Editor 让日志实现不同主题

由于 Monaco Editor 的使用不是本文的重点,后续就不再展开。接下来我们主要为大家介绍如何利用 Monaco Editor 实现日志查看器让不同的类型的日志有不同的高亮主题。

1、实时日志

实时日志中,存在不同的日志类型,如:info、error、warning 等。


根据日志方法可以看出,每条日志都是[xx:xx:xx]开头,紧跟着<日志类型>,后面的是日志内容。(日志类型:info 、success、error、warning。) 注册一个自定义语言 realTimeLog 作为实时日志的一个 language。 这里规则也很简单,在 root 中设置了两条识别规则,分别是匹配日志日期和日志类型。在匹配到对应的日志类型后,给匹配到的内容打上 token,然后通过 next 携带匹配的引用标识( $1 表示正则分组中的第 1 组)进入下一个状态 consoleLog,在状态 consoleLog 中,匹配日志内容,并打上 token,直到遇见终止条件(日志日期)。



状态流转图:


2、普通日志

普通日志与实时日志有些许不同,他的日志类型是不展示出来的,没有一个起始/结束标识符供 Monarch 高亮规则匹配。所以需要一个在文本中不展示,又能作为起始/结束的标识符。

也确实存在这么一个东西,不占宽度,又能被匹配——“零宽字符”。 零宽字符(Zero Width Characters)是指在文本中占用零宽度的字符,通常用于特定的文本处理或编码目的。它们在视觉上不可见,但在程序处理中可能会产生影响。 利用零宽字符创建不同日志类型的标识。

之后的编写语法高亮规则,与任务控制台相同。


状态流转图:


04 在 Monaco Editor 中支持 a 元素

Monaco Editor 本身是不支持在内容中插入 HTML 元素的,原生只支持对链接进行高亮,并且支持 cmd + 点击打开链接。但仍可能会存在需要实现类似 a 元素的效果。 另辟蹊径,查找 Monaco Editor 的 API 后,linkProvider 也许可以大致满足,但仍有不足。 以下是介绍: 在 Monaco Editor 中,linkProvider 是一个用于提供链接功能的接口。它允许开发者为编辑器中的特定文本或代码片段提供链接,当用户悬停或点击这些链接时,可以执行特定的操作,比如打开文档、跳转到定义等。 具体用法:

它是针对已注册的语言进行注册,不会影响到其他语言。在文本内容发生变化时就会触发 provideLinks。 根据这个 API 想到一个思路:

  • 在生成文本时,在需要展示为 a 元素的地方使用 #link#${JSON.stringify(attrs)}#link#包裹,attrs 是一个对象,其中包含了 a 元素的 attribute。 

  • 在文本内容传递给 Monaco Editor 之前,解析文本的内容,利用正则将 a 元素标记匹配出来,使用 attrs 的链接文本替换标记文本,并记录替换后链接文本在文本内容中的索引位置。利用 Monaco Editor 的 getPositionAt 获取链接文本在编辑器中的位置(起始/结束行列信息),生成 Range。 

  • 使用一个容器收集对应的日志中的 Link 信息。在通过 linkProvider 将编辑器中对应的链接文本识别为链接高亮。

  •  给 editor 实例绑定点击事件 onMouseDown,如果点击的内容位置在收集的 Link 中时,触发对外提供的自定义链接点击函数。

根据这一思路进行实现:

  •  生成 a 元素标记。

  • 解析文本内容



  • 使用一个容器存储解析出来的 link


  • 利用存储的 links 注册 LinkProvider


  • 绑定自定义事件

在点击 editor 中的内容时都会触发 onMouseDown,在其中可以获取当前点击位置的 Range 信息,循环遍历收集的所有 Link,判断当前点击位置的 Range 是否在其中。containsRange 方法可以判断一个 Range 是否在另一个 Range 中。


《数据资产管理白皮书》下载地址https://www.dtstack.com/resources/1073/?src=szsm

《行业指标体系白皮书》下载地址https://www.dtstack.com/resources/1057/?src=szsm

《数据治理行业实践白皮书》下载地址https://www.dtstack.com/resources/1001/?src=szsm

《数栈 V6.0 产品白皮书》下载地址https://www.dtstack.com/resources/1004/?src=szsm

想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szsm

同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术群」,交流最新开源技术信息,群号码:30537511,项目地址:https://github.com/DTStack

用户头像

还未添加个人签名 2021-05-06 加入

还未添加个人简介

评论

发布
暂无评论
揭秘如何用Monaco Editor打造功能强大的日志查看器_袋鼠云数栈_InfoQ写作社区