Files
xk-hy-transit-go/internal/applog/applog.go
2026-05-22 08:06:07 +08:00

191 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package applog 监管中转运行日志(与云端 xk_hy_transit_* 表互补,供外网机排错)。
//
// - Pullf:步骤 ①② 拉取/建批次(pull-YYYY-MM-DD.log)
// - Pushf:步骤 ③ 转发政务云、PDF 上传(push-YYYY-MM-DD.log)
// - Appf:进程级事件、定时任务、配置告警(app-YYYY-MM-DD.log)
// - Consolef:仅控制台(开始/结束/错误摘要)
//
// 详情默认只写文件;SetQuietConsole(false) 时 Pullf/Pushf/Appf 同时 echo 到控制台(调试)。
package applog
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
var (
mu sync.Mutex
logRoot string
logService string
currentDay string
pullF *os.File
pushF *os.File
appF *os.File
quietConsole = true
stdFlags = log.LstdFlags
)
// Init 初始化日志目录。service 如 transit、forward。
// LOG_DIR 可覆盖根目录(默认 {程序目录}/log/{service})。
func Init(service string) error {
mu.Lock()
defer mu.Unlock()
logService = service
logRoot = resolveLogRoot(service)
if err := os.MkdirAll(logRoot, 0o755); err != nil {
return fmt.Errorf("mkdir log root: %w", err)
}
if err := rotateDailyFilesLocked(); err != nil {
return err
}
log.Printf("applog: service=%s dir=%s", service, logRoot)
return nil
}
// Dir 返回当前日志根目录({root}/{service})。
func Dir() string {
mu.Lock()
defer mu.Unlock()
if logRoot != "" && logService != "" {
return logRoot
}
return resolveLogRoot("transit")
}
// SetQuietConsole 为 true 时 Pullf/Pushf/Appf 只写文件;false 时同时输出到控制台。
func SetQuietConsole(quiet bool) {
mu.Lock()
quietConsole = quiet
mu.Unlock()
}
// Consolef 仅输出到控制台(定时/单次任务的开始、结束、错误摘要)。
func Consolef(format string, args ...any) {
line := fmt.Sprintf(format, args...)
log.New(os.Stdout, "", stdFlags).Print(line)
}
// RunSeparator 在 app/pull/push 三个日文件中写入执行分隔行。
func RunSeparator(title string) {
mu.Lock()
defer mu.Unlock()
_ = rotateDailyFilesLocked()
line := strings.Repeat("=", 8) + " " + title + " " + strings.Repeat("=", 8)
writeLineLocked(appF, line)
writeLineLocked(pullF, line)
writeLineLocked(pushF, line)
}
func resolveLogRoot(service string) string {
if v := strings.TrimSpace(os.Getenv("LOG_DIR")); v != "" {
return filepath.Join(v, service)
}
base := programDir()
return filepath.Join(base, "log", service)
}
func programDir() string {
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
if strings.Contains(dir, "go-build") {
if wd, err := os.Getwd(); err == nil && wd != "" {
return wd
}
}
return dir
}
if wd, err := os.Getwd(); err == nil && wd != "" {
return wd
}
return "."
}
func rotateDailyFilesLocked() error {
day := time.Now().Format("2006-01-02")
if day == currentDay && pullF != nil && pushF != nil && appF != nil {
return nil
}
closeFile(&pullF)
closeFile(&pushF)
closeFile(&appF)
currentDay = day
var err error
pullF, err = openDailyFile(logRoot, "pull-"+day+".log")
if err != nil {
return err
}
pushF, err = openDailyFile(logRoot, "push-"+day+".log")
if err != nil {
return err
}
appF, err = openDailyFile(logRoot, "app-"+day+".log")
if err != nil {
return err
}
return nil
}
func openDailyFile(dir, name string) (*os.File, error) {
f, err := os.OpenFile(filepath.Join(dir, name), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
log.Printf("applog: open %s: %v", name, err)
return nil, err
}
return f, nil
}
func closeFile(f **os.File) {
if f == nil || *f == nil {
return
}
_ = (*f).Close()
*f = nil
}
func writeLineLocked(f *os.File, line string) {
if f == nil {
return
}
_, _ = fmt.Fprintf(f, "%s\n", line)
}
func writeLocked(tag string, f *os.File, format string, args ...any) {
line := fmt.Sprintf(format, args...)
if f != nil {
log.New(f, "["+tag+"] ", stdFlags).Print(line)
}
if !quietConsole {
log.New(os.Stdout, "["+tag+"] ", stdFlags).Print(line)
}
}
// Pullf 拉取阶段日志(写 pull 日文件)。
func Pullf(format string, args ...any) {
mu.Lock()
defer mu.Unlock()
_ = rotateDailyFilesLocked()
writeLocked("pull", pullF, format, args...)
}
// Pushf 推送阶段日志(写 push 日文件)。
func Pushf(format string, args ...any) {
mu.Lock()
defer mu.Unlock()
_ = rotateDailyFilesLocked()
writeLocked("push", pushF, format, args...)
}
// Appf 应用级日志(写 app 日文件)。
func Appf(format string, args ...any) {
mu.Lock()
defer mu.Unlock()
_ = rotateDailyFilesLocked()
writeLocked("app", appF, format, args...)
}