-
Notifications
You must be signed in to change notification settings - Fork 2
/
loggingservice.go
194 lines (164 loc) · 5.47 KB
/
loggingservice.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package appwrap
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"sync"
"cloud.google.com/go/logging"
mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
)
type LogName string
type LoggerClientInterface interface {
Logger(logID string, opts ...logging.LoggerOption) LoggerInterface
Close() error
SetUpOnError()
Ping(ctx context.Context) error
}
type LoggerInterface interface {
Log(logging.Entry)
Flush() error
}
type LoggingClient struct {
mtx sync.Mutex
client *logging.Client
}
var globalLoggingClient LoggingClient
func (lc *LoggingClient) checkClientInitialization() error {
if lc.client == nil {
return fmt.Errorf("logging client is not initialized")
}
return nil
}
func (lc *LoggingClient) Logger(logID string, opts ...logging.LoggerOption) LoggerInterface {
if err := lc.checkClientInitialization(); err != nil {
panic(err)
}
return lc.client.Logger(logID, opts...)
}
func (lc *LoggingClient) SetUpOnError() {
if err := lc.checkClientInitialization(); err != nil {
panic(err)
}
lc.client.OnError = func(e error) {
fmt.Fprintf(os.Stderr, "logging error: %v", e)
}
}
func (lc *LoggingClient) Ping(ctx context.Context) error {
if err := lc.checkClientInitialization(); err != nil {
return err
}
return lc.client.Ping(ctx)
}
func (lc *LoggingClient) Close() error {
if err := lc.checkClientInitialization(); err != nil {
// Closing the logger here, if it's not initialized we don't care
return nil
}
lc.mtx.Lock()
defer lc.mtx.Unlock()
err := lc.client.Close()
lc.client = nil
return err
}
// CloseGlobalLoggingClient closes the global logging client.
// This should only be called once, only during a shutdown routine.
// Calling this while logging is still happening will produce panics
func CloseGlobalLoggingClient() error {
return globalLoggingClient.Close()
}
func GetOrCreateLoggingClient() *logging.Client {
if globalLoggingClient.client != nil {
return globalLoggingClient.client
}
globalLoggingClient.mtx.Lock()
defer globalLoggingClient.mtx.Unlock()
if globalLoggingClient.client == nil {
c := context.Background()
aeInfo := NewAppengineInfoFromContext(c)
client, err := logging.NewClient(c, fmt.Sprintf("projects/%s", aeInfo.NativeProjectID()))
if err != nil {
panic(fmt.Sprintf("failed to create logging client %s", err.Error()))
}
globalLoggingClient.client = client
}
return globalLoggingClient.client
}
func newLoggingClient() LoggerClientInterface {
client := GetOrCreateLoggingClient()
return &LoggingClient{client: client}
}
type LoggingServiceInterface interface {
CreateLog(labels map[string]string, logName LogName, r *http.Request, traceId string) Logging
Close()
}
func newStandardLoggingService(log Logging) LoggingServiceInterface {
return &StandardLoggingService{
log: log,
}
}
type StandardLoggingService struct {
log Logging
}
// CreateLog simply returns the App Engine logger for legacy standard environments
func (sls StandardLoggingService) CreateLog(labels map[string]string, logName LogName, r *http.Request, traceId string) Logging {
return sls.log
}
// Close is not implemented since there is nothing to shut down on App Engine's standard logger
func (sls StandardLoggingService) Close() {
}
// StackdriverLogging is a logger set up to work with an App Engine flexible environment
type StackdriverLoggingService struct {
appInfo AppengineInfo
client LoggerClientInterface
log Logging
resourceOptions logging.LoggerOption
}
// NewStackdriverLogging will return a new logger set up to work with an App Engine flexible environment
func newStackdriverLoggingService(client LoggerClientInterface, appInfo AppengineInfo, log Logging) LoggingServiceInterface {
client.SetUpOnError()
projectId := appInfo.NativeProjectID()
versionSplit := strings.Split(appInfo.VersionID(), ".")
versionId := versionSplit[0]
moduleName := appInfo.ModuleName()
return &StackdriverLoggingService{
appInfo: appInfo,
client: client,
log: log,
resourceOptions: basicAppEngineOptions(moduleName, projectId, versionId),
}
}
// Close will close the logging service and flush the logs. This will close off the logging service from being able to receive logs
func (sl *StackdriverLoggingService) Close() {
if err := sl.client.Close(); err != nil {
sl.log.Errorf("Unable to close stackdriver logging client: %+v", err)
}
}
// CreateLog will return a new logger to use throughout a single request
//
// Every Request on AppEngine includes a header X-Cloud-Trace-Context which contains a traceId. This id can be
// used to correlate various logs together from a request. Stackdriver will leverage this field when producing
// a child/parent relationship in the Stackdriver UI. However, not all logs include a request object. In that
// instance we will fallback to the provided traceId
func (sl *StackdriverLoggingService) CreateLog(labels map[string]string, logName LogName, r *http.Request, traceId string) Logging {
return sl.newStackdriverLogging(labels, logName, r, traceId)
}
// basicAppEngineOptions creates labels that will map the correct app engine instance to Stackdriver
func basicAppEngineOptions(moduleName, projectId, versionId string) logging.LoggerOption {
return logging.CommonResource(&mrpb.MonitoredResource{
Type: monitoredType(),
Labels: map[string]string{
"module_id": moduleName,
"project_id": projectId,
"version_id": versionId,
},
})
}
func monitoredType() string {
if InKubernetes() {
return "k8s_pod"
} else {
return "gae_app"
}
}