Go實戰(zhàn)項目【三】日志系統(tǒng)和編寫路由文件

日志服務(wù)

在服務(wù)端開發(fā)中,日志系統(tǒng)必不可少的瞒滴,能幫助我們更快的找到問題和運行記錄曲梗,接下來就簡單的做個日志文件系統(tǒng)。
日志是以文件的形式存放在項目的目錄中妓忍,所以需要使用Go對文件操作,封裝一下

pkg/file/file.go

package file

import (
    "io/ioutil"
    "mime/multipart"    //它主要實現(xiàn)了 MIME 的 multipart 解析虏两,主要適用于 HTTP 和常見瀏覽器生成的 multipart 主體
    "os"
    "path"
)

//獲取文件大小
func GetSize(f multipart.File)(int , error)  {
    content,err := ioutil.ReadAll(f)

    return len(content),err
}

//獲取文件后綴
func GetExt(filename string) string {
    return path.Ext(filename)
}

//檢查文件是否存在
/*
   如果返回的錯誤為nil,說明文件或文件夾存在
   如果返回的錯誤類型使用os.IsNotExist()判斷為true,說明文件或文件夾不存在
   如果返回的錯誤為其它類型,則不確定是否在存在
*/
func CheckExist(src string)bool  {
    _, err := os.Stat(src)
    if err == nil {
        return true
    }
    if os.IsNotExist(err) {
        return false
    }
    return false
}

//檢查文件權(quán)限
func CheckPermission(src string)bool  {
    _,err := os.Stat(src)
    return os.IsPermission(err)
}

//新建文件夾
func MKDir(src string)error  {
    err := os.MkdirAll(src,os.ModePerm)
    return err
}

//如果不存在則新建文件夾
func IsNotExistMkDir(src string)error  {
    if exist := CheckExist(src); exist == false {
        if err := MKDir(src);err !=nil  {
            return err
        }
    }
    return nil
}

/*
   調(diào)用文件,支持傳入文件名稱世剖、指定的模式調(diào)用文件碘举、文件權(quán)限,返回的文件的方法可以用于I/O搁廓。如果出現(xiàn)錯誤引颈,則為*PathError
   const (
       // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
       O_RDONLY int = syscall.O_RDONLY // 以只讀模式打開文件
       O_WRONLY int = syscall.O_WRONLY // 以只寫模式打開文件
       O_RDWR   int = syscall.O_RDWR   // 以讀寫模式打開文件
       // The remaining values may be or'ed in to control behavior.
       O_APPEND int = syscall.O_APPEND // 在寫入時將數(shù)據(jù)追加到文件中
       O_CREATE int = syscall.O_CREAT  // 如果不存在,則創(chuàng)建一個新文件
       O_EXCL   int = syscall.O_EXCL   // 使用O_CREATE時境蜕,文件必須不存在
       O_SYNC   int = syscall.O_SYNC   // 同步IO
       O_TRUNC  int = syscall.O_TRUNC  // 如果可以蝙场,打開時
   )
*/
func Open(name string,flag int,perm os.FileMode)(*os.File,error)  {
    f,err := os.OpenFile(name,flag,perm)
    if err != nil {
        return nil,err
    }
    return f,err
}

這個文件把大部分文件操作的方法封裝了。注釋中也有講解粱年。而且以后再有對文件進(jìn)行操作(比如用戶上傳/更改頭像售滤,上傳文件)也都可以使用這些方法進(jìn)行操作。

然后進(jìn)行對log的日志文件進(jìn)行操作
pkg/logging/file.go

package logging

import (
    "api/pkg/file"
    "api/pkg/setting"
    "fmt"
    "os"
    "time"
)

//獲取日志文件路徑
func getLogFilePath() string {
    return fmt.Sprintf("%s%s", setting.AppSetting.RuntimeRootPath, setting.AppSetting.LogSavePath)
}

//獲取日志文件的名稱
func getLogFileName() string {
    return fmt.Sprintf("%s%s.%s",
        setting.AppSetting.LogSaveName,
        time.Now().Format(setting.AppSetting.TimeFormat),
        setting.AppSetting.LogFileExt,
    )
}

