支持历史日志的查看,过滤以及下载

pull/1682/head
648540858 2024-11-03 14:37:30 +08:00
parent 711fefa168
commit f106675dc6
6 changed files with 282 additions and 325 deletions

View File

@ -6,15 +6,8 @@ import lombok.Data;
public class LogFileInfo {
private String fileName;
private String startTime;
private String endTime;
public static LogFileInfo getInstance(String fileName, String startTime, String endTime) {
LogFileInfo logFileInfo = new LogFileInfo();
logFileInfo.setFileName(fileName);
logFileInfo.setStartTime(startTime);
logFileInfo.setEndTime(endTime);
return logFileInfo;
}
private Long fileSize;
private Long startTime;
private Long endTime;
}

View File

@ -9,17 +9,12 @@ import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.input.ReversedLinesFileReader;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Service
@ -37,33 +32,36 @@ public class LogServiceImpl implements ILogService {
if (files == null || files.length == 0) {
return result;
}
// 读取文件创建时间作为开始时间,修改时间为结束时间
Long startTimestamp = null;
if (startTime != null) {
startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime);
}
Long endTimestamp = null;
if (endTime != null) {
endTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(endTime);
}
for (File file : files) {
LogFileInfo logFileInfo = new LogFileInfo();
logFileInfo.setFileName(file.getName());
logFileInfo.setFileSize(file.length());
if (query != null && !file.getName().contains(query)) {
continue;
}
// 读取文件创建时间作为开始时间,修改时间为结束时间
Long startTimestamp = null;
if (startTime != null) {
startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
}
Long endTimestamp = null;
if (startTime != null) {
endTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
}
try {
String[] fileAttributes = getFileAttributes(file);
Long[] fileAttributes = getFileAttributes(file);
if (fileAttributes == null) {
continue;
}
logFileInfo.setStartTime(fileAttributes[0]);
logFileInfo.setEndTime(fileAttributes[1]);
if (startTimestamp != null && startTimestamp > DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(fileAttributes[0])) {
long startTimestampForFile = fileAttributes[0];
long endTimestampForFile = fileAttributes[1];
logFileInfo.setStartTime(startTimestampForFile);
logFileInfo.setEndTime(endTimestampForFile);
if (startTimestamp != null && startTimestamp > startTimestampForFile) {
continue;
}
if (endTimestamp != null && endTimestamp < DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(fileAttributes[1])) {
if (endTimestamp != null && endTimestamp < endTimestampForFile) {
continue;
}
} catch (IOException e) {
@ -73,6 +71,7 @@ public class LogServiceImpl implements ILogService {
result.add(logFileInfo);
}
result.sort((o1, o2) -> o2.getStartTime().compareTo(o1.getStartTime()));
return result;
}
@ -83,7 +82,7 @@ public class LogServiceImpl implements ILogService {
return rollingFile.getParentFile();
}
String[] getFileAttributes(File file) throws IOException {
Long[] getFileAttributes(File file) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
String startLine = bufferedReader.readLine();
if (startLine== null) {
@ -91,15 +90,15 @@ public class LogServiceImpl implements ILogService {
}
String startTime = startLine.substring(0, 19);
String lastLine = "";
try (ReversedLinesFileReader reversedLinesReader = new ReversedLinesFileReader(file, Charset.defaultCharset())) {
lastLine = reversedLinesReader.readLine();
} catch (Exception e) {
log.error("file read error, msg:{}", e.getMessage(), e);
}
String endTime = lastLine.substring(0, 19);
return new String[]{startTime, endTime};
// 最后一行的开头不一定是时间
// String lastLine = "";
// try (ReversedLinesFileReader reversedLinesReader = new ReversedLinesFileReader(file, Charset.defaultCharset())) {
// lastLine = reversedLinesReader.readLine();
// } catch (Exception e) {
// log.error("file read error, msg:{}", e.getMessage(), e);
// }
// String endTime = lastLine.substring(0, 19);
return new Long[]{DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(startTime), file.lastModified()};
}
@Override

View File

@ -62,6 +62,15 @@ public class LogController {
public List<LogFileInfo> queryList(@RequestParam(required = false) String query, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime
) {
if (ObjectUtils.isEmpty(query)) {
query = null;
}
if (ObjectUtils.isEmpty(startTime)) {
startTime = null;
}
if (ObjectUtils.isEmpty(endTime)) {
endTime = null;
}
return logService.queryList(query, startTime, endTime);
}

View File

@ -0,0 +1,155 @@
<template>
<div id="log" style="width: 100%;height: 100%;">
<div style="width: 100%; height: 40px; display: grid; grid-template-columns: 1fr 1fr">
<div style="text-align: left; line-height: 40px;">
<span style="width: 5vw">过滤: </span>
<el-input size="mini" v-model="filter" placeholder="请输入过滤关键字" style="width: 20vw"></el-input>
</div>
<div style="text-align: right; line-height: 40px;">
<el-button size="mini" icon="el-icon-download" @click="downloadFile()">
</el-button>
</div>
</div>
<log-viewer :log="logData" :loading="loading" :auto-scroll="true" :height="winHeight"/>
</div>
</template>
<script>
import userService from "./../service/UserService";
import moment from "moment/moment";
import stripAnsi from "strip-ansi";
export default {
name: 'log',
props: [ 'fileUrl', 'remoteUrl'],
components: {},
data() {
return {
loading: false,
winHeight: window.innerHeight - 300,
data: [],
filter: "",
logData: "",
websocket: null,
};
},
watch: {
remoteUrl(newValue) {
console.log(newValue);
this.remoteUrl = newValue;
this.initData();
},
fileUrl(newValue) {
this.fileUrl = newValue;
this.initData();
},
filter(newValue) {
this.filter = newValue;
this.logData = this.getLogData();
},
data(newValue) {
this.data = newValue;
this.logData = this.getLogData();
}
},
created() {
if (this.fileUrl || this.remoteUrl) {
this.initData();
}
},
destroyed() {
console.log('destroyed');
window.websocket.close();
},
methods: {
initData: function () {
console.log('remoteUrl ' + this.remoteUrl);
console.log('fileUrl ' + this.fileUrl);
if (this.fileUrl) {
this.$axios({
method: 'get',
url: this.fileUrl,
}).then((res) => {
let dataArray = res.data.split("\n");
dataArray.forEach(item => {
this.data.push(item);
})
}).catch((error) => {
console.log(error);
});
}else if (this.remoteUrl) {
console.log('remoteUrl' + this.remoteUrl);
console.log(window.location.host)
window.websocket = new WebSocket(this.remoteUrl, userService.getToken());
window.websocket.onclose = e => {
console.log(`conn closed: code=${e.code}, reason=${e.reason}, wasClean=${e.wasClean}`)
}
window.websocket.onmessage = e => {
this.data.push(e.data);
}
window.websocket.onerror = e => {
console.log(`conn err`)
console.error(e)
}
window.websocket.onopen = e => {
console.log(`conn open: ${e}`);
}
}
},
getLogData: function () {
this.loading = true;
if (this.data.length === 0) {
this.loading = false;
return "";
}else {
let result = '';
for (let i = 0; i < this.data.length; i++) {
if (this.filter.length === 0) {
result += this.data[i] + "\r\n"
}else {
if (this.data[i].indexOf(this.filter) > -1) {
result += this.data[i] + "\r\n"
}
}
}
this.loading = false;
return result;
}
},
getLogDataWithOutAnsi: function () {
if (this.data.length === 0) {
return "";
}else {
let result = '';
for (let i = 0; i < this.data.length; i++) {
if (this.filter.length === 0) {
result += stripAnsi(this.data[i]) + "\r\n"
}else {
if (this.data[i].indexOf(this.filter) > -1) {
result += stripAnsi(this.data[i]) + "\r\n"
}
}
}
return result;
}
},
downloadFile() {
let blob = new Blob([this.getLogDataWithOutAnsi()], {
type: "text/plain;charset=utf-8"
});
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = (e)=> {
let a = document.createElement('a');
a.download = `wvp-${this.filter}-${moment().format('yyyy-MM-DD')}.log`;
a.href = e.target.result;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
},
}
};
</script>

View File

@ -2,43 +2,48 @@
<div id="app" style="width: 100%">
<div class="page-header">
<div class="page-title">
<div >历史日志</div>
<div>历史日志</div>
</div>
<div class="page-header-btn">
搜索:
<el-input @input="getFileList" style="margin-right: 1rem; width: auto;" size="mini" placeholder="关键字"
prefix-icon="el-icon-search" v-model="search" clearable></el-input>
prefix-icon="el-icon-search" v-model="search" clearable></el-input>
开始时间:
<el-date-picker
v-model="startTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
@change="getMediaServerList"
placeholder="选择日期时间">
size="mini"
v-model="startTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
@change="getFileList"
placeholder="选择日期时间">
</el-date-picker>
结束时间:
<el-date-picker
v-model="endTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
@change="getMediaServerList"
placeholder="选择日期时间">
size="mini"
v-model="endTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
@change="getFileList"
placeholder="选择日期时间">
</el-date-picker>
<!-- <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteRecord()"></el-button>-->
<!-- <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteRecord()"></el-button>-->
<el-button icon="el-icon-refresh-right" circle size="mini" :loading="loading"
@click="getFileList()"></el-button>
</div>
</div>
<!--日志列表-->
<el-table size="medium" :data="fileList" style="width: 100%" :height="winHeight">
<el-table size="medium" :data="fileList" style="width: 100%" :height="winHeight">
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column prop="app" label="应用名">
<el-table-column prop="fileName" label="文件名">
</el-table-column>
<el-table-column prop="stream" label="流ID" width="380">
<el-table-column prop="fileSize" label="文件大小">
<template slot-scope="scope">
{{formatFileSize(scope.row.fileSize)}}
</template>
</el-table-column>
<el-table-column label="开始时间">
<template slot-scope="scope">
@ -50,18 +55,9 @@
{{formatTimeStamp(scope.row.endTime)}}
</template>
</el-table-column>
<el-table-column label="时长">
<template slot-scope="scope">
<el-tag>{{formatTime(scope.row.timeLen)}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="fileName" label="文件名称">
</el-table-column>
<el-table-column prop="mediaServerId" label="流媒体">
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template slot-scope="scope">
<el-button size="medium" icon="el-icon-video-play" type="text" @click="play(scope.row)">
<el-button size="medium" icon="el-icon-document" type="text" @click="showLogView(scope.row)">
</el-button>
<el-button size="medium" icon="el-icon-download" type="text" @click="downloadFile(scope.row)">
</el-button>
@ -71,21 +67,12 @@
</template>
</el-table-column>
</el-table>
<el-pagination
style="text-align: right"
@size-change="handleSizeChange"
@current-change="currentChange"
:current-page="currentPage"
:page-size="count"
:page-sizes="[15, 25, 35, 50]"
layout="total, sizes, prev, pager, next"
:total="total">
</el-pagination>
<el-dialog
top="10"
:title="playerTitle"
:visible.sync="showPlayer"
width="50%">
<easyPlayer ref="recordVideoPlayer" :videoUrl="videoUrl" :height="false" ></easyPlayer>
:visible.sync="showLog"
width="90%">
<operationsFoShowLog ref="recordVideoPlayer" :fileUrl="fileUrl" ></operationsFoShowLog>
</el-dialog>
</div>
</template>
@ -93,28 +80,27 @@
<script>
import uiHeader from '../layout/UiHeader.vue'
import MediaServer from './service/MediaServer'
import easyPlayer from './common/easyPlayer.vue'
import moment from 'moment'
import axios from "axios";
import operationsFoShowLog from './dialog/operationsFoShowLog.vue'
import moment from 'moment'
export default {
name: 'app',
components: {
uiHeader,easyPlayer
uiHeader, operationsFoShowLog
},
data() {
return {
search: '',
startTime: '',
endTime: '',
showPlayer: false,
showLog: false,
playerTitle: '',
videoUrl: '',
fileUrl: '',
playerStyle: {
"margin": "auto",
"margin-bottom": "20px",
"width": window.innerWidth/2 + "px",
"height": this.winHeight/2 + "px",
"margin": "auto",
"margin-bottom": "20px",
"width": window.innerWidth / 2 + "px",
"height": this.winHeight / 2 + "px",
},
mediaServerList: [], //
mediaServerId: "", //
@ -124,9 +110,6 @@ export default {
updateLooper: 0, //
winHeight: window.innerHeight - 250,
currentPage: 1,
count: 15,
total: 0,
loading: false,
mediaServerObj: new MediaServer(),
@ -137,62 +120,25 @@ export default {
this.initData();
},
destroyed() {
this.$destroy('recordVideoPlayer');
this.$destroy('recordVideoPlayer');
},
methods: {
initData: function () {
//
this.getMediaServerList();
this.getFileList();
},
currentChange: function (val) {
this.currentPage = val;
this.getFileList();
},
handleSizeChange: function (val) {
this.count = val;
this.getFileList();
},
getMediaServerList: function () {
let that = this;
that.mediaServerObj.getOnlineMediaServerList((data) => {
that.mediaServerList = data.data;
})
},
setMediaServerPath: function (serverId) {
let that = this;
let i;
for (i = 0; i < that.mediaServerList.length; i++) {
if (serverId === that.mediaServerList[i].id) {
break;
}
}
let port = that.mediaServerList[i].httpPort;
if (location.protocol === "https:" && that.mediaServerList[i].httpSSlPort) {
port = that.mediaServerList[i].httpSSlPort
}
that.mediaServerPath = location.protocol + "//" + that.mediaServerList[i].streamIp + ":" + port
console.log(that.mediaServerPath)
},
getFileList: function () {
this.$axios({
method: 'get',
url: `/api/cloud/record/list`,
url: `/api/log/list`,
params: {
app: '',
stream: '',
query: this.search,
startTime: this.startTime,
endTime: this.endTime,
mediaServerId: this.mediaServerId,
page: this.currentPage,
count: this.count
}
}).then((res) => {
console.log(res)
if (res.data.code === 0) {
this.total = res.data.data.total;
this.fileList = res.data.data.list;
this.fileList = res.data.data;
}
this.loading = false;
}).catch((error) => {
@ -200,54 +146,28 @@ export default {
this.loading = false;
});
},
play(row) {
console.log(row)
this.chooseRecord = row;
this.showPlayer = true;
this.$axios({
method: 'get',
url: `/api/cloud/record/play/path`,
params: {
recordId: row.id,
}
}).then((res) => {
console.log(res)
if (res.data.code === 0) {
if (location.protocol === "https:") {
this.videoUrl = res.data.data.httpsPath;
}else {
this.videoUrl = res.data.data.httpPath;
}
console.log(222 )
console.log(this.videoUrl )
}
}).catch((error) => {
console.log(error);
});
showLogView(file) {
this.playerTitle = file.fileName
this.fileUrl = `/api/log/file/${file.fileName}`
// if (process.env.NODE_ENV === 'development') {
// this.fileUrl = `/debug/api/log/file/${file.fileName}`
// }else {
//
// }
this.showLog = true
},
downloadFile(file){
console.log(file)
this.$axios({
method: 'get',
url: `/api/cloud/record/play/path`,
params: {
recordId: file.id,
}
}).then((res) => {
console.log(res)
const link = document.createElement('a');
link.target = "_blank";
if (res.data.code === 0) {
if (location.protocol === "https:") {
link.href = res.data.data.httpsPath + "&save_name=" + file.fileName;
}else {
link.href = res.data.data.httpPath + "&save_name=" + file.fileName;
}
link.click();
}
}).catch((error) => {
console.log(error);
});
downloadFile(file) {
const link = document.createElement('a');
link.target = "_blank";
link.download = file.fileName;
if (process.env.NODE_ENV === 'development') {
link.href = `/debug/api/log/file/${file.fileName}`
}else {
link.href = `/api/log/file/${file.fileName}`
}
link.click();
},
deleteRecord() {
// TODO
@ -279,9 +199,27 @@ export default {
return (h > 0 ? h + `小时` : '') + (minute > 0 ? minute + '分' : '') + (second > 0 ? second + '秒' : '')
},
formatTimeStamp(time) {
return moment.unix(time/1000).format('yyyy-MM-DD HH:mm:ss')
return moment.unix(time / 1000).format('yyyy-MM-DD HH:mm:ss')
},
formatFileSize(fileSize) {
if (fileSize < 1024) {
return fileSize + 'B';
} else if (fileSize < (1024*1024)) {
let temp = fileSize / 1024;
temp = temp.toFixed(2);
return temp + 'KB';
} else if (fileSize < (1024*1024*1024)) {
let temp = fileSize / (1024*1024);
temp = temp.toFixed(2);
return temp + 'MB';
} else {
let temp = fileSize / (1024*1024*1024);
temp = temp.toFixed(2);
return temp + 'GB';
}
}
}
};
</script>

View File

@ -1,175 +1,38 @@
<template>
<div id="log" style="width: 100%;height: 100%;">
<div style="width: 100%; height: 40px; display: grid; grid-template-columns: 1fr 1fr">
<div style="text-align: left; line-height: 40px;">
<span style="width: 5vw">过滤: </span>
<el-input size="mini" v-model="filter" placeholder="请输入过滤关键字" style="width: 20vw"></el-input>
</div>
<div style="text-align: right; line-height: 40px;">
<el-button size="mini" icon="el-icon-download" @click="downloadFile()">
</el-button>
</div>
</div>
<log-viewer :log="getLogData()" :loading="loading" :auto-scroll="true" :height="winHeight" />
<operationsFoShowLog ref="recordVideoPlayer" :remoteUrl="removeUrl" ></operationsFoShowLog>
</div>
</template>
<script>
import userService from "./service/UserService";
import moment from "moment/moment";
import stripAnsi from "strip-ansi";
import operationsFoShowLog from "./dialog/operationsFoShowLog.vue";
export default {
name: 'log',
components: {},
components: {operationsFoShowLog},
data() {
return {
loading: false,
removeUrl: this.getURl(),
winHeight: window.innerHeight - 220,
data: [],
filter: "",
websocket: null,
};
},
created() {
console.log('created');
this.initData();
},
destroyed() {
console.log('destroyed');
window.websocket.close();
},
methods: {
initData: function () {
console.log(window.location.host)
let url = "ws://localhost:18080/channel/log";
getURl: function () {
if (process.env.NODE_ENV !== 'development') {
if (location.protocol === "https:") {
url = `wss://${window.location.host}/channel/log`
return `wss://${window.location.host}/channel/log`;
}else {
url = `ws://${window.location.host}/channel/log`
return `ws://${window.location.host}/channel/log`
}
}
window.websocket = new WebSocket(url, userService.getToken());
window.websocket.onclose = e => {
console.log(`conn closed: code=${e.code}, reason=${e.reason}, wasClean=${e.wasClean}`)
}
window.websocket.onmessage = e => {
this.data.push(e.data);
}
window.websocket.onerror = e => {
console.log(`conn err`)
console.error(e)
}
window.websocket.onopen = e => {
console.log(`conn open: ${e}`);
}
},
getLogData: function () {
if (this.data.length === 0) {
return "";
}else {
let result = '';
for (let i = 0; i < this.data.length; i++) {
if (this.filter.length === 0) {
result += this.data[i] + "\r\n"
}else {
if (this.data[i].indexOf(this.filter) > -1) {
result += this.data[i] + "\r\n"
}
}
}
return result;
}
},
getLogDataWithOutAnsi: function () {
if (this.data.length === 0) {
return "";
}else {
let result = '';
for (let i = 0; i < this.data.length; i++) {
if (this.filter.length === 0) {
result += stripAnsi(this.data[i]) + "\r\n"
}else {
if (this.data[i].indexOf(this.filter) > -1) {
result += stripAnsi(this.data[i]) + "\r\n"
}
}
}
return result;
}
},
downloadFile() {
let blob = new Blob([this.getLogDataWithOutAnsi()], {
type: "text/plain;charset=utf-8"
});
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function(e) {
let a = document.createElement('a');
a.download = `wvp-${moment().format('yyyy-MM-DD')}.log`;
a.href = e.target.result;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
return "ws://localhost:18080/channel/log";
}
},
}
};
</script>
<style>
.videoList {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
.video-item {
position: relative;
width: 15rem;
height: 10rem;
margin-right: 1rem;
background-color: #000000;
}
.video-item-img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 100%;
height: 100%;
}
.video-item-img:after {
content: "";
display: inline-block;
position: absolute;
z-index: 2;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 3rem;
height: 3rem;
background-image: url("../assets/loading.png");
background-size: cover;
background-color: #000000;
}
.video-item-title {
position: absolute;
bottom: 0;
color: #000000;
background-color: #ffffff;
line-height: 1.5rem;
padding: 0.3rem;
width: 14.4rem;
}
</style>