Skip to content

Commit

Permalink
feat(log): Add Highlighting to Logs (#7707)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengkunwang223 authored Jan 13, 2025
1 parent 2185c3d commit e2354b8
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 17 deletions.
2 changes: 1 addition & 1 deletion agent/app/service/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ func collectLogs(params dto.StreamLog, messageChan chan<- string, errorChan chan
if params.Follow {
cmdArgs = append(cmdArgs, "-f")
}
if params.Tail != "all" {
if params.Tail != "0" {
cmdArgs = append(cmdArgs, "--tail", params.Tail)
}
if params.Since != "all" {
Expand Down
41 changes: 32 additions & 9 deletions frontend/src/components/container-log/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,26 @@
{{ $t('commons.button.clean') }}
</el-button>
</div>
<div class="log-container" ref="logContainer">
<!-- <div class="log-container" ref="logContainer">
<DynamicScroller :items="logs" :min-item-size="32" v-if="logs.length">
<template #default="{ item, active }">
<DynamicScrollerItem
:item="item"
:active="active"
class="msgBox"
:size-dependencies="[item]"
:data-index="item"
>
<span class="log-item">{{ item }}</span>
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item]" :data-index="item">
<hightlight :log="item" type="container"></hightlight>
</DynamicScrollerItem>
</template>
</DynamicScroller>
</div> -->

<div class="log-container" ref="logContainer">
<div class="log-spacer" :style="{ height: `${totalHeight}px` }"></div>
<div
v-for="(log, index) in visibleLogs"
:key="startIndex + index"
class="log-item"
:style="{ top: `${(startIndex + index) * logHeight}px` }"
>
<hightlight :log="log" type="container"></hightlight>
</div>
</div>
</template>

Expand All @@ -48,6 +54,7 @@ import { dateFormatForName } from '@/utils/util';
import { onUnmounted, reactive, ref } from 'vue';
import { ElMessageBox } from 'element-plus';
import { MsgError, MsgSuccess } from '@/utils/message';
import hightlight from '@/components/hightlight/index.vue';
const props = defineProps({
container: {
Expand All @@ -71,6 +78,15 @@ const logSearch = reactive({
tail: 100,
compose: '',
});
const logHeight = 20;
const logCount = ref(0);
const totalHeight = computed(() => logHeight * logCount.value);
const startIndex = ref(0);
const containerHeight = ref(500);
const visibleCount = computed(() => Math.ceil(containerHeight.value / logHeight));
const visibleLogs = computed(() => {
return logs.value.slice(startIndex.value, startIndex.value + visibleCount.value);
});
const timeOptions = ref([
{ label: i18n.global.t('container.all'), value: 'all' },
Expand Down Expand Up @@ -178,6 +194,13 @@ onMounted(() => {
logSearch.mode = 'all';
logSearch.isWatch = true;
nextTick(() => {
if (logContainer.value) {
logContainer.value.scrollTop = totalHeight.value;
containerHeight.value = logContainer.value.getBoundingClientRect().height;
}
});
searchLogs();
});
</script>
Expand Down
220 changes: 220 additions & 0 deletions frontend/src/components/hightlight/index.vue
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>
12 changes: 10 additions & 2 deletions frontend/src/components/log-file/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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 {
Expand All @@ -44,6 +45,7 @@ interface LogProps {
name?: string;
tail?: boolean;
taskID?: string;
colorMode?: string;
}
const props = defineProps({
Expand All @@ -54,6 +56,7 @@ const props = defineProps({
type: '',
name: '',
tail: false,
colorMode: 'nginx',
}),
},
defaultButton: {
Expand Down Expand Up @@ -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;
Expand All @@ -336,4 +339,9 @@ defineExpose({ changeTail, onDownload, clearLog });
box-sizing: border-box;
white-space: nowrap;
}
.log-content {
font-size: 14px;
line-height: 20px;
}
</style>
1 change: 1 addition & 0 deletions frontend/src/components/task-log/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const config = reactive({
resourceID: 0,
taskType: '',
tail: true,
colorMode: 'task',
});
const open = ref(false);
const showTail = ref(true);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/views/log/system/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const hasContent = ref(false);
const logConfig = reactive({
type: 'system',
name: '',
colorMode: 'system',
});
const showLog = ref(false);
Expand Down
1 change: 1 addition & 0 deletions frontend/src/views/log/website/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const logConfig = reactive({
type: 'website',
id: undefined,
name: 'access.log',
colorMode: 'nginx',
});
const showLog = ref(false);
const loading = ref(false);
Expand Down
Loading

0 comments on commit e2354b8

Please sign in to comment.