Repository: envoyproxy/gateway
Pull request #5550 (2025-03-20 04:17:35)

Removes inline os.Stdout as log output
**What type of PR is this?**

refactor: removes inline os.Stdout as log output

**What this PR does / why we need it**:

This removes the (almost) remaining places where the logger is configured to use os.Stdout instead of the configured output inherited from the cobra command. Without this change, the logging destination is scattered randomly from the downstream user's perspective. This is a follow up on #5476 #5483 and eliminates almost all places using os.Stdout except for the global loggers in a few places plus the stdout for the standalone mode's Envoy process. The implementation should be agnostic to any io.Writer in reality and there shouldn't be any necessity to directly use os.Stdout for all cases.

Xunzhuo: Plz fix the CI failures @mathetake 
mathetake: yeah sorry done 
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `47.05882%` with `9 lines` in your changes missing coverage. Please review.
> Project coverage is 65.21%. Comparing base [(`8e9fb3f`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/8e9fb3f47c21fcebb29928a3f4028279b4ca5dd6?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`ee34eaf`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/ee34eafdeab83f9ee7a04fd904dbedf57c5bde0f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

| [Files with missing lines](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [internal/cmd/egctl/status.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?src=pr&el=tree&filepath=internal%2Fcmd%2Fegctl%2Fstatus.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvY21kL2VnY3RsL3N0YXR1cy5nbw==) | 0.00% | [6 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |
| [internal/cmd/certgen.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?src=pr&el=tree&filepath=internal%2Fcmd%2Fcertgen.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-aW50ZXJuYWwvY21kL2NlcnRnZW4uZ28=) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

:x: Your patch check has failed because the patch coverage (47.05%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #5550      +/-   ##
==========================================
+ Coverage   65.19%   65.21%   +0.01%     
==========================================
  Files         213      213              
  Lines       34080    34082       +2     
==========================================
+ Hits        22220    22226       +6     
+ Misses      10534    10531       -3     
+ Partials     1326     1325       -1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/5550?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
mathetake: /retest 
mathetake: /retes
mathetake: /retest
mathetake: great

Patch:
diff --git a/VERSION b/VERSION
index 757407982..968e750a6 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-v1.3.1
+v1.3.2
diff --git a/internal/cmd/certgen.go b/internal/cmd/certgen.go
index 6b34158a8..835a583d9 100644
--- a/internal/cmd/certgen.go
+++ b/internal/cmd/certgen.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
 	"path"
 
 	"github.com/spf13/cobra"
@@ -36,7 +37,7 @@ func GetCertGenCommand() *cobra.Command {
 		Use:   "certgen",
 		Short: "Generate Control Plane Certificates",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return certGen(cmd.Context(), local)
+			return certGen(cmd.Context(), cmd.OutOrStdout(), local)
 		},
 	}
 
@@ -48,8 +49,8 @@ func GetCertGenCommand() *cobra.Command {
 }
 
 // certGen generates control plane certificates.
-func certGen(ctx context.Context, local bool) error {
-	cfg, err := config.New()
+func certGen(ctx context.Context, logOut io.Writer, local bool) error {
+	cfg, err := config.New(logOut)
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmd/egctl/status.go b/internal/cmd/egctl/status.go
index 814bd1593..cdb243369 100644
--- a/internal/cmd/egctl/status.go
+++ b/internal/cmd/egctl/status.go
@@ -9,7 +9,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"os"
 	"reflect"
 	"strconv"
 	"strings"
@@ -93,27 +92,27 @@ func newStatusCommand() *cobra.Command {
 			switch strings.ToLower(resourceType) {
 			case "all":
 				for _, rt := range supportedAllTypes {
-					if err = runStatus(ctx, k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
+					if err = runStatus(ctx, cmd.OutOrStdout(), k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
 						return err
 					}
 				}
 				return nil
 			case "xroute":
 				for _, rt := range supportedXRouteTypes {
-					if err = runStatus(ctx, k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
+					if err = runStatus(ctx, cmd.OutOrStdout(), k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
 						return err
 					}
 				}
 				return nil
 			case "xpolicy":
 				for _, rt := range supportedXPolicyTypes {
-					if err = runStatus(ctx, k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
+					if err = runStatus(ctx, cmd.OutOrStdout(), k8sClient, rt, namespace, quiet, verbose, allNamespaces, true, true); err != nil {
 						return err
 					}
 				}
 				return nil
 			default:
-				return runStatus(ctx, k8sClient, resourceType, namespace, quiet, verbose, allNamespaces, false, false)
+				return runStatus(ctx, cmd.OutOrStdout(), k8sClient, resourceType, namespace, quiet, verbose, allNamespaces, false, false)
 			}
 		},
 	}
@@ -138,11 +137,11 @@ func writeStatusTable(table *tabwriter.Writer, header []string, body [][]string)
 }
 
 // runStatus find and write the summary table of status for a specific resource type.
-func runStatus(ctx context.Context, cli client.Client, inputResourceType, namespace string, quiet, verbose, allNamespaces, ignoreEmpty, typedName bool) error {
+func runStatus(ctx context.Context, logOut io.Writer, cli client.Client, inputResourceType, namespace string, quiet, verbose, allNamespaces, ignoreEmpty, typedName bool) error {
 	var (
 		resourcesList client.ObjectList
 		resourceKind  string
-		table         = newStatusTableWriter(os.Stdout)
+		table         = newStatusTableWriter(logOut)
 	)
 
 	if allNamespaces {
diff --git a/internal/cmd/envoy/shutdown_manager.go b/internal/cmd/envoy/shutdown_manager.go
index 627c2f034..e35793df9 100644
--- a/internal/cmd/envoy/shutdown_manager.go
+++ b/internal/cmd/envoy/shutdown_manager.go
@@ -21,7 +21,8 @@ import (
 	"github.com/envoyproxy/gateway/internal/xds/bootstrap"
 )
 
-var logger = logging.DefaultLogger(egv1a1.LogLevelInfo).WithName("shutdown-manager")
+// TODO: Remove the global logger and localize the scope of the logger.
+var logger = logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo).WithName("shutdown-manager")
 
 const (
 	// ShutdownManagerPort is the port Envoy shutdown manager will listen on.
diff --git a/internal/cmd/server.go b/internal/cmd/server.go
index 99c1856b4..6a39bcc24 100644
--- a/internal/cmd/server.go
+++ b/internal/cmd/server.go
@@ -105,7 +105,7 @@ func getConfig(logOut io.Writer) (*config.Server, error) {
 // make `cfgPath` an argument to test it without polluting the global var
 func getConfigByPath(logOut io.Writer, cfgPath string) (*config.Server, error) {
 	// Initialize with default config parameters.
-	cfg, err := config.New()
+	cfg, err := config.New(logOut)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/envoygateway/config/config.go b/internal/envoygateway/config/config.go
index e8d1fece3..63d740de1 100644
--- a/internal/envoygateway/config/config.go
+++ b/internal/envoygateway/config/config.go
@@ -7,6 +7,7 @@ package config
 
 import (
 	"errors"
+	"io"
 
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 	"github.com/envoyproxy/gateway/api/v1alpha1/validation"
@@ -41,12 +42,12 @@ type Server struct {
 }
 
 // New returns a Server with default parameters.
-func New() (*Server, error) {
+func New(logOut io.Writer) (*Server, error) {
 	return &Server{
 		EnvoyGateway: egv1a1.DefaultEnvoyGateway(),
 		Namespace:    env.Lookup("ENVOY_GATEWAY_NAMESPACE", DefaultNamespace),
 		DNSDomain:    env.Lookup("KUBERNETES_CLUSTER_DOMAIN", DefaultDNSDomain),
-		Logger:       logging.DefaultLogger(egv1a1.LogLevelInfo),
+		Logger:       logging.DefaultLogger(logOut, egv1a1.LogLevelInfo),
 		Elected:      make(chan struct{}),
 	}, nil
 }
diff --git a/internal/logging/log.go b/internal/logging/log.go
index e38af9082..394e3919e 100644
--- a/internal/logging/log.go
+++ b/internal/logging/log.go
@@ -19,6 +19,7 @@ import (
 
 type Logger struct {
 	logr.Logger
+	out           io.Writer
 	logging       *egv1a1.EnvoyGatewayLogging
 	sugaredLogger *zap.SugaredLogger
 }
@@ -28,6 +29,7 @@ func NewLogger(w io.Writer, logging *egv1a1.EnvoyGatewayLogging) Logger {
 
 	return Logger{
 		Logger:        zapr.NewLogger(logger),
+		out:           w,
 		logging:       logging,
 		sugaredLogger: logger.Sugar(),
 	}
@@ -45,16 +47,18 @@ func FileLogger(file string, name string, level egv1a1.LogLevel) Logger {
 	return Logger{
 		Logger:        zapr.NewLogger(logger).WithName(name),
 		logging:       logging,
+		out:           writer,
 		sugaredLogger: logger.Sugar(),
 	}
 }
 
-func DefaultLogger(level egv1a1.LogLevel) Logger {
+func DefaultLogger(out io.Writer, level egv1a1.LogLevel) Logger {
 	logging := egv1a1.DefaultEnvoyGatewayLogging()
-	logger := initZapLogger(os.Stdout, logging, level)
+	logger := initZapLogger(out, logging, level)
 
 	return Logger{
 		Logger:        zapr.NewLogger(logger),
+		out:           out,
 		logging:       logging,
 		sugaredLogger: logger.Sugar(),
 	}
@@ -67,11 +71,12 @@ func DefaultLogger(level egv1a1.LogLevel) Logger {
 // more information).
 func (l Logger) WithName(name string) Logger {
 	logLevel := l.logging.Level[egv1a1.EnvoyGatewayLogComponent(name)]
-	logger := initZapLogger(os.Stdout, l.logging, logLevel)
+	logger := initZapLogger(l.out, l.logging, logLevel)
 
 	return Logger{
 		Logger:        zapr.NewLogger(logger).WithName(name),
 		logging:       l.logging,
+		out:           l.out,
 		sugaredLogger: logger.Sugar().Named(name),
 	}
 }
diff --git a/internal/message/watchutil.go b/internal/message/watchutil.go
index 77caa4af3..fc0f65dcc 100644
--- a/internal/message/watchutil.go
+++ b/internal/message/watchutil.go
@@ -7,6 +7,7 @@ package message
 
 import (
 	"fmt"
+	"os"
 	"runtime/debug"
 	"time"
 
@@ -19,7 +20,8 @@ import (
 
 type Update[K comparable, V any] watchable.Update[K, V]
 
-var logger = logging.DefaultLogger(egv1a1.LogLevelInfo).WithName("watchable")
+// TODO: Remove the global logger and localize the scope of the logger.
+var logger = logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo).WithName("watchable")
 
 type Metadata struct {
 	Runner  string
diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go
index 991c8e342..304f22da4 100644
--- a/internal/xds/translator/accesslog.go
+++ b/internal/xds/translator/accesslog.go
@@ -336,10 +336,10 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([
 			ResourceAttributes: convertToKeyValueList(otel.Resources, false),
 		}
 
-		if otel.Text == nil {
-			return nil, errors.New("otel.Text is nil")
+		var format string
+		if otel.Text != nil && *otel.Text != "" {
+			format = *otel.Text
 		}
-		format := *otel.Text
 
 		if format != "" {
 			al.Body = &otlpcommonv1.AnyValue{
@@ -349,9 +349,17 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([
 			}
 		}
 
-		al.Attributes = convertToKeyValueList(otel.Attributes, true)
+		var attrs map[string]string
+		if len(otel.Attributes) != 0 {
+			attrs = otel.Attributes
+		} else if len(otel.Attributes) == 0 && format == "" {
+			// if there are no attributes and text format is unset, use the default EnvoyJSONLogFields
+			attrs = EnvoyJSONLogFields
+		}
+
+		al.Attributes = convertToKeyValueList(attrs, true)
+		formatters := accessLogOpenTelemetryFormatters(format, attrs)
 
-		formatters := accessLogOpenTelemetryFormatters(format, otel.Attributes)
 		if len(formatters) != 0 {
 			al.Formatters = formatters
 		}
diff --git a/release-notes/current.yaml b/release-notes/current.yaml
index b332fc830..3b02988dc 100644
--- a/release-notes/current.yaml
+++ b/release-notes/current.yaml
@@ -36,6 +36,7 @@ bug fixes: |
   Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode.
   Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated.
   Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted.
+  Fix allowing empty text field for opentelemetry sink when using JSON format.
 
 # Enhancements that improve performance.
 performance improvements: |
diff --git a/release-notes/v1.3.2.yaml b/release-notes/v1.3.2.yaml
new file mode 100644
index 000000000..3422655b4
--- /dev/null
+++ b/release-notes/v1.3.2.yaml
@@ -0,0 +1,19 @@
+date: March 24, 2025
+
+bug fixes: |
+  Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode.
+  Fixed updates of endpoints when mirrored backend Pod IPs are changed.
+  Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted.
+  Fix allowing empty text field for opentelemetry sink when using JSON format.
+  Fixed validation of host header in RequestHeaderModifier filter.
+  Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated.
+
+# Enhancements that improve performance.
+performance improvements: |
+  Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in
+  a background goroutine.
+
+# Other notable changes not covered by the above sections.
+Other changes: |
+  Bumped envoy to v1.33.1.
+  Bumped ratelimit to 0141a24f.
diff --git a/site/content/en/news/releases/notes/v1.3.2.md b/site/content/en/news/releases/notes/v1.3.2.md
new file mode 100644
index 000000000..73760e606
--- /dev/null
+++ b/site/content/en/news/releases/notes/v1.3.2.md
@@ -0,0 +1,23 @@
+---
+title: "v1.3.2"
+publishdate: 2025-03-24
+---
+
+Date: March 24, 2025
+
+## Bug fixes
+- Added support for BackendTLSPolicy and EnvoyExtensionPolicy parsing in Standalone mode.
+- Fixed updates of endpoints when mirrored backend Pod IPs are changed.
+- Fix not logging an error and returning it in the K8s Reconcile method when a GatewayClass is not accepted.
+- Fix allowing empty text field for opentelemetry sink when using JSON format.
+- Fixed validation of host header in RequestHeaderModifier filter.
+- Retrigger reconciliation when backendRef of type ServiceImport is updated or when EndpointSlice(s) for a ServiceImport are updated.
+
+## Performance improvements
+- Added a cache for the Wasm OCI image permission checks and check the pullSecrets against the OCI image registry in
+- a background goroutine.
+
+## Other changes
+- Bumped envoy to v1.33.1.
+- Bumped ratelimit to 0141a24f.
+
diff --git a/site/data/ecosystem.yaml b/site/data/ecosystem.yaml
index b71607988..893d5a256 100644
--- a/site/data/ecosystem.yaml
+++ b/site/data/ecosystem.yaml
@@ -15,3 +15,7 @@ adopters:
     logo: "/logos/knative.png"
     url: "https://knative.dev"
     description: "Knative's net-gateway-api integrates with Envoy Gateway by implementing the Kubernetes Gateway API, enabling Knative services to utilize Envoy Gateway for ingress traffic management."
+  - name: "KServe"
+    logo: "/logos/kserve.svg"
+    url: "https://kserve.github.io"
+    description: "KServe leverages Kubernetes Gateway API and Envoy Gateway to simplify routing, traffic management, and secure access to machine learning inference services in Kubernetes environments."
diff --git a/site/static/logos/kserve.svg b/site/static/logos/kserve.svg
new file mode 100644
index 000000000..eaf15c220
--- /dev/null
+++ b/site/static/logos/kserve.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="256" height="256" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <path d="m128 11.457a4.4657 4.4657 0 0 0-1.9336 0.43945l-91.986 44.205a4.4657 4.4657 0 0 0-2.4199 3.0332l-22.771 100.02a4.4657 4.4657 0 0 0 0.85547 3.7656l63.406 79.93a4.4657 4.4657 0 0 0 3.498 1.6895h102.7a4.4657 4.4657 0 0 0 3.498-1.6895l63.406-79.93a4.4657 4.4657 0 0 0 0.85547-3.7656l-22.773-100.02a4.4657 4.4657 0 0 0-2.418-3.0332l-91.984-44.205a4.4657 4.4657 0 0 0-1.9336-0.43945z" color="#000000" fill="#326ce5" stroke-linejoin="round" style="-inkscape-stroke:none"/>
+ <path class="cls-2" d="m143.8 52.865c-11.942 0.51197-23.322 6.4364-31.242 15.282-1.9497 2.1311-3.6052 4.5049-5.2643 6.8608-0.64921 0.801-1.7928 1.3254-2.7632 0.77062-2.3123-0.88779-4.3474-2.4128-6.7642-3.0441-5.9959-1.9483-12.644-1.0843-18.279 1.5919-5.8441 2.6643-10.742 7.3914-13.459 13.227-1.0538 2.0967-1.7501 4.3493-2.7714 6.4542-1.1412 1.8709-3.2363 2.8555-5.3086 3.3003-3.4411 0.94998-6.5858 2.7632-9.306 5.0557-3.815 3.1857-7.0138 7.1238-9.3576 11.504-3.245 6.2141-4.3084 13.462-3.2995 20.378 0.96363 7.7481 4.1025 15.437 9.8131 20.899 5.1328 5.0415 12.069 7.9664 19.149 8.8782 4.4899 0.6423 9.0357 0.40896 13.547 0.17895 19.888-0.74343 39.788 0.14951 59.681-0.0287 18.175-0.0116 36.351 0.0219 54.525-0.01 8.2204-0.40064 15.942-4.9409 20.81-11.499 6.8368-8.9645 8.8171-21.477 4.7924-32.046-2.195-6.3377-6.1179-12.343-11.944-15.872-3.4497-2.1751-7.3908-3.4396-11.37-4.2065-1.499-0.69066-1.6585-2.5443-1.973-3.9653-2.2188-11.007-6.9748-21.667-14.598-30.001-8.7935-9.3673-21.862-14.229-34.618-13.709zm2.123 9.3398c10.425 0.14449 20.526 5.5067 26.844 13.772 5.7234 7.2096 9.062 16.156 9.7059 25.305 0.18111 1.9866 0.0834 4.0499 0.34478 6.0468 0.28157 1.3348 1.5071 2.4159 2.899 2.3706 2.5281 0.23046 5.0881-0.19326 7.5988 0.30488 4.1246 0.60513 8.0192 2.7385 10.629 6.0076 4.7076 5.5654 6.2812 13.285 5.1877 20.389-0.87308 5.8143-3.7568 11.489-8.5614 15.011-2.6816 2.0002-6.0706 3.2627-9.4773 3.1712-35.006 0.0335-70.014 0.0532-105.02 0.0478-5.1856-0.0236-10.374 0.0413-15.558-0.0374-0.8128-0.0656-1.6754-0.0516-2.3722-0.43166 4.5272-3.9177 9.1298-7.7631 13.673-11.668 5.9622-5.089 11.913-10.192 17.87-15.287 6.4121 5.4082 12.824 10.816 19.236 16.225 12.474-10.645 24.949-21.289 37.424-31.934 3.7916 3.2858 7.5835 6.5714 11.375 9.8574-0.6148-8.7905-1.2343-17.581-1.8516-26.371-10.258-0.63356-20.516-1.2682-30.773-1.9004 3.5707 3.0953 7.142 6.1901 10.713 9.2852-8.7747 7.4863-17.549 14.973-26.324 22.459-6.0462-5.0996-12.092-10.199-18.139-15.299-15.131 12.9-30.221 25.847-45.409 38.674-0.755 0.6081-2.0275 0.75574-2.6339-0.1404-3.3895-3.2467-5.3419-7.6966-6.2279-12.22-1.3656-7.2661 0.06881-15.084 4.1132-21.256 2.6883-4.0756 6.6904-7.5615 11.38-8.8911 2.3041-0.62015 4.7624-0.41453 7.1156-0.22303 1.353 0.0866 3.0121-0.43355 3.388-1.8994 0.48869-1.6382 0.25846-3.3831 0.60507-5.0507 0.61888-4.6164 2.4164-9.3141 6.004-12.459 3.4549-3.1478 8.1872-5.1455 12.907-4.6652 2.7622 0.33297 5.6038 1.6034 7.7772 3.4795 3.0988 2.6617 5.2071 6.2316 7.9435 9.2177 1.1574 1.1994 1.381 0.86868 1.9668-1.4141 1.8791-7.118 5.6876-13.682 10.956-18.842 4.4995-4.5065 9.8739-8.2852 15.938-10.338 2.852-0.88461 5.7747-1.3063 8.752-1.2969z" fill="#f9fafd" stroke-width=".57297"/>
+ <path d="m75.119 198.06-3.2227 3.418v8.8672h-4.9414v-28.438h4.9414v13.34l2.7344-3.3789 8.3203-9.9609h5.9766l-10.566 12.598 11.172 15.84h-5.8594zm31.914 4.9609q0-1.875-1.3281-2.8906-1.3086-1.0156-4.7461-2.0508-3.4375-1.0352-5.4687-2.3047-3.8867-2.4414-3.8867-6.3672 0-3.4375 2.793-5.6641 2.8125-2.2266 7.2852-2.2266 2.9688 0 5.293 1.0938t3.6523 3.125q1.3281 2.0117 1.3281 4.4726h-4.9219q0-2.2266-1.4062-3.4766-1.3867-1.2695-3.9844-1.2695-2.4219 0-3.7695 1.0352-1.3281 1.0352-1.3281 2.8906 0 1.5625 1.4453 2.6172 1.4453 1.0352 4.7656 2.0312 3.3203 0.97656 5.332 2.2461 2.0117 1.25 2.9492 2.8906 0.9375 1.6211 0.9375 3.8086 0 3.5547-2.7344 5.6641-2.7148 2.0898-7.3828 2.0898-3.0859 0-5.6836-1.1328-2.5781-1.1523-4.0234-3.1641-1.4258-2.0117-1.4258-4.6875h4.9414q0 2.4219 1.6016 3.75 1.6016 1.3281 4.5898 1.3281 2.5781 0 3.8672-1.0352 1.3086-1.0547 1.3086-2.7734zm18.125 7.7148q-4.5117 0-7.3242-2.832-2.793-2.8516-2.793-7.5781v-0.58594q0-3.1641 1.2109-5.6445 1.2305-2.5 3.4375-3.8867t4.9219-1.3867q4.3164 0 6.6602 2.7539 2.3633 2.7539 2.3633 7.793v1.9141h-13.809q0.21485 2.6172 1.7383 4.1406 1.543 1.5234 3.8672 1.5234 3.2617 0 5.3125-2.6367l2.5586 2.4414q-1.2695 1.8945-3.3984 2.9492-2.1094 1.0352-4.7461 1.0352zm-0.5664-18.105q-1.9531 0-3.1641 1.3672-1.1914 1.3672-1.5234 3.8086h9.043v-0.35156q-0.15625-2.3828-1.2695-3.5938-1.1133-1.2305-3.0859-1.2305zm23.789 0.91797q-0.9375-0.15625-1.9336-0.15625-3.2617 0-4.3945 2.5v14.453h-4.7461v-21.133h4.5312l0.11719 2.3633q1.7188-2.7539 4.7656-2.7539 1.0156 0 1.6797 0.27344zm10.801 10.781 4.4727-15.117h4.9023l-7.3242 21.133h-4.1211l-7.3828-21.133h4.9219zm21.387 6.4062q-4.5117 0-7.3242-2.832-2.793-2.8516-2.793-7.5781v-0.58594q0-3.1641 1.2109-5.6445 1.2305-2.5 3.4375-3.8867t4.9219-1.3867q4.3164 0 6.6602 2.7539 2.3633 2.7539 2.3633 7.793v1.9141h-13.809q0.21484 2.6172 1.7383 4.1406 1.543 1.5234 3.8672 1.5234 3.2617 0 5.3125-2.6367l2.5586 2.4414q-1.2695 1.8945-3.3984 2.9492-2.1094 1.0352-4.7461 1.0352zm-0.56641-18.105q-1.9531 0-3.1641 1.3672-1.1914 1.3672-1.5234 3.8086h9.043v-0.35156q-0.15625-2.3828-1.2695-3.5938-1.1133-1.2305-3.0859-1.2305z" fill="#fff" stroke-opacity="0" style="paint-order:stroke fill markers" aria-label="KServe"/>
+</svg>


Test patch:
diff --git a/internal/crypto/certgen_test.go b/internal/crypto/certgen_test.go
index 429fea518..5e3a6f1d3 100644
--- a/internal/crypto/certgen_test.go
+++ b/internal/crypto/certgen_test.go
@@ -10,6 +10,7 @@ import (
 	"encoding/base64"
 	"encoding/pem"
 	"fmt"
+	"os"
 	"testing"
 	"time"
 
@@ -25,7 +26,7 @@ func TestGenerateCerts(t *testing.T) {
 		wantEnvoyDNSName        string
 	}
 
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	run := func(t *testing.T, name string, tc testcase) {
diff --git a/internal/envoygateway/config/config_test.go b/internal/envoygateway/config/config_test.go
index 59bba1294..1e68977ea 100644
--- a/internal/envoygateway/config/config_test.go
+++ b/internal/envoygateway/config/config_test.go
@@ -6,6 +6,7 @@
 package config
 
 import (
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -21,7 +22,7 @@ var (
 )
 
 func TestValidate(t *testing.T) {
-	cfg, err := New()
+	cfg, err := New(os.Stdout)
 	require.NoError(t, err)
 
 	testCases := []struct {
@@ -56,7 +57,7 @@ func TestValidate(t *testing.T) {
 			name: "unspecified envoy gateway",
 			cfg: &Server{
 				Namespace: "test-ns",
-				Logger:    logging.DefaultLogger(egv1a1.LogLevelInfo),
+				Logger:    logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo),
 			},
 			expect: false,
 		},
diff --git a/internal/envoygateway/config/loader/configloader_test.go b/internal/envoygateway/config/loader/configloader_test.go
index b5aa30bf1..fe6186c88 100644
--- a/internal/envoygateway/config/loader/configloader_test.go
+++ b/internal/envoygateway/config/loader/configloader_test.go
@@ -32,7 +32,7 @@ func TestConfigLoader(t *testing.T) {
 
 	cfgPath := tmpDir + "/config.yaml"
 	require.NoError(t, os.WriteFile(cfgPath, []byte(defaultConfig), 0o600))
-	s, err := config.New()
+	s, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	ctx, cancel := context.WithCancel(context.TODO())
diff --git a/internal/gatewayapi/runner/runner_test.go b/internal/gatewayapi/runner/runner_test.go
index 58515da7e..4596a8290 100644
--- a/internal/gatewayapi/runner/runner_test.go
+++ b/internal/gatewayapi/runner/runner_test.go
@@ -7,6 +7,7 @@ package runner
 
 import (
 	"context"
+	"os"
 	"reflect"
 	"testing"
 	"time"
@@ -30,7 +31,7 @@ func TestRunner(t *testing.T) {
 	pResources := new(message.ProviderResources)
 	xdsIR := new(message.XdsIR)
 	infraIR := new(message.InfraIR)
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	extMgr, closeFunc, err := registry.NewInMemoryManager(egv1a1.ExtensionManager{}, &pb.UnimplementedEnvoyGatewayExtensionServer{})
 	require.NoError(t, err)
@@ -116,7 +117,7 @@ func TestDeleteStatusKeys(t *testing.T) {
 	pResources := new(message.ProviderResources)
 	xdsIR := new(message.XdsIR)
 	infraIR := new(message.InfraIR)
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	extMgr, closeFunc, err := registry.NewInMemoryManager(egv1a1.ExtensionManager{}, &pb.UnimplementedEnvoyGatewayExtensionServer{})
 	require.NoError(t, err)
@@ -217,7 +218,7 @@ func TestDeleteAllStatusKeys(t *testing.T) {
 	pResources := new(message.ProviderResources)
 	xdsIR := new(message.XdsIR)
 	infraIR := new(message.InfraIR)
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	extMgr, closeFunc, err := registry.NewInMemoryManager(egv1a1.ExtensionManager{}, &pb.UnimplementedEnvoyGatewayExtensionServer{})
 	require.NoError(t, err)
diff --git a/internal/globalratelimit/runner/runner_test.go b/internal/globalratelimit/runner/runner_test.go
index 80598f790..bbb8b4fda 100644
--- a/internal/globalratelimit/runner/runner_test.go
+++ b/internal/globalratelimit/runner/runner_test.go
@@ -8,6 +8,7 @@ package runner
 import (
 	"context"
 	"fmt"
+	"os"
 	"testing"
 	"time"
 
@@ -209,7 +210,7 @@ func Test_subscribeAndTranslate(t *testing.T) {
 			defer cancel()
 			xdsIR := new(message.XdsIR)
 			defer xdsIR.Close()
-			cfg, err := config.New()
+			cfg, err := config.New(os.Stdout)
 			require.NoError(t, err)
 
 			r := New(&Config{
diff --git a/internal/infrastructure/host/proxy_infra_test.go b/internal/infrastructure/host/proxy_infra_test.go
index de3651da4..2d82cc689 100644
--- a/internal/infrastructure/host/proxy_infra_test.go
+++ b/internal/infrastructure/host/proxy_infra_test.go
@@ -8,6 +8,7 @@ package host
 import (
 	"bytes"
 	"context"
+	"os"
 	"path"
 	"testing"
 
@@ -42,7 +43,7 @@ func newMockInfra(t *testing.T, cfg *config.Server) *Infra {
 
 	infra := &Infra{
 		HomeDir:         homeDir,
-		Logger:          logging.DefaultLogger(egv1a1.LogLevelInfo),
+		Logger:          logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo),
 		EnvoyGateway:    cfg.EnvoyGateway,
 		proxyContextMap: make(map[string]*proxyContext),
 		sdsConfigPath:   proxyDir,
@@ -51,7 +52,7 @@ func newMockInfra(t *testing.T, cfg *config.Server) *Infra {
 }
 
 func TestInfraCreateProxy(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	infra := newMockInfra(t, cfg)
 
diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
index c005a5db4..478a70dec 100644
--- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
+++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go
@@ -116,7 +116,7 @@ func newTestInfraWithAnnotationsAndLabels(annotations, labels map[string]string)
 }
 
 func TestDeployment(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cases := []struct {
@@ -645,7 +645,7 @@ func loadDeployment(caseName string) (*appsv1.Deployment, error) {
 }
 
 func TestDaemonSet(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cases := []struct {
@@ -1074,7 +1074,7 @@ func loadDaemonSet(caseName string) (*appsv1.DaemonSet, error) {
 }
 
 func TestService(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	svcType := egv1a1.ServiceTypeClusterIP
@@ -1222,7 +1222,7 @@ func loadService(caseName string) (*corev1.Service, error) {
 }
 
 func TestConfigMap(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	cases := []struct {
 		name  string
@@ -1265,7 +1265,7 @@ func loadConfigmap(tc string) (*corev1.ConfigMap, error) {
 }
 
 func TestServiceAccount(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	cases := []struct {
 		name  string
@@ -1308,7 +1308,7 @@ func loadServiceAccount(tc string) (*corev1.ServiceAccount, error) {
 }
 
 func TestPDB(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cases := []struct {
@@ -1414,7 +1414,7 @@ func TestPDB(t *testing.T) {
 }
 
 func TestHorizontalPodAutoscaler(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cases := []struct {
diff --git a/internal/infrastructure/kubernetes/proxy_configmap_test.go b/internal/infrastructure/kubernetes/proxy_configmap_test.go
index ec4c0ec74..60ccbbca6 100644
--- a/internal/infrastructure/kubernetes/proxy_configmap_test.go
+++ b/internal/infrastructure/kubernetes/proxy_configmap_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -26,7 +27,7 @@ import (
 )
 
 func TestCreateOrUpdateProxyConfigMap(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	infra := ir.NewInfra()
@@ -128,7 +129,7 @@ func TestCreateOrUpdateProxyConfigMap(t *testing.T) {
 }
 
 func TestDeleteConfigProxyMap(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	infra := ir.NewInfra()
diff --git a/internal/infrastructure/kubernetes/proxy_daemonset_test.go b/internal/infrastructure/kubernetes/proxy_daemonset_test.go
index 2c1265862..d60c9a617 100644
--- a/internal/infrastructure/kubernetes/proxy_daemonset_test.go
+++ b/internal/infrastructure/kubernetes/proxy_daemonset_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -47,7 +48,7 @@ func daemonsetWithSelectorAndLabel(ds *appsv1.DaemonSet, selector *metav1.LabelS
 }
 
 func TestCreateOrUpdateProxyDaemonSet(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	infra := ir.NewInfra()
diff --git a/internal/infrastructure/kubernetes/proxy_deployment_test.go b/internal/infrastructure/kubernetes/proxy_deployment_test.go
index 188c92961..73460e00e 100644
--- a/internal/infrastructure/kubernetes/proxy_deployment_test.go
+++ b/internal/infrastructure/kubernetes/proxy_deployment_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -52,7 +53,7 @@ func deploymentWithSelectorAndLabel(deploy *appsv1.Deployment, selector *metav1.
 }
 
 func TestCreateOrUpdateProxyDeployment(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	infra := ir.NewInfra()
@@ -264,7 +265,7 @@ func TestDeleteProxyDeployment(t *testing.T) {
 		WithObjects().
 		WithInterceptorFuncs(interceptorFunc).
 		Build()
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	testCases := []struct {
diff --git a/internal/infrastructure/kubernetes/proxy_infra_test.go b/internal/infrastructure/kubernetes/proxy_infra_test.go
index de0690e82..5363b4e80 100644
--- a/internal/infrastructure/kubernetes/proxy_infra_test.go
+++ b/internal/infrastructure/kubernetes/proxy_infra_test.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"os"
 	"reflect"
 	"testing"
 
@@ -81,7 +82,7 @@ func TestCmpBytes(t *testing.T) {
 }
 
 func newTestInfraWithClient(t *testing.T, cli client.Client) *Infra {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cfg.EnvoyGateway = &egv1a1.EnvoyGateway{
diff --git a/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go b/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go
index 44732bf6b..18aa79ffd 100644
--- a/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go
+++ b/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -167,7 +168,7 @@ func TestCreateOrUpdateProxyServiceAccount(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			cfg, err := config.New()
+			cfg, err := config.New(os.Stdout)
 			require.NoError(t, err)
 			cfg.Namespace = tc.ns
 
diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go
index d3bd7243d..5c2a1edf0 100644
--- a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go
+++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go
@@ -89,7 +89,7 @@ func TestRateLimitLabels(t *testing.T) {
 }
 
 func TestServiceAccount(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cfg.EnvoyGateway.RateLimit = &egv1a1.RateLimit{
@@ -122,7 +122,7 @@ func loadServiceAccount() (*corev1.ServiceAccount, error) {
 }
 
 func TestService(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cases := []struct {
@@ -185,7 +185,7 @@ func loadService(caseName string) (*corev1.Service, error) {
 }
 
 func TestConfigmap(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cfg.EnvoyGateway.RateLimit = &egv1a1.RateLimit{
@@ -226,7 +226,7 @@ func loadConfigmap() (*corev1.ConfigMap, error) {
 }
 
 func TestDeployment(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	rateLimit := &egv1a1.RateLimit{
 		Backend: egv1a1.RateLimitDatabaseBackend{
@@ -798,7 +798,7 @@ func loadDeployment(caseName string) (*appsv1.Deployment, error) {
 }
 
 func TestHorizontalPodAutoscaler(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	cases := []struct {
 		caseName            string
diff --git a/internal/infrastructure/kubernetes/ratelimit_deployment_test.go b/internal/infrastructure/kubernetes/ratelimit_deployment_test.go
index ea0e5017f..e66b69a06 100644
--- a/internal/infrastructure/kubernetes/ratelimit_deployment_test.go
+++ b/internal/infrastructure/kubernetes/ratelimit_deployment_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -23,7 +24,7 @@ import (
 )
 
 func TestCreateOrUpdateRateLimitDeployment(t *testing.T) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 
 	cfg.EnvoyGateway.RateLimit = &egv1a1.RateLimit{
diff --git a/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go b/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go
index 630fa2e33..2e5805129 100644
--- a/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go
+++ b/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -103,7 +104,7 @@ func TestCreateOrUpdateRateLimitServiceAccount(t *testing.T) {
 					Build()
 			}
 
-			cfg, err := config.New()
+			cfg, err := config.New(os.Stdout)
 			require.NoError(t, err)
 			cfg.Namespace = tc.ns
 
diff --git a/internal/logging/log_test.go b/internal/logging/log_test.go
index 265df4111..a5f75e438 100644
--- a/internal/logging/log_test.go
+++ b/internal/logging/log_test.go
@@ -38,7 +38,7 @@ func TestLogger(t *testing.T) {
 
 	logger.WithName(string(egv1a1.LogComponentGlobalRateLimitRunner)).WithValues("runner", egv1a1.LogComponentGlobalRateLimitRunner).Info("msg", "k", "v")
 
-	defaultLogger := DefaultLogger(egv1a1.LogLevelInfo)
+	defaultLogger := DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 	assert.NotNil(t, defaultLogger.logging)
 	assert.NotNil(t, defaultLogger.sugaredLogger)
 
diff --git a/internal/provider/file/file_test.go b/internal/provider/file/file_test.go
index 8f681d47d..57909ea69 100644
--- a/internal/provider/file/file_test.go
+++ b/internal/provider/file/file_test.go
@@ -50,7 +50,7 @@ func newDefaultResourcesParam() *resourcesParam {
 }
 
 func newFileProviderConfig(paths []string) (*config.Server, error) {
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go
index d008e7b2f..b2d6cdaf7 100644
--- a/internal/provider/kubernetes/controller_test.go
+++ b/internal/provider/kubernetes/controller_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -256,7 +257,7 @@ func TestProcessGatewayClassParamsRef(t *testing.T) {
 		tc := testCases[i]
 
 		// Create the reconciler.
-		logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+		logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 		r := &gatewayAPIReconciler{
 			log:             logger,
@@ -421,7 +422,7 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) {
 			objs := []client.Object{tc.envoyExtensionPolicy, tc.backend, tc.referenceGrant}
 
 			// Create the reconciler.
-			logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+			logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 			ctx := context.Background()
 
diff --git a/internal/provider/kubernetes/extensionpolicies_test.go b/internal/provider/kubernetes/extensionpolicies_test.go
index 3d74ff270..54010a19e 100644
--- a/internal/provider/kubernetes/extensionpolicies_test.go
+++ b/internal/provider/kubernetes/extensionpolicies_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -206,7 +207,7 @@ func TestProcessExtensionPolicies(t *testing.T) {
 			objs := []client.Object{}
 
 			// Create the reconciler.
-			logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+			logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 			ctx := context.Background()
 
diff --git a/internal/provider/kubernetes/kubernetes_test.go b/internal/provider/kubernetes/kubernetes_test.go
index 4665f510e..5daf6fba6 100644
--- a/internal/provider/kubernetes/kubernetes_test.go
+++ b/internal/provider/kubernetes/kubernetes_test.go
@@ -60,7 +60,7 @@ func TestProvider(t *testing.T) {
 	require.NoError(t, err)
 
 	// Setup and start the kube provider.
-	svr, err := config.New()
+	svr, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	resources := new(message.ProviderResources)
 	provider, err := New(context.Background(), cliCfg, svr, resources)
@@ -1263,7 +1263,7 @@ func TestNamespacedProvider(t *testing.T) {
 	require.NoError(t, err)
 
 	// Setup and start the kube provider.
-	svr, err := config.New()
+	svr, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	// config to watch a subset of namespaces
 	svr.EnvoyGateway.Provider.Kubernetes = &egv1a1.EnvoyGatewayKubernetesProvider{
@@ -1323,7 +1323,7 @@ func TestNamespaceSelectorProvider(t *testing.T) {
 	require.NoError(t, err)
 
 	// Setup and start the kube provider.
-	svr, err := config.New()
+	svr, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	// config to watch a subset of namespaces
 	svr.EnvoyGateway.Provider.Kubernetes = &egv1a1.EnvoyGatewayKubernetesProvider{
diff --git a/internal/provider/kubernetes/predicates_test.go b/internal/provider/kubernetes/predicates_test.go
index 5c9dbba1c..0ccb4999c 100644
--- a/internal/provider/kubernetes/predicates_test.go
+++ b/internal/provider/kubernetes/predicates_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"fmt"
+	"os"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -52,7 +53,7 @@ func TestGatewayClassHasMatchingController(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -104,7 +105,7 @@ func TestGatewayClassHasMatchingNamespaceLabels(t *testing.T) {
 		},
 	}
 
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	for _, tc := range testCases {
 		r := gatewayAPIReconciler{
@@ -158,7 +159,7 @@ func TestValidateGatewayForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -384,7 +385,7 @@ func TestValidateSecretForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -512,7 +513,7 @@ func TestValidateEndpointSliceForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -941,7 +942,7 @@ func TestValidateServiceForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController:    egv1a1.GatewayControllerName,
@@ -1060,7 +1061,7 @@ func TestValidateObjectForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -1167,7 +1168,7 @@ func TestCheckObjectNamespaceLabels(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
@@ -1320,7 +1321,7 @@ func TestValidateHTTPRouteFilerForReconcile(t *testing.T) {
 	}
 
 	// Create the reconciler.
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 	r := gatewayAPIReconciler{
 		classController: egv1a1.GatewayControllerName,
diff --git a/internal/provider/kubernetes/routes_test.go b/internal/provider/kubernetes/routes_test.go
index d1850b9bf..a9e1f7820 100644
--- a/internal/provider/kubernetes/routes_test.go
+++ b/internal/provider/kubernetes/routes_test.go
@@ -7,6 +7,7 @@ package kubernetes
 
 import (
 	"context"
+	"os"
 	"testing"
 	"time"
 
@@ -596,7 +597,7 @@ func TestProcessHTTPRoutes(t *testing.T) {
 			objs := []client.Object{gc, gw}
 
 			// Create the reconciler.
-			logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+			logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 			ctx := context.Background()
 
@@ -756,7 +757,7 @@ func TestProcessGRPCRoutes(t *testing.T) {
 			objs := []client.Object{gc, gw}
 
 			// Create the reconciler.
-			logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+			logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 
 			ctx := context.Background()
 
diff --git a/internal/wasm/cache_test.go b/internal/wasm/cache_test.go
index ba40c0625..6d3edd572 100644
--- a/internal/wasm/cache_test.go
+++ b/internal/wasm/cache_test.go
@@ -691,7 +691,7 @@ func TestWasmCache(t *testing.T) {
 			if c.wasmModuleExpiry != 0 {
 				options.ModuleExpiry = c.wasmModuleExpiry
 			}
-			cache := newLocalFileCache(options, logging.DefaultLogger(egv1a1.LogLevelInfo))
+			cache := newLocalFileCache(options, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 			cache.httpFetcher.initialBackoff = time.Microsecond
 			ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
 			cache.Start(ctx)
@@ -878,7 +878,7 @@ func TestWasmCachePolicyChangesUsingHTTP(t *testing.T) {
 	tmpDir := t.TempDir()
 	options := defaultCacheOptions()
 	options.CacheDir = tmpDir
-	cache := newLocalFileCache(options, logging.DefaultLogger(egv1a1.LogLevelInfo))
+	cache := newLocalFileCache(options, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
 	cache.Start(ctx)
 	defer cancel()
@@ -944,7 +944,7 @@ func TestAllInsecureServer(t *testing.T) {
 	options := defaultCacheOptions()
 	options.CacheDir = tmpDir
 	options.InsecureRegistries = sets.New[string]("*")
-	cache := newLocalFileCache(options, logging.DefaultLogger(egv1a1.LogLevelInfo))
+	cache := newLocalFileCache(options, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
 	cache.Start(ctx)
 	defer cancel()
diff --git a/internal/wasm/httpfetcher_test.go b/internal/wasm/httpfetcher_test.go
index 98fdda500..3a27e19f1 100644
--- a/internal/wasm/httpfetcher_test.go
+++ b/internal/wasm/httpfetcher_test.go
@@ -27,6 +27,7 @@ import (
 	"fmt"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"regexp"
 	"strings"
 	"testing"
@@ -97,7 +98,7 @@ func TestWasmHTTPFetch(t *testing.T) {
 				gotNumRequest++
 			}))
 			defer ts.Close()
-			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(egv1a1.LogLevelInfo))
+			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 			fetcher.initialBackoff = time.Microsecond
 			ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
 			defer cancel()
@@ -156,7 +157,7 @@ func TestWasmHTTPInsecureServer(t *testing.T) {
 				gotNumRequest++
 			}))
 			defer ts.Close()
-			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(egv1a1.LogLevelInfo))
+			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 			fetcher.initialBackoff = time.Microsecond
 			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 			defer cancel()
@@ -257,7 +258,7 @@ func TestWasmHTTPFetchCompressedOrTarFile(t *testing.T) {
 				gotNumRequest++
 			}))
 			defer ts.Close()
-			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(egv1a1.LogLevelInfo))
+			fetcher := NewHTTPFetcher(DefaultHTTPRequestTimeout, DefaultHTTPRequestMaxRetries, logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 			fetcher.initialBackoff = time.Microsecond
 			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 			defer cancel()
diff --git a/internal/wasm/httpserver_test.go b/internal/wasm/httpserver_test.go
index 0f054ea84..0fd7c099b 100644
--- a/internal/wasm/httpserver_test.go
+++ b/internal/wasm/httpserver_test.go
@@ -12,6 +12,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"os"
 	"strings"
 	"testing"
 	"time"
@@ -327,7 +328,7 @@ func setupFakeRegistry(host string) error {
 }
 
 func startLocalHTTPServer(ctx context.Context, cacheDir string, maxFailedAttempts int, failedAttemptResetDelay, failedAttemptsResetInterval time.Duration) (*HTTPServer, error) {
-	logger := logging.DefaultLogger(egv1a1.LogLevelInfo)
+	logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo)
 	s := NewHTTPServerWithFileCache(
 		SeverOptions{
 			Salt:                        []byte("salt"),
diff --git a/internal/wasm/premissioncache_test.go b/internal/wasm/premissioncache_test.go
index 2fdb95ff1..5066a4189 100644
--- a/internal/wasm/premissioncache_test.go
+++ b/internal/wasm/premissioncache_test.go
@@ -25,6 +25,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"os"
 	"sync"
 	"testing"
 	"time"
@@ -256,7 +257,7 @@ func setupTestPermissionCache(options permissionCacheOptions, image, latestImage
 	// Setup the permission cache.
 	cache := newPermissionCache(
 		options,
-		logging.DefaultLogger(egv1a1.LogLevelInfo))
+		logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo))
 
 	entry := &permissionCacheEntry{
 		image: image,
diff --git a/internal/xds/server/runner/runner_test.go b/internal/xds/server/runner/runner_test.go
index e0517d1fa..69dd798b8 100644
--- a/internal/xds/server/runner/runner_test.go
+++ b/internal/xds/server/runner/runner_test.go
@@ -195,7 +195,7 @@ func TestServeXdsServerListenFailed(t *testing.T) {
 	require.NoError(t, err)
 	defer l.Close()
 
-	cfg, _ := config.New()
+	cfg, _ := config.New(os.Stdout)
 	r := New(&Config{
 		Server: *cfg,
 	})
diff --git a/internal/xds/translator/runner/runner_test.go b/internal/xds/translator/runner/runner_test.go
index a154b9f82..ee1765f48 100644
--- a/internal/xds/translator/runner/runner_test.go
+++ b/internal/xds/translator/runner/runner_test.go
@@ -8,6 +8,7 @@ package runner
 import (
 	"context"
 	"fmt"
+	"os"
 	"testing"
 	"time"
 
@@ -28,7 +29,7 @@ func TestRunner(t *testing.T) {
 	xdsIR := new(message.XdsIR)
 	xds := new(message.Xds)
 	pResource := new(message.ProviderResources)
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	r := New(&Config{
 		Server:            *cfg,
@@ -108,7 +109,7 @@ func TestRunner_withExtensionManager(t *testing.T) {
 	xds := new(message.Xds)
 	pResource := new(message.ProviderResources)
 
-	cfg, err := config.New()
+	cfg, err := config.New(os.Stdout)
 	require.NoError(t, err)
 	r := New(&Config{
 		Server:            *cfg,
diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go
index dcb2ff41a..4c5ac11d5 100644
--- a/internal/xds/translator/translator_test.go
+++ b/internal/xds/translator/translator_test.go
@@ -9,6 +9,7 @@ import (
 	"embed"
 	"flag"
 	"path/filepath"
+	"runtime"
 	"sort"
 	"strings"
 	"testing"
@@ -49,6 +50,28 @@ type testFileConfig struct {
 }
 
 func TestTranslateXds(t *testing.T) {
+	// this is a hack to make sure EG render same output on macos and linux
+	defaultCertificateName = "/etc/ssl/certs/ca-certificates.crt"
+	defer func() {
+		defaultCertificateName = func() string {
+			switch runtime.GOOS {
+			case "darwin":
+				// TODO: maybe automatically get the keychain cert? That might be macOS version dependent.
+				// For now, we'll just use the root cert installed by Homebrew: brew install ca-certificates.
+				//
+				// See:
+				// * https://apple.stackexchange.com/questions/226375/where-are-the-root-cas-stored-on-os-x
+				// * https://superuser.com/questions/992167/where-are-digital-certificates-physically-stored-on-a-mac-os-x-machine
+				return "/opt/homebrew/etc/ca-certificates/cert.pem"
+			default:
+				// This is the default location for the system trust store
+				// on Debian derivatives like the envoy-proxy image being used by the infrastructure
+				// controller.
+				// See https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ssl
+				return "/etc/ssl/certs/ca-certificates.crt"
+			}
+		}()
+	}()
 	testConfigs := map[string]testFileConfig{
 		"ratelimit-custom-domain": {
 			dnsDomain: "example-cluster.local",
diff --git a/site/content/en/latest/tasks/traffic/grpc-routing.md b/site/content/en/latest/tasks/traffic/grpc-routing.md
index 15f10601b..3a36e9fd7 100644
--- a/site/content/en/latest/tasks/traffic/grpc-routing.md
+++ b/site/content/en/latest/tasks/traffic/grpc-routing.md
@@ -99,7 +99,7 @@ as well as a match for all services with a method name `Ping` which matches `yag
 
 ```shell
 cat <<EOF | kubectl apply -f -
-apiVersion: gateway.networking.k8s.io/v1alpha2
+apiVersion: gateway.networking.k8s.io/v1
 kind: GRPCRoute
 metadata:
   name: yages
@@ -132,7 +132,7 @@ Save and apply the following resource to your cluster:
 
 ```yaml
 ---
-apiVersion: gateway.networking.k8s.io/v1alpha2
+apiVersion: gateway.networking.k8s.io/v1
 kind: GRPCRoute
 metadata:
   name: yages
@@ -184,7 +184,7 @@ with match type `RegularExpression`. It matches all the services and methods wit
 
 ```shell
 cat <<EOF | kubectl apply -f -
-apiVersion: gateway.networking.k8s.io/v1alpha2
+apiVersion: gateway.networking.k8s.io/v1
 kind: GRPCRoute
 metadata:
   name: yages
@@ -219,7 +219,7 @@ Save and apply the following resource to your cluster:
 
 ```yaml
 ---
-apiVersion: gateway.networking.k8s.io/v1alpha2
+apiVersion: gateway.networking.k8s.io/v1
 kind: GRPCRoute
 metadata:
   name: yages
diff --git a/test/e2e/testdata/accesslog-otel-default.yaml b/test/e2e/testdata/accesslog-otel-default.yaml
new file mode 100644
index 000000000..764e8e02e
--- /dev/null
+++ b/test/e2e/testdata/accesslog-otel-default.yaml
@@ -0,0 +1,61 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: accesslog-gtw
+  namespace: gateway-conformance-infra
+spec:
+  gatewayClassName: {GATEWAY_CLASS_NAME}
+  listeners:
+    - name: http
+      port: 80
+      protocol: HTTP
+      allowedRoutes:
+        namespaces:
+          from: Same
+  infrastructure:
+    parametersRef:
+      group: gateway.envoyproxy.io
+      kind: EnvoyProxy
+      name: accesslog-otel
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: accesslog-otel
+  namespace: gateway-conformance-infra
+spec:
+  ipFamily: IPv4
+  telemetry:
+    accessLog:
+      settings:
+        - matches:
+            - "'x-envoy-logged' in request.headers"
+          sinks:
+            - type: OpenTelemetry
+              openTelemetry:
+                backendRefs:
+                  - name: otel-collector
+                    namespace: monitoring
+                    port: 4317
+                resources:
+                  k8s.cluster.name: "envoy-gateway"
+  shutdown:
+    drainTimeout: 5s
+    minDrainDuration: 1s
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: accesslog-otel
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: accesslog-gtw
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /otel
+      backendRefs:
+        - name: infra-backend-v2
+          port: 8080
diff --git a/test/e2e/testdata/accesslog-otel-json.yaml b/test/e2e/testdata/accesslog-otel-json.yaml
new file mode 100644
index 000000000..219315112
--- /dev/null
+++ b/test/e2e/testdata/accesslog-otel-json.yaml
@@ -0,0 +1,79 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: accesslog-gtw
+  namespace: gateway-conformance-infra
+spec:
+  gatewayClassName: {GATEWAY_CLASS_NAME}
+  listeners:
+    - name: http
+      port: 80
+      protocol: HTTP
+      allowedRoutes:
+        namespaces:
+          from: Same
+  infrastructure:
+    parametersRef:
+      group: gateway.envoyproxy.io
+      kind: EnvoyProxy
+      name: accesslog-otel
+---
+apiVersion: gateway.envoyproxy.io/v1alpha1
+kind: EnvoyProxy
+metadata:
+  name: accesslog-otel
+  namespace: gateway-conformance-infra
+spec:
+  ipFamily: IPv4
+  telemetry:
+    accessLog:
+      settings:
+        - format:
+            type: JSON
+            json:
+              time: "%START_TIME%"
+              method: "%REQ(:METHOD)%"
+              metadata: "%METADATA(ROUTE:envoy-gateway:resources)%"
+              x-envoy-original-path: "%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%"
+              protocol: "%PROTOCOL%"
+              responseCode: "%RESPONSE_CODE%"
+              responseFlags: "%RESPONSE_FLAGS%"
+              bytesReceived: "%BYTES_RECEIVED%"
+              bytesSent: "%BYTES_SENT%"
+              duration: "%DURATION%"
+              x-forwarded-for: "%REQ(X-FORWARDED-FOR)%"
+              user-agent: "%REQ(USER-AGENT)%"
+              x-request-id: "%REQ(X-REQUEST-ID)%"
+              authority: "%REQ(:AUTHORITY)%"
+              upstreamHost: "%UPSTREAM_HOST%"
+          matches:
+            - "'x-envoy-logged' in request.headers"
+          sinks:
+            - type: OpenTelemetry
+              openTelemetry:
+                backendRefs:
+                  - name: otel-collector
+                    namespace: monitoring
+                    port: 4317
+                resources:
+                  k8s.cluster.name: "envoy-gateway"
+  shutdown:
+    drainTimeout: 5s
+    minDrainDuration: 1s
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: accesslog-otel
+  namespace: gateway-conformance-infra
+spec:
+  parentRefs:
+    - name: accesslog-gtw
+  rules:
+    - matches:
+        - path:
+            type: PathPrefix
+            value: /otel
+      backendRefs:
+        - name: infra-backend-v2
+          port: 8080
diff --git a/test/e2e/tests/accesslog.go b/test/e2e/tests/accesslog.go
index ea5773495..5df762868 100644
--- a/test/e2e/tests/accesslog.go
+++ b/test/e2e/tests/accesslog.go
@@ -21,7 +21,7 @@ import (
 )
 
 func init() {
-	ConformanceTests = append(ConformanceTests, FileAccessLogTest, OpenTelemetryTest, ALSTest)
+	ConformanceTests = append(ConformanceTests, FileAccessLogTest, OpenTelemetryTestText, OpenTelemetryTestJSON, ALSTest, OpenTelemetryTestJSONAsDefault)
 }
 
 var FileAccessLogTest = suite.ConformanceTest{
@@ -116,9 +116,9 @@ var FileAccessLogTest = suite.ConformanceTest{
 	},
 }
 
-var OpenTelemetryTest = suite.ConformanceTest{
-	ShortName:   "OpenTelemetryAccessLog",
-	Description: "Make sure OpenTelemetry access log is working",
+var OpenTelemetryTestText = suite.ConformanceTest{
+	ShortName:   "OpenTelemetryTextAccessLog",
+	Description: "Make sure OpenTelemetry text access log is working",
 	Manifests:   []string{"testdata/accesslog-otel.yaml"},
 	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
 		labels := map[string]string{
@@ -169,6 +169,112 @@ var OpenTelemetryTest = suite.ConformanceTest{
 	},
 }
 
+var OpenTelemetryTestJSONAsDefault = suite.ConformanceTest{
+	ShortName:   "OpenTelemetryAccessLogJSONAsDefault",
+	Description: "Make sure OpenTelemetry JSON access log is working as default when no format or type is specified",
+	Manifests:   []string{"testdata/accesslog-otel-default.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		labels := map[string]string{
+			"k8s_namespace_name": "envoy-gateway-system",
+			"exporter":           "OTLP",
+		}
+
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "accesslog-otel", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "accesslog-gtw", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+		t.Run("Positive", func(t *testing.T) {
+			expectedResponse := httputils.ExpectedResponse{
+				Request: httputils.Request{
+					Path: "/otel",
+					Headers: map[string]string{
+						"x-envoy-logged": "1",
+					},
+				},
+				Response: httputils.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			// make sure listener is ready
+			httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+			runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 1)
+		})
+
+		t.Run("Negative", func(t *testing.T) {
+			expectedResponse := httputils.ExpectedResponse{
+				Request: httputils.Request{
+					Path: "/otel",
+					// envoy will not log this request without the header x-envoy-logged
+				},
+				Response: httputils.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			// make sure listener is ready
+			httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+			runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 0)
+		})
+	},
+}
+
+var OpenTelemetryTestJSON = suite.ConformanceTest{
+	ShortName:   "OpenTelemetryAccessLogJSON",
+	Description: "Make sure OpenTelemetry JSON access log is working with custom JSON attributes",
+	Manifests:   []string{"testdata/accesslog-otel-json.yaml"},
+	Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
+		labels := map[string]string{
+			"k8s_namespace_name": "envoy-gateway-system",
+			"exporter":           "OTLP",
+		}
+
+		ns := "gateway-conformance-infra"
+		routeNN := types.NamespacedName{Name: "accesslog-otel", Namespace: ns}
+		gwNN := types.NamespacedName{Name: "accesslog-gtw", Namespace: ns}
+		gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
+
+		t.Run("Positive", func(t *testing.T) {
+			expectedResponse := httputils.ExpectedResponse{
+				Request: httputils.Request{
+					Path: "/otel",
+					Headers: map[string]string{
+						"x-envoy-logged": "1",
+					},
+				},
+				Response: httputils.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			// make sure listener is ready
+			httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+			runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 1)
+		})
+
+		t.Run("Negative", func(t *testing.T) {
+			expectedResponse := httputils.ExpectedResponse{
+				Request: httputils.Request{
+					Path: "/otel",
+					// envoy will not log this request without the header x-envoy-logged
+				},
+				Response: httputils.Response{
+					StatusCode: 200,
+				},
+				Namespace: ns,
+			}
+			// make sure listener is ready
+			httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse)
+
+			runLogTest(t, suite, gwAddr, expectedResponse, labels, "", 0)
+		})
+	},
+}
+
 var ALSTest = suite.ConformanceTest{
 	ShortName:   "ALS",
 	Description: "Make sure ALS access log is working",