501 lines
13 KiB
Vue
501 lines
13 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<div class="head-container">
|
||
<el-form
|
||
:inline="true"
|
||
label-position="right"
|
||
label-width="=100px"
|
||
label-suffix=":"
|
||
>
|
||
<el-form-item :label="$t('Log.label')">
|
||
<el-select
|
||
v-model="query.label"
|
||
clearable
|
||
size="mini"
|
||
:placeholder="$t('Log.label')"
|
||
style="width: 240px;"
|
||
>
|
||
<el-option
|
||
v-for="item in tagList"
|
||
:label="item"
|
||
:value="item"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('Log.level')">
|
||
<el-select
|
||
v-model="query.level"
|
||
clearable
|
||
size="mini"
|
||
:placeholder="$t('Log.level')"
|
||
>
|
||
<el-option
|
||
v-for="item in levelOptions"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('Log.search')">
|
||
<el-input
|
||
v-model="query.message"
|
||
clearable
|
||
size="mini"
|
||
:placeholder="$t('Log.content')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('Log.link_id')">
|
||
<el-input
|
||
v-model="query.tlogTraceId"
|
||
clearable
|
||
size="mini"
|
||
:placeholder="$t('Log.link_id_msg')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item :label="$t('Log.time')">
|
||
<el-date-picker
|
||
v-model="query.createTime"
|
||
type="datetimerange"
|
||
:range-separator="$t('Log.to')"
|
||
:start-placeholder="$t('Log.start_time')"
|
||
:end-placeholder="$t('Log.end_time')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="压缩查看" prop="isRequest">
|
||
<el-switch
|
||
v-model="compressedView"
|
||
active-color="#409EFF"
|
||
inactive-color="#F56C6C"
|
||
:active-value="true"
|
||
:inactive-valu="false"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" size="mini" @click="queryData">
|
||
{{ $t('common.Query') }}
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
<!--表格渲染-->
|
||
<el-card shadow="hover" style="width: 100%; height: 620px; overflow-y: auto" class="log-warpper">
|
||
<div style="width: 100%">
|
||
<div v-for="(log, index) in logs" :key="index">
|
||
<div v-if="compressedView" style="margin-bottom: 5px; font-size: 16px;">
|
||
<span style="color: #000000;">{{ timestampToDate(log.time) }}</span> -
|
||
<span style="color: #000569;">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span> -
|
||
<span style="color: #111fc7;">{{ log.severity }}</span> -
|
||
<span style="color: #3498db;">{{ log.logger }}</span> -
|
||
<span style="color: #28b23a;">[{{ log.thread }}]</span> -
|
||
<span style="color: #d51313;">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span> -
|
||
<span
|
||
style="color: #000000;"
|
||
class="sql-message"
|
||
@click="handleCopySQL(log.message)"
|
||
>
|
||
{{ log.message }}
|
||
</span>
|
||
</div>
|
||
<div v-else class="log-detailed">
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
时间:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-time">{{ timestampToDate(log.time) }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
host:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-host">{{ log.host }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
执行IP:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-ip">{{ log.currIp === null ? '0.0.0.0' : log.currIp }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
API路径:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-api">{{ log.apiPath === null ? '无' : log.apiPath }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
MDC标签:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-tag">{{ log.tag === null ? '未配置MDC' : log.tag }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
日志级别:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span :class="['log-level', getLevelClass(log.severity)]">{{ log.severity }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
类名:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-logger">{{ log.logger }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
线程名:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-thread">{{ log.thread }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
链路标识:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-trace">{{ log.tlogTraceId === null ? '无链路id' : log.tlogTraceId }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row class="log-row">
|
||
<el-col :span="2" class="log-label">
|
||
日志信息:
|
||
</el-col>
|
||
<el-col :span="22">
|
||
<span class="log-message sql-message" @click="handleCopySQL(log.message)">{{ transSQL(log.message) }}</span>
|
||
</el-col>
|
||
</el-row>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-card>
|
||
<!-- 分页 -->
|
||
<el-pagination
|
||
:page-sizes="[100, 500, 1000]"
|
||
:page-size.sync="query.size"
|
||
:total="query.total"
|
||
:current-page.sync="query.page"
|
||
style="margin-top: 8px;"
|
||
layout="total, prev, pager, next, sizes"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import luceneOperation from '@/views/lucene/api/lucene'
|
||
export default {
|
||
name: 'LuceneLog',
|
||
data() {
|
||
return {
|
||
permission: {
|
||
add: ['admin', 'param:add'],
|
||
edit: ['admin', 'param:edit'],
|
||
del: ['admin', 'param:del']
|
||
},
|
||
levelOptions: [{
|
||
value: 'DEBUG',
|
||
label: 'DEBUG'
|
||
}, {
|
||
value: 'INFO',
|
||
label: 'INFO'
|
||
}, {
|
||
value: 'ERROR',
|
||
label: 'ERROR'
|
||
}, {
|
||
value: 'WARN',
|
||
label: 'WARN'
|
||
}],
|
||
rules: {},
|
||
logs: [],
|
||
tagList: [],
|
||
query: {
|
||
tlogTraceId: '',
|
||
message: '',
|
||
page: 0,
|
||
size: 100,
|
||
total: 0,
|
||
createTime: ''
|
||
},
|
||
compressedView: true
|
||
}
|
||
},
|
||
created() {
|
||
this.queryData()
|
||
this.getTagList()
|
||
},
|
||
methods: {
|
||
getLevelClass(level) {
|
||
if (!level) return 'level-unknown'
|
||
switch (level.toUpperCase()) {
|
||
case 'ERROR': return 'level-error'
|
||
case 'WARN': return 'level-warn'
|
||
case 'DEBUG': return 'level-debug'
|
||
case 'INFO': return 'level-info'
|
||
default: return 'level-unknown'
|
||
}
|
||
},
|
||
timestampToDate(timestamp) {
|
||
const date = new Date(timestamp)
|
||
const year = date.getFullYear()
|
||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||
const day = String(date.getDate()).padStart(2, '0')
|
||
const hours = String(date.getHours()).padStart(2, '0')
|
||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||
// 添加毫秒处理(强制3位数)
|
||
const milliseconds = String(date.getMilliseconds()).padStart(3, '0')
|
||
|
||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`
|
||
},
|
||
// 处理SQL复制
|
||
handleCopySQL(message) {
|
||
// 1. 使用正则匹配SQL语句
|
||
const sqlMatch = message.match(/\[ SQL: ([^\]]+)\s*\]/)
|
||
|
||
if (sqlMatch && sqlMatch[1]) {
|
||
const sql = sqlMatch[1].trim()
|
||
this.copyToClipboard(sql)
|
||
} else {
|
||
this.$message.warning('未检测到可复制的SQL语句')
|
||
}
|
||
},
|
||
transSQL(message) {
|
||
const sqlMatch = message.match(/\[ SQL: ([^\]]+)\s*\]/)
|
||
|
||
// 兼容性更好的写法
|
||
if (!sqlMatch || !sqlMatch[1]) {
|
||
return message
|
||
}
|
||
const rawSQL = sqlMatch[1].trim()
|
||
return rawSQL
|
||
},
|
||
// 复制文本到剪贴板
|
||
copyToClipboard(text) {
|
||
// 现代浏览器方法
|
||
if (navigator.clipboard) {
|
||
navigator.clipboard.writeText(text)
|
||
.then(() => this.showSuccess())
|
||
.catch(err => this.showError(err))
|
||
}
|
||
// 兼容旧浏览器
|
||
else {
|
||
const textarea = document.createElement('textarea')
|
||
textarea.value = text
|
||
textarea.style.position = 'fixed'
|
||
document.body.appendChild(textarea)
|
||
textarea.select()
|
||
|
||
try {
|
||
const success = document.execCommand('copy')
|
||
success ? this.showSuccess() : this.showError()
|
||
} catch (err) {
|
||
this.showError(err)
|
||
}
|
||
|
||
document.body.removeChild(textarea)
|
||
}
|
||
},
|
||
|
||
// 显示成功提示
|
||
showSuccess() {
|
||
this.$message({
|
||
message: 'SQL已复制到剪贴板',
|
||
type: 'success',
|
||
duration: 2000
|
||
})
|
||
},
|
||
|
||
// 显示错误提示
|
||
showError(err) {
|
||
console.error('复制失败:', err)
|
||
this.$message.error('复制失败,请手动复制')
|
||
},
|
||
handleSizeChange(val) {
|
||
this.query.size = val
|
||
this.queryData()
|
||
},
|
||
handleCurrentChange(val) {
|
||
this.query.page = val
|
||
this.queryData()
|
||
},
|
||
getTagList() {
|
||
luceneOperation.getTagName().then(res => {
|
||
this.tagList = res
|
||
})
|
||
},
|
||
queryData() {
|
||
if (this.query.createTime) {
|
||
this.query.startTime = this.query.createTime[0]
|
||
this.query.endTime = this.query.createTime[1]
|
||
}
|
||
luceneOperation.getLogData(this.query).then(res => {
|
||
console.log(res)
|
||
this.logs = res.content
|
||
this.query.total = res.totalElements
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 详细视图样式 */
|
||
.log-detailed {
|
||
padding: 10px;
|
||
border: 1px solid #EBEEF5;
|
||
border-radius: 4px;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.log-row {
|
||
margin-bottom: 5px;
|
||
line-height: 22px;
|
||
}
|
||
|
||
.log-label {
|
||
font-weight: 600;
|
||
color: #606266;
|
||
text-align: right;
|
||
padding-right: 12px;
|
||
}
|
||
|
||
/* 日志元素样式 */
|
||
.log-time {
|
||
color: #303133;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.log-ip {
|
||
color: #000569;
|
||
padding: 2px 4px;
|
||
background-color: #f0f9ff;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.log-level {
|
||
font-weight: bold;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.level-error .log-level,
|
||
.log-level.level-error {
|
||
background-color: #fef0f0;
|
||
color: #F56C6C;
|
||
}
|
||
|
||
.level-warn .log-level,
|
||
.log-level.level-warn {
|
||
background-color: #fdf6ec;
|
||
color: #E6A23C;
|
||
}
|
||
|
||
.level-info .log-level,
|
||
.log-level.level-info {
|
||
background-color: #ecf5ff;
|
||
color: #409EFF;
|
||
}
|
||
|
||
.level-debug .log-level,
|
||
.log-level.level-debug {
|
||
background-color: #f0f9eb;
|
||
color: #67C23A;
|
||
}
|
||
|
||
.log-logger {
|
||
color: #3498db;
|
||
font-family: monospace;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.log-thread {
|
||
color: #28b23a;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.log-trace {
|
||
color: #d51313;
|
||
font-family: monospace;
|
||
background-color: #fff7f7;
|
||
padding: 2px 4px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.log-host {
|
||
color: #048815;
|
||
}
|
||
|
||
.log-api {
|
||
color: #8c6a03;
|
||
font-family: monospace;
|
||
}
|
||
|
||
.log-tag {
|
||
color: #d20118;
|
||
}
|
||
|
||
.log-message {
|
||
color: #000000;
|
||
display: block;
|
||
margin-top: 4px;
|
||
padding: 6px;
|
||
background-color: #f8f8f8;
|
||
border-radius: 3px;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
font-family: monospace;
|
||
}
|
||
|
||
/* SQL语句样式 */
|
||
.sql-message {
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
position: relative;
|
||
background: #f4faff;
|
||
color: #2d8cf0;
|
||
font-weight: bold;
|
||
border-left: 3px solid #2d8cf0;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.sql-message:hover {
|
||
background-color: #ecf5ff;
|
||
box-shadow: 0 0 6px rgba(32, 160, 255, 0.3);
|
||
}
|
||
|
||
.sql-message:hover::after {
|
||
content: "点击复制SQL";
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 3px;
|
||
font-size: 12px;
|
||
color: #409EFF;
|
||
background-color: #ecf5ff;
|
||
padding: 2px 5px;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.sql-message.copied {
|
||
background: #e1f3d8;
|
||
color: #67c23a;
|
||
}
|
||
|
||
</style>
|