log

作者: JunChow520 | 来源:发表于2021-04-01 01:37 被阅读0次

一般而言日志可分为事件日志和消息日志两种

  • 事件日志记录发生在系统运行过程中的事件,用来审计操作、诊断问题等。对理解复杂系统的运行非常关键。
  • 消息日志被应用在比如即时通信中,用来记录来往的消息。

标准的日志定义在IETF中,会记录时间、地点、参与者、起因、简要经过等信息。

日志记录一般格式:[时间][日志级别][地点][参与者][事件][起因]

对于系统日志一般还会定义严重性等级(Severity Level)用来标识该条日志记录的紧要程度

紧要程度 日志级别
0 EMERGENCY
1 ALERT
2 CREITICAL
3 ERROR
4 WARNING
5 NOTICE
6 INFORMATION
7 DEBUG

log

package main

import "log"

type User struct {
    Id   int
    Name string
}

func main() {
    user := User{Id: 1, Name: "admin"}
    log.Printf("id = %v, name = %v", user.Id, user.Name) 
}
2021/03/31 22:18:33 id = 1, name = admin

Golang默认的日志记录分为两部分,前面是事件发生的时间,后面是事件记录。

日志输出

  • log日志库默认会输出日志到标准错误stderr
  • log日志库会将每条日志前自动添加上日期和时间
  • 若日志不是以换行符为结尾,则会自动补上换行符。

log日志库提供了三组函数用于输出日志

输出 格式化 换行 描述
log.Print log.Printf log.Println 正常输出日志
log.Fatal log.Fatalf log.Fatalln 输出日志后退出程序
log.Panic log.Panicf log.Panicln 输出日志与相关调用信息后退出程序
  • log.Fatal()会记录FATAL级别错误日志,并通过调用os.Exit(1)来结束程序
  • log.Panic()会记录PANIC级别错误日志,当写入消息消息后会抛出一个panic

日志记录器 log.Logger

  • Logger是对log的简单封装,使用Logger可以使日志记录更加便捷。
type Logger struct {
    mu     sync.Mutex // ensures atomic writes; protects the following fields
    prefix string     // 日志行前缀
    flag   int        // 日志打印格式标志,用于指定每行日志的打印格式。
    out    io.Writer  // 日志输出位置,需实现io.Writer接口。
    buf    []byte     // 日志内容
}
字段 描述
prefix 日志行前缀
flag 日志打印格式标志,用于指定每行日志的打印格式。
out 日志输出位置,需实现io.Writer接口。
buf 日志内容

Go标准库中的Logger最大的优点在于简单,用户只需要设置任何io.Writer作为日志记录输出并向其发送想要写入的日志即可,与之相对的劣势也是由于简单。

  • 基本信息的日志级别中只有log.Print()不支持类似INFO/DEBUG等级别
  • 对于错误日志有log.Fatal()log.Panic(),缺少一个ERROR级别,需要在不抛出panic或退出程序的情况下记录错误。
  • 缺乏日志格式化能力
  • 缺乏日志切割能力

创建日志记录器 log.New

func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}
参数 类型 描述
out io.Writer 设置日志数据接入的目的地,需实现io.Writer接口。
prefix string 每行日志前缀字符串
flag int 设置日志记录选项

例如:以系统标准输出(屏幕)作为日志记录器输出位置,以[DEBUG]作为每行日志前缀,同时自动添加时间。

logger := log.New(os.Stdout, "[DEBUG] ", log.Ldate|log.Ltime|log.Lshortfile)
user := User{Id: 1, Name: "admin"}
logger.Printf("id = %v, name = %v", user.Id, user.Name)
[DEBUG] 2021/03/31 23:00:03 main.go:17: id = 1, name = admin

日志输出位置 out

  • os.Stdinos.Stdoutos.Stderr表示已经打开的文件,分别指向标准输入、标准输出、标准错误的文件描述符。这三个变量都被声明为*os.File类型的指针,而os.File类型实现了io.Writer接口。
package os

var (
    Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
    Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
    Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)

func NewFile(fd uintptr, name string) *File {
    h := syscall.Handle(fd)
    if h == syscall.InvalidHandle {
        return nil
    }
    return newFile(h, name, "file")
}

type File struct {
    *file // os specific
}
  • ioutil.Discard是一个io.Writer接口,调用其Write()方法将不做任何操作并始终返回成功,当某个等级的日志不重要时,可使用ioutil.Discard来禁用。
package ioutil

var Discard io.Writer = io.Discard
  • io.MultiWriter()函数调用返回的是一个io.Writer接口类型值,入参是一个变参函数可接受任意数量实现io.Writer接口的值,返回的值会将所有传入的值绑在一起。当对返回值写入时,会向所有绑在一起的io.Writer值做写入。因此可以同时向多个Writer做输出。
package io

func MultiWriter(writers ...Writer) Writer 

设置输出端 log.SetOutput

