Go 语言 fmt 与 log 打印方式详解

Go 语言 fmt 与 log 打印方式详解

在 Go 语言里,打印信息最常用的两个包是fmtlog

fmt更偏向通用格式化输出,可以输出到控制台、字符串、文件或其他io.Writer

log更偏向日志输出,默认会带时间信息,并且提供了打印后退出程序、打印后触发panic等能力。

一、fmt 包是什么

fmt是 Go 标准库中的格式化输入输出包。

使用前需要导入:

import "fmt"

它最常见的作用是:

fmt.Println("hello") fmt.Printf("name=%s, age=%d\n", "Tom", 18)

二、fmt 的主要打印函数

fmt提供了三大类常用打印函数:

  1. 输出到标准输出:PrintPrintlnPrintf

  2. 输出到字符串:SprintSprintlnSprintf

  3. 输出到指定位置:FprintFprintlnFprintf

三、Print、Println、Printf

1. fmt.Print

Print会直接打印内容,不会自动换行。

package main import "fmt" func main() { fmt.Print("hello") fmt.Print("world") }

输出:

helloworld

如果想要空格,需要自己写:

fmt.Print("hello ") fmt.Print("world")

2. fmt.Println

Println会打印内容,并在最后自动换行。

多个参数之间会自动加空格。

package main import "fmt" func main() { fmt.Println("hello") fmt.Println("name:", "Tom", "age:", 18) }

输出:

hello name: Tom age: 18

3. fmt.Printf

Printf按照指定格式打印内容。

它不会自动换行,如果需要换行,要手动写\n

package main import "fmt" func main() { name := "Tom" age := 18 fmt.Printf("name=%s, age=%d\n", name, age) }

输出:

name=Tom, age=18

其中:

%s // 字符串 %d // 十进制整数 \n // 换行

四、Sprint、Sprintln、Sprintf

这三个函数不会直接打印到控制台,而是返回一个字符串。

1. fmt.Sprint

package main import "fmt" func main() { msg := fmt.Sprint("hello", "world") fmt.Println(msg) }

输出:

helloworld

2. fmt.Sprintln

Sprintln会在参数之间加空格,并在末尾加换行。

package main import "fmt" func main() { msg := fmt.Sprintln("name:", "Tom", "age:", 18) fmt.Print(msg) }

输出:

name: Tom age: 18

3. fmt.Sprintf

Sprintf按格式生成字符串,开发中非常常用。

package main import "fmt" func main() { name := "Tom" age := 18 msg := fmt.Sprintf("name=%s, age=%d", name, age) fmt.Println(msg) }

输出:

name=Tom, age=18

常见用途是拼接错误信息、生成提示文案、构造路径等。

五、Fprint、Fprintln、Fprintf

这三个函数会把内容写到指定的io.Writer中。

常见的io.Writer有:

os.Stdout // 标准输出 os.Stderr // 标准错误输出 文件对象 bytes.Buffer

1. 打印到标准错误输出

package main import ( "fmt" "os" ) func main() { err := "file not found" fmt.Fprintln(os.Stderr, "error:", err) }

os.Stderr通常用来输出错误信息。

命令行程序里一般建议:

fmt.Println("normal output") // 普通输出 fmt.Fprintln(os.Stderr, "error message") // 错误输出

2. 写入文件

package main import ( "fmt" "os" ) func main() { file, err := os.Create("output.txt") if err != nil { fmt.Println("create file failed:", err) return } defer file.Close() fmt.Fprintln(file, "hello file") fmt.Fprintf(file, "name=%s, age=%d\n", "Tom", 18) }

六、fmt.Printf 的常用格式化参数

PrintfSprintfFprintf都会用到格式化参数,也叫占位符。

格式一般是:

fmt.Printf("格式字符串", 参数1, 参数2, 参数3)

例如:

fmt.Printf("name=%s, age=%d\n", "Tom", 18)

这里:

%s 对应 "Tom" %d 对应 18

七、通用格式化参数

1. %v:默认格式

%v表示按照默认格式打印值。

fmt.Printf("%v\n", 123) fmt.Printf("%v\n", "hello") fmt.Printf("%v\n", true)

输出:

123 hello true

