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