-
Notifications
You must be signed in to change notification settings - Fork 1
/
log.go
132 lines (118 loc) · 3.29 KB
/
log.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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Package crzerolog provides a zerolog-based logger for Cloud Run.
package crzerolog
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
)
var (
// CallerSkipFrameCount is the number of stack frames to skip to find the caller.
CallerSkipFrameCount = 3
projectID string
sourceLocationHook = &callerHook{}
// For trace header, see https://cloud.google.com/trace/docs/troubleshooting#force-trace
traceHeaderRegExp = regexp.MustCompile(`^\s*([0-9a-fA-F]+)(?:/(\d+))?(?:;o=[01])?\s*$`)
)
func init() {
zerolog.TimeFieldFormat = time.RFC3339Nano
zerolog.LevelFieldName = "severity"
zerolog.LevelFieldMarshalFunc = func(l zerolog.Level) string {
// mapping to Cloud Logging LogSeverity
// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
switch l {
case zerolog.TraceLevel:
return "DEFAULT"
case zerolog.DebugLevel:
return "DEBUG"
case zerolog.InfoLevel:
return "INFO"
case zerolog.WarnLevel:
return "WARNING"
case zerolog.ErrorLevel:
return "ERROR"
case zerolog.FatalLevel:
return "CRITICAL"
case zerolog.PanicLevel:
return "ALERT"
case zerolog.NoLevel:
return "DEFAULT"
default:
return "DEFAULT"
}
}
if isCloudRun() || isAppEngineSecond() {
// For performance, fetching Project ID here only once,
// rather than fetching it in every request.
id, err := fetchProjectIDFromMetadata()
if err != nil {
log.Fatalf("Failed to fetch mandatory project ID: %v", err)
}
projectID = id
} else {
projectID = fetchProjectIDFromEnv()
}
}
// callerHook implements zerolog.Hook interface.
type callerHook struct{}
// Run adds sourceLocation for the log to zerolog.Event.
func (h *callerHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
var file, line, function string
if pc, filePath, lineNum, ok := runtime.Caller(CallerSkipFrameCount); ok {
if f := runtime.FuncForPC(pc); f != nil {
function = f.Name()
}
line = fmt.Sprintf("%d", lineNum)
parts := strings.Split(filePath, "/")
file = parts[len(parts)-1]
}
e.Dict("logging.googleapis.com/sourceLocation",
zerolog.Dict().Str("file", file).Str("line", line).Str("function", function))
}
func fetchProjectIDFromMetadata() (string, error) {
req, err := http.NewRequest("GET",
"http://metadata.google.internal/computeMetadata/v1/project/project-id", nil)
if err != nil {
return "", err
}
req.Header.Add("Metadata-Flavor", "Google")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(b), nil
}
func fetchProjectIDFromEnv() string {
return os.Getenv("GOOGLE_CLOUD_PROJECT")
}
func traceContextFromHeader(header string) (string, string) {
matched := traceHeaderRegExp.FindStringSubmatch(header)
if len(matched) < 3 {
return "", ""
}
traceID, spanID := matched[1], matched[2]
if spanID == "" {
return traceID, ""
}
spanIDInt, err := strconv.ParseUint(spanID, 10, 64)
if err != nil {
// invalid
return "", ""
}
// spanId for cloud logging must be 16-character hexadecimal number.
// See: https://cloud.google.com/trace/docs/trace-log-integration#associating
spanIDHex := fmt.Sprintf("%016x", spanIDInt)
return traceID, spanIDHex
}