%v很通用,打印错误、整数、字符串、结构体都可以。

2. %+v:打印结构体时带字段名

package main import "fmt" type User struct { Name string Age int } func main() { u := User{Name: "Tom", Age: 18} fmt.Printf("%v\n", u) fmt.Printf("%+v\n", u) }

输出:

{Tom 18} {Name:Tom Age:18}

实际开发中,调试结构体最常用:

fmt.Printf("user=%+v\n", u)

3. %#v:打印 Go 语法形式

fmt.Printf("%#v\n", u)

输出:

main.User{Name:"Tom", Age:18}

%#v更适合调试,可以看到类型和字段信息。

4. %T:打印类型

fmt.Printf("%T\n", 123) fmt.Printf("%T\n", "hello") fmt.Printf("%T\n", u)

输出:

int string main.User

5. %%:打印百分号

fmt.Printf("progress: %d%%\n", 80)

输出:

progress: 80%

八、布尔值

%t

%t用来打印布尔值。

ok := true fmt.Printf("ok=%t\n", ok)

输出:

ok=true

九、整数

1. %d:十进制整数

age := 18 fmt.Printf("age=%d\n", age)

输出:

age=18

2. %b:二进制

fmt.Printf("%b\n", 10)

输出:

1010

3. %o:八进制

fmt.Printf("%o\n", 10)

输出:

12

4. %x 和 %X:十六进制

fmt.Printf("%x\n", 255) fmt.Printf("%X\n", 255)

输出:

ff FF

5. %c:Unicode 字符

fmt.Printf("%c\n", 65) fmt.Printf("%c\n", '中')

输出:

A 中

十、浮点数

1. %f:普通小数

price := 12.3456 fmt.Printf("%f\n", price)

输出:

12.345600

默认保留 6 位小数。

2. %.2f:保留 2 位小数

fmt.Printf("%.2f\n", price)

输出:

12.35

3. %e 和 %E:科学计数法

fmt.Printf("%e\n", 123456.789) fmt.Printf("%E\n", 123456.789)

输出类似:

1.234568e+05 1.234568E+05

4. %g:根据情况自动选择格式

fmt.Printf("%g\n", 123456.789) fmt.Printf("%g\n", 0.000012345)

%g会根据数值大小自动选择普通小数或科学计数法。

十一、字符串和字节

1. %s:字符串

name := "Tom" fmt.Printf("name=%s\n", name)

输出:

name=Tom

2. %q:带引号的字符串

fmt.Printf("%q\n", "hello\nworld")

输出:

"hello\nworld"

%q对调试字符串很有用,因为它能看出换行、制表符等特殊字符。

3. %x:字符串或字节切片的十六进制

fmt.Printf("%x\n", "Go") fmt.Printf("% x\n", []byte("Go"))

输出:

476f 47 6f

% x中间有一个空格,表示每个字节之间也用空格分隔。

十二、指针

%p

%p用来打印指针地址。

age := 18 fmt.Printf("%p\n", &age)

输出类似:

0xc0000120c0

地址每次运行可能不同。

十三、宽度和精度

格式化参数还可以控制宽度和精度。

1. 宽度

fmt.Printf("|%6d|\n", 123) fmt.Printf("|%-6d|\n", 123)

输出:

| 123| |123 |

含义:

%6d // 宽度至少 6,默认右对齐 %-6d // 宽度至少 6,左对齐

2. 补零

fmt.Printf("|%06d|\n", 123)

输出:

|000123|

3. 小数精度

fmt.Printf("%.2f\n", 3.14159) fmt.Printf("%8.2f\n", 3.14159)

输出:

3.14 3.14

含义:

%.2f // 保留 2 位小数 %8.2f // 总宽度至少 8,保留 2 位小数

十四、错误 err 的打印

Go 里的错误类型是error,通常变量名叫err

file, err := os.Open("missing.txt") if err != nil { fmt.Println(err) } defer file.Close()

1. fmt.Println(err)

fmt.Println(err)

会直接打印错误信息。

2. fmt.Printf("%v", err)

fmt.Printf("error: %v\n", err)

%v表示默认格式。对错误来说,通常就是调用它的Error()方法。

3. fmt.Printf("%s", err)

