Detailed explanation of log implementation in go language (printing log, writing log to file and cutting log)

The log package defines the Logger type, which provides methods for formatting output. This package also provides a predefined "standard" logger, which can be used by calling the functions Print series (Print|Printf|Println), Fatal series (Fatal|Fatalf|Fatalln), and Panic series (Panic|Panicf|Panicln) , which is easier to use than creating a logger object yourself.

Logger

package main

import (
	"log"
)

func main() {
	log.Println("This is a test log.")
	v := "very common"
	log.Printf("This is a %s log.\n", v)
	log.Fatalln("This is a log that will trigger fatal.")
	log.Panicln("This is a log that will trigger a panic.")
}

Standard logger configuration

The SetFlags function is used to set the output configuration of the standard logger.

const (
// Control the details of the output log information, but cannot control the order and format of the output.
// The output log will be separated by a colon after each item: for example, 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message
 Ldate = 1 << iota // date: 2009/01/23
 Ltime // Time: 01:23:23
 Lmicroseconds // Time at the microsecond level: 01:23:23.123123 (used to enhance the Ltime bit)
Llongfile // Full file path name + line number: /a/b/c/d.go:23
 Lshortfile// file name + line number: d.go:23 (will overwrite Llongfile)
LUTC // use UTC time
 LstdFlags = Ldate | Ltime // Initial value of standard logger
)
func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("This is a very common log.")
}

Configure the log prefix (SetPrefix)

func main() {
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
	log.Println("This is a very common log.")
	log.SetPrefix("[hahaha]")
	log.Println("This is a very common log.")
}

Configure the log output location

The SetOutput function is used to set the output destination of the standard logger, and the default is the standard error output.

func init() {
	logFile, err := os.OpenFile("./xx.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("open log file failed, err:", err)
		return
	}
	log.SetOutput(logFile)
	log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)
}

create new logger object

The log standard library also provides a constructor for creating a new logger object - New, which supports us to create our own logger examples. The signature of the New function is as follows:

func New(out io.Writer, prefix string, flag int) *Logger

New creates a Logger object. Among them, the parameter out sets the destination where the log information is written. The parameter prefix will be added to the front of each generated log. The parameter flag defines the attributes of the log (time, file, etc.).

for example:

func main() {
	logger := log.New(os.Stdout, "<New>", log.Lshortfile|log.Ldate|log.Ltime)
	logger.Println("This is the log recorded by the custom logger.")
}
//<New>2017/06/19 14:06:51 main.go:34: This is the log recorded by the custom logger.

Supplement: Go's built-in log library has limited functions. For example, it cannot meet the needs of recording logs of different levels. You need to choose a third-party log library, such as logrus, zap, etc.

log library level

package log

import (
	"errors"
	"fmt"
	"strings"
	"time"
)

type LogLevel uint16

// log constant
const (
	UNKNOW LogLevel = iota
	DEBUG 
	TRACE
	INFO
	WARNIG
	ERROR
	FATAL

)
// parse log level
func paraLogLevel(s string) (LogLevel,error) {
	s = strings.ToLower(s)
	switch s {

	case "debug":
		return DEBUG,nil

	case "tarce":
		return TRACE,nil
	case "info":
		return INFO,nil
	case "warnig":
		return WARNIG,nil
	case "error":
		return ERROR,nil
	case "fatal":
		return FATAL,nil
	default:
		err:= errors.New("Invalid log level")
		return UNKNOW,err
	}
}
//Define the log level mechanism
type Logger  struct{
	Level LogLevel
}
//constructor
func NewLog(levelLog string) Logger  {
	level, err := paraLogLevel(levelLog)
	if err !=nil{
		panic(err)
	}
	return Logger{
		Level: level,
	}
}
//Whether it is possible to print a certain level of log
func (l Logger)  enable(logLevel LogLevel) bool {
		return l.Level >logLevel
}

func (l Logger) Debug(msg string) {
	if l.enable(DEBUG){
		now := time.Now()
		fmt.Printf("[%s] [Debug] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}

func (l Logger) Info(msg string) {
	if l.enable(INFO){
		now := time.Now()
		fmt.Printf("[%s] [Info] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}

func (l Logger) Warning(msg string) {
	if l.enable(WARNIG){
		now := time.Now()
		fmt.Printf("[%s] [Warning] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}

func (l Logger) Error(msg string) {
	if l.enable(ERROR){
		now := time.Now()
		fmt.Printf("[%s] [Error] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}

func (l Logger) Fatal(msg string) {
	if l.enable(FATAL){
		now := time.Now()
		fmt.Printf("[%s] [Fatal] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}
import "gostudy/log"

func main()  {

	newLog := log.NewLog("warnig")
	newLog.Debug("This is the debug log")
	newLog.Info("This is the info log")
	newLog.Warning("This is a Warning log")
	newLog.Error("This is the ERROR log")
	newLog.Fatal("This is a FATAL log")
}

Print result: [2022-08-04 10:41:56] [Debug] This is the debug log [2022-08-04 10:41:56] [Info] This is the info log

runtime.Caller

Can get the file name function name and line number

variadic log
//......interface{} represents any variable parameter, which can be passed without or with any length
func (l Logger) Debug(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(DEBUG){
		now := time.Now()
		fmt.Printf("[%s] [Debug] %s",now.Format("2006-01-02 15:04:05"),msg);
	}

}
Realize writing logs to the file
1.new build fileloger.go file, used to provide the function of writing to the log

package log

import (
	"errors"
	"fmt"
	"os"
	"path"
	"strings"
	"time"
)

type LogLevel uint16
//log level
const (
	UNKNOW LogLevel = iota
	DEBUG 
	TRACE
	INFO
	WARNIG
	ERROR
	FATAL

)
//parsing log
func paraLogLevel(s string) (LogLevel,error) {
	s = strings.ToLower(s)
	switch s {

	case "debug":
		return DEBUG,nil

	case "tarce":
		return TRACE,nil
	case "info":
		return INFO,nil
	case "warnig":
		return WARNIG,nil
	case "error":
		return ERROR,nil
	case "fatal":
		return FATAL,nil
	default:
		err:= errors.New("invalid log level")
		return UNKNOW,err
	}
}

//Get the string format of the log
func getLogStr (level LogLevel) string {

	switch level {

	case DEBUG:
		return "debug"

	case TRACE:
		return "tarce"
	case INFO:
		return "info"
	case WARNIG:
		return "warnig"
	case ERROR:
		return "error"
	case FATAL:
		return "fatal"
	default:
		return "unknow"
	}
}

//Define the structure of the log
type FileLogger  struct{
	Level LogLevel
	filePath string
	fileName string
	//Files to open and write to, a log file and an error log file
	fileObj *os.File
	errfileObj *os.File
	maxFileSize int64
}

//Constructor
func NewFlieLogger(LeveStr ,fp,fn string,size int64)  *FileLogger{

	level, err := paraLogLevel(LeveStr)
	if err != nil {
		panic(err)
	}
	f1 := &FileLogger{
		Level: level,
		filePath: fp,
		fileName: fn,
		maxFileSize: size,
	}
	err= f1.initFile()
	if err != nil {
		panic(err)
	}
	return f1
}

//Operation that initializes the log file to be opened and written
func (f *FileLogger) initFile() (error) {
	join := path.Join(f.filePath, f.fileName)
	fileObj, err := os.OpenFile(join, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open log fail ,err: %v\n",err)
		return err
	}

	errFileObj, err := os.OpenFile(join+".err", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		fmt.Printf("open log fail ,err: %v\n",err)
		return err
	}
	//log files are open
	f.fileObj = fileObj;
	f.errfileObj = errFileObj;
	return  nil

}
//judgment level
func (l FileLogger)  enable(logLevel LogLevel) bool {
		return l.Level >logLevel
}
//print log operation
func (f *FileLogger) Log(leve LogLevel,msg string)  {
	now := time.Now()
	if f.enable(leve){

		fmt.Fprintf(f.fileObj,"[%s] [%s] %s",now.Format("2006-01-02 15:04:05"),getLogStr(leve),msg);
	}

	if leve >ERROR{

		fmt.Fprintf(f.errfileObj,"[%s] [%s] %s",now.Format("2006-01-02 15:04:05"),getLogStr(leve),msg);
	}
}

func (l FileLogger) Debug(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(DEBUG){
		l.Log(DEBUG,msg)
	}

}

func (l FileLogger) Info(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(WARNIG){
		l.Log(WARNIG,msg)
	}

}


func (l FileLogger) Warning(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(WARNIG){
		l.Log(WARNIG,msg)
	}

}

func (l FileLogger) Error(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(ERROR){
		l.Log(ERROR,msg)
	}

}

func (l FileLogger) Fatal(msg string, a ...interface{}) {
	msg = fmt.Sprint(msg,a)
	if l.enable(FATAL){
		l.Log(FATAL,msg)
	}

}

func (f *FileLogger) Colse()  {
	f.fileObj.Close()
	f.errfileObj.Close()
}
2.test:

func main()  {

	newLog := log.NewFlieLogger("warnig","./","now.log",100*1024*1024)
	newLog.Debug("This is debug log")
	newLog.Info("This is info log")
	newLog.Warning("This is Warning log")
	newLog.Error("This is ERROR log")
	newLog.Fatal("This is FATAL log")
	newLog.Colse()

}
//After running it twice, print the result:

 

Log cutting (cutting by file size, cutting by date)

In fact, every time the size of the record file is exceeded, a new file will be written.

Get some information about the file through the Stat() function

	open, _:= os.Open("file name")
	stat, _, := open.Stat()
	stat.Size()//Get the file size

Date cut:

Get the name of the file or check if there is a log file of the day, if not, create a new one.

Tags: Back-end Go programming language

Posted by Blulagoon on Fri, 09 Dec 2022 09:27:38 +1030