feat: 3.2.0. 新增重试日志详情

This commit is contained in:
xlsea 2024-03-22 17:15:07 +08:00 committed by byteblogs168
parent 21ab71e831
commit d9115e9060
6 changed files with 357 additions and 85 deletions

View File

@ -95,13 +95,15 @@ public class RetryTaskLogServiceImpl implements RetryTaskLogService {
String namespaceId = UserSessionUtils.currentUserSession().getNamespaceId();
PageDTO<RetryTaskLogMessage> pageDTO = new PageDTO<>(queryVO.getPage(), queryVO.getSize());
LambdaQueryWrapper<RetryTaskLogMessage> retryTaskLogLambdaQueryWrapper = new LambdaQueryWrapper<>();
retryTaskLogLambdaQueryWrapper.eq(RetryTaskLogMessage::getNamespaceId, namespaceId);
retryTaskLogLambdaQueryWrapper.eq(RetryTaskLogMessage::getUniqueId, queryVO.getUniqueId());
retryTaskLogLambdaQueryWrapper.eq(RetryTaskLogMessage::getGroupName, queryVO.getGroupName());
retryTaskLogLambdaQueryWrapper.orderByAsc(RetryTaskLogMessage::getId).orderByAsc(RetryTaskLogMessage::getRealTime);
LambdaQueryWrapper<RetryTaskLogMessage> wrapper = new LambdaQueryWrapper<>();
wrapper.select(RetryTaskLogMessage::getId, RetryTaskLogMessage::getLogNum);
wrapper.ge(RetryTaskLogMessage::getId, queryVO.getStartId());
wrapper.eq(RetryTaskLogMessage::getNamespaceId, namespaceId);
wrapper.eq(RetryTaskLogMessage::getUniqueId, queryVO.getUniqueId());
wrapper.eq(RetryTaskLogMessage::getGroupName, queryVO.getGroupName());
wrapper.orderByAsc(RetryTaskLogMessage::getId).orderByAsc(RetryTaskLogMessage::getRealTime);
PageDTO<RetryTaskLogMessage> selectPage = retryTaskLogMessageMapper.selectPage(pageDTO, retryTaskLogLambdaQueryWrapper.orderByDesc(RetryTaskLogMessage::getCreateDt));
PageDTO<RetryTaskLogMessage> selectPage = retryTaskLogMessageMapper.selectPage(pageDTO, wrapper.orderByDesc(RetryTaskLogMessage::getCreateDt));
List<RetryTaskLogMessage> records = selectPage.getRecords();

View File

@ -0,0 +1,234 @@
<!-- eslint-disable -->
<template>
<div class="log">
<table class="scroller">
<tbody>
<tr v-for="(log, index) in logList" :key="index">
<td class="index">
{{ index + 1 }}
</td>
<td>
<div class="content">
<div class="line">
<div class="flex">
<div class="text" style="color: #2db7f5">{{ timestampToDate(log.time_stamp) }}</div>
<div class="text" :style="{ color: LevelEnum[log.level].color }">
{{ log.level.length === 4 ? log.level + ' ' : log.level }}
</div>
<div class="text" style="color: #00a3a3">[{{ log.thread }}]</div>
<div class="text" style="color: #a771bf; font-weight: 500">{{ log.location }}</div>
<div class="text">:</div>
</div>
<div class="text" style="font-size: 16px">{{ log.message }}</div>
<div class="text" style="font-size: 16px">{{ log.throwable }}</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'Log',
components: {},
props: {
value: {
type: Array,
default: () => []
}
},
watch: {
value: {
deep: true,
immediate: true,
handler (val) {
this.logList = val
}
}
},
data () {
return {
logList: [],
indicator: <a-icon type="loading" style="font-size: 24px; color: '#d9d9d9'" spin/>,
LevelEnum: {
DEBUG: {
name: 'DEBUG',
color: '#2647cc'
},
INFO: {
name: 'INFO',
color: '#5c962c'
},
WARN: {
name: 'WARN',
color: '#da9816'
},
ERROR: {
name: 'ERROR',
color: '#dc3f41'
}
}
}
},
methods: {
timestampToDate (timestamp) {
const date = new Date(Number.parseInt(timestamp.toString()))
const year = date.getFullYear()
const month =
(date.getMonth() + 1).toString().length === 1 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1).toString()
const day = date.getDate()
const hours = date.getHours()
const minutes = date.getMinutes().toString().length === 1 ? '0' + date.getMinutes() : date.getMinutes().toString()
const seconds = date.getSeconds().toString().length === 1 ? '0' + date.getSeconds() : date.getSeconds().toString()
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${date.getMilliseconds()}`
}
}
}
</script>
<style scoped lang="less">
.log {
height: calc(100vh - 56px);
color: #abb2bf;
background-color: #282c34;
position: relative !important;
box-sizing: border-box;
display: flex !important;
flex-direction: column;
//
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
width: 6px;
background: rgba(#101F1C, 0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: rgba(144,147,153,.5);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
transition: background-color .3s;
cursor: pointer;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(144,147,153,.3);
}
.scroller {
height: 100%;
overflow: auto;
display: flex !important;
align-items: flex-start !important;
font-family: monospace;
line-height: 1.4;
position: relative;
z-index: 0;
.index{
width: 32px;
min-width: 32px;
height: 100%;
background-color: #1e1f22;
color: #7d8799;
text-align: center;
vertical-align: top;
padding-top: 4px;
font-size: 16px;
z-index: 200;
}
.gutters {
min-height: 100%;
position: sticky;
background-color: #1e1f22;
color: #7d8799;
border: none;
flex-shrink: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
inset-inline-start: 0;
z-index: 200;
.gutter-element {
height: 25px;
font-size: 14px;
padding: 0 8px 0 5px;
min-width: 20px;
text-align: right;
white-space: nowrap;
box-sizing: border-box;
color: #7d8799;
display: flex;
align-items: center;
justify-content: flex-end;
}
}
.content {
tab-size: 4;
caret-color: transparent !important;
margin: 0;
//flex-grow: 2;
//flex-shrink: 0;
// display: block;
white-space: pre;
//word-wrap: normal;
//box-sizing: border-box;
//min-height: 100%;
padding: 4px 8px;
outline: none;
color: #bcbec4;
.line {
height: 25px;
caret-color: transparent !important;
font-size: 16px;
// background-color: #6699ff0b;
display: contents;
padding: 0 2px 0 6px;
.flex{
display: flex;
align-items: center;
gap: 5px;
}
.text {
font-size: 16px;
}
}
}
}
}
/deep/ .ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
/deep/ .ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
}
/deep/ .ant-modal-body {
flex: 1;
padding: 0;
}
</style>

View File

@ -6,43 +6,17 @@
:footer="null"
title="日志详情"
@cancel="onCancel">
<div class="log">
<table class="scroller">
<tbody>
<tr v-for="(log, index) in logList" :key="index">
<td class="index">
{{ index + 1 }}
</td>
<td>
<div class="content">
<div class="line">
<div class="flex">
<div class="text" style="color: #2db7f5">{{ timestampToDate(log.time_stamp) }}</div>
<div class="text" :style="{ color: LevelEnum[log.level].color }">
{{ log.level.length === 4 ? log.level + ' ' : log.level }}
</div>
<div class="text" style="color: #00a3a3">[{{ log.thread }}]</div>
<div class="text" style="color: #a771bf; font-weight: 500">{{ log.location }}</div>
<div class="text">:</div>
</div>
<div class="text" style="font-size: 16px">{{ log.message }}</div>
<div class="text" style="font-size: 16px">{{ log.throwable }}</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<log :value="value" />
</a-modal>
</template>
<script>
import request from '@/utils/request'
import Log from '@/components/Log/index.vue'
export default {
name: 'JobBatchLog',
components: {},
components: { Log },
props: {
open: {
type: Boolean,
@ -81,26 +55,7 @@ export default {
interval: null,
startId: 0,
fromIndex: 0,
controller: new AbortController(),
indicator: <a-icon type="loading" style="font-size: 24px; color: '#d9d9d9'" spin/>,
LevelEnum: {
DEBUG: {
name: 'DEBUG',
color: '#2647cc'
},
INFO: {
name: 'INFO',
color: '#5c962c'
},
WARN: {
name: 'WARN',
color: '#da9816'
},
ERROR: {
name: 'ERROR',
color: '#dc3f41'
}
}
controller: new AbortController()
}
},
mounted () {
@ -152,17 +107,6 @@ export default {
.catch(() => {
this.finished = true
})
},
timestampToDate (timestamp) {
const date = new Date(Number.parseInt(timestamp.toString()))
const year = date.getFullYear()
const month =
(date.getMonth() + 1).toString().length === 1 ? '0' + (date.getMonth() + 1) : (date.getMonth() + 1).toString()
const day = date.getDate()
const hours = date.getHours()
const minutes = date.getMinutes().toString().length === 1 ? '0' + date.getMinutes() : date.getMinutes().toString()
const seconds = date.getSeconds().toString().length === 1 ? '0' + date.getSeconds() : date.getSeconds().toString()
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${date.getMilliseconds()}`
}
}
}