fmt.Printf("error: %s\n", err)

%s表示字符串格式。因为errorError() string方法,所以通常也能打印错误文本。

实际开发中更推荐:

fmt.Printf("error: %v\n", err)

原因是%v更通用。

4. fmt.Errorf

fmt.Errorf不是直接打印,而是创建一个错误。

return fmt.Errorf("open file failed: %v", err)

如果要包装原始错误,推荐使用%w

return fmt.Errorf("open file failed: %w", err)

这样后续可以用errors.Iserrors.As判断错误链。

if errors.Is(err, os.ErrNotExist) { fmt.Println("file does not exist") }

十五、完整 fmt 示例

package main import ( "errors" "fmt" "os" ) type User struct { Name string Age int } func main() { u := User{Name: "Tom", Age: 18} fmt.Println("Println:", u) fmt.Printf("default: %v\n", u) fmt.Printf("with fields: %+v\n", u) fmt.Printf("go syntax: %#v\n", u) fmt.Printf("type: %T\n", u) fmt.Printf("string: %s\n", "hello") fmt.Printf("quoted string: %q\n", "hello\nworld") fmt.Printf("int: %d\n", 18) fmt.Printf("binary: %b\n", 10) fmt.Printf("hex: %x\n", 255) fmt.Printf("float: %.2f\n", 3.14159) fmt.Printf("percent: %d%%\n", 80) _, err := os.Open("missing.txt") if err != nil { fmt.Printf("error: %v\n", err) } err = fmt.Errorf("wrap error: %w", errors.New("original error")) fmt.Println(err) }

十六、log 包是什么

log是 Go 标准库提供的日志包。

使用前需要导入:

import "log"

fmt相比,log更适合记录程序运行信息。

默认情况下,log会输出到标准错误os.Stderr,并且带日期和时间。

package main import "log" func main() { log.Println("server started") }

输出类似:

2026/06/30 12:00:00 server started

十七、log 的常用打印函数

log常用函数主要有三组:

  1. PrintPrintlnPrintf

  2. FatalFatallnFatalf

  3. PanicPaniclnPanicf

十八、log.Print、log.Println、log.Printf

这组函数只是打印日志,不会退出程序。

1. log.Print

package main import "log" func main() { log.Print("hello") log.Print("world") }

2. log.Println

package main import "log" func main() { log.Println("hello") log.Println("name:", "Tom", "age:", 18) }

3. log.Printf

package main import "log" func main() { name := "Tom" age := 18 log.Printf("name=%s, age=%d", name, age) }

log.Printffmt.Printf的格式化规则基本一样,也使用%s%d%v等占位符。

十九、log.Fatal、log.Fatalln、log.Fatalf

这组函数会先打印日志,然后调用:

os.Exit(1)

也就是说,程序会立即退出。

1. log.Fatal

package main import "log" func main() { log.Fatal("program failed") log.Println("this line will not run") }

2. log.Fatalf

package main import "log" func main() { err := connectDB() if err != nil { log.Fatalf("connect db failed: %v", err) } } func connectDB() error { return fmt.Errorf("connection refused") }

注意:上面代码需要同时导入:

import ( "fmt" "log" )

Fatal适合用于程序启动阶段的不可恢复错误,比如配置读取失败、端口监听失败、数据库连接失败等。

不建议在普通业务函数里随便使用log.Fatal,因为它会直接结束整个程序。

二十、log.Panic、log.Panicln、log.Panicf

这组函数会先打印日志,然后触发panic

package main import "log" func main() { log.Panic("something terrible happened") }

等价于:

log.Print("something terrible happened") panic("something terrible happened")

panic一般用于程序无法继续运行的严重错误,不适合普通错误处理。

二十一、设置 log 输出格式

1. log.SetFlags

默认日志会带日期和时间。

可以通过log.SetFlags修改日志前缀格式。

package main import "log" func main() { log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.Println("hello") }

输出类似:

2026/06/30 12:00:00 main.go:7: hello

常用 flag:

