Go by Example - Logging, HTTP Client, HTTP Server and Context

Go by Example - Logging, HTTP Client, HTTP Server and Context

介绍go中的日志, http客户端, http服务端和上下文

Logging

Go 标准库提供了从 Go 程序输出日志的直接工具,其中 log 包用于自由格式输出,log/slog 包用于结构化输出。

logging.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import (
"bytes"
"fmt"
"log"
"os"

"log/slog"
)

func main() {
// 只需调用日志包中的 Println 等函数,就会使用标准日志程序
// 该程序已预先配置为向 os.Stderr 进行合理的日志输出
// Fatal* 或 Panic* 等附加方法将在记录日志后退出程序
log.Println("standard logger")

// 日志记录器可使用标志进行配置,以设置其输出格式
// 默认情况下,标准日志记录器设置了 log.Ldate 和 log.Ltime 标志
// 这些标志收集在 log.LstdFlags 中
// 例如,我们可以更改其标志,以输出微秒级精度的时间
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("with micro")

// 它还支持发出文件名和调用日志函数的行
log.SetFlags(log.LstdFlags | log.Lshortfile)
log.Println("with file/line")

// 创建一个自定义日志记录器并将其传递出去可能会很有用
// 创建新日志记录器时,我们可以设置一个前缀,将其输出与其他日志记录器区分开来
mylog := log.New(os.Stdout, "my:", log.LstdFlags)
mylog.Println("from mylog")

// 我们可以使用 SetPrefix 方法为现有记录仪(包括标准记录仪)设置前缀
mylog.SetPrefix("ohmy:")
mylog.Println("from mylog")

// 记录器可以具有自定义输出目标;任何io.Writer作品
var buf bytes.Buffer
buflog := log.New(&buf, "buf:", log.LstdFlags)

// 该调用会将日志输出写入 buf
buflog.Println("hello")

// 这实际上会在标准输出中显示出来
fmt.Print("from buflog:", buf.String())

// slog 软件包提供结构化日志输出。例如,可以直接以 JSON 格式记录日志
jsonHandler := slog.NewJSONHandler(os.Stderr, nil)
myslog := slog.New(jsonHandler)
myslog.Info("hi there")

// 除了消息之外,slog输出还可以包含任意数量的key=value对
myslog.Info("hello again", "key", "val", "age", 25)
}

输出示例;输出的日期和时间取决于示例运行的时间。

log
1
2
3
4
5
6
7
8
9
10
11
12
13
$ go run logging.go
2023/08/22 10:45:16 standard logger
2023/08/22 10:45:16.904141 with micro
2023/08/22 10:45:16 logging.go:40: with file/line
my:2023/08/22 10:45:16 from mylog
ohmy:2023/08/22 10:45:16 from mylog
from buflog:buf:2023/08/22 10:45:16 hello

{"time":"2023-08-22T10:45:16.904166391-07:00",
"level":"INFO","msg":"hi there"}
{"time":"2023-08-22T10:45:16.904178985-07:00",
"level":"INFO","msg":"hello again",
"key":"val","age":25}

为了在网站上更清晰地展示,这些内容被包裹起来;实际上,它们是在一行中发出的。

HTTP Client

Go 标准库中的 net/http 包为 HTTP 客户端和服务器提供了出色的支持。在本例中,我们将使用它来发出简单的 HTTP 请求。

http-clients.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"bufio"
"fmt"
"net/http"
)

func main() {
// http.Get 是创建 http.Client 对象并调用其 Get 方法的快捷方式
// 它使用 http.DefaultClient 对象,该对象具有有用的默认设置
resp, err := http.Get("https://gobyexample.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()

// 打印 HTTP 响应状态
fmt.Println("Response status:", resp.Status)

// 打印响应正文的前 5 行
scanner := bufio.NewScanner(resp.Body)
for i := 0; scanner.Scan() && i < 5; i++ {
fmt.Println(scanner.Text())
}

if err := scanner.Err(); err != nil {
panic(err)
}
}
log
1
2
3
4
5
6
7
$ go run http-clients.go
Response status: 200 OK
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go by Example</title>

HTTP Server

使用 net/http 软件包,编写基本 HTTP 服务器非常简单。

http-servers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
"fmt"
"net/http"
)

// 处理程序是 net/http 服务器的一个基本概念
// 处理程序是一个实现 http.Handler 接口的对象
// 编写处理程序的常用方法是在具有适当签名的函数上使用 http.HandlerFunc 适配器
func hello(w http.ResponseWriter, req *http.Request) {

// 作为处理程序的函数将 http.ResponseWriter 和 http.Request 作为参数
// 响应编写器用于填写 HTTP 响应。在这里,我们的简单响应只是 "hello/n"
fmt.Fprintf(w, "hello\n")
}

func headers(w http.ResponseWriter, req *http.Request) {
// 该处理程序通过读取所有 HTTP 请求头并将其回放到响应正文中,来完成一些更复杂的操作
for name, headers := range req.Header {
for _, h := range headers {
fmt.Fprintf(w, "%v: %v\n", name, h)
}
}
}

func main() {
// 我们使用 http.HandleFunc 方便函数在服务器路由上注册处理程序
// 该函数在 net/http 包中设置了默认路由器,并将一个函数作为参数
http.HandleFunc("/hello", hello)
http.HandleFunc("/headers", headers)

// 最后,我们使用端口和处理程序调用 ListenAndServe
http.ListenAndServe(":8090", nil)
}

在后台运行服务器, 访问 /hello 路由。

log
1
2
3
4
$ go run http-servers.go &

$ curl localhost:8090/hello
hello

Context

在上一个示例中,我们了解了如何设置一个简单的 HTTP 服务器。HTTP 服务器可用于演示如何使用 context.Context 控制取消。Context 可跨 API 边界和 goroutines 传递截止日期、取消信号和其他请求范围值。

context-in-http-servers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"net/http"
"time"
)

func hello(w http.ResponseWriter, req *http.Request) {
// net/http 机制会为每个请求创建一个 context.Context,并可通过 Context() 方法使用
ctx := req.Context()
fmt.Println("server: hello handler started")
defer fmt.Println("server: hello handler ended")

// 等待几秒钟后再向客户端发送回复。这可以模拟服务器正在进行的一些工作
// 在工作时,请密切关注上下文的 Done() 频道,查看是否有信号表明我们应该取消工作并尽快返回
select {
case <-time.After(10 * time.Second):
fmt.Fprintf(w, "hello\n")
case <-ctx.Done():

// 上下文的 Err() 方法会返回一个错误,解释 Done() 通道关闭的原因
err := ctx.Err()
fmt.Println("server:", err)
internalError := http.StatusInternalServerError
http.Error(w, err.Error(), internalError)
}
}

func main() {
// 和之前一样,我们在"/hello "路由上注册处理程序,然后开始提供服务
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}

在后台运行服务, 模拟客户端向 /hello 提出请求,并在开始后不久按下 Ctrl+C 以示取消。

log
1
2
3
4
5
6
7
$ go run context-in-http-servers.go &

$ curl localhost:8090/hello
server: hello handler started
^C
server: context canceled
server: hello handler ended

参考链接

Go by Example - Logging, HTTP Client, HTTP Server and Context

https://blog.wty.cool/2024/06/02/go_by_example/Logging-HTTP_Client-HTTP_Server-Context/

作者

孤独小狼

发布于

2024-06-02

更新于

2024-06-02

许可协议

评论