//打開日志文件
func openLogFile(fileName, filePath string) (*os.File, error) {

    dir, err := os.Getwd()  //返回與當(dāng)前目錄對應(yīng)的根路徑名
    if err != nil {
        return nil, fmt.Errorf("os.Getwd err: %v", err)
    }

    src := dir + "/" + filePath
    perm := file.CheckPermission(src)   //檢查文件權(quán)限
    if perm == true {
        return nil, fmt.Errorf("file.CheckPermission Permission denied src: %s", src)
    }

    err = file.IsNotExistMkDir(src)     //如果不存在則新建文件夾
    if err != nil {
        return nil, fmt.Errorf("file.IsNotExistMkDir src: %s, err: %v", src, err)
    }

    //調(diào)用文件台诗,在寫入時將數(shù)據(jù)追加到文件中 | 如果不存在完箩,則創(chuàng)建一個新文件 | 以只寫模式打開文件
    f, err := file.Open(src + fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, fmt.Errorf("Fail to OpenFile :%v", err)
    }

    return f, nil
}

日志的文件操作,將會以log+日期.log為命名文件拉队。日志文件夾保存的路徑在是根據(jù)配置文件設(shè)置的弊知,路徑為runtime/logs/

最后對打印日志的格式進(jìn)行封裝
pkg/logging/log.go

package logging

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
    "runtime"
)

type Level int

var (
    F * os.File             //文件

    DefaultPrefix = ""      //默認(rèn)的前綴
    DefaultCallDepth = 2    //調(diào)用深度

    logger *log.Logger      //打印
    logPrefix = ""          //打印前綴
    levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"}    //調(diào)試,信息粱快,警告秩彤,錯誤叔扼,崩潰
)

const (
    DEBUG Level = iota
    INFO
    WARNING
    ERROR
    FATAL
)

func SetUp()  {

    var err error
    filePath := getLogFilePath()        //獲取日志文件路徑
    fileName := getLogFileName()        //獲取日志文件名稱
    F, err = openLogFile(fileName, filePath)    //打開日志文件
    if err != nil {
        log.Fatalln(err)
    }

    /*
       log.New創(chuàng)建一個新的日志記錄器。out定義要寫入日志數(shù)據(jù)的IO句柄漫雷。prefix定義每個生成的日志行的開頭瓜富。flag定義了日志記錄屬性
       log.LstdFlags:日志記錄的格式屬性之一,其余的選項如下
       const (
         Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
         Ltime                         // the time in the local time zone: 01:23:23
         Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
         Llongfile                     // full file name and line number: /a/b/c/d.go:23
         Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
         LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
         LstdFlags     = Ldate | Ltime // initial values for the standard logger
       )
    */
    logger = log.New(F, DefaultPrefix, log.LstdFlags)
}

//設(shè)置前綴
func setPrefix(level Level)  {
    _,file,line,ok := runtime.Caller(DefaultCallDepth)
    if ok {
        logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line)
    }else {
        logPrefix = fmt.Sprintf("[%s]", levelFlags[level])
    }

    logger.SetPrefix(logPrefix)
}

func Debug(v ...interface{})  {
    setPrefix(DEBUG)
    logger.Println(v)
}

func Info(v ...interface{}) {
    setPrefix(INFO)
    logger.Println(v)
}

func Warn(v ...interface{}) {
    setPrefix(WARNING)
    logger.Println(v)
}

func Error(v ...interface{}) {
    setPrefix(ERROR)
    logger.Println(v)
}

func Fatal(v ...interface{}) {
    setPrefix(FATAL)
    logger.Fatalln(v)
}

日志打印有5個級別降盹,調(diào)試与柑,信息,警告蓄坏,錯誤价捧,崩潰〗1瑁可以根據(jù)不同環(huán)境下的信息進(jìn)行調(diào)用不同的級別。
main.go文件中初始化

...
setting.SetUp() //初始化配置文件
logging.SetUp()     //設(shè)置日志文件
...

路由文件

用Gin框架啟動服務(wù)沒問題后渠欺,進(jìn)行路由文件的編寫妹蔽。
創(chuàng)建routers/routers.go文件,在這個文件中創(chuàng)建初始化路由的方法
routers/routers.go

package routers

import (
    "api/pkg/e"
    "api/pkg/setting"
    "github.com/gin-gonic/gin"
)

