mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-03-17 03:04:46 +08:00
feat: 计划任务增加网站日志切割 (#1116)
Refs https://github.com/1Panel-dev/1Panel/issues/495
This commit is contained in:
parent
3e068a0020
commit
626782102a
@ -3,9 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1Panel-dev/1Panel/backend/utils/files"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||||
@ -54,6 +56,11 @@ func (u *CronjobService) HandleJob(cronjob *model.Cronjob) {
|
|||||||
}
|
}
|
||||||
message = []byte(stdout)
|
message = []byte(stdout)
|
||||||
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||||
|
case "cutWebsiteLog":
|
||||||
|
record.File, err = u.handleCutWebsiteLog(cronjob, record.StartTime)
|
||||||
|
if err != nil {
|
||||||
|
global.LOG.Errorf("cut website log file failed, err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
|
cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), string(message))
|
||||||
@ -157,7 +164,7 @@ func handleTar(sourceDir, targetDir, name, exclusionRules string) error {
|
|||||||
if len(exclude) == 0 {
|
if len(exclude) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
excludeRules += (" --exclude " + exclude)
|
excludeRules += " --exclude " + exclude
|
||||||
}
|
}
|
||||||
path := ""
|
path := ""
|
||||||
if strings.Contains(sourceDir, "/") {
|
if strings.Contains(sourceDir, "/") {
|
||||||
@ -262,6 +269,72 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, app *repo.RootInf
|
|||||||
return paths, nil
|
return paths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time) (string, error) {
|
||||||
|
var (
|
||||||
|
websites []string
|
||||||
|
err error
|
||||||
|
filePaths []string
|
||||||
|
)
|
||||||
|
if cronjob.Website == "all" {
|
||||||
|
websites, _ = NewIWebsiteService().GetWebsiteOptions()
|
||||||
|
if len(websites) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
websites = append(websites, cronjob.Website)
|
||||||
|
}
|
||||||
|
|
||||||
|
nginx, err := getAppInstallByKey(constant.AppOpenresty)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
baseDir := path.Join(nginx.GetPath(), "www", "sites")
|
||||||
|
fileOp := files.NewFileOp()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(websites))
|
||||||
|
for _, websiteName := range websites {
|
||||||
|
name := websiteName
|
||||||
|
go func() {
|
||||||
|
website, _ := websiteRepo.GetFirst(websiteRepo.WithDomain(name))
|
||||||
|
if website.ID == 0 {
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
websiteLogDir := path.Join(baseDir, website.PrimaryDomain, "log")
|
||||||
|
srcAccessLogPath := path.Join(websiteLogDir, "access.log")
|
||||||
|
srcErrorLogPath := path.Join(websiteLogDir, "error.log")
|
||||||
|
dstLogDir := path.Join(global.CONF.System.Backup, "log", "website", website.PrimaryDomain)
|
||||||
|
if !fileOp.Stat(dstLogDir) {
|
||||||
|
_ = os.MkdirAll(dstLogDir, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstName := fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format("20060102150405"))
|
||||||
|
filePaths = append(filePaths, path.Join(dstLogDir, dstName))
|
||||||
|
if err = fileOp.Compress([]string{srcAccessLogPath, srcErrorLogPath}, dstLogDir, dstName, files.Gz); err != nil {
|
||||||
|
global.LOG.Errorf("There was an error in compressing the website[%s] access.log", website.PrimaryDomain)
|
||||||
|
} else {
|
||||||
|
_ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), 0755)
|
||||||
|
_ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), 0755)
|
||||||
|
}
|
||||||
|
global.LOG.Infof("The website[%s] log file was successfully rotated in the directory [%s]", website.PrimaryDomain, dstLogDir)
|
||||||
|
var record model.BackupRecord
|
||||||
|
record.Type = "cutWebsiteLog"
|
||||||
|
record.Name = cronjob.Website
|
||||||
|
record.Source = "LOCAL"
|
||||||
|
record.BackupType = "LOCAL"
|
||||||
|
record.FileDir = dstLogDir
|
||||||
|
record.FileName = dstName
|
||||||
|
if err = backupRepo.CreateRecord(&record); err != nil {
|
||||||
|
global.LOG.Errorf("save backup record failed, err: %v", err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
u.HandleRmExpired("LOCAL", "", cronjob, nil)
|
||||||
|
return strings.Join(filePaths, ","), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
func (u *CronjobService) handleWebsite(cronjob model.Cronjob, backup model.BackupAccount, startTime time.Time) ([]string, error) {
|
||||||
var paths []string
|
var paths []string
|
||||||
localDir, err := loadLocalDir()
|
localDir, err := loadLocalDir()
|
||||||
|
10
frontend/components.d.ts
vendored
10
frontend/components.d.ts
vendored
@ -5,7 +5,6 @@
|
|||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
404: typeof import('./src/components/error-message/404.vue')['default']
|
404: typeof import('./src/components/error-message/404.vue')['default']
|
||||||
AppLayout: typeof import('./src/components/app-layout/index.vue')['default']
|
|
||||||
AppStatus: typeof import('./src/components/app-status/index.vue')['default']
|
AppStatus: typeof import('./src/components/app-status/index.vue')['default']
|
||||||
BackButton: typeof import('./src/components/back-button/index.vue')['default']
|
BackButton: typeof import('./src/components/back-button/index.vue')['default']
|
||||||
Backup: typeof import('./src/components/backup/index.vue')['default']
|
Backup: typeof import('./src/components/backup/index.vue')['default']
|
||||||
@ -13,13 +12,11 @@ declare module 'vue' {
|
|||||||
BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default']
|
BreadCrumbsItem: typeof import('./src/components/bread-crumbs/bread-crumbs-item.vue')['default']
|
||||||
CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default']
|
CardWithHeader: typeof import('./src/components/card-with-header/index.vue')['default']
|
||||||
Codemirror: typeof import('./src/components/codemirror-dialog/codemirror.vue')['default']
|
Codemirror: typeof import('./src/components/codemirror-dialog/codemirror.vue')['default']
|
||||||
Collapse: typeof import('./src/components/app-layout/menu/components/Collapse.vue')['default']
|
|
||||||
ComplexTable: typeof import('./src/components/complex-table/index.vue')['default']
|
ComplexTable: typeof import('./src/components/complex-table/index.vue')['default']
|
||||||
ConfirmDialog: typeof import('./src/components/confirm-dialog/index.vue')['default']
|
ConfirmDialog: typeof import('./src/components/confirm-dialog/index.vue')['default']
|
||||||
ContainerLog: typeof import('./src/components/container-log/index.vue')['default']
|
ContainerLog: typeof import('./src/components/container-log/index.vue')['default']
|
||||||
DrawerHeader: typeof import('./src/components/drawer-header/index.vue')['default']
|
DrawerHeader: typeof import('./src/components/drawer-header/index.vue')['default']
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
@ -29,7 +26,6 @@ declare module 'vue' {
|
|||||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
|
||||||
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
|
||||||
@ -40,7 +36,6 @@ declare module 'vue' {
|
|||||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
ElFooter: typeof import('element-plus/es')['ElFooter']
|
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
@ -48,7 +43,6 @@ declare module 'vue' {
|
|||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
ElLink: typeof import('element-plus/es')['ElLink']
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
@ -76,20 +70,16 @@ declare module 'vue' {
|
|||||||
Err_ip: typeof import('./src/components/error-message/err_ip.vue')['default']
|
Err_ip: typeof import('./src/components/error-message/err_ip.vue')['default']
|
||||||
FileList: typeof import('./src/components/file-list/index.vue')['default']
|
FileList: typeof import('./src/components/file-list/index.vue')['default']
|
||||||
FileRole: typeof import('./src/components/file-role/index.vue')['default']
|
FileRole: typeof import('./src/components/file-role/index.vue')['default']
|
||||||
Footer: typeof import('./src/components/app-layout/footer/index.vue')['default']
|
|
||||||
FormButton: typeof import('./src/components/layout-content/form-button.vue')['default']
|
FormButton: typeof import('./src/components/layout-content/form-button.vue')['default']
|
||||||
Group: typeof import('./src/components/group/index.vue')['default']
|
Group: typeof import('./src/components/group/index.vue')['default']
|
||||||
InfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']
|
InfiniteScroll: typeof import('element-plus/es')['ElInfiniteScroll']
|
||||||
LayoutContent: typeof import('./src/components/layout-content/index.vue')['default']
|
LayoutContent: typeof import('./src/components/layout-content/index.vue')['default']
|
||||||
Line: typeof import('./src/components/v-charts/components/Line.vue')['default']
|
Line: typeof import('./src/components/v-charts/components/Line.vue')['default']
|
||||||
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
Logo: typeof import('./src/components/app-layout/menu/components/Logo.vue')['default']
|
|
||||||
Menu: typeof import('./src/components/app-layout/menu/index.vue')['default']
|
|
||||||
MsgInfo: typeof import('./src/components/msg-info/index.vue')['default']
|
MsgInfo: typeof import('./src/components/msg-info/index.vue')['default']
|
||||||
Popover: typeof import('element-plus/es')['ElPopoverDirective']
|
Popover: typeof import('element-plus/es')['ElPopoverDirective']
|
||||||
RouterButton: typeof import('./src/components/router-button/index.vue')['default']
|
RouterButton: typeof import('./src/components/router-button/index.vue')['default']
|
||||||
Status: typeof import('./src/components/status/index.vue')['default']
|
Status: typeof import('./src/components/status/index.vue')['default']
|
||||||
SubItem: typeof import('./src/components/app-layout/menu/components/sub-item.vue')['default']
|
|
||||||
SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default']
|
SvgIcon: typeof import('./src/components/svg-icon/svg-icon.vue')['default']
|
||||||
SystemUpgrade: typeof import('./src/components/system-upgrade/index.vue')['default']
|
SystemUpgrade: typeof import('./src/components/system-upgrade/index.vue')['default']
|
||||||
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
|
TableSetting: typeof import('./src/components/table-setting/index.vue')['default']
|
||||||
|
@ -656,6 +656,8 @@ const message = {
|
|||||||
cleanDataHelper: 'Delete the backup file generated during this task.',
|
cleanDataHelper: 'Delete the backup file generated during this task.',
|
||||||
noLogs: 'No task output yet...',
|
noLogs: 'No task output yet...',
|
||||||
errPath: 'Backup path [{0}] error, cannot download!',
|
errPath: 'Backup path [{0}] error, cannot download!',
|
||||||
|
cutWebsiteLog: 'cut website log',
|
||||||
|
cutWebsiteLogHelper: 'The cut log files will be backed up to the backup directory of 1Panel',
|
||||||
},
|
},
|
||||||
monitor: {
|
monitor: {
|
||||||
monitor: 'Monitor',
|
monitor: 'Monitor',
|
||||||
|
@ -663,6 +663,8 @@ const message = {
|
|||||||
cleanDataHelper: '删除该任务执行过程中产生的备份文件',
|
cleanDataHelper: '删除该任务执行过程中产生的备份文件',
|
||||||
noLogs: '暂无任务输出...',
|
noLogs: '暂无任务输出...',
|
||||||
errPath: '备份路径 [{0}] 错误,无法下载!',
|
errPath: '备份路径 [{0}] 错误,无法下载!',
|
||||||
|
cutWebsiteLog: '切割网站日志',
|
||||||
|
cutWebsiteLogHelper: '切割的日志文件会备份到 1Panel 的 backup 目录下',
|
||||||
},
|
},
|
||||||
monitor: {
|
monitor: {
|
||||||
monitor: '监控',
|
monitor: '监控',
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
<el-option value="database" :label="$t('cronjob.database')" />
|
<el-option value="database" :label="$t('cronjob.database')" />
|
||||||
<el-option value="directory" :label="$t('cronjob.directory')" />
|
<el-option value="directory" :label="$t('cronjob.directory')" />
|
||||||
<el-option value="curl" :label="$t('cronjob.curl')" />
|
<el-option value="curl" :label="$t('cronjob.curl')" />
|
||||||
|
<el-option value="cutWebsiteLog" :label="$t('cronjob.cutWebsiteLog')" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-tag v-else>{{ dialogData.rowData!.type }}</el-tag>
|
<el-tag v-else>{{ dialogData.rowData!.type }}</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@ -82,14 +83,17 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item
|
<el-form-item
|
||||||
v-if="dialogData.rowData!.type === 'website'"
|
v-if="dialogData.rowData!.type === 'website' || dialogData.rowData!.type === 'cutWebsiteLog'"
|
||||||
:label="$t('cronjob.website')"
|
:label="dialogData.rowData!.type === 'website' ? $t('cronjob.website'):$t('website.website')"
|
||||||
prop="website"
|
prop="website"
|
||||||
>
|
>
|
||||||
<el-select style="width: 100%" v-model="dialogData.rowData!.website">
|
<el-select style="width: 100%" v-model="dialogData.rowData!.website">
|
||||||
<el-option :label="$t('commons.table.all')" value="all" />
|
<el-option :label="$t('commons.table.all')" value="all" />
|
||||||
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
|
<el-option v-for="item in websiteOptions" :key="item" :value="item" :label="item" />
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<span class="input-help" v-if="dialogData.rowData!.type === 'cutWebsiteLog'">
|
||||||
|
{{ $t('cronjob.cutWebsiteLogHelper') }}
|
||||||
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div v-if="dialogData.rowData!.type === 'database'">
|
<div v-if="dialogData.rowData!.type === 'database'">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user