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"
|
||||
"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()
|
||||
if err != nil {
|
||||
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"
|
||||
|
||||
"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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+11
-5
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user