-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgcp.go
111 lines (100 loc) · 2.75 KB
/
gcp.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
package gcplogger
import (
"bytes"
"context"
"encoding/json"
"fmt"
"cloud.google.com/go/logging"
"google.golang.org/api/option"
)
type GCPLogConfig struct {
GCP GCPConfig
LogID string
LevelModifier LevelModifier
}
type GCPConfig struct {
ProjectID string
ServiceAccountPath string
}
// Specify to modify logfields before sending to GCP logging.
// The most common case is to map other logging's library level to GCP's, read: https://github.com/rs/zerolog/issues/174
//
// OriginalField: field key of other logging's log level
// RemoveOriginal: remove original level field or not
// Mapping: function to convert other logging's log level value to GCP's log level
//
// list of GCP log level in: https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#logseverity
type LevelModifier struct {
OriginalField string
RemoveOriginal bool
Mapping func(originalLvl interface{}) logging.Severity
}
type StructuredLog map[string]interface{}
type StructuredLogParser func(b []byte) (StructuredLog, error)
type Writer struct {
cfg GCPLogConfig
logger *logging.Logger
sLogParser StructuredLogParser
}
// TODO use better auth method than service account
func NewWriter(
ctx context.Context,
cfg GCPLogConfig,
sLogParser StructuredLogParser,
) (*Writer, error) {
client, err := logging.NewClient(
ctx,
cfg.GCP.ProjectID,
option.WithCredentialsFile(cfg.GCP.ServiceAccountPath),
)
if err != nil {
return nil, fmt.Errorf("failed to init stackdriver NewClient: %w", err)
}
s := &Writer{
cfg: cfg,
logger: client.Logger(cfg.LogID),
}
if sLogParser != nil {
s.sLogParser = sLogParser
} else {
s.sLogParser = JsonStructuredLogParser
}
return s, nil
}
func NoStructuredLogParser(b []byte) (StructuredLog, error) {
return StructuredLog{
"message": string(b),
}, nil
}
func JsonStructuredLogParser(b []byte) (StructuredLog, error) {
var logFields StructuredLog
err := json.NewDecoder(bytes.NewReader(b)).Decode(&logFields)
if err != nil {
return nil, fmt.Errorf("failed to decode logFields: %w", err)
}
return logFields, nil
}
func (s *Writer) Write(b []byte) (n int, err error) {
entry := logging.Entry{}
if s.sLogParser == nil {
return 0, fmt.Errorf("nil structured log parser")
}
logFields, err := s.sLogParser(b)
if err != nil {
return 0, fmt.Errorf("error from structured log parser: %w", err)
}
mod := s.cfg.LevelModifier
// if true, need to modify the severity field in the original data
if mod.Mapping != nil || mod.RemoveOriginal {
oriLvl, ok := logFields[mod.OriginalField]
if ok {
entry.Severity = mod.Mapping(oriLvl)
}
}
if s.cfg.LevelModifier.RemoveOriginal {
delete(logFields, mod.OriginalField)
}
entry.Payload = logFields
s.logger.Log(entry)
return len(b), nil
}