/*
    初始化路由
*/
func InitRouter() *gin.Engine {
    r := gin.New()        //創(chuàng)建gin框架路由實例
    r.Use(gin.Logger())   //使用gin框架中的打印中間件
    r.Use(gin.Recovery()) //使用gin框架中的恢復(fù)中間件挠将,可以從任何恐慌中恢復(fù)胳岂,如果有,則寫入500

    gin.SetMode(setting.ServerSetting.RunMode) //設(shè)置運行模式舔稀,debug或release,如果放在gin.New或者gin.Default之后乳丰,還是會打印一些信息的。放之前則不會

    apiv1 := r.Group("/api/v1") //路由分組内贮,apiv1代表v1版本的路由組
    {
        apiv1.GET("version",v1.GetAppVersionTest)   //app版本升級
    }

    return r
}

創(chuàng)建routers/v1/app_version.go

package v1

import (
    "api/pkg/e"
    "github.com/gin-gonic/gin"
)

//app更新接口
func GetAppVersionTest(c *gin.Context)  {

    c.JSON(e.SUCCESS,gin.H{
        "Code":e.SUCCESS,
        "Msg":e.GetMsg(e.SUCCESS),
        "Data":"返回數(shù)據(jù)成功",
    })
}

同樣的产园,也必須把main方法中的初始化路由修改下
main.go

...
func main() {
    log.Println("Hello, api 正在啟動中...")
    setting.SetUp() //初始化配置文件

    router := routers.InitRouter()  //初始化路由

    s := &http.Server{
        Addr:fmt.Sprintf(":%d", setting.ServerSetting.HttpPort),        //設(shè)置端口號
        Handler:router,                                         //http句柄,實質(zhì)為ServeHTTP夜郁,用于處理程序響應(yīng)HTTP請求
        ReadTimeout:setting.ServerSetting.ReadTimeout,          //允許讀取的最大時間
        WriteTimeout:setting.ServerSetting.WriteTimeout,        //允許寫入的最大時間
        MaxHeaderBytes: 1 << 20,                                //請求頭的最大字節(jié)數(shù)
    }

    /*
       使用 http.Server - Shutdown() 優(yōu)雅的關(guān)閉http服務(wù)
    */
    go func() {
        if err := s.ListenAndServe(); err != nil{
            log.Printf("Listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal)
    signal.Notify(quit,os.Interrupt)
    <- quit

    log.Println("Shutdown Server ...")
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    defer cancel()
    if err := s.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }

    log.Println("程序服務(wù)關(guān)閉退出")
}

可以看到對路由分組以后什燕,現(xiàn)在接口的url是http://127.0.0.1:9999/api/v1/version
重新進(jìn)行go run后,瀏覽器查看是否返回數(shù)據(jù)

以后的路由代碼都會在routers/v1目錄下編寫竞端,比如app_version.go文件就是有關(guān)app升級相關(guān)的代碼屎即,這里就把路由服務(wù)分解開來了。

點關(guān)注事富,不迷路

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末技俐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子统台,更是在濱河造成了極大的恐慌雕擂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贱勃,死亡現(xiàn)場離奇詭異捂刺,居然都是意外死亡谣拣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門族展,熙熙樓的掌柜王于貴愁眉苦臉地迎上來森缠,“玉大人,你說我怎么就攤上這事仪缸」蠛” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵恰画,是天一觀的道長宾茂。 經(jīng)常有香客問我,道長拴还,這世上最難降的妖魔是什么跨晴? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮片林,結(jié)果婚禮上端盆,老公的妹妹穿的比我還像新娘。我一直安慰自己费封,他們只是感情好焕妙,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弓摘,像睡著了一般焚鹊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上韧献,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天末患,我揣著相機(jī)與錄音,去河邊找鬼锤窑。 笑死阻塑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的果复。 我是一名探鬼主播陈莽,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼虽抄!你這毒婦竟也來了走搁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤迈窟,失蹤者是張志新(化名)和其女友劉穎私植,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體车酣,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡曲稼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年索绪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贫悄。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡瑞驱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窄坦,到底是詐尸還是另有隱情唤反,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布鸭津,位于F島的核電站彤侍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逆趋。R本人自食惡果不足惜盏阶,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望闻书。 院中可真熱鬧名斟,春花似錦、人聲如沸惠窄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杆融。三九已至,卻和暖如春霜运,著一層夾襖步出監(jiān)牢的瞬間脾歇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工淘捡, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留藕各,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓焦除,卻偏偏與公主長得像激况,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子膘魄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345