# 控制台

> 正在运行的程序的实时诊断流：内存里 500 条条目，可按级别、功能区和组件搜索与筛选，配有详细捕获和排查用的技术支持包。

[事件](events)的控制台标签页（`/events?view=console`）是全程序范围的诊断流：每一个子系统（应用、内嵌的 OPC UA 服务器、驱动、Logic、Process、仪表画面、代理访问和授权）都往同一条流里写，控制台实时显示它最近的 500 条条目。它排在最后一个标签页，因为它是最后才读的技术细节，读在那些有类型的时间线之后。看到它需要**查看事件**；导出行或者创建技术支持包需要**导出/导入历史**；开始或停止一次详细捕获需要**更改设置**。

## 这条流和它的缓冲区 {#the-feed-and-its-buffer}

控制台是这个进程唯一那条诊断流的一个投影，保存在内存里：最新的 500 条条目，由显示它的每一个界面共用（工作站自己的窗口、每一个远程浏览器、每一个标签页）。新条目进来时旧的就滚出去；这个标签页上的东西没有一样能挺过重启。同一条流还写进一个按天滚动的文件，也就是工作站数据文件夹里的 `logs\ganter-lab-<date>.log`（保留 30 天，每个文件 50 MB），技术支持包收集的就是它。

每一条条目在到达控制台或文件之前都会被清洗过：URL 里的密码、令牌、密钥和凭据换成 “[REDACTED]”，协议载荷（原始字节、JSON 或 XML 文档）换成 “[PROTOCOL PAYLOAD OMITTED]”，消息在 16 KB 处、异常在 64 KB 处被截断，并写上 “…[TRUNCATED]”。只有信息级别及以上的条目进入这条流；详尽的细节，以及产生方标为诊断细节的条目（原始的、重复的协议往来），只进入详细捕获文件。反复出现的通信故障会被它们的产生方合并成一行首次故障、若干条定期摘要和一行恢复，因此一台坏掉的设备不会把列表淹掉。

## 一条条目 {#an-entry}

每一行显示当天的时间、一个按级别取色的圆点（提示用中性色，警告用警示色调，错误用严重色调）、“功能区 · 组件”和消息。行右边缘的**复制该行**按钮复制“完整时间戳 [级别] 功能区 · 组件 消息”，操作记录条答“该行已复制到剪贴板。”（在不肯给剪贴板的浏览器上则答“剪贴板拒绝了这次复制。”）。点这一行会选中它并打开详情面板。

## 搜索 {#search}

“搜索消息和全部可用的上下文”那个输入框边输入边筛选，没有延迟。只要文字（不区分大小写）包含在下列任意一项里，这一行就算命中：消息、功能区、组件、日志器、设备、点位、操作、客户端、会话、单元、流程、运行 id，或者细节文字（一个异常和它的调用栈）。

## 级别筛选项 {#the-level-chips}

三个筛选项，**提示**、**警告**和**错误**，各是一个开关，带一个按级别取色的圆点和一个实时计数。打开的筛选项显示它那个级别；点它就把那个级别隐藏起来。计数是在当前的搜索、功能区和组件条件下，这个筛选项能露出多少条条目：它遵守这三个筛选条件，而不理会级别开关本身，因此被隐藏的级别仍然报出它藏了多少。三个一开始都是打开的。

没有调试筛选项：低于信息级别的条目根本不进入这条流，因此给它们做一个筛选项永远只会写 0，也藏不住任何东西。详尽的细节要到详细捕获文件里去找。

## 功能区和组件两个下拉框 {#the-area-and-component-lists}

| 下拉框 | 取值 | 做什么 |
| --- | --- | --- |
| 功能区 | **全部功能区**（默认），然后每个子系统一项：**App**、**Server**、**Connector**、**Logic**、**Process**、**Dashboard**、**Agent**、**Licensing** | 只保留一个子系统的条目。 |
| 组件 | **全部组件**（默认），然后是所选功能区在缓冲区里出现过的每一个组件，按字母排序 | 只保留功能区里某一个具体产生方的条目（一个驱动、一个工作线程、OPC UA SDK）。 |

| 功能区 | 谁往那里写 |
| --- | --- |
| App | 应用的生命周期：桌面宿主、日志、崩溃和更新处理、事件日志自己的故障，以及没有指明别的功能区的一切。 |
| Server | 内嵌的 OPC UA 服务器和它的 SDK。 |
| Connector | 设备通信：连接器、驱动、点位的读取和写入。 |
| Logic | 变量、动作、触发器、报警和写入出口。 |
| Process | 模型、配方、判定、事件条目、联锁监视器和运行记录的生命周期。 |
| Dashboard | 在仪表画面上按下的命令，以及供应它们的内嵌 Web 宿主。 |
| Agent | 一个 AI 代理通过 MCP 做了什么。 |
| Licensing | 工作站的许可证和未获许可证时的运行时限时器。 |

