istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/telemetry_logging_test.go (about) 1 // Copyright Istio Authors 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 package model 16 17 import ( 18 "reflect" 19 "sort" 20 "testing" 21 22 accesslog "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3" 23 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 24 fileaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/file/v3" 25 grpcaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" 26 otelaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/open_telemetry/v3" 27 otlpcommon "go.opentelemetry.io/proto/otlp/common/v1" 28 "google.golang.org/protobuf/types/known/structpb" 29 wrappers "google.golang.org/protobuf/types/known/wrapperspb" 30 31 meshconfig "istio.io/api/mesh/v1alpha1" 32 tpb "istio.io/api/telemetry/v1alpha1" 33 "istio.io/api/type/v1beta1" 34 "istio.io/istio/pilot/pkg/networking" 35 "istio.io/istio/pilot/pkg/serviceregistry/provider" 36 "istio.io/istio/pilot/pkg/util/protoconv" 37 "istio.io/istio/pkg/config" 38 "istio.io/istio/pkg/config/protocol" 39 "istio.io/istio/pkg/config/schema/gvk" 40 "istio.io/istio/pkg/test/util/assert" 41 "istio.io/istio/pkg/util/protomarshal" 42 "istio.io/istio/pkg/wellknown" 43 ) 44 45 func TestFileAccessLogFormat(t *testing.T) { 46 cases := []struct { 47 name string 48 formatString string 49 expected string 50 }{ 51 { 52 name: "empty", 53 expected: EnvoyTextLogFormat, 54 }, 55 { 56 name: "contains newline", 57 formatString: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% \n", 58 expected: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% \n", 59 }, 60 { 61 name: "miss newline", 62 formatString: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%", 63 expected: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\n", 64 }, 65 } 66 67 for _, tc := range cases { 68 t.Run(tc.name, func(t *testing.T) { 69 got := fileAccessLogFormat(tc.formatString) 70 assert.Equal(t, tc.expected, got) 71 }) 72 } 73 } 74 75 func TestAccessLogging(t *testing.T) { 76 labels := map[string]string{"app": "test"} 77 sidecar := &Proxy{ 78 ConfigNamespace: "default", 79 Labels: labels, 80 Metadata: &NodeMetadata{Labels: labels}, 81 } 82 prometheus := &tpb.Telemetry{ 83 Metrics: []*tpb.Metrics{ 84 { 85 Providers: []*tpb.ProviderRef{ 86 { 87 Name: "envoy", 88 }, 89 }, 90 }, 91 }, 92 } 93 envoy := &tpb.Telemetry{ 94 AccessLogging: []*tpb.AccessLogging{ 95 { 96 Providers: []*tpb.ProviderRef{ 97 { 98 Name: "envoy", 99 }, 100 }, 101 }, 102 }, 103 } 104 client := &tpb.Telemetry{ 105 AccessLogging: []*tpb.AccessLogging{ 106 { 107 Match: &tpb.AccessLogging_LogSelector{ 108 Mode: tpb.WorkloadMode_CLIENT, 109 }, 110 Providers: []*tpb.ProviderRef{ 111 { 112 Name: "envoy", 113 }, 114 }, 115 }, 116 }, 117 } 118 clientDisabled := &tpb.Telemetry{ 119 AccessLogging: []*tpb.AccessLogging{ 120 { 121 Match: &tpb.AccessLogging_LogSelector{ 122 Mode: tpb.WorkloadMode_CLIENT, 123 }, 124 Providers: []*tpb.ProviderRef{ 125 { 126 Name: "envoy", 127 }, 128 }, 129 Disabled: &wrappers.BoolValue{ 130 Value: true, 131 }, 132 }, 133 }, 134 } 135 targetRefClient := &tpb.Telemetry{ 136 TargetRef: &v1beta1.PolicyTargetReference{ 137 Group: gvk.KubernetesGateway.Group, 138 Kind: gvk.KubernetesGateway.Kind, 139 Name: "my-gateway", 140 }, 141 AccessLogging: []*tpb.AccessLogging{ 142 { 143 Match: &tpb.AccessLogging_LogSelector{ 144 Mode: tpb.WorkloadMode_CLIENT, 145 }, 146 Providers: []*tpb.ProviderRef{ 147 { 148 Name: "envoy", 149 }, 150 }, 151 }, 152 }, 153 } 154 sidecarClient := &tpb.Telemetry{ 155 Selector: &v1beta1.WorkloadSelector{ 156 MatchLabels: labels, 157 }, 158 AccessLogging: []*tpb.AccessLogging{ 159 { 160 Match: &tpb.AccessLogging_LogSelector{ 161 Mode: tpb.WorkloadMode_CLIENT, 162 }, 163 Providers: []*tpb.ProviderRef{ 164 { 165 Name: "envoy", 166 }, 167 }, 168 }, 169 }, 170 } 171 server := &tpb.Telemetry{ 172 AccessLogging: []*tpb.AccessLogging{ 173 { 174 Match: &tpb.AccessLogging_LogSelector{ 175 Mode: tpb.WorkloadMode_SERVER, 176 }, 177 Providers: []*tpb.ProviderRef{ 178 { 179 Name: "envoy", 180 }, 181 }, 182 }, 183 }, 184 } 185 serverDisabled := &tpb.Telemetry{ 186 AccessLogging: []*tpb.AccessLogging{ 187 { 188 Match: &tpb.AccessLogging_LogSelector{ 189 Mode: tpb.WorkloadMode_SERVER, 190 }, 191 Providers: []*tpb.ProviderRef{ 192 { 193 Name: "envoy", 194 }, 195 }, 196 Disabled: &wrappers.BoolValue{ 197 Value: true, 198 }, 199 }, 200 }, 201 } 202 serverAndClient := &tpb.Telemetry{ 203 AccessLogging: []*tpb.AccessLogging{ 204 { 205 Match: &tpb.AccessLogging_LogSelector{ 206 Mode: tpb.WorkloadMode_CLIENT_AND_SERVER, 207 }, 208 Providers: []*tpb.ProviderRef{ 209 { 210 Name: "envoy", 211 }, 212 }, 213 }, 214 }, 215 } 216 stackdriver := &tpb.Telemetry{ 217 AccessLogging: []*tpb.AccessLogging{ 218 { 219 Providers: []*tpb.ProviderRef{ 220 { 221 Name: "stackdriver", 222 }, 223 }, 224 }, 225 }, 226 } 227 empty := &tpb.Telemetry{ 228 AccessLogging: []*tpb.AccessLogging{{}}, 229 } 230 defaultJSON := &tpb.Telemetry{ 231 AccessLogging: []*tpb.AccessLogging{ 232 { 233 Providers: []*tpb.ProviderRef{ 234 { 235 Name: "envoy-json", 236 }, 237 }, 238 }, 239 }, 240 } 241 disabled := &tpb.Telemetry{ 242 AccessLogging: []*tpb.AccessLogging{ 243 { 244 Disabled: &wrappers.BoolValue{Value: true}, 245 }, 246 }, 247 } 248 nonExistant := &tpb.Telemetry{ 249 AccessLogging: []*tpb.AccessLogging{ 250 { 251 Providers: []*tpb.ProviderRef{ 252 { 253 Name: "custom-provider", 254 }, 255 }, 256 }, 257 }, 258 } 259 multiAccessLogging := &tpb.Telemetry{ 260 AccessLogging: []*tpb.AccessLogging{ 261 { 262 Providers: []*tpb.ProviderRef{ 263 { 264 Name: "envoy", 265 }, 266 }, 267 }, 268 { 269 Providers: []*tpb.ProviderRef{ 270 { 271 Name: "envoy-json", 272 }, 273 }, 274 }, 275 }, 276 } 277 multiAccessLoggingWithDisabled := &tpb.Telemetry{ 278 AccessLogging: []*tpb.AccessLogging{ 279 { 280 Providers: []*tpb.ProviderRef{ 281 { 282 Name: "envoy", 283 }, 284 }, 285 }, 286 { 287 Providers: []*tpb.ProviderRef{ 288 { 289 Name: "envoy-json", 290 }, 291 }, 292 Disabled: &wrappers.BoolValue{ 293 Value: true, 294 }, 295 }, 296 }, 297 } 298 multiAccessLoggingAndProviders := &tpb.Telemetry{ 299 AccessLogging: []*tpb.AccessLogging{ 300 { 301 Providers: []*tpb.ProviderRef{ 302 { 303 Name: "envoy", 304 }, 305 { 306 Name: "envoy-json", 307 }, 308 }, 309 }, 310 { 311 Providers: []*tpb.ProviderRef{ 312 { 313 Name: "envoy", 314 }, 315 }, 316 }, 317 }, 318 } 319 multiFilters := &tpb.Telemetry{ 320 AccessLogging: []*tpb.AccessLogging{ 321 { 322 Match: &tpb.AccessLogging_LogSelector{ 323 Mode: tpb.WorkloadMode_SERVER, 324 }, 325 Providers: []*tpb.ProviderRef{ 326 { 327 Name: "envoy", 328 }, 329 }, 330 }, 331 { 332 Match: &tpb.AccessLogging_LogSelector{ 333 Mode: tpb.WorkloadMode_CLIENT_AND_SERVER, // pickup last filter 334 }, 335 Providers: []*tpb.ProviderRef{ 336 { 337 Name: "envoy", 338 }, 339 }, 340 }, 341 }, 342 } 343 multiFiltersDisabled := &tpb.Telemetry{ 344 AccessLogging: []*tpb.AccessLogging{ 345 { 346 Match: &tpb.AccessLogging_LogSelector{ 347 Mode: tpb.WorkloadMode_SERVER, 348 }, 349 Providers: []*tpb.ProviderRef{ 350 { 351 Name: "envoy", 352 }, 353 }, 354 }, 355 { 356 Match: &tpb.AccessLogging_LogSelector{ 357 Mode: tpb.WorkloadMode_CLIENT_AND_SERVER, // pickup last filter 358 }, 359 Providers: []*tpb.ProviderRef{ 360 { 361 Name: "envoy", 362 }, 363 }, 364 Disabled: &wrappers.BoolValue{ 365 Value: true, 366 }, 367 }, 368 }, 369 } 370 serverAndClientDifferent := &tpb.Telemetry{ 371 AccessLogging: []*tpb.AccessLogging{ 372 { 373 Match: &tpb.AccessLogging_LogSelector{ 374 Mode: tpb.WorkloadMode_SERVER, 375 }, 376 Providers: []*tpb.ProviderRef{ 377 { 378 Name: "envoy", 379 }, 380 }, 381 }, 382 { 383 Match: &tpb.AccessLogging_LogSelector{ 384 Mode: tpb.WorkloadMode_CLIENT, 385 }, 386 Providers: []*tpb.ProviderRef{ 387 { 388 Name: "envoy-json", 389 }, 390 }, 391 }, 392 }, 393 } 394 tests := []struct { 395 name string 396 cfgs []config.Config 397 class networking.ListenerClass 398 proxy *Proxy 399 defaultProviders []string 400 want []string 401 }{ 402 { 403 "empty", 404 nil, 405 networking.ListenerClassSidecarOutbound, 406 sidecar, 407 nil, 408 nil, // No Telemetry API configured, fall back to legacy mesh config setting 409 }, 410 { 411 "prometheus-mesh", 412 []config.Config{newTelemetry("istio-system", prometheus)}, 413 networking.ListenerClassSidecarOutbound, 414 sidecar, 415 nil, 416 nil, // No Telemetry API configured, fall back to legacy mesh config setting 417 }, 418 { 419 "prometheus-namespace", 420 []config.Config{newTelemetry("default", prometheus)}, 421 networking.ListenerClassSidecarOutbound, 422 sidecar, 423 nil, 424 nil, // No Telemetry API configured, fall back to legacy mesh config setting 425 }, 426 { 427 "prometheus-workload", 428 []config.Config{newTelemetry("default", &tpb.Telemetry{ 429 Selector: &v1beta1.WorkloadSelector{ 430 MatchLabels: labels, 431 }, 432 Metrics: []*tpb.Metrics{ 433 { 434 Providers: []*tpb.ProviderRef{ 435 { 436 Name: "envoy", 437 }, 438 }, 439 }, 440 }, 441 })}, 442 networking.ListenerClassSidecarOutbound, 443 sidecar, 444 nil, 445 nil, // No Telemetry API configured, fall back to legacy mesh config setting 446 }, 447 { 448 "default provider only", 449 nil, 450 networking.ListenerClassSidecarOutbound, 451 sidecar, 452 []string{"envoy"}, 453 []string{"envoy"}, 454 }, 455 { 456 "provider only", 457 []config.Config{newTelemetry("istio-system", envoy)}, 458 networking.ListenerClassSidecarOutbound, 459 sidecar, 460 nil, 461 []string{"envoy"}, 462 }, 463 { 464 "client - gateway", 465 []config.Config{newTelemetry("istio-system", client)}, 466 networking.ListenerClassGateway, 467 sidecar, 468 nil, 469 []string{"envoy"}, 470 }, 471 { 472 "client - gateway defined by targetRef", 473 []config.Config{newTelemetry("default", targetRefClient)}, 474 networking.ListenerClassGateway, 475 sidecar, 476 nil, 477 []string{"envoy"}, 478 }, 479 { 480 "client - outbound", 481 []config.Config{newTelemetry("istio-system", client)}, 482 networking.ListenerClassSidecarOutbound, 483 sidecar, 484 nil, 485 []string{"envoy"}, 486 }, 487 { 488 "client - inbound", 489 []config.Config{newTelemetry("istio-system", client)}, 490 networking.ListenerClassSidecarInbound, 491 sidecar, 492 nil, 493 []string{}, 494 }, 495 { 496 "client - disabled server", 497 []config.Config{newTelemetry("istio-system", client), newTelemetry("default", serverDisabled)}, 498 networking.ListenerClassSidecarOutbound, 499 sidecar, 500 nil, 501 []string{"envoy"}, 502 }, 503 { 504 "client - disabled client", 505 []config.Config{newTelemetry("istio-system", client), newTelemetry("default", clientDisabled)}, 506 networking.ListenerClassSidecarOutbound, 507 sidecar, 508 nil, 509 []string{}, 510 }, 511 { 512 "client - disabled - enabled", 513 []config.Config{newTelemetry("istio-system", client), newTelemetry("default", clientDisabled), newTelemetry("default", sidecarClient)}, 514 networking.ListenerClassSidecarOutbound, 515 sidecar, 516 nil, 517 []string{"envoy"}, 518 }, 519 { 520 "server - gateway", 521 []config.Config{newTelemetry("istio-system", server)}, 522 networking.ListenerClassGateway, 523 sidecar, 524 nil, 525 []string{}, 526 }, 527 { 528 "server - inbound", 529 []config.Config{newTelemetry("istio-system", server)}, 530 networking.ListenerClassSidecarInbound, 531 sidecar, 532 nil, 533 []string{"envoy"}, 534 }, 535 { 536 "server - outbound", 537 []config.Config{newTelemetry("istio-system", server)}, 538 networking.ListenerClassSidecarOutbound, 539 sidecar, 540 nil, 541 []string{}, 542 }, 543 { 544 "server and client - gateway", 545 []config.Config{newTelemetry("istio-system", serverAndClient)}, 546 networking.ListenerClassGateway, 547 sidecar, 548 nil, 549 []string{"envoy"}, 550 }, 551 { 552 "server and client - inbound", 553 []config.Config{newTelemetry("istio-system", serverAndClient)}, 554 networking.ListenerClassSidecarInbound, 555 sidecar, 556 nil, 557 []string{"envoy"}, 558 }, 559 { 560 "server and client - outbound", 561 []config.Config{newTelemetry("istio-system", serverAndClient)}, 562 networking.ListenerClassSidecarOutbound, 563 sidecar, 564 nil, 565 []string{"envoy"}, 566 }, 567 { 568 "override default", 569 []config.Config{newTelemetry("istio-system", envoy)}, 570 networking.ListenerClassSidecarOutbound, 571 sidecar, 572 []string{"stackdriver"}, 573 []string{"envoy"}, 574 }, 575 { 576 "override namespace", 577 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", defaultJSON)}, 578 networking.ListenerClassSidecarOutbound, 579 sidecar, 580 nil, 581 []string{"envoy-json"}, 582 }, 583 { 584 "empty config inherits", 585 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", empty)}, 586 networking.ListenerClassSidecarOutbound, 587 sidecar, 588 nil, 589 []string{"envoy"}, 590 }, 591 { 592 "stackdriver", 593 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", stackdriver)}, 594 networking.ListenerClassSidecarOutbound, 595 sidecar, 596 nil, 597 []string{}, 598 }, 599 { 600 "default envoy JSON", 601 []config.Config{newTelemetry("istio-system", defaultJSON)}, 602 networking.ListenerClassSidecarOutbound, 603 sidecar, 604 nil, 605 []string{"envoy-json"}, 606 }, 607 { 608 "disable config", 609 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", disabled)}, 610 networking.ListenerClassSidecarOutbound, 611 sidecar, 612 nil, 613 []string{}, 614 }, 615 { 616 "disable default", 617 []config.Config{newTelemetry("default", disabled)}, 618 networking.ListenerClassSidecarOutbound, 619 sidecar, 620 []string{"envoy"}, 621 []string{}, 622 }, 623 { 624 "non existing", 625 []config.Config{newTelemetry("default", nonExistant)}, 626 networking.ListenerClassSidecarOutbound, 627 sidecar, 628 []string{"envoy"}, 629 []string{}, 630 }, 631 { 632 "server - multi filters", 633 []config.Config{newTelemetry("istio-system", multiFilters)}, 634 networking.ListenerClassSidecarOutbound, 635 sidecar, 636 nil, 637 []string{"envoy"}, 638 }, 639 { 640 "server - multi filters disabled", 641 []config.Config{newTelemetry("istio-system", multiFiltersDisabled)}, 642 networking.ListenerClassSidecarOutbound, 643 sidecar, 644 nil, 645 []string{}, 646 }, 647 { 648 "multi accesslogging", 649 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", multiAccessLogging)}, 650 networking.ListenerClassSidecarOutbound, 651 sidecar, 652 nil, 653 []string{"envoy", "envoy-json"}, 654 }, 655 { 656 "multi accesslogging with disabled", 657 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", multiAccessLoggingWithDisabled)}, 658 networking.ListenerClassSidecarOutbound, 659 sidecar, 660 nil, 661 []string{"envoy"}, 662 }, 663 { 664 "multi accesslogging - multi providers", 665 []config.Config{newTelemetry("istio-system", envoy), newTelemetry("default", multiAccessLoggingAndProviders)}, 666 networking.ListenerClassSidecarOutbound, 667 sidecar, 668 nil, 669 []string{"envoy", "envoy-json"}, 670 }, 671 { 672 "server and client different - inbound", 673 []config.Config{newTelemetry("istio-system", serverAndClientDifferent)}, 674 networking.ListenerClassSidecarInbound, 675 sidecar, 676 nil, 677 []string{"envoy"}, 678 }, 679 { 680 "server and client different - outbound", 681 []config.Config{newTelemetry("istio-system", serverAndClientDifferent)}, 682 networking.ListenerClassSidecarOutbound, 683 sidecar, 684 nil, 685 []string{"envoy-json"}, 686 }, 687 } 688 for _, tt := range tests { 689 t.Run(tt.name, func(t *testing.T) { 690 telemetry, ctx := createTestTelemetries(tt.cfgs, t) 691 telemetry.meshConfig.DefaultProviders.AccessLogging = tt.defaultProviders 692 var got []string 693 cfgs := telemetry.AccessLogging(ctx, tt.proxy, tt.class, nil) 694 if cfgs != nil { 695 got = []string{} 696 for _, p := range cfgs { 697 if p.Disabled { 698 continue 699 } 700 got = append(got, p.Provider.Name) 701 } 702 sort.Strings(got) 703 } 704 if !reflect.DeepEqual(got, tt.want) { 705 t.Fatalf("got %v want %v", got, tt.want) 706 } 707 }) 708 } 709 } 710 711 func TestAccessLoggingWithFilter(t *testing.T) { 712 sidecar := &Proxy{ 713 ConfigNamespace: "default", 714 Labels: map[string]string{"app": "test"}, 715 Metadata: &NodeMetadata{}, 716 } 717 code400filter := &tpb.Telemetry{ 718 AccessLogging: []*tpb.AccessLogging{ 719 { 720 Providers: []*tpb.ProviderRef{ 721 { 722 Name: "envoy-json", 723 }, 724 }, 725 Filter: &tpb.AccessLogging_Filter{ 726 Expression: "response.code >= 400", 727 }, 728 }, 729 }, 730 } 731 code500filter := &tpb.Telemetry{ 732 AccessLogging: []*tpb.AccessLogging{ 733 { 734 Providers: []*tpb.ProviderRef{ 735 { 736 Name: "envoy-json", 737 }, 738 }, 739 Filter: &tpb.AccessLogging_Filter{ 740 Expression: "response.code >= 500", 741 }, 742 }, 743 }, 744 } 745 multiAccessLoggingFilter := &tpb.Telemetry{ 746 AccessLogging: []*tpb.AccessLogging{ 747 { 748 Providers: []*tpb.ProviderRef{ 749 { 750 Name: "envoy-json", 751 }, 752 }, 753 Filter: &tpb.AccessLogging_Filter{ 754 Expression: "response.code >= 500", 755 }, 756 }, 757 { 758 Providers: []*tpb.ProviderRef{ 759 { 760 Name: "envoy-json", 761 }, 762 }, 763 Filter: &tpb.AccessLogging_Filter{ 764 Expression: "response.code >= 400", 765 }, 766 }, 767 }, 768 } 769 multiAccessLoggingNilFilter := &tpb.Telemetry{ 770 AccessLogging: []*tpb.AccessLogging{ 771 { 772 Providers: []*tpb.ProviderRef{ 773 { 774 Name: "envoy-json", 775 }, 776 }, 777 Filter: &tpb.AccessLogging_Filter{ 778 Expression: "response.code >= 500", 779 }, 780 }, 781 { 782 Providers: []*tpb.ProviderRef{ 783 { 784 Name: "envoy-json", 785 }, 786 }, 787 }, 788 }, 789 } 790 serverAndClientDifferent := &tpb.Telemetry{ 791 AccessLogging: []*tpb.AccessLogging{ 792 { 793 Match: &tpb.AccessLogging_LogSelector{ 794 Mode: tpb.WorkloadMode_CLIENT, 795 }, 796 Providers: []*tpb.ProviderRef{ 797 { 798 Name: "envoy-json", 799 }, 800 }, 801 Filter: &tpb.AccessLogging_Filter{ 802 Expression: "response.code >= 500", 803 }, 804 }, 805 { 806 Match: &tpb.AccessLogging_LogSelector{ 807 Mode: tpb.WorkloadMode_SERVER, 808 }, 809 Providers: []*tpb.ProviderRef{ 810 { 811 Name: "envoy-json", 812 }, 813 }, 814 Filter: &tpb.AccessLogging_Filter{ 815 Expression: "response.code >= 400", 816 }, 817 }, 818 }, 819 } 820 821 tests := []struct { 822 name string 823 cfgs []config.Config 824 proxy *Proxy 825 defaultProviders []string 826 excepted []LoggingConfig 827 }{ 828 { 829 "filter", 830 []config.Config{newTelemetry("default", code400filter)}, 831 sidecar, 832 []string{"envoy"}, 833 []LoggingConfig{ 834 { 835 AccessLog: &accesslog.AccessLog{ 836 Name: wellknown.FileAccessLog, 837 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 838 }, 839 Provider: jsonTextProvider, 840 Filter: &tpb.AccessLogging_Filter{ 841 Expression: "response.code >= 400", 842 }, 843 }, 844 }, 845 }, 846 { 847 "namespace-filter", 848 []config.Config{newTelemetry("istio-system", code400filter), newTelemetry("default", code500filter)}, 849 sidecar, 850 []string{"envoy"}, 851 []LoggingConfig{ 852 { 853 AccessLog: &accesslog.AccessLog{ 854 Name: wellknown.FileAccessLog, 855 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 856 }, 857 Provider: jsonTextProvider, 858 Filter: &tpb.AccessLogging_Filter{ 859 Expression: "response.code >= 500", 860 }, 861 }, 862 }, 863 }, 864 { 865 "multi-accesslogging", 866 []config.Config{newTelemetry("default", multiAccessLoggingFilter)}, 867 sidecar, 868 []string{"envoy"}, 869 []LoggingConfig{ 870 { 871 AccessLog: &accesslog.AccessLog{ 872 Name: wellknown.FileAccessLog, 873 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 874 }, 875 Provider: jsonTextProvider, 876 Filter: &tpb.AccessLogging_Filter{ 877 Expression: "response.code >= 400", 878 }, 879 }, 880 }, 881 }, 882 { 883 "multi-accesslogging-nil", 884 []config.Config{newTelemetry("default", multiAccessLoggingNilFilter)}, 885 sidecar, 886 []string{"envoy"}, 887 []LoggingConfig{ 888 { 889 AccessLog: &accesslog.AccessLog{ 890 Name: wellknown.FileAccessLog, 891 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 892 }, 893 Provider: jsonTextProvider, 894 }, 895 }, 896 }, 897 { 898 "server-and-client-different", 899 []config.Config{newTelemetry("default", serverAndClientDifferent)}, 900 sidecar, 901 []string{"envoy"}, 902 []LoggingConfig{ 903 { 904 AccessLog: &accesslog.AccessLog{ 905 Name: wellknown.FileAccessLog, 906 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 907 }, 908 Provider: jsonTextProvider, 909 Filter: &tpb.AccessLogging_Filter{ 910 Expression: "response.code >= 500", 911 }, 912 }, 913 }, 914 }, 915 } 916 for _, tt := range tests { 917 t.Run(tt.name, func(t *testing.T) { 918 telemetry, ctx := createTestTelemetries(tt.cfgs, t) 919 telemetry.meshConfig.DefaultProviders.AccessLogging = tt.defaultProviders 920 got := telemetry.AccessLogging(ctx, tt.proxy, networking.ListenerClassSidecarOutbound, nil) 921 assert.Equal(t, tt.excepted, got) 922 }) 923 } 924 } 925 926 func TestAccessLoggingCache(t *testing.T) { 927 sidecar := &Proxy{ConfigNamespace: "default", Metadata: &NodeMetadata{Labels: map[string]string{"app": "test"}}} 928 otherNamespace := &Proxy{ConfigNamespace: "common", Metadata: &NodeMetadata{Labels: map[string]string{"app": "test"}}} 929 cfgs := &tpb.Telemetry{ 930 AccessLogging: []*tpb.AccessLogging{ 931 { 932 Providers: []*tpb.ProviderRef{ 933 { 934 Name: "envoy-json", 935 }, 936 }, 937 Filter: &tpb.AccessLogging_Filter{ 938 Expression: "response.code >= 400", 939 }, 940 }, 941 }, 942 } 943 944 telemetry, ctx := createTestTelemetries([]config.Config{newTelemetry("default", cfgs)}, t) 945 for _, s := range []*Proxy{sidecar, otherNamespace} { 946 t.Run(s.ConfigNamespace, func(t *testing.T) { 947 first := telemetry.AccessLogging(ctx, s, networking.ListenerClassSidecarOutbound, nil) 948 second := telemetry.AccessLogging(ctx, s, networking.ListenerClassSidecarOutbound, nil) 949 assert.Equal(t, first, second) 950 }) 951 } 952 } 953 954 func TestBuildOpenTelemetryAccessLogConfig(t *testing.T) { 955 fakeCluster := "outbound|55680||otel-collector.monitoring.svc.cluster.local" 956 fakeAuthority := "otel-collector.monitoring.svc.cluster.local" 957 for _, tc := range []struct { 958 name string 959 logName string 960 clusterName string 961 hostname string 962 body string 963 labels *structpb.Struct 964 expected *otelaccesslog.OpenTelemetryAccessLogConfig 965 }{ 966 { 967 name: "default", 968 logName: OtelEnvoyAccessLogFriendlyName, 969 clusterName: fakeCluster, 970 hostname: fakeAuthority, 971 body: EnvoyTextLogFormat, 972 expected: &otelaccesslog.OpenTelemetryAccessLogConfig{ 973 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 974 LogName: OtelEnvoyAccessLogFriendlyName, 975 GrpcService: &core.GrpcService{ 976 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 977 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 978 ClusterName: fakeCluster, 979 Authority: fakeAuthority, 980 }, 981 }, 982 }, 983 TransportApiVersion: core.ApiVersion_V3, 984 FilterStateObjectsToLog: envoyWasmStateToLog, 985 }, 986 DisableBuiltinLabels: true, 987 Body: &otlpcommon.AnyValue{ 988 Value: &otlpcommon.AnyValue_StringValue{ 989 StringValue: EnvoyTextLogFormat, 990 }, 991 }, 992 }, 993 }, 994 { 995 name: "with attrs", 996 logName: OtelEnvoyAccessLogFriendlyName, 997 clusterName: fakeCluster, 998 hostname: fakeAuthority, 999 body: EnvoyTextLogFormat, 1000 labels: &structpb.Struct{ 1001 Fields: map[string]*structpb.Value{ 1002 "protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}}, 1003 }, 1004 }, 1005 expected: &otelaccesslog.OpenTelemetryAccessLogConfig{ 1006 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 1007 LogName: OtelEnvoyAccessLogFriendlyName, 1008 GrpcService: &core.GrpcService{ 1009 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1010 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 1011 ClusterName: fakeCluster, 1012 Authority: fakeAuthority, 1013 }, 1014 }, 1015 }, 1016 TransportApiVersion: core.ApiVersion_V3, 1017 FilterStateObjectsToLog: envoyWasmStateToLog, 1018 }, 1019 DisableBuiltinLabels: true, 1020 Body: &otlpcommon.AnyValue{ 1021 Value: &otlpcommon.AnyValue_StringValue{ 1022 StringValue: EnvoyTextLogFormat, 1023 }, 1024 }, 1025 Attributes: &otlpcommon.KeyValueList{ 1026 Values: []*otlpcommon.KeyValue{ 1027 { 1028 Key: "protocol", 1029 Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "%PROTOCOL%"}}, 1030 }, 1031 }, 1032 }, 1033 }, 1034 }, 1035 } { 1036 t.Run(tc.name, func(t *testing.T) { 1037 got := buildOpenTelemetryAccessLogConfig(tc.logName, tc.hostname, tc.clusterName, tc.body, tc.labels) 1038 assert.Equal(t, tc.expected, got) 1039 }) 1040 } 1041 } 1042 1043 func TestTelemetryAccessLogExhaustiveness(t *testing.T) { 1044 AssertProvidersHandled(telemetryAccessLogHandled) 1045 } 1046 1047 func TestTelemetryAccessLog(t *testing.T) { 1048 stdoutFormat := &meshconfig.MeshConfig_ExtensionProvider{ 1049 Name: "stdout", 1050 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1051 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1052 Path: DevStdout, 1053 }, 1054 }, 1055 } 1056 1057 customTextFormat := &meshconfig.MeshConfig_ExtensionProvider{ 1058 Name: "custom-text", 1059 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1060 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1061 Path: DevStdout, 1062 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat{ 1063 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Text{ 1064 Text: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%", 1065 }, 1066 }, 1067 }, 1068 }, 1069 } 1070 1071 customLabelsFormat := &meshconfig.MeshConfig_ExtensionProvider{ 1072 Name: "custom-label", 1073 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1074 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1075 Path: DevStdout, 1076 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat{ 1077 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Labels{ 1078 Labels: &structpb.Struct{ 1079 Fields: map[string]*structpb.Value{ 1080 "start_time": {Kind: &structpb.Value_StringValue{StringValue: "%START_TIME%"}}, 1081 "route_name": {Kind: &structpb.Value_StringValue{StringValue: "%ROUTE_NAME%"}}, 1082 "method": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:METHOD)%"}}, 1083 "path": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"}}, 1084 "protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}}, 1085 "response_code": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE%"}}, 1086 "response_flags": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_FLAGS%"}}, 1087 "response_code_details": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE_DETAILS%"}}, 1088 "connection_termination_details": {Kind: &structpb.Value_StringValue{StringValue: "%CONNECTION_TERMINATION_DETAILS%"}}, 1089 "bytes_received": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_RECEIVED%"}}, 1090 "bytes_sent": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_SENT%"}}, 1091 "duration": {Kind: &structpb.Value_StringValue{StringValue: "%DURATION%"}}, 1092 "upstream_service_time": {Kind: &structpb.Value_StringValue{StringValue: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"}}, 1093 "x_forwarded_for": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-FORWARDED-FOR)%"}}, 1094 "user_agent": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(USER-AGENT)%"}}, 1095 "request_id": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-REQUEST-ID)%"}}, 1096 "authority": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:AUTHORITY)%"}}, 1097 "upstream_host": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_HOST%"}}, 1098 "upstream_cluster": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_CLUSTER%"}}, 1099 "upstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_LOCAL_ADDRESS%"}}, 1100 "downstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_LOCAL_ADDRESS%"}}, 1101 "downstream_remote_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_REMOTE_ADDRESS%"}}, 1102 "requested_server_name": {Kind: &structpb.Value_StringValue{StringValue: "%REQUESTED_SERVER_NAME%"}}, 1103 }, 1104 }, 1105 }, 1106 }, 1107 }, 1108 }, 1109 } 1110 1111 stderr := &meshconfig.MeshConfig_ExtensionProvider{ 1112 Name: "stderr", 1113 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1114 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1115 Path: "/dev/stderr", 1116 }, 1117 }, 1118 } 1119 1120 fakeFilterStateObjects := []string{"fake-filter-state-object1", "fake-filter-state-object1"} 1121 grpcHTTPCfg := &meshconfig.MeshConfig_ExtensionProvider{ 1122 Name: "grpc-http-als", 1123 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyHttpAls{ 1124 EnvoyHttpAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyHttpGrpcV3LogProvider{ 1125 LogName: "grpc-http-als", 1126 Service: "grpc-als.foo.svc.cluster.local", 1127 Port: 9811, 1128 AdditionalRequestHeadersToLog: []string{"fake-request-header1"}, 1129 AdditionalResponseHeadersToLog: []string{"fake-response-header1"}, 1130 AdditionalResponseTrailersToLog: []string{"fake-response-trailer1"}, 1131 FilterStateObjectsToLog: fakeFilterStateObjects, 1132 }, 1133 }, 1134 } 1135 1136 grpcTCPCfg := &meshconfig.MeshConfig_ExtensionProvider{ 1137 Name: "grpc-tcp-als", 1138 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyTcpAls{ 1139 EnvoyTcpAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyTcpGrpcV3LogProvider{ 1140 LogName: "grpc-tcp-als", 1141 Service: "grpc-als.foo.svc.cluster.local", 1142 Port: 9811, 1143 FilterStateObjectsToLog: fakeFilterStateObjects, 1144 }, 1145 }, 1146 } 1147 1148 labels := &structpb.Struct{ 1149 Fields: map[string]*structpb.Value{ 1150 "protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}}, 1151 "start_time": {Kind: &structpb.Value_StringValue{StringValue: "%START_TIME%"}}, 1152 }, 1153 } 1154 1155 otelCfg := &meshconfig.MeshConfig_ExtensionProvider{ 1156 Name: OtelEnvoyAccessLogFriendlyName, 1157 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls{ 1158 EnvoyOtelAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider{ 1159 Service: "otel.foo.svc.cluster.local", 1160 Port: 9811, 1161 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider_LogFormat{ 1162 Labels: labels, 1163 }, 1164 }, 1165 }, 1166 } 1167 1168 defaultEnvoyProvider := &meshconfig.MeshConfig_ExtensionProvider{ 1169 Name: "envoy", 1170 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1171 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1172 Path: "/dev/stdout", 1173 }, 1174 }, 1175 } 1176 1177 grpcBackendClusterName := "outbound|9811||grpc-als.foo.svc.cluster.local" 1178 grpcBackendAuthority := "grpc-als.foo.svc.cluster.local" 1179 otelAtrributeCfg := &otelaccesslog.OpenTelemetryAccessLogConfig{ 1180 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 1181 LogName: OtelEnvoyAccessLogFriendlyName, 1182 GrpcService: &core.GrpcService{ 1183 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1184 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 1185 ClusterName: grpcBackendClusterName, 1186 Authority: grpcBackendAuthority, 1187 }, 1188 }, 1189 }, 1190 TransportApiVersion: core.ApiVersion_V3, 1191 FilterStateObjectsToLog: envoyWasmStateToLog, 1192 }, 1193 DisableBuiltinLabels: true, 1194 Body: &otlpcommon.AnyValue{ 1195 Value: &otlpcommon.AnyValue_StringValue{ 1196 StringValue: EnvoyTextLogFormat, 1197 }, 1198 }, 1199 Attributes: &otlpcommon.KeyValueList{ 1200 Values: []*otlpcommon.KeyValue{ 1201 { 1202 Key: "protocol", 1203 Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "%PROTOCOL%"}}, 1204 }, 1205 { 1206 Key: "start_time", 1207 Value: &otlpcommon.AnyValue{Value: &otlpcommon.AnyValue_StringValue{StringValue: "%START_TIME%"}}, 1208 }, 1209 }, 1210 }, 1211 } 1212 1213 clusterLookupFn = func(push *PushContext, service string, port int) (hostname string, cluster string, err error) { 1214 return grpcBackendAuthority, grpcBackendClusterName, nil 1215 } 1216 1217 stdout := &fileaccesslog.FileAccessLog{ 1218 Path: DevStdout, 1219 AccessLogFormat: &fileaccesslog.FileAccessLog_LogFormat{ 1220 LogFormat: &core.SubstitutionFormatString{ 1221 Format: &core.SubstitutionFormatString_TextFormatSource{ 1222 TextFormatSource: &core.DataSource{ 1223 Specifier: &core.DataSource_InlineString{ 1224 InlineString: EnvoyTextLogFormat, 1225 }, 1226 }, 1227 }, 1228 }, 1229 }, 1230 } 1231 1232 customTextOut := &fileaccesslog.FileAccessLog{ 1233 Path: DevStdout, 1234 AccessLogFormat: &fileaccesslog.FileAccessLog_LogFormat{ 1235 LogFormat: &core.SubstitutionFormatString{ 1236 Format: &core.SubstitutionFormatString_TextFormatSource{ 1237 TextFormatSource: &core.DataSource{ 1238 Specifier: &core.DataSource_InlineString{ 1239 InlineString: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n", 1240 }, 1241 }, 1242 }, 1243 }, 1244 }, 1245 } 1246 1247 customLabelsOut := &fileaccesslog.FileAccessLog{ 1248 Path: DevStdout, 1249 AccessLogFormat: &fileaccesslog.FileAccessLog_LogFormat{ 1250 LogFormat: &core.SubstitutionFormatString{ 1251 Format: &core.SubstitutionFormatString_JsonFormat{ 1252 JsonFormat: &structpb.Struct{ 1253 Fields: map[string]*structpb.Value{ 1254 "start_time": {Kind: &structpb.Value_StringValue{StringValue: "%START_TIME%"}}, 1255 "route_name": {Kind: &structpb.Value_StringValue{StringValue: "%ROUTE_NAME%"}}, 1256 "method": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:METHOD)%"}}, 1257 "path": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"}}, 1258 "protocol": {Kind: &structpb.Value_StringValue{StringValue: "%PROTOCOL%"}}, 1259 "response_code": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE%"}}, 1260 "response_flags": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_FLAGS%"}}, 1261 "response_code_details": {Kind: &structpb.Value_StringValue{StringValue: "%RESPONSE_CODE_DETAILS%"}}, 1262 "connection_termination_details": {Kind: &structpb.Value_StringValue{StringValue: "%CONNECTION_TERMINATION_DETAILS%"}}, 1263 "bytes_received": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_RECEIVED%"}}, 1264 "bytes_sent": {Kind: &structpb.Value_StringValue{StringValue: "%BYTES_SENT%"}}, 1265 "duration": {Kind: &structpb.Value_StringValue{StringValue: "%DURATION%"}}, 1266 "upstream_service_time": {Kind: &structpb.Value_StringValue{StringValue: "%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%"}}, 1267 "x_forwarded_for": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-FORWARDED-FOR)%"}}, 1268 "user_agent": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(USER-AGENT)%"}}, 1269 "request_id": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(X-REQUEST-ID)%"}}, 1270 "authority": {Kind: &structpb.Value_StringValue{StringValue: "%REQ(:AUTHORITY)%"}}, 1271 "upstream_host": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_HOST%"}}, 1272 "upstream_cluster": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_CLUSTER%"}}, 1273 "upstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%UPSTREAM_LOCAL_ADDRESS%"}}, 1274 "downstream_local_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_LOCAL_ADDRESS%"}}, 1275 "downstream_remote_address": {Kind: &structpb.Value_StringValue{StringValue: "%DOWNSTREAM_REMOTE_ADDRESS%"}}, 1276 "requested_server_name": {Kind: &structpb.Value_StringValue{StringValue: "%REQUESTED_SERVER_NAME%"}}, 1277 }, 1278 }, 1279 }, 1280 JsonFormatOptions: &core.JsonFormatOptions{SortProperties: true}, 1281 }, 1282 }, 1283 } 1284 1285 stderrout := &fileaccesslog.FileAccessLog{ 1286 Path: "/dev/stderr", 1287 AccessLogFormat: &fileaccesslog.FileAccessLog_LogFormat{ 1288 LogFormat: &core.SubstitutionFormatString{ 1289 Format: &core.SubstitutionFormatString_TextFormatSource{ 1290 TextFormatSource: &core.DataSource{ 1291 Specifier: &core.DataSource_InlineString{ 1292 InlineString: EnvoyTextLogFormat, 1293 }, 1294 }, 1295 }, 1296 }, 1297 }, 1298 } 1299 1300 grpcHTTPout := &grpcaccesslog.HttpGrpcAccessLogConfig{ 1301 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 1302 LogName: "grpc-http-als", 1303 GrpcService: &core.GrpcService{ 1304 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1305 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 1306 ClusterName: grpcBackendClusterName, 1307 Authority: grpcBackendAuthority, 1308 }, 1309 }, 1310 }, 1311 TransportApiVersion: core.ApiVersion_V3, 1312 FilterStateObjectsToLog: fakeFilterStateObjects, 1313 }, 1314 AdditionalRequestHeadersToLog: []string{"fake-request-header1"}, 1315 AdditionalResponseHeadersToLog: []string{"fake-response-header1"}, 1316 AdditionalResponseTrailersToLog: []string{"fake-response-trailer1"}, 1317 } 1318 1319 grpcTCPOut := &grpcaccesslog.TcpGrpcAccessLogConfig{ 1320 CommonConfig: &grpcaccesslog.CommonGrpcAccessLogConfig{ 1321 LogName: "grpc-tcp-als", 1322 GrpcService: &core.GrpcService{ 1323 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 1324 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ 1325 ClusterName: grpcBackendClusterName, 1326 Authority: grpcBackendAuthority, 1327 }, 1328 }, 1329 }, 1330 TransportApiVersion: core.ApiVersion_V3, 1331 FilterStateObjectsToLog: fakeFilterStateObjects, 1332 }, 1333 } 1334 1335 defaultFormatJSON, _ := protomarshal.ToJSON(EnvoyJSONLogFormatIstio) 1336 1337 ctx := NewPushContext() 1338 ctx.ServiceIndex.HostnameAndNamespace["otel-collector.foo.svc.cluster.local"] = map[string]*Service{ 1339 "foo": { 1340 Hostname: "otel-collector.foo.svc.cluster.local", 1341 DefaultAddress: "172.217.0.0/16", 1342 Ports: PortList{ 1343 &Port{ 1344 Name: "grpc-port", 1345 Port: 3417, 1346 Protocol: protocol.TCP, 1347 }, 1348 &Port{ 1349 Name: "http-port", 1350 Port: 3418, 1351 Protocol: protocol.HTTP, 1352 }, 1353 }, 1354 Resolution: ClientSideLB, 1355 Attributes: ServiceAttributes{ 1356 Name: "otel-collector", 1357 Namespace: "foo", 1358 ServiceRegistry: provider.Kubernetes, 1359 }, 1360 }, 1361 } 1362 1363 for _, tc := range []struct { 1364 name string 1365 ctx *PushContext 1366 meshConfig *meshconfig.MeshConfig 1367 fp *meshconfig.MeshConfig_ExtensionProvider 1368 expected *accesslog.AccessLog 1369 }{ 1370 { 1371 name: "stdout", 1372 meshConfig: &meshconfig.MeshConfig{ 1373 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1374 }, 1375 fp: stdoutFormat, 1376 expected: &accesslog.AccessLog{ 1377 Name: wellknown.FileAccessLog, 1378 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(stdout)}, 1379 }, 1380 }, 1381 { 1382 name: "stderr", 1383 meshConfig: &meshconfig.MeshConfig{ 1384 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1385 }, 1386 fp: stderr, 1387 expected: &accesslog.AccessLog{ 1388 Name: wellknown.FileAccessLog, 1389 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(stderrout)}, 1390 }, 1391 }, 1392 { 1393 name: "custom-text", 1394 meshConfig: &meshconfig.MeshConfig{ 1395 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1396 }, 1397 fp: customTextFormat, 1398 expected: &accesslog.AccessLog{ 1399 Name: wellknown.FileAccessLog, 1400 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(customTextOut)}, 1401 }, 1402 }, 1403 { 1404 name: "default-labels", 1405 meshConfig: &meshconfig.MeshConfig{ 1406 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1407 }, 1408 fp: jsonTextProvider, 1409 expected: &accesslog.AccessLog{ 1410 Name: wellknown.FileAccessLog, 1411 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 1412 }, 1413 }, 1414 { 1415 name: "custom-labels", 1416 meshConfig: &meshconfig.MeshConfig{ 1417 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1418 }, 1419 fp: customLabelsFormat, 1420 expected: &accesslog.AccessLog{ 1421 Name: wellknown.FileAccessLog, 1422 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(customLabelsOut)}, 1423 }, 1424 }, 1425 { 1426 name: "otel", 1427 ctx: ctx, 1428 meshConfig: &meshconfig.MeshConfig{ 1429 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1430 }, 1431 fp: otelCfg, 1432 expected: &accesslog.AccessLog{ 1433 Name: OtelEnvoyALSName, 1434 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(otelAtrributeCfg)}, 1435 }, 1436 }, 1437 { 1438 name: "grpc-http", 1439 fp: grpcHTTPCfg, 1440 meshConfig: &meshconfig.MeshConfig{ 1441 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1442 }, 1443 expected: &accesslog.AccessLog{ 1444 Name: wellknown.HTTPGRPCAccessLog, 1445 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(grpcHTTPout)}, 1446 }, 1447 }, 1448 { 1449 name: "grpc-tcp", 1450 fp: grpcTCPCfg, 1451 meshConfig: &meshconfig.MeshConfig{ 1452 AccessLogEncoding: meshconfig.MeshConfig_TEXT, 1453 }, 1454 expected: &accesslog.AccessLog{ 1455 Name: TCPEnvoyALSName, 1456 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(grpcTCPOut)}, 1457 }, 1458 }, 1459 { 1460 name: "builtin-fallback", 1461 ctx: ctx, 1462 meshConfig: &meshconfig.MeshConfig{ 1463 AccessLogEncoding: meshconfig.MeshConfig_JSON, 1464 AccessLogFormat: defaultFormatJSON, 1465 }, 1466 fp: defaultEnvoyProvider, 1467 expected: &accesslog.AccessLog{ 1468 Name: wellknown.FileAccessLog, 1469 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(defaultJSONLabelsOut)}, 1470 }, 1471 }, 1472 { 1473 name: "builtin-not-fallback", 1474 ctx: ctx, 1475 meshConfig: &meshconfig.MeshConfig{ 1476 AccessLogEncoding: meshconfig.MeshConfig_JSON, 1477 AccessLogFormat: defaultFormatJSON, 1478 }, 1479 fp: &meshconfig.MeshConfig_ExtensionProvider{ 1480 Name: "envoy", 1481 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{ 1482 EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{ 1483 Path: "/dev/stdout", 1484 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat{ 1485 LogFormat: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider_LogFormat_Text{ 1486 Text: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%", 1487 }, 1488 }, 1489 }, 1490 }, 1491 }, 1492 expected: &accesslog.AccessLog{ 1493 Name: wellknown.FileAccessLog, 1494 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(&fileaccesslog.FileAccessLog{ 1495 Path: DevStdout, 1496 AccessLogFormat: &fileaccesslog.FileAccessLog_LogFormat{ 1497 LogFormat: &core.SubstitutionFormatString{ 1498 Format: &core.SubstitutionFormatString_TextFormatSource{ 1499 TextFormatSource: &core.DataSource{ 1500 Specifier: &core.DataSource_InlineString{ 1501 InlineString: "%LOCAL_REPLY_BODY%:%RESPONSE_CODE%:path=%REQ(:path)%\n", 1502 }, 1503 }, 1504 }, 1505 }, 1506 }, 1507 })}, 1508 }, 1509 }, 1510 } { 1511 t.Run(tc.name, func(t *testing.T) { 1512 push := tc.ctx 1513 if push == nil { 1514 push = NewPushContext() 1515 } 1516 push.Mesh = tc.meshConfig 1517 1518 got := telemetryAccessLog(push, tc.fp) 1519 if got == nil { 1520 t.Fatalf("get nil accesslog") 1521 } 1522 assert.Equal(t, tc.expected, got) 1523 }) 1524 } 1525 } 1526 1527 func TestAccessLogJSONFormatters(t *testing.T) { 1528 cases := []struct { 1529 name string 1530 json *structpb.Struct 1531 expected []*core.TypedExtensionConfig 1532 }{ 1533 { 1534 name: "default", 1535 json: EnvoyJSONLogFormatIstio, 1536 expected: []*core.TypedExtensionConfig{}, 1537 }, 1538 { 1539 name: "with-req-without-query", 1540 json: &structpb.Struct{ 1541 Fields: map[string]*structpb.Value{ 1542 "key1": {Kind: &structpb.Value_StringValue{StringValue: "%REQ_WITHOUT_QUERY(key1:val1)%"}}, 1543 }, 1544 }, 1545 expected: []*core.TypedExtensionConfig{ 1546 reqWithoutQueryFormatter, 1547 }, 1548 }, 1549 { 1550 name: "with-metadata", 1551 json: &structpb.Struct{ 1552 Fields: map[string]*structpb.Value{ 1553 "key1": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(CLUSTER:istio)%"}}, 1554 }, 1555 }, 1556 expected: []*core.TypedExtensionConfig{ 1557 metadataFormatter, 1558 }, 1559 }, 1560 { 1561 name: "with-both", 1562 json: &structpb.Struct{ 1563 Fields: map[string]*structpb.Value{ 1564 "key1": {Kind: &structpb.Value_StringValue{StringValue: "%REQ_WITHOUT_QUERY(key1:val1)%"}}, 1565 "key2": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(UPSTREAM_HOST:istio)%"}}, 1566 }, 1567 }, 1568 expected: []*core.TypedExtensionConfig{ 1569 reqWithoutQueryFormatter, 1570 metadataFormatter, 1571 }, 1572 }, 1573 { 1574 name: "with-multi-metadata", 1575 json: &structpb.Struct{ 1576 Fields: map[string]*structpb.Value{ 1577 "key1": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(CLUSTER:istio)%"}}, 1578 "key2": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(UPSTREAM_HOST:istio)%"}}, 1579 }, 1580 }, 1581 expected: []*core.TypedExtensionConfig{ 1582 metadataFormatter, 1583 }, 1584 }, 1585 { 1586 name: "more-complex", 1587 json: &structpb.Struct{ 1588 Fields: map[string]*structpb.Value{ 1589 "req1": {Kind: &structpb.Value_StringValue{StringValue: "%REQ_WITHOUT_QUERY(key1:val1)%"}}, 1590 "req2": {Kind: &structpb.Value_StringValue{StringValue: "%REQ_WITHOUT_QUERY(key2:val1)%"}}, 1591 "key1": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(CLUSTER:istio)%"}}, 1592 "key2": {Kind: &structpb.Value_StringValue{StringValue: "%METADATA(UPSTREAM_HOST:istio)%"}}, 1593 }, 1594 }, 1595 expected: []*core.TypedExtensionConfig{ 1596 reqWithoutQueryFormatter, 1597 metadataFormatter, 1598 }, 1599 }, 1600 } 1601 1602 for _, tc := range cases { 1603 t.Run(tc.name, func(t *testing.T) { 1604 got := accessLogJSONFormatters(tc.json) 1605 assert.Equal(t, tc.expected, got) 1606 }) 1607 } 1608 } 1609 1610 func TestAccessLogTextFormatters(t *testing.T) { 1611 cases := []struct { 1612 name string 1613 text string 1614 expected []*core.TypedExtensionConfig 1615 }{ 1616 { 1617 name: "default", 1618 text: EnvoyTextLogFormat, 1619 expected: []*core.TypedExtensionConfig{}, 1620 }, 1621 { 1622 name: "with-req-without-query", 1623 text: EnvoyTextLogFormat + " %REQ_WITHOUT_QUERY(key1:val1)%", 1624 expected: []*core.TypedExtensionConfig{ 1625 reqWithoutQueryFormatter, 1626 }, 1627 }, 1628 { 1629 name: "with-metadata", 1630 text: EnvoyTextLogFormat + " %METADATA(CLUSTER:istio)%", 1631 expected: []*core.TypedExtensionConfig{ 1632 metadataFormatter, 1633 }, 1634 }, 1635 { 1636 name: "with-both", 1637 text: EnvoyTextLogFormat + " %REQ_WITHOUT_QUERY(key1:val1)% %METADATA(CLUSTER:istio)%", 1638 expected: []*core.TypedExtensionConfig{ 1639 reqWithoutQueryFormatter, 1640 metadataFormatter, 1641 }, 1642 }, 1643 { 1644 name: "with-multi-metadata", 1645 text: EnvoyTextLogFormat + " %METADATA(UPSTREAM_HOST:istio)% %METADATA(CLUSTER:istio)%", 1646 expected: []*core.TypedExtensionConfig{ 1647 metadataFormatter, 1648 }, 1649 }, 1650 { 1651 name: "more-complex", 1652 text: EnvoyTextLogFormat + " %REQ_WITHOUT_QUERY(key1:val1)% REQ_WITHOUT_QUERY(key2:val1)% %METADATA(UPSTREAM_HOST:istio)% %METADATA(CLUSTER:istio)%", 1653 expected: []*core.TypedExtensionConfig{ 1654 reqWithoutQueryFormatter, 1655 metadataFormatter, 1656 }, 1657 }, 1658 } 1659 1660 for _, tc := range cases { 1661 t.Run(tc.name, func(t *testing.T) { 1662 got := accessLogTextFormatters(tc.text) 1663 assert.Equal(t, tc.expected, got) 1664 }) 1665 } 1666 } 1667 1668 func TestTelemetryAccessLogWithFormatter(t *testing.T) { 1669 sidecar := &Proxy{ 1670 ConfigNamespace: "default", 1671 Labels: map[string]string{"app": "test"}, 1672 Metadata: &NodeMetadata{}, 1673 } 1674 1675 textFormatters := &tpb.Telemetry{ 1676 AccessLogging: []*tpb.AccessLogging{ 1677 { 1678 Providers: []*tpb.ProviderRef{ 1679 { 1680 Name: "envoy-text-formatters", 1681 }, 1682 }, 1683 }, 1684 }, 1685 } 1686 1687 jsonFormatters := &tpb.Telemetry{ 1688 AccessLogging: []*tpb.AccessLogging{ 1689 { 1690 Providers: []*tpb.ProviderRef{ 1691 { 1692 Name: "envoy-json-formatters", 1693 }, 1694 }, 1695 }, 1696 }, 1697 } 1698 1699 tests := []struct { 1700 name string 1701 cfgs []config.Config 1702 proxy *Proxy 1703 defaultProviders []string 1704 excepted []LoggingConfig 1705 }{ 1706 { 1707 "text", 1708 []config.Config{newTelemetry("default", textFormatters)}, 1709 sidecar, 1710 []string{}, 1711 []LoggingConfig{ 1712 { 1713 AccessLog: &accesslog.AccessLog{ 1714 Name: wellknown.FileAccessLog, 1715 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(formattersTextLabelsOut)}, 1716 }, 1717 Provider: textFormattersProvider, 1718 }, 1719 }, 1720 }, 1721 { 1722 "json", 1723 []config.Config{newTelemetry("default", jsonFormatters)}, 1724 sidecar, 1725 []string{}, 1726 []LoggingConfig{ 1727 { 1728 AccessLog: &accesslog.AccessLog{ 1729 Name: wellknown.FileAccessLog, 1730 ConfigType: &accesslog.AccessLog_TypedConfig{TypedConfig: protoconv.MessageToAny(formattersJSONLabelsOut)}, 1731 }, 1732 Provider: jsonFormattersProvider, 1733 }, 1734 }, 1735 }, 1736 } 1737 for _, tt := range tests { 1738 t.Run(tt.name, func(t *testing.T) { 1739 telemetry, ctx := createTestTelemetries(tt.cfgs, t) 1740 telemetry.meshConfig.DefaultProviders.AccessLogging = tt.defaultProviders 1741 got := telemetry.AccessLogging(ctx, tt.proxy, networking.ListenerClassSidecarOutbound, nil) 1742 assert.Equal(t, tt.excepted, got) 1743 }) 1744 } 1745 }