log-stream.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. var logSource = null;
  2. var currentUnit = null;
  3. var logPid = 0;
  4. var MAX_LOG_LINES = 800;
  5. var MIN_LOG_LINES = 500;
  6. var logLines = [];
  7. function escapeHtml(s) {
  8. return s
  9. .replace(/&/g, "&")
  10. .replace(/</g, "&lt;")
  11. .replace(/>/g, "&gt;")
  12. .replace(/"/g, "&quot;")
  13. .replace(/'/g, "&#39;");
  14. }
  15. function colorLog(line) {
  16. var s = escapeHtml(line);
  17. var levels = [
  18. ["TRACE", "log-trace"],
  19. ["DEBUG", "log-debug"],
  20. ["INFO", "log-info"],
  21. ["WARN", "log-warn"],
  22. ["WARNING", "log-warn"],
  23. ["ERROR", "log-error"],
  24. ["FATAL", "log-fatal"],
  25. ["PANIC", "log-panic"],
  26. ];
  27. for (var i = 0; i < levels.length; i++) {
  28. var lvl = levels[i][0],
  29. cls = levels[i][1];
  30. if (s.includes(lvl) || s.includes(lvl.toLowerCase())) {
  31. return '<span class="' + cls + '">' + s + "</span>";
  32. }
  33. }
  34. return '<span class="log-default">' + s + "</span>";
  35. }
  36. function connectLog(unit, target) {
  37. var el = typeof target === "string" ? document.getElementById(target) : target;
  38. if (!el) {
  39. console.error("[log-stream] target element not found");
  40. return;
  41. }
  42. if (currentUnit === unit && logSource && logSource.readyState !== EventSource.CLOSED) {
  43. return;
  44. }
  45. disconnectLog();
  46. currentUnit = unit;
  47. logLines = [];
  48. el.textContent = "";
  49. var source = new EventSource("/api/service/log?unit=" + encodeURIComponent(unit));
  50. logSource = source;
  51. source.onmessage = function (e) {
  52. if (source !== logSource) {
  53. return;
  54. }
  55. logLines.push(e.data);
  56. if (logLines.length > MAX_LOG_LINES) {
  57. logLines.shift();
  58. }
  59. var line = document.createElement("div");
  60. line.innerHTML = colorLog(e.data);
  61. var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 20;
  62. el.appendChild(line);
  63. if (el.childElementCount > MAX_LOG_LINES) {
  64. while (el.childElementCount > MIN_LOG_LINES) {
  65. el.firstElementChild.remove();
  66. }
  67. }
  68. if (atBottom) {
  69. requestAnimationFrame(function () {
  70. el.scrollTop = el.scrollHeight;
  71. });
  72. }
  73. };
  74. source.addEventListener("pid", function (e) {
  75. if (source !== logSource) {
  76. return;
  77. }
  78. logPid = Number(e.data);
  79. });
  80. source.onerror = function () {
  81. if (source !== logSource) {
  82. return;
  83. }
  84. console.warn("[log-stream] error unit=" + unit + ", state=" + source.readyState);
  85. };
  86. }
  87. function disconnectLog() {
  88. if (logSource) {
  89. logSource.close();
  90. logSource = null;
  91. }
  92. if (logPid > 0) {
  93. fetch("/api/service/log/close?pid=" + logPid).catch(function () {});
  94. logPid = 0;
  95. }
  96. currentUnit = null;
  97. logLines = [];
  98. }
  99. function getCurrentUnit() {
  100. return currentUnit;
  101. }
  102. function clearLog(target) {
  103. logLines = [];
  104. var el = typeof target === "string" ? document.getElementById(target) : target;
  105. if (el) {
  106. // replaceChildren 兼容兜底 (Chrome < 86 / Safari < 14)
  107. if (el.replaceChildren) {
  108. el.replaceChildren();
  109. } else {
  110. while (el.firstChild) {
  111. el.removeChild(el.firstChild);
  112. }
  113. }
  114. el.scrollTop = 0;
  115. }
  116. }
  117. function copyLog(target) {
  118. var el = typeof target === "string" ? document.getElementById(target) : target;
  119. var text = el ? el.textContent : logLines.join("\n");
  120. if (navigator.clipboard) {
  121. navigator.clipboard
  122. .writeText(text)
  123. .then(function () {
  124. return alert("复制成功");
  125. })
  126. .catch(function () {
  127. return alert("复制失败");
  128. });
  129. } else {
  130. var ta = document.createElement("textarea");
  131. ta.value = text;
  132. document.body.appendChild(ta);
  133. ta.select();
  134. document.execCommand("copy");
  135. ta.remove();
  136. alert("复制成功");
  137. }
  138. }
  139. ["beforeunload", "pagehide"].forEach(function (e) {
  140. return window.addEventListener(e, disconnectLog);
  141. });