你选中的那个组件会一直留在下拉框里，即使带着它的条目已经全部滚出缓冲区，这样一来，盯着某一个驱动的筛选条件就不会被悄悄放宽到所有组件；那时列表为空才是诚实的答复。改动功能区会为那个功能区重建组件下拉框。

## 跟随末尾 {#following-the-tail}

**跟随末尾（自动滚动）**打开时（默认如此），列表会随着流的增长贴住最新的那条条目。往上滚就把它关掉，这样在条目不断到来的时候你也能读点东西；滚回到离底部 24 像素以内又把它打开。它关着的时候，会有一个“N 条新条目”（或者“1 条新条目”）的小药丸，数的是符合当前筛选条件的到达条目；点它或者点工具栏上的按钮，就跳回末尾。切到别的标签页再切回来，跟随状态还在，原本打开的话还会跳到末尾。

## 工具栏 {#the-toolbar}

| 命令 | 做什么 | 什么时候变灰 | 什么时候不画出来 |
| --- | --- | --- | --- |
| 开始捕获 / 停止捕获 | 开始一次至多 15 分钟的详细诊断捕获，或者停掉正在进行的那一次（见下）。 | 正在应用这个开关时 | 角色没有更改设置 |
| 在新窗口中打开 | 在新的浏览器标签页或窗口里打开事件页面，工作站自己的窗口上和远程浏览器上都可以，这样就能把这条流放在第二块显示器上。新窗口打开在控制台标签页，也就是这个按钮所在的那一个。会拦截弹出窗口的浏览器什么也不会打开，操作记录条会说明这件事：“浏览器不肯打开第二个窗口。请为本工作站允许弹出窗口后重试。” | | |
| 自动换行 | 把长消息折成几行，而不是把它裁掉。 | | |
| 跟随末尾（自动滚动） | 打开跟随（跳到末尾并清掉新条目计数）或者关掉它。 | | |
| 清空视图 | 一次性清空每一个界面上内存里的这条流：工作站的窗口、每一个远程浏览器和每一个标签页看到的都是同一个空列表。操作记录条答“已为整个工作站清空控制台视图。”滚动日志文件、捕获文件和事件日志都没有被动过。 | 运行时已停止：按钮变灰，悬停提示说明工作站此刻不接受改动 | 角色没有更改设置 |
| 导出 | 打开菜单：**导出为 CSV…**、**导出为 JSON…**，一条分隔线之后是**创建技术支持包…**。 | 有技术支持包正在打包时，创建技术支持包是灰的 | 角色没有导出/导入历史 |

## 详细捕获 {#detailed-capture}

**开始捕获**把整个进程提到详尽日志级别，至多十五分钟，并把一切都写进它自己的一个捕获文件，详尽的细节和协议往来也在内：工作站数据文件夹里的 `logs\diagnostic-captures\ganter-temporary-diagnostic-<stamp>-<id>.log`（一次会话在 25 MB 处滚动，保留两个文件；最近的十次会话会被保留）。那条 500 条的流和平常的滚动文件保持原样：没有额外的东西进到它们里面。

按钮随后写着**停止捕获**；它的悬停提示写“提前停止详细诊断捕获。开始于 hh:mm:ss。”捕获会在倒计时归零、你按下按钮，或者应用关闭时自己停下来。两端都会在操作记录条上播报（“详细捕获已开始，最长 15 分钟。”“详细捕获已停止。”），并在 App · Diagnostic capture 下留下控制台记录。

这个捕获是一个工作站范围的开关：每一条电路看到的是同一个倒计时，而且它比按下它的那个窗口活得更久。这就是它需要**更改设置**的原因：没有这项权限的角色看不到那个按钮，即便有按压绕过界面到了处理程序，也会答“启动或停止详细捕获需要‘更改设置’权限。”捕获在做什么则不受任何权限限制：录制期间，按钮旁边有一处显示，带一个录制圆点、“详细捕获进行中”和它剩下的时间（mm:ss，从 15:00 开始），每一个角色都读得到；没有在录制时，同一处显示只留给屏幕阅读器，读出“详细捕获未进行”。

## 创建技术支持包 {#create-support-package}

导出菜单的最后一项打一个 zip 交给技术支持，并通过浏览器下载下来；操作记录条答“技术支持包已创建：”再跟上文件名（`ganter-support-<stamp>-<id>.zip`），或者答“技术支持包创建失败：”再跟上原因。它和每一次导出一样需要**导出/导入历史**。包里有：