log.Ldate // 日期,例如 2026/06/30 log.Ltime // 时间,例如 12:00:00 log.Lmicroseconds // 微秒 log.Llongfile // 完整文件名和行号 log.Lshortfile // 短文件名和行号 log.LUTC // 使用 UTC 时间 log.Lmsgprefix // 把 prefix 放在日志信息前 log.LstdFlags // 标准格式,等于 Ldate | Ltime

2. log.SetPrefix

SetPrefix可以设置日志前缀。

package main import "log" func main() { log.SetPrefix("[myapp] ") log.Println("server started") }

输出类似:

[myapp] 2026/06/30 12:00:00 server started

3. log.SetOutput

SetOutput可以修改日志输出位置。

例如输出到文件:

package main import ( "log" "os" ) func main() { file, err := os.Create("app.log") if err != nil { log.Fatal(err) } defer file.Close() log.SetOutput(file) log.Println("server started") log.Println("server stopped") }

运行后日志会写入app.log

二十二、自定义 Logger

除了使用全局的log.Println,也可以创建自己的 logger。

package main import ( "log" "os" ) func main() { infoLogger := log.New(os.Stdout, "[INFO] ", log.Ldate|log.Ltime) errorLogger := log.New(os.Stderr, "[ERROR] ", log.Ldate|log.Ltime|log.Lshortfile) infoLogger.Println("server started") errorLogger.Println("file not found") }

log.New的参数含义:

log.New(输出位置, 日志前缀, 日志格式)

也就是:

log.New(io.Writer, prefix string, flag int)

二十三、fmt 和 log 怎么选

适合用 fmt 的场景

fmt.Println("hello") fmt.Printf("name=%s\n", name) fmt.Fprintln(os.Stderr, "error:", err)

适合:

  1. 学习和临时调试

  2. 命令行程序输出结果

  3. 格式化生成字符串

  4. 写入任意io.Writer

适合用 log 的场景

log.Println("server started") log.Printf("request id=%s cost=%dms", requestID, cost) log.Fatal(err)

适合:

  1. 记录程序运行日志

  2. 服务端程序输出运行状态

  3. 需要日期、时间、文件行号

  4. 程序启动失败后直接退出

二十四、常用速查表

fmt 函数

fmt.Print // 打印,不换行 fmt.Println // 打印,自动换行,参数之间加空格 fmt.Printf // 按格式打印 fmt.Sprint // 返回字符串,不换行 fmt.Sprintln // 返回字符串,自动加空格和换行 fmt.Sprintf // 按格式返回字符串 fmt.Fprint // 写入指定 io.Writer,不换行 fmt.Fprintln // 写入指定 io.Writer,自动换行 fmt.Fprintf // 按格式写入指定 io.Writer

fmt 占位符

%v // 默认格式 %+v // 打印结构体时带字段名 %#v // Go 语法形式 %T // 类型 %% // 百分号 %t // 布尔值 %d // 十进制整数 %b // 二进制整数 %o // 八进制整数 %x // 十六进制,小写 %X // 十六进制,大写 %c // Unicode 字符 %f // 浮点数 %.2f // 保留 2 位小数 %e // 科学计数法,小写 e %E // 科学计数法,大写 E %g // 自动选择浮点格式 %s // 字符串 %q // 带引号字符串 %p // 指针地址

log 函数

log.Print // 打印日志 log.Println // 打印日志并换行 log.Printf // 按格式打印日志 log.Fatal // 打印日志后 os.Exit(1) log.Fatalln // 打印日志后 os.Exit(1) log.Fatalf // 按格式打印日志后 os.Exit(1) log.Panic // 打印日志后 panic log.Panicln // 打印日志后 panic log.Panicf // 按格式打印日志后 panic

二十五、总结

fmt解决的是“怎么格式化输出”的问题。

log解决的是“怎么记录日志”的问题。

日常开发中可以简单记住:

fmt.Println(value) // 简单打印 fmt.Printf("%+v\n", structVal) // 调试结构体 fmt.Sprintf("id=%d", id) // 生成字符串 fmt.Fprintln(os.Stderr, err) // 输出错误到 stderr log.Println("started") // 打日志 log.Printf("id=%d", id) // 格式化日志 log.Fatal(err) // 打印错误并退出程序

如果只是学习、调试、命令行输出,优先用fmt

如果是服务运行日志、错误记录、程序启动失败退出,优先用log