| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- let logSource = null;
- let currentUnit = null;
- const MAX_LOG_LINES = 800;
- const MIN_LOG_LINES = 500;
- let logLines = [];
- function escapeHtml(s) {
- return s
- .replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">");
- }
- function colorLog(line) {
- const s = escapeHtml(line);
- const levels = [
- ["TRACE", "log-trace"],
- ["DEBUG", "log-debug"],
- ["INFO", "log-info"],
- ["WARN", "log-warn"],
- ["WARNING", "log-warn"],
- ["ERROR", "log-error"],
- ["FATAL", "log-fatal"],
- ["PANIC", "log-panic"]
- ];
- for (const [lvl, cls] of levels) {
- if (s.includes(lvl) || s.includes(lvl.toLowerCase())) {
- return `<span class="${cls}">${s}</span>`;
- }
- }
- return `<span class="log-default">${s}</span>`;
- }
- function connectLog(unit, target) {
- const el = typeof target === "string"
- ? document.getElementById(target)
- : target;
- if (!el) {
- console.error("[log-stream] target element not found");
- return;
- }
- if (currentUnit === unit && logSource &&
- logSource.readyState !== EventSource.CLOSED) {
- return;
- }
- disconnectLog();
- currentUnit = unit;
- logLines = [];
- el.textContent = "";
- const source = new EventSource(
- `/api/service/log?unit=${encodeURIComponent(unit)}`
- );
- logSource = source;
- source.onmessage = (e) => {
- if (source !== logSource) {
- return;
- }
- logLines.push(e.data);
- if (logLines.length > MAX_LOG_LINES) {
- logLines.shift();
- }
- const line = document.createElement("div");
- line.innerHTML = colorLog(e.data);
- const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 20;
- el.appendChild(line);
- if (el.childElementCount > MAX_LOG_LINES) {
- while (el.childElementCount > MIN_LOG_LINES) {
- el.firstElementChild.remove();
- }
- }
- if (atBottom) {
- requestAnimationFrame(() => {
- el.scrollTop = el.scrollHeight;
- });
- }
- };
- source.onerror = () => {
- if (source !== logSource) {
- return;
- }
- console.warn(
- `[log-stream] error unit=${unit}, state=${source.readyState}`
- );
- };
- }
- function disconnectLog() {
- if (logSource) {
- logSource.close();
- logSource = null;
- }
- currentUnit = null;
- logLines = [];
- }
- function getCurrentUnit() {
- return currentUnit;
- }
- function clearLog(target) {
- logLines = [];
- const el = typeof target === "string"
- ? document.getElementById(target)
- : target;
- if (el) {
- el.replaceChildren();
- el.scrollTop = 0;
- }
- }
- function copyLog(target) {
- const text = logLines.join("\n");
- if (navigator.clipboard) {
- navigator.clipboard.writeText(text)
- .then(() => alert("复制成功"))
- .catch(() => alert("复制失败"));
- } else {
- const ta = document.createElement("textarea");
- ta.value = text;
- document.body.appendChild(ta);
- ta.select();
- document.execCommand("copy");
- ta.remove();
- alert("复制成功");
- }
- }
|