-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(log): Add Highlighting to Logs #7707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| <template> | ||
| <span v-for="(token, index) in tokens" :key="index" :class="['token', token.type]" :style="{ color: token.color }"> | ||
| {{ token.text }} | ||
| </span> | ||
| </template> | ||
| <script setup lang="ts"> | ||
| interface TokenRule { | ||
| type: string; | ||
| pattern: RegExp; | ||
| color: string; | ||
| } | ||
| interface Token { | ||
| text: string; | ||
| type: string; | ||
| color: string; | ||
| } | ||
| const props = defineProps<{ | ||
| log: string; | ||
| type: string; | ||
| }>(); | ||
| let rules = ref<TokenRule[]>([]); | ||
| const nginxRules: TokenRule[] = [ | ||
| { | ||
| type: 'log-level', | ||
| pattern: /\[(error|warn|notice|info|debug)\]/gi, | ||
| color: '#E74C3C', | ||
| }, | ||
| { | ||
| type: 'path', | ||
| pattern: | ||
| /(?:(?<=GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+|(?<=open\(\s*")|(?<="\s*))(\/[^"\s]+(?:\.\w+)?(?:\?\w+=\w+)?)/g, | ||
| color: '#B87A2B', | ||
| }, | ||
| { | ||
| type: 'http-method', | ||
| pattern: /(?<=")(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)(?=\s)/g, | ||
| color: '#27AE60', | ||
| }, | ||
| { | ||
| type: 'status-success', | ||
| pattern: /\s(2\d{2})\s/g, | ||
| color: '#2ECC71', | ||
| }, | ||
| { | ||
| type: 'status-error', | ||
| pattern: /\s([45]\d{2})\s/g, | ||
| color: '#E74C3C', | ||
| }, | ||
| { | ||
| type: 'process-info', | ||
| pattern: /\d+#\d+/g, | ||
| color: '#7F8C8D', | ||
| }, | ||
| ]; | ||
| const systemRules: TokenRule[] = [ | ||
| { | ||
| type: 'log-error', | ||
| pattern: /\[(ERROR|WARN|FATAL)\]/g, | ||
| color: '#E74C3C', | ||
| }, | ||
| { | ||
| type: 'log-normal', | ||
| pattern: /\[(INFO|DEBUG)\]/g, | ||
| color: '#8B8B8B', | ||
| }, | ||
| { | ||
| type: 'timestamp', | ||
| pattern: /\[\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\]/g, | ||
| color: '#8B8B8B', | ||
| }, | ||
| { | ||
| type: 'bracket-text', | ||
| pattern: /\[([^\]]+)\]/g, | ||
| color: '#B87A2B', | ||
| }, | ||
| { | ||
| type: 'referrer-ua', | ||
| pattern: /https?:\/\/(?:[\w-]+\.)+[\w-]+(?::\d+)?(?:\/[^\s\]\)"]*)?/g, | ||
| color: '#786C88', | ||
| }, | ||
| ]; | ||
| const taskRules: TokenRule[] = [ | ||
| { | ||
| type: 'bracket-text', | ||
| pattern: /\[([^\]]+)\]/g, | ||
| color: '#B87A2B', | ||
| }, | ||
| ]; | ||
| const defaultRules: TokenRule[] = [ | ||
| { | ||
| type: 'timestamp', | ||
| pattern: | ||
| /(?:\[\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+-]\d{4}\]|\d{4}[-\/]\d{2}[-\/]\d{2}\s\d{2}:\d{2}:\d{2})/g, | ||
| color: '#8B8B8B', | ||
| }, | ||
| { | ||
| type: 'referrer-ua', | ||
| pattern: /"(?:https?:\/\/[^"]+|Mozilla[^"]+|curl[^"]+)"/g, | ||
| color: '#786C88', | ||
| }, | ||
| { | ||
| type: 'ip', | ||
| pattern: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, | ||
| color: '#4A90E2', | ||
| }, | ||
| { | ||
| type: 'server-host', | ||
| pattern: /(?:server|host):\s*[^,\s]+/g, | ||
| color: '#5D6D7E', | ||
| }, | ||
| ]; | ||
| const containerRules: TokenRule[] = [ | ||
| { | ||
| type: 'timestamp', | ||
| pattern: /\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}/g, | ||
| color: '#8B8B8B', | ||
| }, | ||
| { | ||
| type: 'bracket-text', | ||
| pattern: /\[([^\]]+)\]/g, | ||
| color: '#B87A2B', | ||
| }, | ||
| ]; | ||
| function tokenizeLog(log: string): Token[] { | ||
| const tokens: Token[] = []; | ||
| let lastIndex = 0; | ||
| let matches: { index: number; text: string; type: string; color: string }[] = []; | ||
| rules.value.forEach((rule) => { | ||
| const regex = new RegExp(rule.pattern.source, 'g'); | ||
| let match; | ||
| while ((match = regex.exec(log)) !== null) { | ||
| matches.push({ | ||
| index: match.index, | ||
| text: match[0], | ||
| type: rule.type, | ||
| color: rule.color, | ||
| }); | ||
| } | ||
| }); | ||
| matches.sort((a, b) => a.index - b.index); | ||
| matches = matches.filter((match, index) => { | ||
| if (index === 0) return true; | ||
| const prev = matches[index - 1]; | ||
| return match.index >= prev.index + prev.text.length; | ||
| }); | ||
| matches.forEach((match) => { | ||
| if (match.index > lastIndex) { | ||
| tokens.push({ | ||
| text: log.substring(lastIndex, match.index), | ||
| type: 'plain', | ||
| color: '#666666', | ||
| }); | ||
| } | ||
| tokens.push({ | ||
| text: match.text, | ||
| type: match.type, | ||
| color: match.color, | ||
| }); | ||
| lastIndex = match.index + match.text.length; | ||
| }); | ||
| if (lastIndex < log.length) { | ||
| tokens.push({ | ||
| text: log.substring(lastIndex), | ||
| type: 'plain', | ||
| color: '#666666', | ||
| }); | ||
| } | ||
| return tokens; | ||
| } | ||
| const tokens = computed(() => tokenizeLog(props.log)); | ||
| onMounted(() => { | ||
| switch (props.type) { | ||
| case 'nginx': | ||
| rules.value = nginxRules.concat(defaultRules); | ||
| break; | ||
| case 'system': | ||
| rules.value = systemRules.concat(defaultRules); | ||
| break; | ||
| case 'container': | ||
| rules.value = containerRules.concat(defaultRules); | ||
| break; | ||
| case 'task': | ||
| rules.value = taskRules.concat(defaultRules); | ||
| break; | ||
| default: | ||
| rules.value = defaultRules; | ||
| break; | ||
| } | ||
| }); | ||
| </script> | ||
|
|
||
| <style scoped> | ||
| .token { | ||
| font-family: 'JetBrains Mono', Monaco, Menlo, Consolas, 'Courier New', monospace; | ||
| font-size: 14px; | ||
| font-weight: 500; | ||
| } | ||
| .ip { | ||
| text-decoration: underline; | ||
| text-decoration-style: dotted; | ||
| text-decoration-thickness: 1px; | ||
| } | ||
| </style> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Based on my knowledge as of September 2021, there is no evident irregularity or issue with this code snippet. The syntax and structure appear to be sound, the use of variables such as However, I would like to make some generic optimization suggestions that could potentially add value:
Remember that these are general tips based solely on past experiences and should not replace actual debugging techniques or best coding practices. It's always important to analyze code thoroughly to understand the nuances, including those hidden under comments but difficult to uncover without proper context. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ | |
| class="log-item" | ||
| :style="{ top: `${(startIndex + index) * logHeight}px` }" | ||
| > | ||
| <span>{{ log }}</span> | ||
| <hightlight :log="log" :type="config.colorMode"></hightlight> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
@@ -36,6 +36,7 @@ import { downloadFile } from '@/utils/util'; | |
| import { ReadByLine } from '@/api/modules/files'; | ||
| import { GlobalStore } from '@/store'; | ||
| import bus from '@/global/bus'; | ||
| import hightlight from '@/components/hightlight/index.vue'; | ||
| const globalStore = GlobalStore(); | ||
|
|
||
| interface LogProps { | ||
|
|
@@ -44,6 +45,7 @@ interface LogProps { | |
| name?: string; | ||
| tail?: boolean; | ||
| taskID?: string; | ||
| colorMode?: string; | ||
| } | ||
|
|
||
| const props = defineProps({ | ||
|
|
@@ -54,6 +56,7 @@ const props = defineProps({ | |
| type: '', | ||
| name: '', | ||
| tail: false, | ||
| colorMode: 'nginx', | ||
| }), | ||
| }, | ||
| defaultButton: { | ||
|
|
@@ -315,7 +318,7 @@ defineExpose({ changeTail, onDownload, clearLog }); | |
| </script> | ||
| <style lang="scss" scoped> | ||
| .log-container { | ||
| height: calc(100vh - 405px); | ||
| height: calc(100vh - 420px); | ||
| overflow-y: auto; | ||
| overflow-x: auto; | ||
| position: relative; | ||
|
|
@@ -336,4 +339,9 @@ defineExpose({ changeTail, onDownload, clearLog }); | |
| box-sizing: border-box; | ||
| white-space: nowrap; | ||
| } | ||
|
|
||
| .log-content { | ||
| font-size: 14px; | ||
| line-height: 20px; | ||
| } | ||
| </style> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There aren't any obvious issues or regularities in the given CSS code snippet. It seems to be perfectly valid and consistent in terms of its layout, formatting styles, etc. However, keep in mind that this is not an exhaustive assessment of all potential concerns with regard to style guidelines best practices and web accessibility standards. Here's what you might want to consider:
Keep refining these small details based on specific needs and industry best practices if applicable to your project scope! |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The provided changes do not introduce significant issues but suggest room for improvement and further optimizations. It would be beneficial to refactor such that it looks more consistent with modern Vue practices. Currently, the use of
hightlightcomponent does not seem necessary since it's imported from another file. Additionally, there might be unnecessary comments or redundant logic.Here is a refined version: