diff --git a/cmd/bilidown/main.go b/cmd/bilidown/main.go index 1ba26a0..0826533 100644 --- a/cmd/bilidown/main.go +++ b/cmd/bilidown/main.go @@ -8,6 +8,7 @@ import ( "log" "net/http" + "bilidown/internal/logger" "bilidown/internal/router" "bilidown/internal/util" @@ -28,12 +29,10 @@ var ffmpegAvailable bool func main() { ffmpegAvailable = checkFFmpeg() + logger.FFmpegStatus(ffmpegAvailable) mustInitTables() mustRunServer() - fmt.Printf("Bilidown %s server running at %s\n", VERSION, urlLocal) - if !ffmpegAvailable { - fmt.Println("Warning: FFmpeg is not installed. Video download will not work.") - } + logger.ServerStarted(HTTP_PORT, VERSION) select {} // 保持运行 } @@ -48,7 +47,7 @@ func mustRunServer() { // 从嵌入的文件系统中获取静态文件 staticFS, err := fs.Sub(staticFiles, "static") if err != nil { - log.Fatal("Failed to load static files:", err) + logger.Fatal("加载静态文件失败: " + err.Error()) } // 前端静态文件 http.Handle("/", http.FileServer(http.FS(staticFS))) @@ -77,7 +76,7 @@ func mustInitTables() { "name" TEXT PRIMARY KEY NOT NULL, "value" TEXT )`); err != nil { - log.Fatalln("create table field:", err) + logger.Fatal("创建 field 表失败: " + err.Error()) } if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "log" ( @@ -85,7 +84,7 @@ func mustInitTables() { "content" TEXT NOT NULL, "create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP )`); err != nil { - log.Fatalln("create table log:", err) + logger.Fatal("创建 log 表失败: " + err.Error()) } if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS "task" ( @@ -102,21 +101,23 @@ func mustInitTables() { "download_type" text NOT NULL DEFAULT 'merge', "create_at" text NOT NULL DEFAULT CURRENT_TIMESTAMP )`); err != nil { - log.Fatalln("create table task:", err) + logger.Fatal("创建 task 表失败: " + err.Error()) } if _, err := util.GetCurrentFolder(db); err != nil { - log.Fatalln("util.GetCurrentFolder:", err) + logger.Fatal("获取下载目录失败: " + err.Error()) } if err := initHistoryTask(db); err != nil { - log.Fatalln("initHistoryTask:", err) + logger.Fatal("初始化历史任务失败: " + err.Error()) } // 添加可能缺失的列(用于数据库迁移) if err := addMissingColumns(db); err != nil { - log.Fatalln("addMissingColumns:", err) + logger.Fatal("添加缺失列失败: " + err.Error()) } + + logger.Info("数据库初始化完成") } // addMissingColumns 添加可能缺失的列(用于数据库迁移) @@ -131,7 +132,14 @@ func addMissingColumns(db *sql.DB) error { // initHistoryTask 将上一次程序运行时未完成的任务状态变为 error func initHistoryTask(db *sql.DB) error { 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() - return err + if err != nil { + return err + } + rowsAffected, _ := result.RowsAffected() + if rowsAffected > 0 { + logger.Infof("重置 %d 个未完成任务状态为 error", rowsAffected) + } + return nil } \ No newline at end of file diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..e422dbe --- /dev/null +++ b/internal/logger/logger.go @@ -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) +} \ No newline at end of file diff --git a/internal/router/login.go b/internal/router/login.go index a293f0a..c40e8b1 100644 --- a/internal/router/login.go +++ b/internal/router/login.go @@ -5,6 +5,7 @@ import ( "net/http" "bilidown/internal/bilibili" + "bilidown/internal/logger" "bilidown/internal/util" "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) return } + logger.UserLogin() 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) return } + logger.UserLogout() util.Res{Success: true, Message: "退出成功"}.Write(w) } diff --git a/internal/router/router.go b/internal/router/router.go index e673125..94c8988 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -6,6 +6,7 @@ import ( "net/http" "os" + "bilidown/internal/logger" "bilidown/internal/util" "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) return } + logger.Info("设置保存成功") util.Res{Success: true, Message: "保存成功"}.Write(w) } diff --git a/internal/task/task.go b/internal/task/task.go index a9ed0ac..0d3b524 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "log" "net/http" "os" "os/exec" @@ -20,6 +19,7 @@ import ( "bilidown/internal/bilibili" "bilidown/internal/common" + "bilidown/internal/logger" "bilidown/internal/util" ) @@ -83,6 +83,7 @@ func CancelTask(taskID int64) bool { if task.ID == taskID && (task.Status == "waiting" || task.Status == "running") { task.Cancelled = true task.Status = "error" + logger.TaskCancelled(task.ID, task.Title) return true } } @@ -111,6 +112,7 @@ func (task *Task) Create(db *sql.DB) error { task.ID, err = result.LastInsertId() task.CreateAt = time.Now() + logger.TaskCreated(task.ID, task.Title, task.Bvid) return err } @@ -155,6 +157,7 @@ func (task *Task) Start() { } task.UpdateStatus(db, "running") + logger.TaskStarted(task.ID, task.Title) if task.DownloadType == "audio" { // 仅音频模式:只下载音频,重命名音频文件为输出文件 @@ -178,9 +181,10 @@ func (task *Task) Start() { } // 添加元数据 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") + logger.TaskCompleted(task.ID, task.Title, time.Since(task.CreateAt)) return } else if task.DownloadType == "video" { // 仅视频模式:只下载视频,重命名视频文件为输出文件 @@ -204,9 +208,10 @@ func (task *Task) Start() { } // 添加元数据 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") + logger.TaskCompleted(task.ID, task.Title, time.Since(task.CreateAt)) return } else { // 合并模式:下载音频和视频,然后合并 @@ -262,7 +267,7 @@ func (task *Task) Start() { GlobalMergeSem.Release() // 添加元数据 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") } @@ -356,8 +361,9 @@ func (task *Task) UpdateStatus(db *sql.DB, status TaskStatus, errs ...error) err if err != nil { err = util.CreateLog(db, fmt.Sprintf("Task-%d-Error: %v", task.ID, err)) if err != nil { - log.Fatalln("CreateLog:", err) + logger.Fatal("CreateLog: " + err.Error()) } + logger.TaskFailed(task.ID, task.Title, err) } } task.Status = status