网站建设资讯

NEWS

网站建设资讯

go语言日志输出,go 日志打印

Golang将日志同时输出到控制台和文件

日常开发当中需要将golang的log包打印的日志同时输出到控制台和文件,应该如何解决这个问题?

创新互联建站专注于剑河网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供剑河营销型网站建设,剑河网站制作、剑河网页设计、剑河网站官网定制、重庆小程序开发公司服务,打造剑河网络公司原创品牌,更为您提供剑河网站排名全网营销落地服务。

log包可以通过SetOutput()方法指定日志输出的方式(Writer),但是只能指定一个输出的方式(Writer)。我们利用io.MultiWriter()将多个Writer拼成一个Writer使用的特性,把log.Println()输出的内容分流到控制台和文件当中。

原文地址

golang logger输出格式怎么修改

1.Logger结构

首先来看下类型Logger的定义:

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

}

主要有5个成员,其中3个我们比较熟悉,分别是表示Log前缀的 "prefix",表示Log头标签的 "flag" ,以及Log的输出目的地out。 buf是一个字节数组,主要用来存放即将刷入out的内容,相当于一个临时缓存,在对输出内容进行序列化时作为存储目的地。 mu是一个mutex主要用来作线程安全的实习,当有多个goroutine同时往一个目的刷内容的时候,通过mutex保证每次写入是一条完整的信息。

2.std及整体结构

在前一篇文章中我们提到了log模块提供了一套包级别的简单接口,使用该接口可以直接将日志内容打印到标准错误。那么该过程是怎么实现的呢?其实就是通过一个内置的Logger类型的变量 "std" 来实现的。该变量使用:

var std = New(os.Stderr, "", LstdFlags)

进行初始化,默认输出到系统的标准输出 "os.Stderr" ,前缀为空,使用日期加时间作为Log抬头。

当我们调用 log.Print的时候是怎么执行的呢?我们看其代码:

func Print(v ...interface{}) {

std.Output(2, fmt.Sprint(v...))

}

这里实际就是调用了Logger对象的 Output方法,将日志内容按照fmt包中约定的格式转义后传给Output。Output定义如下 :

func (l *Logger) Output(calldepth int, s string) error

其中s为日志没有加前缀和Log抬头的具体内容,xxxxx 。该函数执行具体的将日志刷入到对应的位置。

3.核心函数的实现

Logger.Output是执行具体的将日志刷入到对应位置的方法。

该方法首先根据需要获得当前时间和调用该方法的文件及行号信息。然后调用formatHeader方法将Log的前缀和Log抬头先格式化好 放入Logger.buf中,然后再将Log的内容存入到Logger.buf中,最后调用Logger.out.Write方法将完整的日志写入到输出目的地中。

由于写入文件以及拼接buf的过程是线程非安全的,因此使用mutex保证每次写入的原子性。

l.mu.Lock()

defer l.mu.Unlock()

将buf的拼接和文件的写入放入这个后面,使得在多个goroutine使用同一个Logger对象是,不会弄乱buf,也不会杂糅的写入。

该方法的第一个参数最终会传递给runtime.Caller的skip,指的是跳过的栈的深度。这里我记住给2就可以了。这样就会得到我们调用log 是所处的位置。

在golang的注释中说锁住 runtime.Caller的过程比较重,这点我还是不很了解,只是从代码中看到其在这里把锁打开了。

if l.flag(Lshortfile|Llongfile) != 0 {

// release lock while getting caller info - it‘s expensive.

l.mu.Unlock()

var ok bool

_, file, line, ok = runtime.Caller(calldepth)

if !ok {

file = "???"

line = 0

}

l.mu.Lock()

}

在formatHeader里面首先将前缀直接复制到Logger.buf中,然后根据flag选择Log抬头的内容,这里用到了一个log模块实现的 itoa的方法,作用类似c的itoa,将一个整数转换成一个字符串。只是其转换后将结果直接追加到了buf的尾部。

纵观整个实现,最值得学习的就是线程安全的部分。在什么位置合适做怎样的同步操作。

4.对外接口的实现

在了解了核心格式化和输出结构后,在看其封装就非常简单了,几乎都是首先用Output进行日志的记录,然后在必要的时候 做os.exit或者panic的操作,这里看下Fatal的实现。

func (l *Logger) Fatal(v ...interface{}) {

l.Output(2, fmt.Sprint(v...))

os.Exit(1)

}

// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).

func (l *Logger) Fatalf(format string, v ...interface{}) {

l.Output(2, fmt.Sprintf(format, v...))

os.Exit(1)

}

// Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).

func (l *Logger) Fatalln(v ...interface{}) {

l.Output(2, fmt.Sprintln(v...))

os.Exit(1)

}

这里也验证了我们之前做的Panic的结果,先做输出日志操作。再进行panic。

Go语言基础语法(一)

本文介绍一些Go语言的基础语法。

先来看一个简单的go语言代码:

go语言的注释方法:

代码执行结果:

下面来进一步介绍go的基础语法。

go语言中格式化输出可以使用 fmt 和 log 这两个标准库,

常用方法:

示例代码:

执行结果:

更多格式化方法可以访问中的fmt包。

log包实现了简单的日志服务,也提供了一些格式化输出的方法。

执行结果:

下面来介绍一下go的数据类型

下表列出了go语言的数据类型:

int、float、bool、string、数组和struct属于值类型,这些类型的变量直接指向存在内存中的值;slice、map、chan、pointer等是引用类型,存储的是一个地址,这个地址存储最终的值。

常量是在程序编译时就确定下来的值,程序运行时无法改变。

执行结果:

执行结果:

Go 语言的运算符主要包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符以及指针相关运算符。

算术运算符:

关系运算符:

逻辑运算符:

位运算符:

赋值运算符:

指针相关运算符:

下面介绍一下go语言中的if语句和switch语句。另外还有一种控制语句叫select语句,通常与通道联用,这里不做介绍。

if语法格式如下:

if ... else :

else if:

示例代码:

语法格式:

另外,添加 fallthrough 会强制执行后面的 case 语句,不管下一条case语句是否为true。

示例代码:

执行结果:

下面介绍几种循环语句:

执行结果:

执行结果:

也可以通过标记退出循环:

--THE END--


标题名称:go语言日志输出,go 日志打印
新闻来源:http://njwzjz.com/article/dsipeic.html