func (l *Logger) SetOutput(w io.Writer) {
    l.mu.Lock()
    defer l.mu.Unlock()
    l.out = w
}
  • 默认输出端为os.stdout系统标准输出
  • 设置输出到指定文件
file,err := os.OpenFile(*logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
log.SetOutput(file)
  • 设置同时将日志输出到多个位置
file,err := os.OpenFile(*logFileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
writer := io.MultiWriter(file, os.Stderr)
log.SetOutput(writer)
  • 禁止输出

ioutil.Discard是一个io.Writer,对其进行的Write调用都将无条件成功,类似于黑洞。

log.SetOutput(ioutil.Discard)

例如:自定义日志输出位置

func init(){
    dirpath := "./tmp/log/"
    filename := fmt.Sprintf("%s.log", time.Now().Format("20060102"))

    if _,err := os.Stat(dirpath); os.IsNotExist(err) {
        err := os.MkdirAll(dirpath, os.ModePerm)
        if err!=nil {
            log.Fatalln(err)
            return
        }
    }

    filePath := path.Join(dirpath, filename)
    fh,err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
    if err!=nil {
        log.Fatalln(err)
        return
    }
    log.SetOutput(fh)
}
func main(){
    log.Println("log tester\n")
}

设置前缀 log.SetPrefix

  • 调用log.SetPrefix()会为每条日志文本前添加一个前缀
log.SetPrefix("[ERROR] ")
user := User{Id: 1, Name: "admin"}
log.Printf("id = %v, name = %v", user.Id, user.Name)
[ERROR] 2021/03/31 22:32:31 id = 1, name = admin
  • 设置前缀文本颜色
//设置颜色
const (
    red = uint8(91 + iota)
    greeen
    yellow
    blue
    magenta
)

//color 为文本设置颜色
func color(str string, color uint8) string {
    text := fmt.Sprintf("\x1b[%dm%s\x1b[0m", color, str)
    fmt.Printf("%v\n", text)
    return text
}

日志格式标志 flag

log包定义了一些日志格式标志的常量

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
    Lmsgprefix                    // move the "prefix" from the beginning of the line to before the message
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)
格式标志 常量值 描述
log.Ldate 1 输出当地时区日期
log.Ltime 2 输出当地时区时间
log.Lmicroseconds 3 输出毫秒级时间,会覆盖Ltime。
log.Llongfile 4 输出长文件:全路径+执行文件+行数
log.Lshortfile 5 输出段文件:执行文件名+行数,会覆盖Llongfile。
log.LUTC 6 若已设置LdateLtime则输出UTC时间,而非当地时区。
log.LstdFlags 8 默认值,等价于 log.Ldate|log.Ltime

可在log.New()创建日志记录器时指定,也可使用Logger.setFlat()方法动态指定。

设置选项 log.SetFlags

  • 调用log.SetFlags为输出的日志添加选项,可同时设置多个,多个|分割。
  • 设置选项可为每条日志输出的文本前增加额外信息
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile | log.LUTC)
log.SetPrefix("[ERROR] ")
user := User{Id: 1, Name: "admin"}
log.Printf("id = %v, name = %v", user.Id, user.Name)
[ERROR] 2021/03/31 14:31:21.368463 D:/go/project/example/main.go:14: id = 1, name = admin
  • 取消日志格式化输出,可设置为0。
log.SetFlags(0)

协程安全

日志记录器默认是多goroutine安全的,这也就意味着多个goroutine可以同时调用来自同一个日志记录器的输出函数,而不会发生写冲突。

例如:定制不同日志等级的日志记录器

日志等级 记录信息 输出位置
Trace 记录所有信息 禁止输出
Info 记录重要信息 输出到标准输出屏幕上
Warn 记录警告信息 输出到标准输出屏幕上
Error 记录错误信息 输出到日志文件中
$ mkdir logx && cd logx
$ vim logx.log
package logx

import (
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path"
    "time"
)

var (
    Trace *log.Logger // 记录所有日志
    Info  *log.Logger // 记录重要日志
    Warn  *log.Logger // 记录警告信息
    Error *log.Logger // 记录错误信息
)

func init() {
    //禁用Trace,不输出任何信息。
    Trace = log.New(ioutil.Discard, "[TRACE] ", log.Ldate|log.Ltime|log.Lshortfile)
    //信息输出到屏幕
    Info = log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime|log.Lshortfile)
    //警告输出到屏幕
    Warn = log.New(os.Stdout, "[WARN] ", log.Ldate|log.Ltime|log.Lshortfile)
    //错误输出到文件
    dirpath := "./tmp/log/"
    filename := fmt.Sprintf("%s.log", time.Now().Format("20060102"))
    file, err := MkDirFile(dirpath, filename, os.ModePerm)
    if err != nil {
        log.Fatalln(err)
    }
    Error = log.New(io.MultiWriter(file, os.Stderr), "[ERROR] ", log.Ldate|log.Ltime|log.Llongfile)
}