View File

@ -3,8 +3,8 @@
<page-header-wrapper @back="() => $router.replace('/retry/log/list')" style="margin: -24px -1px 0" v-if="showHeader">
<div></div>
</page-header-wrapper>
<a-card :bordered="false">
<a-descriptions title="" :column="column" bordered v-if="retryInfo !== null">
<a-card :bordered="false" :loading="loading">
<a-descriptions title="" :column="column" bordered>
<a-descriptions-item label="组名称">
{{ retryInfo.groupName }}
</a-descriptions-item>
@ -21,11 +21,11 @@
{{ retryInfo.bizNo }}
</a-descriptions-item>
<a-descriptions-item label="当前重试状态 | 数据类型">
<a-tag color="red">
<a-tag v-if="retryInfo.taskType" color="red">
{{ retryStatus[retryInfo.retryStatus] }}
</a-tag>
<a-divider type="vertical" />
<a-tag :color="taskType[retryInfo.taskType].color">
<a-tag v-if="retryInfo.taskType" :color="taskType[retryInfo.taskType].color">
{{ taskType[retryInfo.taskType].name }}
</a-tag>
</a-descriptions-item>
@ -43,19 +43,19 @@
</a-descriptions-item>
</a-descriptions>
</a-card>
<RetryTaskLogMessageList ref="retryTaskLogMessageListRef" />
<RetryTaskLogMessage :value="retryInfo" />
</div>
</template>
<script>
import { getRetryTaskLogById } from '@/api/manage'
import { STable } from '@/components'
import RetryTaskLogMessageList from '@/views/task/RetryTaskLogMessageList'
import RetryTaskLogMessage from '@/views/task/RetryTaskLogMessage'
export default {
name: 'RetryLogInfo',
components: {
RetryTaskLogMessageList,
RetryTaskLogMessage,
STable
},
props: {
@ -70,7 +70,8 @@ export default {
},
data () {
return {
retryInfo: null,
loading: true,
retryInfo: {},
retryStatus: {
'0': '处理中',
'1': '处理成功',
@ -99,10 +100,11 @@ export default {
getRetryTaskLogById(id).then(res => {
this.retryInfo = res.data
this.queryParam = {
groupName: this.retryInfo.groupName,
uniqueId: this.retryInfo.uniqueId
groupName: res.data.groupName,
uniqueId: res.data.uniqueId
}
this.$refs.retryTaskLogMessageListRef.refreshTable(this.queryParam)
}).finally(() => {
this.loading = false
})
}

View File

@ -3,7 +3,7 @@
<page-header-wrapper @back="() => $router.replace('/retry/list')" style="margin: -24px -1px 0" v-if="showHeader">
<div></div>
</page-header-wrapper>
<a-card :bordered="false" v-if="retryTaskInfo !== null">
<a-card :bordered="false" :loading="loading">
<a-descriptions title="" :column="column" bordered>
<a-descriptions-item label="组名称">
{{ retryTaskInfo.groupName }}
@ -24,11 +24,11 @@
{{ retryTaskInfo.retryCount }}
</a-descriptions-item>
<a-descriptions-item label="重试状态 | 数据类型">
<a-tag color="red">
<a-tag v-if="retryTaskInfo.retryStatus" color="red">
{{ retryStatus[retryTaskInfo.retryStatus] }}
</a-tag>
<a-divider type="vertical" />
<a-tag :color="taskType[retryTaskInfo.taskType].color">
<a-tag v-if="retryTaskInfo.retryStatus" :color="taskType[retryTaskInfo.taskType].color">
{{ taskType[retryTaskInfo.taskType].name }}
</a-tag>
</a-descriptions-item>
@ -49,18 +49,18 @@
</a-descriptions-item>
</a-descriptions>
</a-card>
<RetryTaskLogMessageList ref="retryTaskLogMessageListRef" />
<RetryTaskLogMessage :value="retryTaskInfo" />
</div>
</template>
<script>
import { getRetryTaskById } from '@/api/manage'
import RetryTaskLogMessageList from './RetryTaskLogMessageList'
import RetryTaskLogMessage from './RetryTaskLogMessage'
export default {
name: 'RetryTaskInfo',
components: {
RetryTaskLogMessageList
RetryTaskLogMessage
},
props: {
showHeader: {
@ -74,7 +74,8 @@ export default {
},
data () {
return {
retryTaskInfo: null,
loading: true,
retryTaskInfo: {},
retryStatus: {
'0': '处理中',
'1': '处理成功',
@ -112,7 +113,8 @@ export default {
groupName: this.retryTaskInfo.groupName,
uniqueId: this.retryTaskInfo.uniqueId
}
this.$refs.retryTaskLogMessageListRef.refreshTable(this.queryParam)
}).finally(() => {
this.loading = false
})
}
}

View File

@ -0,0 +1,88 @@
<template>
<div>
<div style="margin: 20px 0; border-left: #f5222d 5px solid; font-size: medium; font-weight: bold">
<span style="padding-left: 18px">调用日志详情</span>
<span style="padding-left: 18px"><a-icon type="sync" @click="getLogData"/></span>
</div>
<a-card>
<log :value="logList" />
</a-card>
</div>
</template>
<script>
import { getRetryTaskLogMessagePage } from '@/api/manage'
import Log from '@/components/Log/index.vue'
export default {
name: 'RetryTaskLogMessage',
components: { Log },
props: {
value: {
type: Object,
default: () => {}
}
},
watch: {
value: {
deep: true,
immediate: true,
handler () {
this.getLogData()
}
}
},
data () {
return {
startId: 0,
fromIndex: 0,
finished: false,
logList: [],
interval: null,
controller: new AbortController()
}
},
mounted () {
this.getLogData()
},
beforeDestroy () {
this.stopLog()
},
methods: {
stopLog () {
this.finished = true
this.controller.abort()
clearTimeout(this.interval)
this.interval = undefined
},
getLogData () {
if (!this.value) {
return
}
getRetryTaskLogMessagePage({ groupName: this.value.groupName, uniqueId: this.value.uniqueId, startId: this.startId, fromIndex: this.fromIndex })
.then((res) => {
this.finished = res.data.finished
this.startId = res.data.nextStartId
this.fromIndex = res.data.fromIndex
if (res.data.message) {
this.logList.push(...res.data.message)
this.logList.sort((a, b) => a.time_stamp - b.time_stamp)
}
if (!this.finished) {
clearTimeout(this.interval)
this.interval = setTimeout(this.getLogData, 1000)
}
})
.catch(() => {
this.finished = true
})
}
}
}
</script>
<style scoped>
</style>