Go 语言 fmt 与 log 打印方式详解
在 Go 语言里,打印信息最常用的两个包是fmt和log。
fmt更偏向通用格式化输出,可以输出到控制台、字符串、文件或其他io.Writer。
log更偏向日志输出,默认会带时间信息,并且提供了打印后退出程序、打印后触发panic等能力。
一、fmt 包是什么
fmt是 Go 标准库中的格式化输入输出包。
使用前需要导入:
import "fmt"它最常见的作用是:
fmt.Println("hello") fmt.Printf("name=%s, age=%d\n", "Tom", 18)二、fmt 的主要打印函数
fmt提供了三大类常用打印函数:
输出到标准输出:
Print、Println、Printf输出到字符串:
Sprint、Sprintln、Sprintf输出到指定位置:
Fprint、Fprintln、Fprintf
三、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: 183. 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) }输出:
helloworld2. fmt.Sprintln
Sprintln会在参数之间加空格,并在末尾加换行。
package main import "fmt" func main() { msg := fmt.Sprintln("name:", "Tom", "age:", 18) fmt.Print(msg) }输出:
name: Tom age: 183. 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.Buffer1. 打印到标准错误输出
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 的常用格式化参数
Printf、Sprintf、Fprintf都会用到格式化参数,也叫占位符。
格式一般是:
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.User5. %%:打印百分号
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=182. %b:二进制
fmt.Printf("%b\n", 10)输出:
10103. %o:八进制
fmt.Printf("%o\n", 10)输出:
124. %x 和 %X:十六进制
fmt.Printf("%x\n", 255) fmt.Printf("%X\n", 255)输出:
ff FF5. %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.353. %e 和 %E:科学计数法
fmt.Printf("%e\n", 123456.789) fmt.Printf("%E\n", 123456.789)输出类似:
1.234568e+05 1.234568E+054. %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=Tom2. %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表示字符串格式。因为error有Error() 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.Is或errors.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常用函数主要有三组:
Print、Println、PrintfFatal、Fatalln、FatalfPanic、Panicln、Panicf
十八、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.Printf和fmt.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 | Ltime2. log.SetPrefix
SetPrefix可以设置日志前缀。
package main import "log" func main() { log.SetPrefix("[myapp] ") log.Println("server started") }输出类似:
[myapp] 2026/06/30 12:00:00 server started3. 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)适合:
学习和临时调试
命令行程序输出结果
格式化生成字符串
写入任意
io.Writer
适合用 log 的场景
log.Println("server started") log.Printf("request id=%s cost=%dms", requestID, cost) log.Fatal(err)适合:
记录程序运行日志
服务端程序输出运行状态
需要日期、时间、文件行号
程序启动失败后直接退出
二十四、常用速查表
fmt 函数
fmt.Print // 打印,不换行 fmt.Println // 打印,自动换行,参数之间加空格 fmt.Printf // 按格式打印 fmt.Sprint // 返回字符串,不换行 fmt.Sprintln // 返回字符串,自动加空格和换行 fmt.Sprintf // 按格式返回字符串 fmt.Fprint // 写入指定 io.Writer,不换行 fmt.Fprintln // 写入指定 io.Writer,自动换行 fmt.Fprintf // 按格式写入指定 io.Writerfmt 占位符
%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。