func MkDirFile(dirpath, filename string, perm os.FileMode) (*os.File, error) {
    if _, err := os.Stat(dirpath); os.IsNotExist(err) {
        err := os.MkdirAll(dirpath, perm)
        if err != nil {
            return nil, err
        }
    }

    filePath := path.Join(dirpath, filename)
    f, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, perm)
    if err != nil {
        return nil, err
    }

    return f, nil
}

运行测试

func main() {
    logx.Trace.Println("trace test")
    logx.Info.Println("info test")
    logx.Warn.Println("warn test")
    logx.Error.Println("error test")
}

例如:将错误记录到日志同时打印到文件

package main

import (
    "io"
    "log"
    "os"
)

var (
    Error *log.Logger
)

func init() {
    filename := "error.log"
    file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatalln(err)
    }
    out := io.MultiWriter(file, os.Stderr)

    Error = log.New(out, "[ERROR] ", log.Ldate|log.Ltime|log.Llongfile)
}

func main() {
    user := &struct {
        Id   int
        Name string
    }{
        Id:   1,
        Name: "admin",
    }

    Error.Printf("id = %v, name = %v", user.Id, user.Name)
}

创建文件

file,err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
选项 描述
os.O_CREATE 若文件不存在则创建新文件
os.O_WRONLY 打开文件的读写权限
os.O_APPEND 写入数据时采用追加而非覆盖的方式

例如:

  • 指定日志文件保存路径,若目录不存在则创建。
  • 向指定目录的日志文件内写入日志,同时打印到标准输出。
package main

import (
    "io"
    "log"
    "os"
)

var (
    Error *log.Logger
)

//MakeDir 创建目录
func MakeDir(dir string) (bool, error) {
    //判断文件是否存在
    _, err := os.Stat(dir)
    if err == nil {
        return true, nil
    }
    //递归创建文件夹
    err = os.MkdirAll(dir, 0755)
    if err != nil {
        return false, err
    }
    return true, nil
}

func init() {
    dir := "./runtime/log/"
    filename := "error.log"
    filepath := dir + filename

    ok, err := MakeDir(dir)
    if err != nil && !ok {
        panic(err)
    }

    file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatalln(err)
    }

    out := io.MultiWriter(file, os.Stderr)
    Error = log.New(out, "[ERROR] ", log.Ldate|log.Ltime|log.Llongfile)
}

func main() {
    user := &struct {
        Id   int
        Name string
    }{
        Id:   1,
        Name: "admin",
    }

    Error.Printf("id = %v, name = %v", user.Id, user.Name)
}

例如:根据日志不同紧急程度输出日志

紧急程度 级别名称 级别方法 日志前缀 颜色
0 fatal Fatal/Fatalf [FATAL] 红色背景色
1 error Error/Errorf [ERROR] 红色前景色
2 warn Warn/Warnf/Warningf [WARN] 杨红背景色
3 info Info/Infof [INFO] 请求前景色
4 debug Debug/Debugf [DEBUG] 黄色前景色
$ vim ./log/log.go
package log

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "sync"
)

var (
    mutex       sync.Mutex
    infoLogger  *log.Logger   = log.New(os.Stdout, color("[INFO] ", blue), log.LstdFlags|log.Lshortfile)
    errorLogger *log.Logger   = log.New(os.Stdout, color("[ERROR] ", red), log.LstdFlags|log.Lshortfile)
    loggers     []*log.Logger = []*log.Logger{errorLogger, infoLogger}
)

//设置日志方法
var (
    Info   = infoLogger.Println
    Infof  = infoLogger.Printf
    Error  = errorLogger.Println
    Errorf = errorLogger.Printf
)

//设置颜色
const (
    red = uint8(91 + iota)
    greeen
    yellow
    blue
    magenta
)

//color 为文本设置颜色
func color(str string, color uint8) string {
    text := fmt.Sprintf("\x1b[%dm%s\x1b[0m", color, str)
    return text
}

//设置日志级别
const (
    InfoLevel = iota
    ErrorLevel
    Disabled
)

//SetLevel 设置日志级别
func SetLevel(level int) {
    //添加互斥锁
    mutex.Lock()
    defer mutex.Unlock()
    //设置日志记录器输出位置
    for _, logger := range loggers {
        logger.SetOutput(os.Stdout) //输出定向到标准输出
    }
    //设置自定义输出位置
    if level > InfoLevel {
        infoLogger.SetOutput(ioutil.Discard) //输出会被定向到 ioutil.Discard 不打印该日志
    } else if level > ErrorLevel {
        errorLogger.SetOutput(ioutil.Discard) //输出会被定向到 ioutil.Discard 不打印该日志
    }
}

相关文章

网友评论

      本文标题:log

      本文链接:https://www.haomeiwen.com/subject/ufzahltx.html