1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package logadmin
18
19 import (
20 "context"
21 "flag"
22 "log"
23 "net/http"
24 "net/url"
25 "os"
26 "strings"
27 "testing"
28 "time"
29
30 "cloud.google.com/go/internal/testutil"
31 "cloud.google.com/go/logging"
32 logpb "cloud.google.com/go/logging/apiv2/loggingpb"
33 ltesting "cloud.google.com/go/logging/internal/testing"
34 "github.com/google/go-cmp/cmp/cmpopts"
35 "google.golang.org/api/option"
36 mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
37 audit "google.golang.org/genproto/googleapis/cloud/audit"
38 logtypepb "google.golang.org/genproto/googleapis/logging/type"
39 "google.golang.org/grpc"
40 "google.golang.org/protobuf/types/known/anypb"
41 durpb "google.golang.org/protobuf/types/known/durationpb"
42 structpb "google.golang.org/protobuf/types/known/structpb"
43 "google.golang.org/protobuf/types/known/timestamppb"
44 )
45
46 var (
47 client *Client
48 testProjectID string
49 )
50
51 var (
52
53 integrationTest bool
54
55 newClient func(ctx context.Context, projectID string) *Client
56 )
57
58 func TestMain(m *testing.M) {
59 flag.Parse()
60 ctx := context.Background()
61 testProjectID = testutil.ProjID()
62 if testProjectID == "" || testing.Short() {
63 integrationTest = false
64 if testProjectID != "" {
65 log.Print("Integration tests skipped in short mode (using fake instead)")
66 }
67 testProjectID = "PROJECT_ID"
68 addr, err := ltesting.NewServer()
69 if err != nil {
70 log.Fatalf("creating fake server: %v", err)
71 }
72 newClient = func(ctx context.Context, projectID string) *Client {
73 conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock())
74 if err != nil {
75 log.Fatalf("dialing %q: %v", addr, err)
76 }
77 c, err := NewClient(ctx, projectID, option.WithGRPCConn(conn))
78 if err != nil {
79 log.Fatalf("creating client for fake at %q: %v", addr, err)
80 }
81 return c
82 }
83 } else {
84 integrationTest = true
85 ts := testutil.TokenSource(ctx, logging.AdminScope)
86 if ts == nil {
87 log.Fatal("The project key must be set. See CONTRIBUTING.md for details")
88 }
89 log.Printf("running integration tests with project %s", testProjectID)
90 newClient = func(ctx context.Context, projectID string) *Client {
91 c, err := NewClient(ctx, projectID, option.WithTokenSource(ts),
92 option.WithGRPCDialOption(grpc.WithBlock()))
93 if err != nil {
94 log.Fatalf("creating prod client: %v", err)
95 }
96 return c
97 }
98 }
99 client = newClient(ctx, testProjectID)
100 initMetrics(ctx)
101 cleanup := initSinks(ctx)
102 exit := m.Run()
103 cleanup()
104 client.Close()
105 os.Exit(exit)
106 }
107
108
109
110 func TestClientClose(t *testing.T) {
111 c := newClient(context.Background(), testProjectID)
112 if err := c.Close(); err != nil {
113 t.Errorf("want got %v, want nil", err)
114 }
115 }
116
117 func TestFromLogEntry(t *testing.T) {
118 now := time.Now()
119 res := &mrpb.MonitoredResource{Type: "global"}
120 ts := timestamppb.New(now)
121 logEntry := logpb.LogEntry{
122 LogName: "projects/PROJECT_ID/logs/LOG_ID",
123 Resource: res,
124 Payload: &logpb.LogEntry_TextPayload{TextPayload: "hello"},
125 Timestamp: ts,
126 Severity: logtypepb.LogSeverity_INFO,
127 InsertId: "123",
128 HttpRequest: &logtypepb.HttpRequest{
129 RequestMethod: "GET",
130 RequestUrl: "http:://example.com/path?q=1",
131 RequestSize: 100,
132 Status: 200,
133 ResponseSize: 25,
134 Latency: &durpb.Duration{Seconds: 100},
135 UserAgent: "user-agent",
136 RemoteIp: "127.0.0.1",
137 ServerIp: "127.0.0.1",
138 Referer: "referer",
139 CacheLookup: true,
140 CacheHit: true,
141 CacheValidatedWithOriginServer: true,
142 CacheFillBytes: 2048,
143 },
144 Labels: map[string]string{
145 "a": "1",
146 "b": "two",
147 "c": "true",
148 },
149 SourceLocation: &logpb.LogEntrySourceLocation{
150 File: "some_file.go",
151 Line: 1,
152 Function: "someFunction",
153 },
154 }
155 u, err := url.Parse("http:://example.com/path?q=1")
156 if err != nil {
157 t.Fatal(err)
158 }
159 want := &logging.Entry{
160 LogName: "projects/PROJECT_ID/logs/LOG_ID",
161 Resource: res,
162 Timestamp: now.In(time.UTC),
163 Severity: logging.Info,
164 Payload: "hello",
165 Labels: map[string]string{
166 "a": "1",
167 "b": "two",
168 "c": "true",
169 },
170 InsertID: "123",
171 HTTPRequest: &logging.HTTPRequest{
172 Request: &http.Request{
173 Method: "GET",
174 URL: u,
175 Header: map[string][]string{
176 "User-Agent": {"user-agent"},
177 "Referer": {"referer"},
178 },
179 },
180 RequestSize: 100,
181 Status: 200,
182 ResponseSize: 25,
183 Latency: 100 * time.Second,
184 LocalIP: "127.0.0.1",
185 RemoteIP: "127.0.0.1",
186 CacheLookup: true,
187 CacheHit: true,
188 CacheValidatedWithOriginServer: true,
189 CacheFillBytes: 2048,
190 },
191 SourceLocation: &logpb.LogEntrySourceLocation{
192 File: "some_file.go",
193 Line: 1,
194 Function: "someFunction",
195 },
196 }
197 got, err := fromLogEntry(&logEntry)
198 if err != nil {
199 t.Fatal(err)
200 }
201 if diff := testutil.Diff(got, want, cmpopts.IgnoreUnexported(http.Request{})); diff != "" {
202 t.Errorf("FullEntry:\n%s", diff)
203 }
204
205
206 alog := &audit.AuditLog{
207 ServiceName: "svc",
208 MethodName: "method",
209 ResourceName: "shelves/S/books/B",
210 }
211 any, err := anypb.New(alog)
212 if err != nil {
213 t.Fatal(err)
214 }
215 logEntry = logpb.LogEntry{
216 LogName: "projects/PROJECT_ID/logs/LOG_ID",
217 Resource: res,
218 Timestamp: ts,
219 Payload: &logpb.LogEntry_ProtoPayload{ProtoPayload: any},
220 }
221 got, err = fromLogEntry(&logEntry)
222 if err != nil {
223 t.Fatal(err)
224 }
225 if !ltesting.PayloadEqual(got.Payload, alog) {
226 t.Errorf("got %+v, want %+v", got.Payload, alog)
227 }
228
229
230 jstruct := &structpb.Struct{Fields: map[string]*structpb.Value{
231 "f": {Kind: &structpb.Value_NumberValue{NumberValue: 3.1}},
232 }}
233 logEntry = logpb.LogEntry{
234 LogName: "projects/PROJECT_ID/logs/LOG_ID",
235 Resource: res,
236 Timestamp: ts,
237 Payload: &logpb.LogEntry_JsonPayload{JsonPayload: jstruct},
238 }
239 got, err = fromLogEntry(&logEntry)
240 if err != nil {
241 t.Fatal(err)
242 }
243 if !ltesting.PayloadEqual(got.Payload, jstruct) {
244 t.Errorf("got %+v, want %+v", got.Payload, jstruct)
245 }
246
247
248 logEntry = logpb.LogEntry{
249 LogName: "projects/PROJECT_ID/logs/LOG_ID",
250 Resource: res,
251 Timestamp: ts,
252 }
253 got, err = fromLogEntry(&logEntry)
254 if err != nil {
255 t.Fatal(err)
256 }
257 if !ltesting.PayloadEqual(got.Payload, nil) {
258 t.Errorf("got %+v, want %+v", got.Payload, nil)
259 }
260 }
261
262 func TestListLogEntriesRequestDefaults(t *testing.T) {
263 const timeFilterPrefix = "timestamp >= "
264
265 got := listLogEntriesRequest("projects/PROJECT_ID", nil)
266
267
268 if len(got.Filter) < len(timeFilterPrefix) {
269 t.Errorf("got %v; want len(%v) start with '%v'", got, got.Filter, timeFilterPrefix)
270 }
271 filterTime, err := time.Parse(time.RFC3339, strings.Trim(got.Filter[len(timeFilterPrefix):], "\""))
272 if err != nil {
273 t.Errorf("got %v; want %v in RFC3339", err, got.Filter)
274 }
275 timeDiff := time.Now().UTC().Sub(filterTime)
276
277
278 if !testutil.Equal(got.ResourceNames, []string{"projects/PROJECT_ID"}) || got.OrderBy != "" || timeDiff.Hours() < 24 {
279 t.Errorf("got %v; want resource_names:\"projects/PROJECT_ID\" filter: %v - 24 hours order_by:\"\"", got, filterTime)
280 }
281 }
282
283 func TestListLogEntriesRequest(t *testing.T) {
284 for _, test := range []struct {
285 opts []EntriesOption
286 resourceNames []string
287 filterPrefix string
288 orderBy string
289 pageSize int32
290 }{
291
292 {
293
294 opts: []EntriesOption{
295 NewestFirst(),
296 Filter(`timestamp > "2020-10-30T15:39:09Z"`),
297 },
298 resourceNames: []string{"projects/PROJECT_ID"},
299 filterPrefix: `timestamp > "2020-10-30T15:39:09Z"`,
300 orderBy: "timestamp desc",
301 },
302 {
303
304 opts: []EntriesOption{
305 NewestFirst(),
306 Filter("f"),
307 },
308 resourceNames: []string{"projects/PROJECT_ID"},
309 filterPrefix: "f AND timestamp >= \"",
310 orderBy: "timestamp desc",
311 },
312 {
313
314 opts: []EntriesOption{
315 ProjectIDs([]string{"foo"}),
316 },
317 resourceNames: []string{"projects/foo"},
318 filterPrefix: "timestamp >= \"",
319 orderBy: "",
320 },
321 {
322
323 opts: []EntriesOption{
324 ResourceNames([]string{"folders/F", "organizations/O"}),
325 },
326 resourceNames: []string{"folders/F", "organizations/O"},
327 filterPrefix: "timestamp >= \"",
328 orderBy: "",
329 },
330 {
331
332 opts: []EntriesOption{
333 NewestFirst(),
334 Filter("f"),
335 ProjectIDs([]string{"foo"}),
336 },
337 resourceNames: []string{"projects/foo"},
338 filterPrefix: "f AND timestamp >= \"",
339 orderBy: "timestamp desc",
340 },
341 {
342
343 opts: []EntriesOption{
344 NewestFirst(),
345 Filter("no"),
346 ProjectIDs([]string{"foo"}),
347 Filter("f"),
348 },
349 resourceNames: []string{"projects/foo"},
350 filterPrefix: "f AND timestamp >= \"",
351 orderBy: "timestamp desc",
352 },
353 {
354
355 opts: []EntriesOption{
356 ProjectIDs([]string{"foo"}),
357 PageSize(100),
358 },
359 resourceNames: []string{"projects/foo"},
360 filterPrefix: "timestamp >= \"",
361 pageSize: 100,
362 },
363 } {
364 got := listLogEntriesRequest("projects/PROJECT_ID", test.opts)
365 want := &logpb.ListLogEntriesRequest{
366 ResourceNames: test.resourceNames,
367 Filter: test.filterPrefix,
368 OrderBy: test.orderBy,
369 PageSize: test.pageSize,
370 }
371 if !testutil.Equal(got.ResourceNames, want.ResourceNames) || !strings.HasPrefix(got.Filter, want.Filter) || got.OrderBy != want.OrderBy || got.PageSize != want.PageSize {
372 t.Errorf("got: %v; want %v (mind wanted Filter is prefix)", got, want)
373 }
374 }
375 }
376
View as plain text