mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 10:25:52 +02:00
perf: improves performance when loading large logs
This commit is contained in:
parent
d63ad3e989
commit
92fb679b55
4 changed files with 239 additions and 81 deletions
16
frontend/src/api/nginx_log.ts
Normal file
16
frontend/src/api/nginx_log.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import http from '@/lib/http'
|
||||
|
||||
interface IData {
|
||||
type: string
|
||||
conf_name: string
|
||||
server_idx: number
|
||||
directive_idx: number
|
||||
}
|
||||
|
||||
const nginx_log = {
|
||||
page(page = 0, data: IData) {
|
||||
return http.post('/nginx_log?page=' + page, data)
|
||||
}
|
||||
}
|
||||
|
||||
export default nginx_log
|
|
@ -5,6 +5,8 @@ import {nextTick, onMounted, onUnmounted, reactive, ref, watch} from 'vue'
|
|||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
|
||||
import nginx_log from '@/api/nginx_log'
|
||||
import {debounce} from 'lodash'
|
||||
|
||||
const {$gettext} = useGettext()
|
||||
|
||||
|
@ -18,7 +20,6 @@ function logType() {
|
|||
}
|
||||
|
||||
const control = reactive({
|
||||
fetch: 'new',
|
||||
type: logType(),
|
||||
conf_name: route.query.conf_name,
|
||||
server_idx: parseInt(route.query.server_idx as string),
|
||||
|
@ -30,26 +31,51 @@ function openWs() {
|
|||
|
||||
websocket.onopen = () => {
|
||||
websocket.send(JSON.stringify({
|
||||
...control,
|
||||
fetch: 'new'
|
||||
...control
|
||||
}))
|
||||
}
|
||||
|
||||
websocket.onmessage = (m: any) => {
|
||||
const para = document.createElement('p')
|
||||
para.appendChild(document.createTextNode(m.data.trim()));
|
||||
|
||||
(logContainer.value as any as Node).appendChild(para);
|
||||
|
||||
(logContainer.value as any as Element).scroll({
|
||||
top: (logContainer.value as any as Element).scrollHeight,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
addLog(m.data)
|
||||
}
|
||||
}
|
||||
|
||||
function addLog(data: string, prepend: boolean = false) {
|
||||
const para = document.createElement('p')
|
||||
para.appendChild(document.createTextNode(data.trim()))
|
||||
|
||||
const node = (logContainer.value as any as Node)
|
||||
|
||||
if (prepend) {
|
||||
node.insertBefore(para, node.firstChild)
|
||||
} else {
|
||||
node.appendChild(para)
|
||||
}
|
||||
const elem = (logContainer.value as any as Element)
|
||||
elem.scroll({
|
||||
top: elem.scrollHeight,
|
||||
left: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const page = ref(0)
|
||||
|
||||
function init() {
|
||||
nginx_log.page(0, {
|
||||
conf_name: (route.query.conf_name as string),
|
||||
type: logType(),
|
||||
server_idx: 0,
|
||||
directive_idx: 0
|
||||
}).then(r => {
|
||||
page.value = r.page - 1
|
||||
r.content.split('\n').forEach((v: string) => {
|
||||
addLog(v)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
openWs()
|
||||
})
|
||||
|
||||
|
@ -66,6 +92,8 @@ watch(auto_refresh, (value) => {
|
|||
})
|
||||
|
||||
watch(route, () => {
|
||||
init()
|
||||
|
||||
control.type = logType();
|
||||
(logContainer.value as any as Element).innerHTML = ''
|
||||
|
||||
|
@ -88,6 +116,31 @@ onUnmounted(() => {
|
|||
})
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
|
||||
function on_scroll_log() {
|
||||
if (!loading.value && page.value > 0) {
|
||||
loading.value = true
|
||||
const elem = (logContainer.value as any as Element)
|
||||
if (elem.scrollTop / elem.scrollHeight < 0.333) {
|
||||
nginx_log.page(page.value, {
|
||||
conf_name: (route.query.conf_name as string),
|
||||
type: logType(),
|
||||
server_idx: 0,
|
||||
directive_idx: 0
|
||||
}).then(r => {
|
||||
page.value = r.page - 1
|
||||
r.content.split('\n').forEach((v: string) => {
|
||||
addLog(v, true)
|
||||
})
|
||||
}).finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
@ -97,20 +150,11 @@ const router = useRouter()
|
|||
<a-form-item :label="$gettext('Auto Refresh')">
|
||||
<a-switch v-model:checked="auto_refresh"/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$gettext('Fetch')">
|
||||
<a-select v-model:value="control.fetch" style="max-width: 200px">
|
||||
<a-select-option value="all">
|
||||
<translate>All logs</translate>
|
||||
</a-select-option>
|
||||
<a-select-option value="new">
|
||||
<translate>New logs</translate>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-card>
|
||||
<pre class="nginx-log-container" ref="logContainer"></pre>
|
||||
<pre class="nginx-log-container" ref="logContainer"
|
||||
@scroll="debounce(on_scroll_log,100, null)()"></pre>
|
||||
</a-card>
|
||||
</a-card>
|
||||
<footer-tool-bar v-if="control.type==='site'">
|
||||
|
@ -125,6 +169,7 @@ const router = useRouter()
|
|||
height: 60vh;
|
||||
overflow: scroll;
|
||||
padding: 5px;
|
||||
margin-bottom: 0;
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
|
|
|
@ -8,25 +8,168 @@ import (
|
|||
"github.com/gorilla/websocket"
|
||||
"github.com/hpcloud/tail"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cast"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
PageSize = 128 * 1024
|
||||
)
|
||||
|
||||
type controlStruct struct {
|
||||
Fetch string `json:"fetch"`
|
||||
Type string `json:"type"`
|
||||
ConfName string `json:"conf_name"`
|
||||
ServerIdx int `json:"server_idx"`
|
||||
DirectiveIdx int `json:"directive_idx"`
|
||||
}
|
||||
|
||||
type nginxLogPageResp struct {
|
||||
Content string `json:"content"`
|
||||
Page int64 `json:"page"`
|
||||
}
|
||||
|
||||
func GetNginxLogPage(c *gin.Context) {
|
||||
page := cast.ToInt64(c.Query("page"))
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
|
||||
var control controlStruct
|
||||
if !BindAndValid(c, &control) {
|
||||
return
|
||||
}
|
||||
|
||||
logPath, err := getLogPath(&control)
|
||||
|
||||
if err != nil {
|
||||
log.Println("error GetNginxLogPage", err)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(logPath)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||
log.Println("error GetNginxLogPage open file", err)
|
||||
return
|
||||
}
|
||||
|
||||
logFileStat, err := os.Stat(logPath)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||
log.Println("error GetNginxLogPage stat", err)
|
||||
return
|
||||
}
|
||||
|
||||
totalPage := logFileStat.Size() / PageSize
|
||||
|
||||
if logFileStat.Size()%PageSize > 0 {
|
||||
totalPage++
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
var offset int64
|
||||
if page == 0 {
|
||||
page = totalPage
|
||||
}
|
||||
|
||||
buf = make([]byte, PageSize)
|
||||
offset = (page - 1) * PageSize
|
||||
|
||||
// seek
|
||||
_, err = f.Seek(offset, io.SeekStart)
|
||||
if err != nil && err != io.EOF {
|
||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||
log.Println("error GetNginxLogPage seek", err)
|
||||
return
|
||||
}
|
||||
|
||||
n, err := f.Read(buf)
|
||||
|
||||
if err != nil && err != io.EOF {
|
||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||
log.Println("error GetNginxLogPage read buf", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, nginxLogPageResp{
|
||||
Page: page,
|
||||
Content: string(buf[:n]),
|
||||
})
|
||||
}
|
||||
|
||||
func getLogPath(control *controlStruct) (logPath string, err error) {
|
||||
switch control.Type {
|
||||
case "site":
|
||||
var config *nginx.NgxConfig
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), control.ConfName)
|
||||
config, err = nginx.ParseNgxConfig(path)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error parsing ngx config")
|
||||
return
|
||||
}
|
||||
|
||||
if control.ServerIdx >= len(config.Servers) {
|
||||
err = errors.New("serverIdx out of range")
|
||||
return
|
||||
}
|
||||
|
||||
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
|
||||
err = errors.New("DirectiveIdx out of range")
|
||||
return
|
||||
}
|
||||
|
||||
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
|
||||
|
||||
switch directive.Directive {
|
||||
case "access_log", "error_log":
|
||||
// ok
|
||||
default:
|
||||
err = errors.New("directive.Params neither access_log nor error_log")
|
||||
return
|
||||
}
|
||||
|
||||
if directive.Params == "" {
|
||||
err = errors.New("directive.Params is empty")
|
||||
return
|
||||
}
|
||||
|
||||
logPath = directive.Params
|
||||
|
||||
case "error":
|
||||
if settings.NginxLogSettings.ErrorLogPath == "" {
|
||||
err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
|
||||
" see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
|
||||
return
|
||||
}
|
||||
logPath = settings.NginxLogSettings.ErrorLogPath
|
||||
|
||||
default:
|
||||
if settings.NginxLogSettings.AccessLogPath == "" {
|
||||
err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
|
||||
" see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
|
||||
return
|
||||
}
|
||||
logPath = settings.NginxLogSettings.AccessLogPath
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("tailNginxLog recovery", err)
|
||||
_ = ws.WriteMessage(websocket.TextMessage, err.([]byte))
|
||||
err = ws.WriteMessage(websocket.TextMessage, err.([]byte))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
@ -34,63 +177,16 @@ func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan ch
|
|||
control := <-controlChan
|
||||
|
||||
for {
|
||||
var seek tail.SeekInfo
|
||||
if control.Fetch != "all" {
|
||||
seek.Offset = 0
|
||||
seek.Whence = io.SeekEnd
|
||||
logPath, err := getLogPath(&control)
|
||||
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
var logPath string
|
||||
switch control.Type {
|
||||
case "site":
|
||||
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), control.ConfName)
|
||||
config, err := nginx.ParseNgxConfig(path)
|
||||
if err != nil {
|
||||
errChan <- errors.Wrap(err, "error parsing ngx config")
|
||||
return
|
||||
}
|
||||
|
||||
if control.ServerIdx >= len(config.Servers) {
|
||||
errChan <- errors.New("serverIdx out of range")
|
||||
return
|
||||
}
|
||||
|
||||
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
|
||||
errChan <- errors.New("DirectiveIdx out of range")
|
||||
return
|
||||
}
|
||||
|
||||
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
|
||||
|
||||
switch directive.Directive {
|
||||
case "access_log", "error_log":
|
||||
// ok
|
||||
default:
|
||||
errChan <- errors.New("directive.Params neither access_log nor error_log")
|
||||
return
|
||||
}
|
||||
|
||||
if directive.Params == "" {
|
||||
errChan <- errors.New("directive.Params is empty")
|
||||
return
|
||||
}
|
||||
|
||||
logPath = directive.Params
|
||||
|
||||
case "error":
|
||||
if settings.NginxLogSettings.ErrorLogPath == "" {
|
||||
errChan <- errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
|
||||
" see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
|
||||
return
|
||||
}
|
||||
logPath = settings.NginxLogSettings.ErrorLogPath
|
||||
|
||||
default:
|
||||
if settings.NginxLogSettings.AccessLogPath == "" {
|
||||
errChan <- errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
|
||||
" see https://github.com/0xJacky/nginx-ui/wiki/Nginx-Log-Configuration for more information")
|
||||
return
|
||||
}
|
||||
logPath = settings.NginxLogSettings.AccessLogPath
|
||||
seek := tail.SeekInfo{
|
||||
Offset: 0,
|
||||
Whence: io.SeekEnd,
|
||||
}
|
||||
|
||||
// Create a tail
|
||||
|
|
|
@ -95,6 +95,7 @@ func InitRouter() *gin.Engine {
|
|||
|
||||
// Nginx log
|
||||
g.GET("nginx_log", api.NginxLog)
|
||||
g.POST("nginx_log", api.GetNginxLogPage)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue