我们在应用调试或者线上业务中经常会用到日志功能,而 Go 语言内置了 log 模块。
先来看下 log 模块的基本使用:
package main
import (
"log"
)
func main() {
log.Println("hello log.Println")
log.Printf("hello %s", "log.Printf")
}
我们运行一下可以看到如下输出:
> $ go run main.go
2018/10/29 10:45:39 hello log.Println
2018/10/29 10:45:39 hello log.Printf
其实我们知道 fmt 包提供的函数有同样的输出函数,但是我们根据上面的输出可以看到 log 包输出的日志包括了日期和时间,这些输出有时候对于我们排查特定问题有一定的帮助作用。
我们可以自己设定输出的前缀,我们可以看下 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
LstdFlags = Ldate | Ltime // initial values for the standard logger
)
var std = New(os.Stderr, "", LstdFlags)
我们可以看到 log 包支持的可选输出项包括 Ldate、Ltime、Lmicroseconds、Llongfile、Lshortfile、LUTC,而 log 包默认的输出使用的是 LstdFlags = Ldate|Ltime。log 包提供的 SetFlags 函数可以让我们自定义输出选项。我们这里修改输出选项:
package main
import (
"log"
)
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Llongfile)
log.Println("hello log.Println")
log.Printf("hello %s", "log.Printf")
}
我们可以看到如下输出:
> $ go run main.go [10:56:22]
2018/10/29 10:58:04.244899 /Users/liushuai/Documents/goProject/src/github.com/yushuailiu/easyGolang/log/main.go:9: hello log.Println
2018/10/29 10:58:04.245070 /Users/liushuai/Documents/goProject/src/github.com/yushuailiu/easyGolang/log/main.go:10: hello log.Printf
log 包的输出选项有两个选项需要注意,第一个就是 Lshortfile,如果我们设置了该选项那么它会覆盖 Llongfile。第二个是 LUTC,如果我们设置了该选项输出时间将会使用 UTC 时间而不是本地时间。
log 包还提供了一个 SetPrefix 函数用来设置日志的输出前缀,我们可以用日志前缀来区分不同业务或不同类型的日志输出。
package main
import (
"log"
)
func main() {
log.SetPrefix("[User] ")
log.Println("hello log.Println")
log.Printf("hello %s", "log.Printf")
}
输出
> $ go run main.go
[User] 2018/10/29 11:04:56 hello log.Println
[User] 2018/10/29 11:04:56 hello log.Printf
这样我们就可以很容易拿到用户相关的日志了。
log 模块还提供所有输出函数分为三种:第一种包括 Print Printf Println,这一种只是简单地把信息输出,不会做其他操作。第二种包括 Fatal Fatalf Fatalln,这一种会在把信息输出之后并执行 os.Exit(1) 退出程序执行。最后一种包括Panic Panicf Panicln ,这种会在信息输出之后执行 panic(s)。三种函数可以被用到不同级别的信息输出中。部分函数源码展示:
func Fatalf(format string, v ...interface{}) {
std.Output(2, fmt.Sprintf(format, v...))
os.Exit(1)
}
func Panic(v ...interface{}) {
s := fmt.Sprint(v...)
std.Output(2, s)
panic(s)
}
log 模块的日志输出最关键的就是 Logger 这个 struct
type Logger struct {
mu sync.Mutex // ensures atomic writes; protects the following fields
prefix string // prefix to write at beginning of each line
flag int // properties
out io.Writer // destination for output
buf []byte // for accumulating text to write
}
各个字段作用
我们的所有日志输出都是调用 Logger 的实例输出,而我们上面直接调用 log.Println 则是 log 包提供的默认 Logger。相关代码如下:
var std = New(os.Stderr, "", LstdFlags)
func New(out io.Writer, prefix string, flag int) *Logger {
return &Logger{out: out, prefix: prefix, flag: flag}
}
func Println(v ...interface{}) {
std.Output(2, fmt.Sprintln(v...))
}
可以看到 log 包给我们提供的默认 Logger 是 std,std 的默认输出是 os.Stderr,前缀为空,日志选项是 LstdFlags。
而 log 包最重要的函数就是 Output 函数:
func (l *Logger) Output(calldepth int, s string) error {
// 获得当前时间
now := time.Now()
var file string
var line int
l.mu.Lock()
defer l.mu.Unlock()
if l.flag&(Lshortfile|Llongfile) != 0 {
// 去获得调用堆栈的时候先释放锁
l.mu.Unlock()
var ok bool
// 获得调用日志输出函数的文件名和行号
_, file, line, ok = runtime.Caller(calldepth)
if !ok {
file = "???"
line = 0
}
l.mu.Lock()
}
l.buf = l.buf[:0]
// 拼接日志前缀和输出选项
l.formatHeader(&l.buf, now, file, line)
// 拼接日志内容
l.buf = append(l.buf, s...)
if len(s) == 0 || s[len(s)-1] != '\n' {
l.buf = append(l.buf, '\n')
}
// 输出日志到 out
_, err := l.out.Write(l.buf)
return err
}
我们看到 std 的定义,可以想到我们也可以自己定义日志。
package main
import (
"log"
"os"
)
func main() {
errorLogFile ,err:=os.OpenFile("/tmp/error.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND,0666)
if err != nil {
panic(err)
}
stdoutLog := log.New(os.Stdout, "[User]", log.LstdFlags)
errLog := log.New(errorLogFile, "Message", log.Ldate|log.Ltime|log.Lshortfile)
stdoutLog.Println("stdout log test")
errLog.Println("file log test")
}
查看相关输出
> $ go run main.go
[User]2018/10/29 11:42:17 stdout log test
> $ cat /tmp/error.log
Message2018/10/29 11:42:17 main.go:17: file log test非著名程序员,全栈开发工程师,长期专注系统开发与架构设计。
功能待开通!
gorm 简介 gorm 是 go 语言中实现的比较好的 ORM 包,且是国人开发的。项目地址 事故描述 Scan 是 gorm 提供的一个把数据库结果读取到 struct 的函数。定义如下: // Scan scan value to a struct func (s *DB) Scan(dest interface{}) *DB { return s.NewScope(s.Value).Set("gorm:query_destination", dest).callCallbacks(s.parent.callbacks.queries).db } 今天同事小张写代码的时候写了一个
三个工具介绍 go fmt是用来规范go文件格式,比如格式化单个文件 go fmt xxx.go goimports 用来检查导入包,导入依赖包,删除不依赖的包 gometalinter 集成go语言几乎所有检测工具,静态分析代码,包含功能如下 go vet -工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前return的逻辑,struct的tag是否符合标准等。 go tool vet --shadow -用来检查作用域里面设置的局部变量名和全局变量名设置一样导致全局变量设置无效的问题 gotype -类型检测用来检测传递过来的变量和预期变量类型一致 gotype -x
Go 语言的可移植性 Java 平台可移植性是众所都知的,Java 的可移植性依赖于其虚拟机 JVM,Java 实现了对不同平台的 JVM 的支持,那么一份 Java 代码就可以在各个平台上运行。而 Go 语言的可移植性也是依赖于其 runtime,runtime 去对接操作系统层,用户代码在 runtime 中运行,用户代码就不用去关心平台问题。 查看 Go 支持的OS和平台: > $ go version go version go1.11 darwin/amd64 liushuai@liushuaideMacBook-Pro ~/Documents/goProject/src
临时忽略掉struct中空字段 type User struct { Email string `json:"email"` Password string `json:"password"` } 当我们把用户信息返回给前端的时候显然需要忽略调Password 字段,则可以这样做: json.Marshal(struct{ *User Password bool `json:"password,omitempty"` }{ User:user, }) 临时添加额外字段 type User struct { Email string `json:"
golang中字符串格式化输出 package main import ( "fmt" "os" ) type point struct { x, y int } func main() { // Go提供了几种打印格式,用来格式化一般的Go值,例如 // 下面的%v打印了一个point结构体的对象的值 p := point{1, 2} fmt.Printf("%v\n", p) // 如果所格式化的值是一个结构体对象,那么`%+v`的格式化输出 // 将包括结构体的成员名称和值 fmt.Printf("%