10 / access and error logs

This commit is contained in:
serogaq 2024-12-11 13:31:11 +03:00
parent 26abb907a0
commit 1a9c0cf875
No known key found for this signature in database
GPG key ID: 6657A27160536D7E
17 changed files with 261 additions and 105 deletions

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"os" "os"
"time" "time"
"strings"
"github.com/op/go-logging" "github.com/op/go-logging"
) )
@ -127,35 +126,3 @@ func GetLogs(c int, level string) []string {
} }
return output return output
} }
func GetLogsSniffedDomains(c int) []string {
var output []string
logLevel, _ := logging.LogLevel("info")
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
if logBuffer[i].level <= logLevel && strings.Contains(logBuffer[i].log, "sniffed domain: ") {
index := strings.LastIndex(logBuffer[i].log, ": ")
if index != -1 {
domain := logBuffer[i].log[index+2:]
output = append(output, fmt.Sprintf("%s - %s", logBuffer[i].time, domain))
}
}
}
return output
}
func GetLogsBlockedDomains(c int) []string {
var output []string
logLevel, _ := logging.LogLevel("info")
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
if logBuffer[i].level <= logLevel && strings.Contains(logBuffer[i].log, "[blocked] for ") {
index := strings.LastIndex(logBuffer[i].log, "for [")
if index != -1 {
domain := strings.Replace(logBuffer[i].log[index+5:], "]", "", -1)
output = append(output, fmt.Sprintf("%s - %s", logBuffer[i].time, domain))
}
}
}
return output
}

View file

@ -45,8 +45,8 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/restartXrayService", a.restartXrayService) g.POST("/restartXrayService", a.restartXrayService)
g.POST("/installXray/:version", a.installXray) g.POST("/installXray/:version", a.installXray)
g.POST("/logs/:count", a.getLogs) g.POST("/logs/:count", a.getLogs)
g.GET("/logs-sniffed/:count", a.getLogsSniffedDomains) g.POST("/access-log/:count", a.getAccessLog)
g.GET("/logs-blocked/:count", a.getLogsBlockedDomains) g.POST("/error-log/:count", a.getErrorLog)
g.POST("/getConfigJson", a.getConfigJson) g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb) g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB) g.POST("/importDB", a.importDB)
@ -127,15 +127,17 @@ func (a *ServerController) getLogs(c *gin.Context) {
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
} }
func (a *ServerController) getLogsSniffedDomains(c *gin.Context) { func (a *ServerController) getAccessLog(c *gin.Context) {
count := c.Param("count") count := c.Param("count")
logs := a.serverService.GetLogsSniffedDomains(count) grep := c.PostForm("grep")
logs := a.serverService.GetAccessLog(count, grep)
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
} }
func (a *ServerController) getLogsBlockedDomains(c *gin.Context) { func (a *ServerController) getErrorLog(c *gin.Context) {
count := c.Param("count") count := c.Param("count")
logs := a.serverService.GetLogsBlockedDomains(count) grep := c.PostForm("grep")
logs := a.serverService.GetErrorLog(count, grep)
jsonObj(c, logs, nil) jsonObj(c, logs, nil)
} }

View file

