加入日志异常检测

This commit is contained in:
marvzhang
2020-04-18 18:42:46 +08:00
parent 9d7b42e295
commit c7675ca40a
10 changed files with 110 additions and 32 deletions

5
backend/constants/log.go Normal file
View File

@@ -0,0 +1,5 @@
package constants
const (
ErrorRegexPattern = "(?:[ :,.]|^)((?:error|exception|traceback)s?)(?:[ :,.]|$)"
)

View File

@@ -29,6 +29,7 @@ type UserSetting struct {
DingTalkRobotWebhook string `json:"ding_talk_robot_webhook" bson:"ding_talk_robot_webhook"`
WechatRobotWebhook string `json:"wechat_robot_webhook" bson:"wechat_robot_webhook"`
EnabledNotifications []string `json:"enabled_notifications" bson:"enabled_notifications"`
ErrorRegexPattern string `json:"error_regex_pattern" bson:"error_regex_pattern"`
}
func (user *User) Save() error {

View File

@@ -48,6 +48,9 @@ export default {
// get latest version
await this.$store.dispatch('version/getLatestRelease')
// get user info
await this.$store.dispatch('user/getInfo')
// remove loading-placeholder
const elLoading = document.querySelector('#loading-placeholder')
elLoading.remove()

View File

@@ -13,6 +13,10 @@
</template>
<script>
import {
mapGetters
} from 'vuex'
export default {
name: 'LogItem',
props: {
@@ -45,10 +49,19 @@ export default {
},
data () {
return {
errorRegex: this.$utils.log.errorRegex
}
},
computed: {
...mapGetters('user', [
'userInfo'
]),
errorRegex () {
if (!this.userInfo.setting.error_regex_pattern) {
return this.$utils.log.errorRegex
}
console.log(this.userInfo.setting.error_regex_pattern)
return new RegExp(this.userInfo.setting.error_regex_pattern, 'i')
},
dataHtml () {
let html = this.data.replace(this.errorRegex, ' <span style="font-weight: bolder; text-decoration: underline">$1</span> ')
if (!this.searchString) return html

View File

@@ -8,18 +8,19 @@
style="margin-right: 10px"
>
</el-switch>
<el-switch
v-model="isLogAutoFetch"
:inactive-text="$t('Auto-Refresh')"
style="margin-right: 10px"
>
</el-switch>
<!-- <el-switch-->
<!-- v-model="isLogAutoFetch"-->
<!-- :inactive-text="$t('Auto-Refresh')"-->
<!-- style="margin-right: 10px"-->
<!-- >-->
<!-- </el-switch>-->
<el-input
v-model="logKeyword"
size="small"
suffix-icon="el-icon-search"
:placeholder="$t('Search Log')"
style="width: 240px; margin-right: 10px"
clearable
/>
<el-button
size="small"
@@ -138,11 +139,11 @@ export default {
'taskForm',
'taskLogTotal',
'logKeyword',
'isLogFetchLoading'
'isLogFetchLoading',
'errorLogData'
]),
...mapGetters('task', [
'logData',
'errorLogData'
'logData'
]),
currentLogIndex: {
get () {
@@ -257,9 +258,6 @@ export default {
},
toBottom () {
this.$el.querySelector('.log-view').scrollTo({ top: 99999999 })
},
onAutoScroll () {
},
toggleErrors () {
this.isErrorsCollapsed = !this.isErrorsCollapsed
@@ -268,15 +266,14 @@ export default {
this.isErrorCollapsing = false
}, 300)
},
onClickError (item) {
this.currentLogIndex = item.index
this.isLogAutoScroll = false
const handle = setInterval(() => {
this.isLogAutoScroll = false
}, 10)
setTimeout(() => {
clearInterval(handle)
}, 500)
async onClickError (item) {
const page = Math.ceil(item.seq / this.taskLogPageSize)
this.$store.commit('task/SET_LOG_KEYWORD', '')
this.$store.commit('task/SET_TASK_LOG_PAGE', page)
this.$store.commit('task/SET_IS_LOG_AUTO_SCROLL', false)
this.$store.commit('task/SET_ACTIVE_ERROR_LOG_ITEM', item)
this.$emit('search')
this.$st.sendEv('任务详情', '日志', '点击错误日志')
},
onSearchLog () {
this.$emit('search')

View File

@@ -486,6 +486,8 @@ export default {
'Allow Sending Statistics': '允许发送统计信息',
'General': '通用',
'Enable Tutorial': '启用教程',
'Error Regex Pattern': '异常正则表达式',
'By default: ': '默认: ',
// 挑战
'Challenge': '挑战',

View File

@@ -30,6 +30,7 @@ const state = {
taskLogTotal: 0,
taskLogPage: 1,
taskLogPageSize: 5000,
activeErrorLogItem: {},
// results
resultsPageNum: 1,
resultsPageSize: 10
@@ -141,6 +142,9 @@ const mutations = {
},
SET_IS_LOG_FETCH_LOADING (state, value) {
state.isLogFetchLoading = value
},
SET_ACTIVE_ERROR_LOG_ITEM (state, value) {
state.activeErrorLogItem = value
}
}
@@ -185,7 +189,7 @@ const actions = {
dispatch('getTaskList')
})
},
getTaskLog ({ state, commit }, { id, keyword, isAutoScrolling }) {
getTaskLog ({ state, commit }, { id, keyword }) {
return request.get(`/tasks/${id}/log`, {
keyword,
page_num: state.taskLogPage,
@@ -202,7 +206,7 @@ const actions = {
})
},
getTaskErrorLog ({ state, commit }, id) {
return request.get(`/tasks/${id}/log`, { keyword: utils.log.errorRegex.source })
return request.get(`/tasks/${id}/error-log`, {})
.then(response => {
commit('SET_ERROR_LOG_DATA', response.data.data || [])
})

View File

@@ -2,7 +2,7 @@ const regexToken = ' :,.'
export default {
// errorRegex: new RegExp(`(?:[${regexToken}]|^)((?:error|exception|traceback)s?)(?:[${regexToken}]|$)`, 'gi')
errorRegex: new RegExp(`(?:${regexToken})(error)(?:${regexToken})`, 'gi'),
errorRegex: new RegExp(`(?:[${regexToken}]|^)((?:error|exception|traceback)s?)(?:[${regexToken}]|$)`, 'gi'),
errorWhitelist: [
'log_count/ERROR'
]

View File

@@ -33,8 +33,10 @@
<!--./新增全局变量-->
<el-tabs v-model="activeName" @tab-click="tabActiveHandle" type="border-card">
<!--通用-->
<el-tab-pane :label="$t('General')" name="general">
<el-form :model="userInfo" class="setting-form" ref="setting-form" label-width="200px" :rules="rules"
<el-form :model="userInfo" class="setting-form" ref="setting-form" label-width="200px"
:rules="rulesNotification"
inline-message>
<el-form-item prop="username" :label="$t('Username')">
<el-input v-model="userInfo.username" disabled></el-input>
@@ -67,8 +69,12 @@
</el-form-item>
</el-form>
</el-tab-pane>
<!--./通用-->
<!--消息通知-->
<el-tab-pane :label="$t('Notifications')" name="notify">
<el-form :model="userInfo" class="setting-form" ref="setting-form" label-width="200px" :rules="rules"
<el-form :model="userInfo" class="setting-form" ref="setting-form" label-width="200px"
:rules="rulesNotification"
inline-message>
<el-form-item :label="$t('Notification Trigger Timing')">
<el-radio-group v-model="userInfo.setting.notification_trigger">
@@ -110,6 +116,31 @@
</el-form-item>
</el-form>
</el-tab-pane>
<!--./消息通知-->
<!--日志-->
<el-tab-pane :label="$t('Log')" name="log">
<el-form :model="userInfo" class="setting-form" ref="log-form" label-width="200px" :rules="rulesLog"
inline-message>
<el-form-item :label="$t('Error Regex Pattern')" prop="setting.error_regex_pattern">
<el-input
v-model="userInfo.setting.error_regex_pattern"
:placeholder="$t('By default: ') + $utils.log.errorRegex.source"
clearable
/>
</el-form-item>
<el-form-item>
<div style="text-align: right">
<el-button type="success" size="small" @click="saveUserInfo">
{{$t('Save')}}
</el-button>
</div>
</el-form-item>
</el-form>
</el-tab-pane>
<!--./日志-->
<!--全局变量-->
<el-tab-pane :label="$t('Global Variable')" name="global-variable">
<div style="text-align: right;margin-bottom: 10px">
<el-button size="small" @click="addGlobalVariableHandle(true)"
@@ -131,6 +162,7 @@
</el-table-column>
</el-table>
</el-tab-pane>
<!--./全局变量-->
</el-tabs>
</div>
</template>
@@ -175,12 +207,13 @@ export default {
}
return {
userInfo: { setting: { enabled_notifications: [] } },
rules: {
rulesNotification: {
password: [{ trigger: 'blur', validator: validatePass }],
email: [{ trigger: 'blur', validator: validateEmail }],
'setting.ding_talk_robot_webhook': [{ trigger: 'blur', validator: validateDingTalkRobotWebhook }],
'setting.wechat_robot_webhook': [{ trigger: 'blur', validator: validateWechatRobotWebhook }]
},
rulesLog: {},
isShowDingTalkAppSecret: false,
activeName: 'general',
addDialogVisible: false,
@@ -233,8 +266,9 @@ export default {
])
},
watch: {
userInfoStr () {
this.saveUserInfo()
async userInfoStr () {
await this.saveUserInfo()
await this.$store.dispatch('user/getInfo')
}
},
methods: {

View File

@@ -138,10 +138,13 @@ export default {
'taskResultsTotalCount',
'taskLog',
'logKeyword',
'isLogAutoFetch'
'isLogAutoFetch',
'currentLogIndex',
'activeErrorLogItem'
]),
...mapGetters('task', [
'taskResultsColumns'
'taskResultsColumns',
'logData'
]),
...mapState('file', [
'currentPath'
@@ -189,6 +192,21 @@ export default {
this.$store.commit('task/SET_IS_LOG_FETCH_LOADING', value)
}
},
currentLogIndex: {
get () {
return this.$store.state.task.currentLogIndex
},
set (value) {
this.$store.commit('task/SET_CURRENT_LOG_INDEX', value)
}
},
logIndexMap () {
const map = new Map()
this.logData.forEach((d, index) => {
map.set(d._id, index)
})
return map
},
isRunning () {
return ['pending', 'running'].includes(this.taskForm.status)
}
@@ -215,6 +233,7 @@ export default {
this.isLogFetchLoading = true
}
await this.$store.dispatch('task/getTaskLog', { id: this.$route.params.id, keyword: this.logKeyword })
this.currentLogIndex = (this.logIndexMap.get(this.activeErrorLogItem.log_id) + 1) || 0
this.isLogFetchLoading = false
await this.$store.dispatch('task/getTaskErrorLog', this.$route.params.id)
}