1// Copyright 2016 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Tests that require access to unexported names of the logging package. 16 17package logging 18 19import ( 20 "encoding/json" 21 "net/http" 22 "net/url" 23 "testing" 24 "time" 25 26 "cloud.google.com/go/internal/testutil" 27 "github.com/golang/protobuf/proto" 28 durpb "github.com/golang/protobuf/ptypes/duration" 29 structpb "github.com/golang/protobuf/ptypes/struct" 30 "google.golang.org/api/support/bundler" 31 mrpb "google.golang.org/genproto/googleapis/api/monitoredres" 32 logtypepb "google.golang.org/genproto/googleapis/logging/type" 33) 34 35func TestLoggerCreation(t *testing.T) { 36 const logID = "testing" 37 c := &Client{parent: "projects/PROJECT_ID"} 38 customResource := &mrpb.MonitoredResource{ 39 Type: "global", 40 Labels: map[string]string{ 41 "project_id": "ANOTHER_PROJECT", 42 }, 43 } 44 defaultBundler := &bundler.Bundler{ 45 DelayThreshold: DefaultDelayThreshold, 46 BundleCountThreshold: DefaultEntryCountThreshold, 47 BundleByteThreshold: DefaultEntryByteThreshold, 48 BundleByteLimit: 0, 49 BufferedByteLimit: DefaultBufferedByteLimit, 50 } 51 for _, test := range []struct { 52 options []LoggerOption 53 wantLogger *Logger 54 defaultResource bool 55 wantBundler *bundler.Bundler 56 }{ 57 { 58 options: nil, 59 wantLogger: &Logger{}, 60 defaultResource: true, 61 wantBundler: defaultBundler, 62 }, 63 { 64 options: []LoggerOption{ 65 CommonResource(nil), 66 CommonLabels(map[string]string{"a": "1"}), 67 }, 68 wantLogger: &Logger{ 69 commonResource: nil, 70 commonLabels: map[string]string{"a": "1"}, 71 }, 72 wantBundler: defaultBundler, 73 }, 74 { 75 options: []LoggerOption{CommonResource(customResource)}, 76 wantLogger: &Logger{commonResource: customResource}, 77 wantBundler: defaultBundler, 78 }, 79 { 80 options: []LoggerOption{ 81 DelayThreshold(time.Minute), 82 EntryCountThreshold(99), 83 EntryByteThreshold(17), 84 EntryByteLimit(18), 85 BufferedByteLimit(19), 86 }, 87 wantLogger: &Logger{}, 88 defaultResource: true, 89 wantBundler: &bundler.Bundler{ 90 DelayThreshold: time.Minute, 91 BundleCountThreshold: 99, 92 BundleByteThreshold: 17, 93 BundleByteLimit: 18, 94 BufferedByteLimit: 19, 95 }, 96 }, 97 } { 98 gotLogger := c.Logger(logID, test.options...) 99 if got, want := gotLogger.commonResource, test.wantLogger.commonResource; !test.defaultResource && !proto.Equal(got, want) { 100 t.Errorf("%v: resource: got %v, want %v", test.options, got, want) 101 } 102 if got, want := gotLogger.commonLabels, test.wantLogger.commonLabels; !testutil.Equal(got, want) { 103 t.Errorf("%v: commonLabels: got %v, want %v", test.options, got, want) 104 } 105 if got, want := gotLogger.bundler.DelayThreshold, test.wantBundler.DelayThreshold; got != want { 106 t.Errorf("%v: DelayThreshold: got %v, want %v", test.options, got, want) 107 } 108 if got, want := gotLogger.bundler.BundleCountThreshold, test.wantBundler.BundleCountThreshold; got != want { 109 t.Errorf("%v: BundleCountThreshold: got %v, want %v", test.options, got, want) 110 } 111 if got, want := gotLogger.bundler.BundleByteThreshold, test.wantBundler.BundleByteThreshold; got != want { 112 t.Errorf("%v: BundleByteThreshold: got %v, want %v", test.options, got, want) 113 } 114 if got, want := gotLogger.bundler.BundleByteLimit, test.wantBundler.BundleByteLimit; got != want { 115 t.Errorf("%v: BundleByteLimit: got %v, want %v", test.options, got, want) 116 } 117 if got, want := gotLogger.bundler.BufferedByteLimit, test.wantBundler.BufferedByteLimit; got != want { 118 t.Errorf("%v: BufferedByteLimit: got %v, want %v", test.options, got, want) 119 } 120 } 121} 122 123func TestToProtoStruct(t *testing.T) { 124 v := struct { 125 Foo string `json:"foo"` 126 Bar int `json:"bar,omitempty"` 127 Baz []float64 `json:"baz"` 128 Moo map[string]interface{} `json:"moo"` 129 }{ 130 Foo: "foovalue", 131 Baz: []float64{1.1}, 132 Moo: map[string]interface{}{ 133 "a": 1, 134 "b": "two", 135 "c": true, 136 }, 137 } 138 139 got, err := toProtoStruct(v) 140 if err != nil { 141 t.Fatal(err) 142 } 143 want := &structpb.Struct{ 144 Fields: map[string]*structpb.Value{ 145 "foo": {Kind: &structpb.Value_StringValue{StringValue: v.Foo}}, 146 "baz": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{ 147 {Kind: &structpb.Value_NumberValue{NumberValue: 1.1}}, 148 }}}}, 149 "moo": {Kind: &structpb.Value_StructValue{ 150 StructValue: &structpb.Struct{ 151 Fields: map[string]*structpb.Value{ 152 "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, 153 "b": {Kind: &structpb.Value_StringValue{StringValue: "two"}}, 154 "c": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, 155 }, 156 }, 157 }}, 158 }, 159 } 160 if !proto.Equal(got, want) { 161 t.Errorf("got %+v\nwant %+v", got, want) 162 } 163 164 // Non-structs should fail to convert. 165 for v := range []interface{}{3, "foo", []int{1, 2, 3}} { 166 _, err := toProtoStruct(v) 167 if err == nil { 168 t.Errorf("%v: got nil, want error", v) 169 } 170 } 171 172 // Test fast path. 173 got, err = toProtoStruct(want) 174 if err != nil { 175 t.Fatal(err) 176 } 177 if got != want { 178 t.Error("got and want should be identical, but are not") 179 } 180} 181 182func TestToLogEntryPayload(t *testing.T) { 183 var logger Logger 184 for _, test := range []struct { 185 in interface{} 186 wantText string 187 wantStruct *structpb.Struct 188 }{ 189 { 190 in: "string", 191 wantText: "string", 192 }, 193 { 194 in: map[string]interface{}{"a": 1, "b": true}, 195 wantStruct: &structpb.Struct{ 196 Fields: map[string]*structpb.Value{ 197 "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, 198 "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, 199 }, 200 }, 201 }, 202 { 203 in: json.RawMessage([]byte(`{"a": 1, "b": true}`)), 204 wantStruct: &structpb.Struct{ 205 Fields: map[string]*structpb.Value{ 206 "a": {Kind: &structpb.Value_NumberValue{NumberValue: 1}}, 207 "b": {Kind: &structpb.Value_BoolValue{BoolValue: true}}, 208 }, 209 }, 210 }, 211 } { 212 e, err := logger.toLogEntry(Entry{Payload: test.in}) 213 if err != nil { 214 t.Fatalf("%+v: %v", test.in, err) 215 } 216 if test.wantStruct != nil { 217 got := e.GetJsonPayload() 218 if !proto.Equal(got, test.wantStruct) { 219 t.Errorf("%+v: got %s, want %s", test.in, got, test.wantStruct) 220 } 221 } else { 222 got := e.GetTextPayload() 223 if got != test.wantText { 224 t.Errorf("%+v: got %s, want %s", test.in, got, test.wantText) 225 } 226 } 227 } 228} 229 230func TestToLogEntryTrace(t *testing.T) { 231 logger := &Logger{client: &Client{parent: "projects/P"}} 232 // Verify that we get the trace from the HTTP request if it isn't 233 // provided by the caller. 234 u := &url.URL{Scheme: "http"} 235 for _, test := range []struct { 236 in Entry 237 want string 238 }{ 239 {Entry{}, ""}, 240 {Entry{Trace: "t1"}, "t1"}, 241 { 242 Entry{ 243 HTTPRequest: &HTTPRequest{ 244 Request: &http.Request{URL: u, Header: http.Header{"foo": {"bar"}}}, 245 }, 246 }, 247 "", 248 }, 249 { 250 Entry{ 251 HTTPRequest: &HTTPRequest{ 252 Request: &http.Request{ 253 URL: u, 254 Header: http.Header{"X-Cloud-Trace-Context": {"t2"}}, 255 }, 256 }, 257 }, 258 "projects/P/traces/t2", 259 }, 260 { 261 Entry{ 262 HTTPRequest: &HTTPRequest{ 263 Request: &http.Request{ 264 URL: u, 265 Header: http.Header{"X-Cloud-Trace-Context": {"t3"}}, 266 }, 267 }, 268 Trace: "t4", 269 }, 270 "t4", 271 }, 272 } { 273 e, err := logger.toLogEntry(test.in) 274 if err != nil { 275 t.Fatalf("%+v: %v", test.in, err) 276 } 277 if got := e.Trace; got != test.want { 278 t.Errorf("%+v: got %q, want %q", test.in, got, test.want) 279 } 280 } 281} 282 283func TestFromHTTPRequest(t *testing.T) { 284 const testURL = "http:://example.com/path?q=1" 285 u, err := url.Parse(testURL) 286 if err != nil { 287 t.Fatal(err) 288 } 289 req := &HTTPRequest{ 290 Request: &http.Request{ 291 Method: "GET", 292 URL: u, 293 Header: map[string][]string{ 294 "User-Agent": {"user-agent"}, 295 "Referer": {"referer"}, 296 }, 297 }, 298 RequestSize: 100, 299 Status: 200, 300 ResponseSize: 25, 301 Latency: 100 * time.Second, 302 LocalIP: "127.0.0.1", 303 RemoteIP: "10.0.1.1", 304 CacheHit: true, 305 CacheValidatedWithOriginServer: true, 306 } 307 got := fromHTTPRequest(req) 308 want := &logtypepb.HttpRequest{ 309 RequestMethod: "GET", 310 RequestUrl: testURL, 311 RequestSize: 100, 312 Status: 200, 313 ResponseSize: 25, 314 Latency: &durpb.Duration{Seconds: 100}, 315 UserAgent: "user-agent", 316 ServerIp: "127.0.0.1", 317 RemoteIp: "10.0.1.1", 318 Referer: "referer", 319 CacheHit: true, 320 CacheValidatedWithOriginServer: true, 321 } 322 if !proto.Equal(got, want) { 323 t.Errorf("got %+v\nwant %+v", got, want) 324 } 325} 326 327func TestMonitoredResource(t *testing.T) { 328 for _, test := range []struct { 329 parent string 330 want *mrpb.MonitoredResource 331 }{ 332 { 333 "projects/P", 334 &mrpb.MonitoredResource{ 335 Type: "project", 336 Labels: map[string]string{"project_id": "P"}, 337 }, 338 }, 339 340 { 341 "folders/F", 342 &mrpb.MonitoredResource{ 343 Type: "folder", 344 Labels: map[string]string{"folder_id": "F"}, 345 }, 346 }, 347 { 348 "billingAccounts/B", 349 &mrpb.MonitoredResource{ 350 Type: "billing_account", 351 Labels: map[string]string{"account_id": "B"}, 352 }, 353 }, 354 { 355 "organizations/123", 356 &mrpb.MonitoredResource{ 357 Type: "organization", 358 Labels: map[string]string{"organization_id": "123"}, 359 }, 360 }, 361 { 362 "unknown/X", 363 &mrpb.MonitoredResource{ 364 Type: "global", 365 Labels: map[string]string{"project_id": "X"}, 366 }, 367 }, 368 { 369 "whatever", 370 &mrpb.MonitoredResource{ 371 Type: "global", 372 Labels: map[string]string{"project_id": "whatever"}, 373 }, 374 }, 375 } { 376 got := monitoredResource(test.parent) 377 if !testutil.Equal(got, test.want) { 378 t.Errorf("%q: got %+v, want %+v", test.parent, got, test.want) 379 } 380 } 381} 382 383// Used by the tests in logging_test. 384func SetNow(f func() time.Time) { 385 now = f 386} 387