feat: add structured logging system for Docker deployment
添加日志系统,支持 Docker logs 捕获: - 新增 logger 模块,提供 Info/Warn/Error/Debug 等日志级别 - 任务生命周期日志:创建、开始、完成、失败、取消 - 用户登录/登出日志 - 服务启动和 FFmpeg 状态日志 - 数据库初始化日志 日志格式:[时间] [级别] 消息,便于 Docker logs 查看 Co-Authored-By: AI
This commit is contained in:
+20
-12
@@ -8,6 +8,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"bilidown/internal/logger"
|
||||||
"bilidown/internal/router"
|
"bilidown/internal/router"
|
||||||
"bilidown/internal/util"
|
"bilidown/internal/util"
|
||||||
|
|
||||||
@@ -28,12 +29,10 @@ var ffmpegAvailable bool
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ffmpegAvailable = checkFFmpeg()
|
ffmpegAvailable = checkFFmpeg()
|
||||||
|
logger.FFmpegStatus(ffmpegAvailable)
|
||||||
mustInitTables()
|
mustInitTables()
|
||||||
mustRunServer()
|
mustRunServer()
|
||||||
fmt.Printf("Bilidown %s server running at %s\n", VERSION, urlLocal)
|
logger.ServerStarted(HTTP_PORT, VERSION)
|
||||||
if !ffmpegAvailable {
|
|
||||||
fmt.Println("Warning: FFmpeg is not installed. Video download will not work.")
|
|
||||||
}
|
|
||||||
select {} // 保持运行
|
select {} // 保持运行
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ func mustRunServer() {
|
|||||||
// 从嵌入的文件系统中获取静态文件
|
// 从嵌入的文件系统中获取静态文件
|
||||||
staticFS, err := fs.Sub(staticFiles, "static")
|
staticFS, err := fs.Sub(staticFiles, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to load static files:", err)
|
logger.Fatal("加载静态文件失败: " + err.Error())
|
||||||
}
|
}
|
||||||
// 前端静态文件
|
// 前端静态文件
|
||||||
http.Handle("/", http.FileServer(http.FS(staticFS)))
|
http.Handle("/", http.FileServer(http.FS(staticFS)))
|
||||||
@@ -77,7 +76,7 @@ func mustInitTables() {
|
|||||||
"name" TEXT PRIMARY KEY NOT NULL,
|
"name" TEXT PRIMARY KEY NOT NULL,
|
||||||
"value" TEXT
|
"value" TEXT
|
||||||
)`); err != nil {
|
)`); err != nil {
|
||||||
log.Fatalln("create table field:", err)
|
logger.Fatal("创建 field 表失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "log" (
|
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "log" (
|
||||||
@@ -85,7 +84,7 @@ func mustInitTables() {
|
|||||||
"content" TEXT NOT NULL,
|
"content" TEXT NOT NULL,
|
||||||
"create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
|
"create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
)`); err != nil {
|
)`); err != nil {
|
||||||
log.Fatalln("create table log:", err)
|
logger.Fatal("创建 log 表失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "task" (
|
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "task" (
|
||||||
@@ -102,21 +101,23 @@ func mustInitTables() {
|
|||||||
"download_type" text NOT NULL DEFAULT 'merge',
|
"download_type" text NOT NULL DEFAULT 'merge',
|
||||||
"create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
|
"create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
)`); err != nil {
|
)`); err != nil {
|
||||||
log.Fatalln("create table task:", err)
|
logger.Fatal("创建 task 表失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := util.GetCurrentFolder(db); err != nil {
|
if _, err := util.GetCurrentFolder(db); err != nil {
|
||||||
log.Fatalln("util.GetCurrentFolder:", err)
|
logger.Fatal("获取下载目录失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := initHistoryTask(db); err != nil {
|
if err := initHistoryTask(db); err != nil {
|
||||||
log.Fatalln("initHistoryTask:", err)
|
logger.Fatal("初始化历史任务失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加可能缺失的列(用于数据库迁移)
|
// 添加可能缺失的列(用于数据库迁移)
|
||||||
if err := addMissingColumns(db); err != nil {
|
if err := addMissingColumns(db); err != nil {
|
||||||
log.Fatalln("addMissingColumns:", err)
|
logger.Fatal("添加缺失列失败: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("数据库初始化完成")
|
||||||
}
|
}
|
||||||
|
|
||||||
// addMissingColumns 添加可能缺失的列(用于数据库迁移)
|
// addMissingColumns 添加可能缺失的列(用于数据库迁移)
|
||||||
@@ -131,7 +132,14 @@ func addMissingColumns(db *sql.DB) error {
|
|||||||
// initHistoryTask 将上一次程序运行时未完成的任务状态变为 error
|
// initHistoryTask 将上一次程序运行时未完成的任务状态变为 error
|
||||||
func initHistoryTask(db *sql.DB) error {
|
func initHistoryTask(db *sql.DB) error {
|
||||||
util.SqliteLock.Lock()
|
util.SqliteLock.Lock()
|
||||||
_, err := db.Exec(`UPDATE "task" SET "status" = 'error' WHERE "status" IN ('waiting', 'running')`)
|
result, err := db.Exec(`UPDATE "task" SET "status" = 'error' WHERE "status" IN ('waiting', 'running')`)
|
||||||
util.SqliteLock.Unlock()
|
util.SqliteLock.Unlock()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
rowsAffected, _ := result.RowsAffected()
|
||||||
|
if rowsAffected > 0 {
|
||||||
|
logger.Infof("重置 %d 个未完成任务状态为 error", rowsAffected)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// 日志级别
|
||||||
|
level = "info"
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetLevel 设置日志级别
|
||||||
|
func SetLevel(l string) {
|
||||||
|
mu.Lock()
|
||||||
|
level = l
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志格式:[时间] [级别] 消息
|
||||||
|
func formatLog(levelStr, msg string) string {
|
||||||
|
return fmt.Sprintf("[%s] [%s] %s", time.Now().Format("2006-01-02 15:04:05"), levelStr, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info 输出信息日志
|
||||||
|
func Info(msg string) {
|
||||||
|
log.Println(formatLog("INFO", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof 输出格式化信息日志
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
Info(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn 输出警告日志
|
||||||
|
func Warn(msg string) {
|
||||||
|
log.Println(formatLog("WARN", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf 输出格式化警告日志
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
Warn(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error 输出错误日志
|
||||||
|
func Error(msg string) {
|
||||||
|
log.Println(formatLog("ERROR", msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf 输出格式化错误日志
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug 输出调试日志(仅在 debug 别别时输出)
|
||||||
|
func Debug(msg string) {
|
||||||
|
mu.Lock()
|
||||||
|
currentLevel := level
|
||||||
|
mu.Unlock()
|
||||||
|
if currentLevel == "debug" {
|
||||||
|
log.Println(formatLog("DEBUG", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf 输出格式化调试日志
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
Debug(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal 输出致命错误日志并退出
|
||||||
|
func Fatal(msg string) {
|
||||||
|
log.Println(formatLog("FATAL", msg))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf 输出格式化致命错误日志并退出
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
Fatal(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Task 相关日志
|
||||||
|
|
||||||
|
// TaskCreated 任务创建日志
|
||||||
|
func TaskCreated(taskID int64, title string, bvid string) {
|
||||||
|
Infof("任务创建 #%d: %s (%s)", taskID, title, bvid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskStarted 任务开始日志
|
||||||
|
func TaskStarted(taskID int64, title string) {
|
||||||
|
Infof("任务开始 #%d: %s", taskID, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskCompleted 任务完成日志
|
||||||
|
func TaskCompleted(taskID int64, title string, duration time.Duration) {
|
||||||
|
Infof("任务完成 #%d: %s (耗时 %.1f秒)", taskID, title, duration.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskFailed 任务失败日志
|
||||||
|
func TaskFailed(taskID int64, title string, err error) {
|
||||||
|
Errorf("任务失败 #%d: %s - %v", taskID, title, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskCancelled 任务取消日志
|
||||||
|
func TaskCancelled(taskID int64, title string) {
|
||||||
|
Warnf("任务取消 #%d: %s", taskID, title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// User 用户相关日志
|
||||||
|
|
||||||
|
// UserLogin 用户登录日志
|
||||||
|
func UserLogin() {
|
||||||
|
Info("用户登录成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserLogout 用户登出日志
|
||||||
|
func UserLogout() {
|
||||||
|
Info("用户登出")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server 服务相关日志
|
||||||
|
|
||||||
|
// ServerStarted 服务启动日志
|
||||||
|
func ServerStarted(port int, version string) {
|
||||||
|
Infof("服务启动 %s,端口 %d", version, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FFmpegStatus FFmpeg 状态日志
|
||||||
|
func FFmpegStatus(available bool) {
|
||||||
|
if available {
|
||||||
|
Info("FFmpeg 检测成功")
|
||||||
|
} else {
|
||||||
|
Warn("FFmpeg 未安装,视频下载功能不可用")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API API 相关日志
|
||||||
|
|
||||||
|
// APICall API 调用日志
|
||||||
|
func APICall(method, path string) {
|
||||||
|
Debugf("API %s %s", method, path)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"bilidown/internal/bilibili"
|
"bilidown/internal/bilibili"
|
||||||
|
"bilidown/internal/logger"
|
||||||
"bilidown/internal/util"
|
"bilidown/internal/util"
|
||||||
|
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
@@ -87,6 +88,7 @@ func getQRStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.UserLogin()
|
||||||
util.Res{Success: true, Message: "登录成功"}.Write(w)
|
util.Res{Success: true, Message: "登录成功"}.Write(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,5 +100,6 @@ func logout(w http.ResponseWriter, r *http.Request) {
|
|||||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.UserLogout()
|
||||||
util.Res{Success: true, Message: "退出成功"}.Write(w)
|
util.Res{Success: true, Message: "退出成功"}.Write(w)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"bilidown/internal/logger"
|
||||||
"bilidown/internal/util"
|
"bilidown/internal/util"
|
||||||
"bilidown/internal/util/res_error"
|
"bilidown/internal/util/res_error"
|
||||||
)
|
)
|
||||||
@@ -109,5 +110,6 @@ func saveFields(w http.ResponseWriter, r *http.Request) {
|
|||||||
util.Res{Success: false, Message: err.Error()}.Write(w)
|
util.Res{Success: false, Message: err.Error()}.Write(w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Info("设置保存成功")
|
||||||
util.Res{Success: true, Message: "保存成功"}.Write(w)
|
util.Res{Success: true, Message: "保存成功"}.Write(w)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-5
@@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -20,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"bilidown/internal/bilibili"
|
"bilidown/internal/bilibili"
|
||||||
"bilidown/internal/common"
|
"bilidown/internal/common"
|
||||||
|
"bilidown/internal/logger"
|
||||||
"bilidown/internal/util"
|
"bilidown/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -83,6 +83,7 @@ func CancelTask(taskID int64) bool {
|
|||||||
if task.ID == taskID && (task.Status == "waiting" || task.Status == "running") {
|
if task.ID == taskID && (task.Status == "waiting" || task.Status == "running") {
|
||||||
task.Cancelled = true
|
task.Cancelled = true
|
||||||
task.Status = "error"
|
task.Status = "error"
|
||||||
|
logger.TaskCancelled(task.ID, task.Title)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,7 @@ func (task *Task) Create(db *sql.DB) error {
|
|||||||
|
|
||||||
task.ID, err = result.LastInsertId()
|
task.ID, err = result.LastInsertId()
|
||||||
task.CreateAt = time.Now()
|
task.CreateAt = time.Now()
|
||||||
|
logger.TaskCreated(task.ID, task.Title, task.Bvid)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +157,7 @@ func (task *Task) Start() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task.UpdateStatus(db, "running")
|
task.UpdateStatus(db, "running")
|
||||||
|
logger.TaskStarted(task.ID, task.Title)
|
||||||
|
|
||||||
if task.DownloadType == "audio" {
|
if task.DownloadType == "audio" {
|
||||||
// 仅音频模式:只下载音频,重命名音频文件为输出文件
|
// 仅音频模式:只下载音频,重命名音频文件为输出文件
|
||||||
@@ -178,9 +181,10 @@ func (task *Task) Start() {
|
|||||||
}
|
}
|
||||||
// 添加元数据
|
// 添加元数据
|
||||||
if err := task.addMetadata(outputPath); err != nil {
|
if err := task.addMetadata(outputPath); err != nil {
|
||||||
log.Printf("添加元数据失败 (任务ID: %d): %v", task.ID, err)
|
logger.Warnf("添加元数据失败 #%d: %v", task.ID, err)
|
||||||
}
|
}
|
||||||
task.UpdateStatus(db, "done")
|
task.UpdateStatus(db, "done")
|
||||||
|
logger.TaskCompleted(task.ID, task.Title, time.Since(task.CreateAt))
|
||||||
return
|
return
|
||||||
} else if task.DownloadType == "video" {
|
} else if task.DownloadType == "video" {
|
||||||
// 仅视频模式:只下载视频,重命名视频文件为输出文件
|
// 仅视频模式:只下载视频,重命名视频文件为输出文件
|
||||||
@@ -204,9 +208,10 @@ func (task *Task) Start() {
|
|||||||
}
|
}
|
||||||
// 添加元数据
|
// 添加元数据
|
||||||
if err := task.addMetadata(outputPath); err != nil {
|
if err := task.addMetadata(outputPath); err != nil {
|
||||||
log.Printf("添加元数据失败 (任务ID: %d): %v", task.ID, err)
|
logger.Warnf("添加元数据失败 #%d: %v", task.ID, err)
|
||||||
}
|
}
|
||||||
task.UpdateStatus(db, "done")
|
task.UpdateStatus(db, "done")
|
||||||
|
logger.TaskCompleted(task.ID, task.Title, time.Since(task.CreateAt))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// 合并模式:下载音频和视频,然后合并
|
// 合并模式:下载音频和视频,然后合并
|
||||||
@@ -262,7 +267,7 @@ func (task *Task) Start() {
|
|||||||
GlobalMergeSem.Release()
|
GlobalMergeSem.Release()
|
||||||
// 添加元数据
|
// 添加元数据
|
||||||
if err := task.addMetadata(outputPath); err != nil {
|
if err := task.addMetadata(outputPath); err != nil {
|
||||||
log.Printf("添加元数据失败 (任务ID: %d): %v", task.ID, err)
|
logger.Warnf("添加元数据失败 #%d: %v", task.ID, err)
|
||||||
}
|
}
|
||||||
task.UpdateStatus(db, "done")
|
task.UpdateStatus(db, "done")
|
||||||
}
|
}
|
||||||
@@ -356,8 +361,9 @@ func (task *Task) UpdateStatus(db *sql.DB, status TaskStatus, errs ...error) err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
err = util.CreateLog(db, fmt.Sprintf("Task-%d-Error: %v", task.ID, err))
|
err = util.CreateLog(db, fmt.Sprintf("Task-%d-Error: %v", task.ID, err))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("CreateLog:", err)
|
logger.Fatal("CreateLog: " + err.Error())
|
||||||
}
|
}
|
||||||
|
logger.TaskFailed(task.ID, task.Title, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.Status = status
|
task.Status = status
|
||||||
|
|||||||
Reference in New Issue
Block a user