@ -125,7 +125,8 @@
<a-card hoverable> <a-card hoverable>
<b>{{ i18n "menu.link" }}:</b> <b>{{ i18n "menu.link" }}:</b>
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openLogDomains()">{{ i18n "pages.index.logDomains" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openAccessLog">{{ i18n "pages.index.accessLog" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openErrorLog">{{ i18n "pages.index.errorLog" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag> <a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
</a-card> </a-card>
@ -315,42 +316,87 @@
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div>
</a-modal> </a-modal>
<a-modal id="log-domains-modal" v-model="logDomainsModal.visible" <a-modal id="access-log-modal" v-model="accessLogModal.visible"
:closable="true" @cancel="() => logDomainsModal.visible = false" :closable="true" @cancel="() => accessLogModal.visible = false"
:class="themeSwitcher.currentTheme" :class="themeSwitcher.currentTheme"
width="800px" footer=""> width="800px" footer="">
<template slot="title"> <template slot="title">
{{ i18n "pages.index.logDomains" }} {{ i18n "pages.index.accessLog" }}
<a-icon :spin="logDomainsModal.loading" <a-icon :spin="accessLogModal.loading"
type="sync" type="sync"
style="vertical-align: middle; margin-left: 10px;" style="vertical-align: middle; margin-left: 10px;"
:disabled="logDomainsModal.loading" :disabled="accessLogModal.loading"
@click="openLogDomains()"> @click="openAccessLog()">
</a-icon> </a-icon>
</template> </template>
<a-form layout="inline"> <a-form layout="inline">
<a-form-item style="margin-right: 0.5rem;"> <a-form-item style="margin-right: 0.5rem;">
<a-input-group compact> <a-input-group compact>
<a-select size="small" v-model="logDomainsModal.rows" style="width:90px;" <a-select size="small" v-model="accessLogModal.rows" style="width:90px;"
@change="openLogDomains()" :dropdown-class-name="themeSwitcher.currentTheme"> @change="openAccessLog()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="500">Few</a-select-option> <a-select-option value="500">500</a-select-option>
<a-select-option value="2500">Medium</a-select-option> <a-select-option value="2500">2500</a-select-option>
<a-select-option value="7000">Many</a-select-option> <a-select-option value="7000">7000</a-select-option>
</a-select>
<a-select size="small" v-model="logDomainsModal.type" style="width:95px;"
@change="openLogDomains()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="sniffed">Sniffed</a-select-option>
<a-select-option value="blocked">Blocked</a-select-option>
</a-select> </a-select>
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item>
<a-input-search
v-model="accessLogModal.grep"
placeholder="Search.."
size="small"
enter-button
@search="openAccessLog"
/>
</a-form-item>
<a-form-item style="float: right;"> <a-form-item style="float: right;">
<a-button type="primary" icon="download" <a-button type="primary" icon="download"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logDomainsModal.logs?.join('\n'))" download="x-ui-domains.log"> :href="'data:application/text;charset=utf-8,' + encodeURIComponent(accessLogModal.logs?.join('\n'))" download="xray-access.log">
</a-button> </a-button>
</a-form-item> </a-form-item>
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logDomainsModal.formattedLogs"></div> <div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="accessLogModal.formattedLogs"></div>
</a-modal>
<a-modal id="error-log-modal" v-model="errorLogModal.visible"
:closable="true" @cancel="() => errorLogModal.visible = false"
:class="themeSwitcher.currentTheme"
width="800px" footer="">
<template slot="title">
{{ i18n "pages.index.errorLog" }}
<a-icon :spin="errorLogModal.loading"
type="sync"
style="vertical-align: middle; margin-left: 10px;"
:disabled="errorLogModal.loading"
@click="openErrorLog()">
</a-icon>
</template>
<a-form layout="inline">
<a-form-item style="margin-right: 0.5rem;">
<a-input-group compact>
<a-select size="small" v-model="errorLogModal.rows" style="width:90px;"
@change="openErrorLog()" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="500">500</a-select-option>
<a-select-option value="2500">2500</a-select-option>
<a-select-option value="7000">7000</a-select-option>
</a-select>
</a-input-group>
</a-form-item>
<a-form-item>
<a-input-search
v-model="errorLogModal.grep"
placeholder="Search.."
size="small"
enter-button
@search="openErrorLog"
/>
</a-form-item>
<a-form-item style="float: right;">
<a-button type="primary" icon="download"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(errorLogModal.logs?.join('\n'))" download="xray-error.log">
</a-button>
</a-form-item>
</a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="errorLogModal.formattedLogs"></div>
</a-modal> </a-modal>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
:closable="true" footer="" :closable="true" footer=""
@ -528,38 +574,99 @@
}, },
}; };
const logDomainsModal = { const accessLogModal = {
visible: false, visible: false,
logs: [], logs: [],
rows: 500, rows: 500,
type: 'sniffed', grep: '',
loading: false, loading: false,
show(logs) { show(logs) {
this.visible = true; this.visible = true;
this.logs = logs; this.logs = logs;
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs, this.type) : "No Record..."; this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
}, },
formatLogs(logs, type) { formatLogs(logs) {
let formattedLogs = ''; let formattedLogs = '';
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
logs.forEach((log, index) => { logs.forEach((log, index) => {
let [data, message] = log.split(" - ",2); if (log.length <= 3) {
const parts = data.split(" "); return;
if(index>0) formattedLogs += '<br>'; }
let [date, time] = log.split(' ', 2);
let message = log.substr(date?.length !== undefined && time?.length !== undefined ? (date.length+time.length+2) : 0);
let messageColor = levelColors[5];
if (message && message.indexOf('-> blocked') !== -1) {
messageColor = levelColors[4];
}
if (index > 0) formattedLogs += '<br/>';
formattedLogs += `<span style="color: ${levelColors[0]};">${date} ${time}</span> `;
formattedLogs += `- <span style="color: ${messageColor};">${message}</span>`;
});
if (parts.length === 2) { return formattedLogs;
const d = parts[0]; },
const t = parts[1]; hide() {
formattedLogs += `<span style="color: gray;">${d} ${t}</span>`; this.visible = false;
},
};
const errorLogModal = {
visible: false,
logs: [],
rows: 500,
grep: '',
loading: false,
show(logs) {
this.visible = true;
this.logs = logs;
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
},
formatLogs(logs) {
let formattedLogs = '';
const levels = ["DEBUG","INFO","NOTICE","WARNING","ERROR"];
const levelsMap = {"[Debug]": levels[0], "[Info]": levels[1], "[Notice]": levels[2], "[Warning]": levels[3], "[Error]": levels[4]};
const levelColors = ["#3c89e8","#008771","#008771","#f37b24","#e04141","#bcbcbc"];
const idColors = ['#CADABF','#5F6F65','#FFDFD6','#BC9F8B','#C9DABF','#9CA986','#808D7C','#E7E8D8','#B5CFB7'];
let idColorIndex = 0;
let lastLogId = '';
logs.forEach((log, index) => {
if (log.length <= 3) {
return;
}
let [date, time, levelTag, id] = log.split(' ', 4);
if (date?.length === undefined || time?.length === undefined || levelTag?.length === undefined || id?.length === undefined) {
if (index > 0) formattedLogs += '<br/>';
formattedLogs += log;
} else {
let message = log.substr(
date?.length !== undefined && time?.length !== undefined && levelTag?.length !== undefined && id?.length !== undefined ?
(date.length + time.length + levelTag.length + id.length + 4) : 0
);
let level = levelsMap[levelTag];
const levelIndex = levels.indexOf(level, levels) || 5;
if (index > 0) formattedLogs += '<br/>';
formattedLogs += `<span style="color: ${levelColors[0]};">${date} ${time}</span> `;
formattedLogs += `<span style="color: ${levelColors[levelIndex]};">${level}</span> `;
formattedLogs += ' - ';
if (id.substr(0, 1) === '[') {
let idColor = idColors[idColorIndex];
if (lastLogId !== '' && lastLogId !== id) {
idColorIndex++;
}
if (idColorIndex >= idColors.length) {
idColorIndex = 0;
}
lastLogId = id;
formattedLogs += `<span style="color: ${idColor};">${id}</span> <span style="color: ${levelColors[5]};">${message}</span>`;
} else { } else {
formattedLogs += `<span style="color: gray;">${data}</span>`; formattedLogs += `<span style="color: ${levelColors[5]};">${id} ${message}</span>`;
} }
}
if (message) {
message = "<b>"+(type === 'sniffed' ? 'Sniffed' : 'Blocked')+": </b>" + message;
}
formattedLogs += message ? ' - ' + message : '';
}); });
return formattedLogs; return formattedLogs;
@ -674,15 +781,25 @@
await PromiseUtil.sleep(500); await PromiseUtil.sleep(500);
logModal.loading = false; logModal.loading = false;
}, },
async openLogDomains(){ async openAccessLog() {
logDomainsModal.loading = true; accessLogModal.loading = true;
const msg = await HttpUtil.get('server/logs-'+(logDomainsModal.type==='blocked'?'blocked':'sniffed')+'/'+logDomainsModal.rows); const msg = await HttpUtil.post('server/access-log/'+accessLogModal.rows, { grep: accessLogModal.grep });
if (!msg.success) { if (!msg.success) {
return; return;
} }
logDomainsModal.show(msg.obj); accessLogModal.show(msg.obj);
await PromiseUtil.sleep(500); await PromiseUtil.sleep(500);
logDomainsModal.loading = false; accessLogModal.loading = false;
},
async openErrorLog() {
errorLogModal.loading = true;
const msg = await HttpUtil.post('server/error-log/'+errorLogModal.rows, { grep: errorLogModal.grep });
if (!msg.success) {
return;
}
errorLogModal.show(msg.obj);
await PromiseUtil.sleep(500);
errorLogModal.loading = false;
}, },
async openConfig() { async openConfig() {
this.loading(true); this.loading(true);

View file

@ -448,22 +448,56 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
return lines return lines
} }
func (s *ServerService) GetLogsSniffedDomains(count string) []string { func (s *ServerService) GetAccessLog(count string, grep string) []string {
c, _ := strconv.Atoi(count) accessLogPath, err := xray.GetAccessLogPath()
var lines []string if err != nil {
return []string{"Error in Access Log retrieval: " + err.Error()}
}
lines = logger.GetLogsSniffedDomains(c) if accessLogPath != "none" && accessLogPath != "" {
var cmdArgs []string
return lines if grep != "" {
cmdArgs = []string{"bash", "-c", fmt.Sprintf("tail -n %s %s | grep '%s' | sort -r", count, accessLogPath, grep)}
} else {
cmdArgs = []string{"bash", "-c", fmt.Sprintf("tail -n %s %s | sort -r", count, accessLogPath)}
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return []string{"Failed to run command: " + err.Error()}
}
return strings.Split(out.String(), "\n")
} else {
return []string{"Access Log disabled!"}
}
} }
func (s *ServerService) GetLogsBlockedDomains(count string) []string { func (s *ServerService) GetErrorLog(count string, grep string) []string {
c, _ := strconv.Atoi(count) errorLogPath, err := xray.GetErrorLogPath()
var lines []string if err != nil {
return []string{"Error in Error Log retrieval: " + err.Error()}
}
lines = logger.GetLogsBlockedDomains(c) if errorLogPath != "none" && errorLogPath != "" {
var cmdArgs []string
return lines if grep != "" {
cmdArgs = []string{"bash", "-c", fmt.Sprintf("tail -n %s %s | grep '%s' | sort -r", count, errorLogPath, grep)}
} else {
cmdArgs = []string{"bash", "-c", fmt.Sprintf("tail -n %s %s | sort -r", count, errorLogPath)}
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return []string{"Failed to run command: " + err.Error()}
}
return strings.Split(out.String(), "\n")
} else {
return []string{"Error Log disabled!"}
}
} }
func (s *ServerService) GetConfigJson() (interface{}, error) { func (s *ServerService) GetConfigJson() (interface{}, error) {

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to" "xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to"
"dontRefresh" = "Installation is in progress, please do not refresh this page" "dontRefresh" = "Installation is in progress, please do not refresh this page"
"logs" = "Logs" "logs" = "Logs"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Config" "config" = "Config"
"backup" = "Backup & Restore" "backup" = "Backup & Restore"
"backupTitle" = "Database Backup & Restore" "backupTitle" = "Database Backup & Restore"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "¿Estás seguro de que deseas cambiar la versión de Xray a" "xraySwitchVersionDialogDesc" = "¿Estás seguro de que deseas cambiar la versión de Xray a"
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página." "dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
"logs" = "Registros" "logs" = "Registros"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Configuración" "config" = "Configuración"
"backup" = "Copia de Seguridad y Restauración" "backup" = "Copia de Seguridad y Restauración"
"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos" "backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟" "xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه‌ مطمئن هستید؟"
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید" "dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
"logs" = "گزارش‌ها" "logs" = "گزارش‌ها"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "پیکربندی" "config" = "پیکربندی"
"backup" = "پشتیبان‌گیری" "backup" = "پشتیبان‌گیری"
"backupTitle" = "پشتیبان‌گیری دیتابیس" "backupTitle" = "پشتیبان‌گیری دیتابیس"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi" "xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini" "dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log" "logs" = "Log"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Konfigurasi" "config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan" "backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database" "backupTitle" = "Cadangan & Pulihkan Database"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか" "xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか"
"dontRefresh" = "インストール中、このページをリロードしないでください" "dontRefresh" = "インストール中、このページをリロードしないでください"
"logs" = "ログ" "logs" = "ログ"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "設定" "config" = "設定"
"backup" = "バックアップと復元" "backup" = "バックアップと復元"
"backupTitle" = "データベースのバックアップと復元" "backupTitle" = "データベースのバックアップと復元"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Tem certeza de que deseja alterar a versão do Xray para" "xraySwitchVersionDialogDesc" = "Tem certeza de que deseja alterar a versão do Xray para"
"dontRefresh" = "Instalação em andamento, por favor não atualize a página" "dontRefresh" = "Instalação em andamento, por favor não atualize a página"
"logs" = "Logs" "logs" = "Logs"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Configuração" "config" = "Configuração"
"backup" = "Backup e Restauração" "backup" = "Backup e Restauração"
"backupTitle" = "Backup e Restauração do Banco de Dados" "backupTitle" = "Backup e Restauração do Banco de Dados"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" "xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
"dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу" "dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу"
"logs" = "Логи" "logs" = "Логи"
"logDomains" = "Логи доменов" "accessLog" = "Access Лог"
"errorLog" = "Error Лог"
"config" = "Конфигурация" "config" = "Конфигурация"
"backup" = "Бэкап и восстановление" "backup" = "Бэкап и восстановление"
"backupTitle" = "База данных бэкапа и восстановления" "backupTitle" = "База данных бэкапа и восстановления"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Xray sürümünü değiştirmek istediğinizden emin misiniz" "xraySwitchVersionDialogDesc" = "Xray sürümünü değiştirmek istediğinizden emin misiniz"
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin" "dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
"logs" = "Günlükler" "logs" = "Günlükler"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Yapılandırma" "config" = "Yapılandırma"
"backup" = "Yedekle & Geri Yükle" "backup" = "Yedekle & Geri Yükle"
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme" "backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на" "xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на"
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку" "dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
"logs" = "Журнали" "logs" = "Журнали"
"logDomains" = "Логи доменов" "accessLog" = "Access Лог"
"errorLog" = "Error Лог"
"config" = "Конфігурація" "config" = "Конфігурація"
"backup" = "Резервне копіювання та відновлення" "backup" = "Резервне копіювання та відновлення"
"backupTitle" = "Резервне копіювання та відновлення бази даних" "backupTitle" = "Резервне копіювання та відновлення бази даних"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang" "xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang"
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này." "dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
"logs" = "Nhật ký" "logs" = "Nhật ký"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "Cấu hình" "config" = "Cấu hình"
"backup" = "Sao lưu & Khôi phục" "backup" = "Sao lưu & Khôi phục"
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu" "backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至" "xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至"
"dontRefresh" = "安装中,请勿刷新此页面" "dontRefresh" = "安装中,请勿刷新此页面"
"logs" = "日志" "logs" = "日志"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "配置" "config" = "配置"
"backup" = "备份和恢复" "backup" = "备份和恢复"
"backupTitle" = "备份和恢复数据库" "backupTitle" = "备份和恢复数据库"

View file

@ -105,7 +105,8 @@
"xraySwitchVersionDialogDesc" = "是否切換 Xray 版本至" "xraySwitchVersionDialogDesc" = "是否切換 Xray 版本至"
"dontRefresh" = "安裝中,請勿重新整理此頁面" "dontRefresh" = "安裝中,請勿重新整理此頁面"
"logs" = "日誌" "logs" = "日誌"
"logDomains" = "Log Domains" "accessLog" = "Access Log"
"errorLog" = "Error Log"
"config" = "配置" "config" = "配置"
"backup" = "備份和恢復" "backup" = "備份和恢復"
"backupTitle" = "備份和恢復資料庫" "backupTitle" = "備份和恢復資料庫"

View file

@ -81,6 +81,30 @@ func GetAccessLogPath() (string, error) {
return "", err return "", err
} }
func GetErrorLogPath() (string, error) {
config, err := os.ReadFile(GetConfigPath())
if err != nil {
logger.Warningf("Failed to read configuration file: %s", err)
return "", err
}
jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig)
if err != nil {
logger.Warningf("Failed to parse JSON configuration: %s", err)
return "", err
}
if jsonConfig["log"] != nil {
jsonLog := jsonConfig["log"].(map[string]interface{})
if jsonLog["error"] != nil {
errorLogPath := jsonLog["error"].(string)
return errorLogPath, nil
}
}
return "", err
}
func stopProcess(p *Process) { func stopProcess(p *Process) {
p.Stop() p.Stop()
} }