| 条目 | 内容 |
| --- | --- |
| `recent-console.json` | 内存里那条流的条目，以结构化记录的形式（每一条条目的每一个字段，不是筛选之后的视图）。 |
| `logs/ganter-lab-*.log` | 平常的滚动日志文件。 |
| `diagnostic-captures/ganter-temporary-diagnostic-*.log` | 详细捕获文件。 |
| `manifest.json` | 它是什么时候生成的、捕获状态（进行中、开始、结束、已停止、文件名）、纳入了多少条流条目、各项上限、纳入文件的清单和它们的大小，以及每一处省略和它的原因。 |

文件按最新优先纳入，并有一个预算：至多 8 个文件，每个至多 24 MB，合计至多 96 MB；超过自己那一份的文件只纳入尾部（它最新的那些字节），清单里会写明这一点。zip 在系统的临时文件夹里打好，下载送出之后就删掉。

## 详情面板 {#the-detail-panel}

选中一条条目会打开面板，里面是级别标记、“功能区 · 组件”和一个关闭的叉，然后是：

| 小节 | 字段 | 内容 |
| --- | --- | --- |
| 标题 | 时间戳 | 精确到毫秒的完整日期和时间。 |
| 标题 | 消息 | 那一行消息。 |
| 上下文 | 功能区 | 子系统。 |
| 上下文 | 组件 | 具体的产生方。 |
| 上下文 | 记录器名称 | 这条条目背后的日志分类，知道时才显示。 |
| 上下文 | 级别 | 该级别筛选项上的那个词：提示、警告或错误。调试级别从不出现，因为低于信息级别的条目根本不进入这条流。 |
| 上下文 | 设备、点位、操作、客户端、会话、单元、流程、运行 | 每一项只有条目带着它时才显示：一行连接器记录所指的设备和点位、正在被诊断的操作（一次读取、一次写入、一次连接）、OPC 或协议的客户端和会话，以及 Process 的单元、流程和运行 id。 |
| 详细信息 | | 条目带有异常时，异常的文本和调用栈。 |

底部的**复制条目**把整个面板复制成文本（标题那一行、消息、每个上下文字段一行“标签：值”，然后是细节那一块），并答“该条目已复制到剪贴板。”条目滚出缓冲区时选择会被丢弃，清空视图也会把它清掉。

## 导出 {#export}

**导出为 CSV…**和**导出为 JSON…**把当前可见的那些行（筛选之后的视图）写成浏览器下载，文件名是 `app-logs-<yyyyMMdd-HHmmss>.csv` 或 `.json`；操作记录条答“已导出 N 行到”再跟上文件名。两者带着同样的列，顺序如下：`Timestamp`（CSV 里表头是“Timestamp (UTC-03:00)”，时间基准只声明一次）、`Severity`、`Area`、`Component`、`Logger`、`Device`、`Tag`、`Operation`、`Client`、`Session`、`Unit`、`Procedure`、`RunId`、`Message` 和 `Exception`。JSON 是一个信封，`{ "TimeZone": "...", "Entries": [ ... ] }`。以 `=`、`+`、`-` 或 `@` 开头的 CSV 单元格前面会加一个撇号，好让电子表格把它当文本来读。

## 状态行 {#the-status-line}

“显示 N 条 · 缓冲区 M 条”，有任何搜索、功能区、组件或级别筛选生效时后面跟着“ · 已筛选”；右边在有条目被选中时写“已选 1 条”，并写“更新于 hh:mm:ss”，也就是缓冲区里最新一条条目的时间，页面一打开就有。什么都不匹配时，列表写着“没有条目符合当前的筛选条件。调整级别、功能区、组件或搜索词即可看到更多。”

## 控制台不做的事 {#what-console-does-not-do}

- 它不是记录：它在内存里保存 500 条条目，重启即失，清空视图还会为所有人把它清空。持久的记录是那三条时间线，以及磁盘上的滚动日志文件。
- 它不显示低于信息级别的任何东西，也从不显示协议细节；那正是详细捕获的用处。
- 清空视图不删除任何文件、也不删除日志里的任何一行，运行时停止期间它会被拒绝，而且它和旁边的导出、开始捕获一样，只对可以更改设置的角色画出来。
- 详细捕获无法延长到十五分钟以上；再开一次就是了。
- 导出写的是可见的东西，从不写磁盘上的那些文件；文件要离开工作站，走的是技术支持包。
- 打开了日志范围的代理，通过 `logs_read` 读同一条流（见[工具](agent-tools)）；它清空不了它。
