Repository: GoogleCloudPlatform/pgadapter
Pull request #1626 (2024-04-17 14:47:22)

feat: log info to stdout and warnings and errors to stderr
__NOTE__: This changes the default logging behavior of PGAdapter.

To change back to the previous logging behavior, start PGAdapter with the `-legacy_logging` command line argument:

```bash
java -jar pgadapter.jar -p my-project -i my-instance -legacy_logging
```

Automatically sets up logging in PGAdapter in a more logical way than the default `java.util.logging` configuration:
1. Log records with level INFO or lower are written to `stdout`.
2. Log records with level WARNING or higher are written to `stderr`.
3. The log format contains more information, and timestamps are formatted in a way that is easier to extract and sort.



Patch:
diff --git a/README.md b/README.md
index aa5c66cd..dee9aca6 100644
--- a/README.md
+++ b/README.md
@@ -355,8 +355,30 @@ PGAdapter has the following known limitations at this moment:
 
 ## Logging
 
-PGAdapter uses `java.util.logging` for logging. Create a `logging.properties` file to configure
-logging messages. See the following example for an example to get fine-grained logging.
+PGAdapter uses `java.util.logging` for logging.
+
+### Default Logging
+
+PGAdapter by default configures `java.util.logging` to do the following:
+1. Log messages of level WARNING and higher are logged to `stderr`.
+2. Log messages of level INFO are logged to `stdout`.
+3. Log messages of higher levels than INFO are not logged.
+
+You can supply your own logging configuration with the `-Djava.util.logging.config.file`
+System property. See the next section for an example.
+
+The default log configuration described in this section was introduced in version 0.33.0 of
+PGAdapter. Prior to that, PGAdapter used the default `java.util.logging` configuration, which
+logs everything to `stderr`.
+
+You can disable the default PGAdapter log configuration and go back to the standard
+`java.util.logging` configuration by starting PGAdapter with the command line argument
+`-legacy_logging`.
+
+### Custom Logging Configuration
+
+Create a `logging.properties` file to configure logging messages.
+See the following example for an example to get fine-grained logging.
 
 ```
 handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
diff --git a/benchmarks/latency-comparison/golang/go.mod b/benchmarks/latency-comparison/golang/go.mod
index 73fcf71f..97a266c0 100644
--- a/benchmarks/latency-comparison/golang/go.mod
+++ b/benchmarks/latency-comparison/golang/go.mod
@@ -61,10 +61,10 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	go.opencensus.io v0.24.0 // indirect
-	golang.org/x/crypto v0.20.0 // indirect
-	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/crypto v0.21.0 // indirect
+	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/sync v0.3.0 // indirect
-	golang.org/x/sys v0.17.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/benchmarks/latency-comparison/golang/go.sum b/benchmarks/latency-comparison/golang/go.sum
index c3862c59..6c759d78 100644
--- a/benchmarks/latency-comparison/golang/go.sum
+++ b/benchmarks/latency-comparison/golang/go.sum
@@ -821,8 +821,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
-golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -896,8 +896,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -995,8 +995,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
diff --git a/benchmarks/latency-comparison/golang/runners/client_library_runner.go b/benchmarks/latency-comparison/golang/runners/client_library_runner.go
index 3acf11eb..ad9c8cfd 100644
--- a/benchmarks/latency-comparison/golang/runners/client_library_runner.go
+++ b/benchmarks/latency-comparison/golang/runners/client_library_runner.go
@@ -142,7 +142,7 @@ func printProgress(finished *atomic.Bool, ops *atomic.Int64, totalOps int) {
 	go func() {
 		for !finished.Load() {
 			time.Sleep(time.Second)
-			fmt.Printf("\r%d/%d", ops.Load(), totalOps)
+			fmt.Printf("\r%d/%d        ", ops.Load(), totalOps)
 		}
 	}()
 }
diff --git a/benchmarks/latency-comparison/golang/runners/pgx_runner.go b/benchmarks/latency-comparison/golang/runners/pgx_runner.go
index 4ed1f003..9319369a 100644
--- a/benchmarks/latency-comparison/golang/runners/pgx_runner.go
+++ b/benchmarks/latency-comparison/golang/runners/pgx_runner.go
@@ -31,7 +31,7 @@ func RunPgx(database, sql string, readWrite bool, numOperations, numClients, wai
 	// Connect to Cloud Spanner through PGAdapter.
 	var connString string
 	if useUnixSocket {
-		connString = fmt.Sprintf("host=%s port=%d database=%s", host, port, url.QueryEscape(database))
+		connString = fmt.Sprintf("host=%s port=%d database=%s", host, port, database)
 	} else {
 		connString = fmt.Sprintf("postgres://uid:pwd@%s:%d/%s?sslmode=disable", host, port, url.QueryEscape(database))
 	}
diff --git a/benchmarks/latency-comparison/golang/runners/pgx_v4_runner.go b/benchmarks/latency-comparison/golang/runners/pgx_v4_runner.go
index be07c942..6fa8d308 100644
--- a/benchmarks/latency-comparison/golang/runners/pgx_v4_runner.go
+++ b/benchmarks/latency-comparison/golang/runners/pgx_v4_runner.go
@@ -31,7 +31,7 @@ func RunPgxV4(database, sql string, readWrite bool, numOperations, numClients, w
 	// Connect to Cloud Spanner through PGAdapter.
 	var connString string
 	if useUnixSocket {
-		connString = fmt.Sprintf("host=%s port=%d database=%s", host, port, url.QueryEscape(database))
+		connString = fmt.Sprintf("host=%s port=%d database=%s", host, port, database)
 	} else {
 		connString = fmt.Sprintf("postgres://uid:pwd@%s:%d/%s?sslmode=disable", host, port, url.QueryEscape(database))
 	}
diff --git a/benchmarks/latency-comparison/java/pom.xml b/benchmarks/latency-comparison/java/pom.xml
index 619de3e0..f78b3f63 100644
--- a/benchmarks/latency-comparison/java/pom.xml
+++ b/benchmarks/latency-comparison/java/pom.xml
@@ -35,7 +35,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-shared-config</artifactId>
-    <version>1.7.6</version>
+    <version>1.7.7</version>
   </parent>
 
   <dependencyManagement>
@@ -92,7 +92,7 @@
     <dependency>
       <groupId>commons-cli</groupId>
       <artifactId>commons-cli</artifactId>
-      <version>1.6.0</version>
+      <version>1.7.0</version>
     </dependency>
 
     <dependency>
diff --git a/logging.properties b/logging.properties
index 8cc5a245..88f78eaf 100644
--- a/logging.properties
+++ b/logging.properties
@@ -1,4 +1,4 @@
-handlers=java.util.logging.ConsoleHandler,java.util.logging.FileHandler
+handlers=com.google.cloud.spanner.pgadapter.logging.StdoutHandler,com.google.cloud.spanner.pgadapter.logging.StderrHandler,java.util.logging.FileHandler
 .level=INFO
 com.google.cloud.spanner.pgadapter.statements.level=FINEST
 com.google.cloud.spanner.pgadapter.wireprotocol.level=FINEST
diff --git a/pom.xml b/pom.xml
index fc79fb45..600a0a20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-shared-config</artifactId>
-    <version>1.7.6</version>
+    <version>1.7.7</version>
   </parent>
   <scm>
     <connection>scm:git:git@github.com:GoogleCloudPlatform/pgadapter.git</connection>
@@ -185,7 +185,7 @@
     <dependency>
       <groupId>commons-cli</groupId>
       <artifactId>commons-cli</artifactId>
-      <version>1.6.0</version>
+      <version>1.7.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
@@ -195,7 +195,7 @@
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-text</artifactId>
-      <version>1.11.0</version>
+      <version>1.12.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.maven</groupId>
@@ -205,7 +205,7 @@
     <dependency>
       <groupId>org.bouncycastle</groupId>
       <artifactId>bcpkix-jdk18on</artifactId>
-      <version>1.78</version>
+      <version>1.78.1</version>
     </dependency>
 
     <!-- Test dependencies -->
diff --git a/samples/golang/pgx/go.mod b/samples/golang/pgx/go.mod
index 11fa93a9..18f6646e 100644
--- a/samples/golang/pgx/go.mod
+++ b/samples/golang/pgx/go.mod
@@ -39,12 +39,12 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/testcontainers/testcontainers-go v0.22.0 // indirect
-	golang.org/x/crypto v0.17.0 // indirect
+	golang.org/x/crypto v0.21.0 // indirect
 	golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
 	golang.org/x/mod v0.11.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/oauth2 v0.11.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/tools v0.10.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
diff --git a/samples/golang/pgx/go.sum b/samples/golang/pgx/go.sum
index 537e1503..a87c0074 100644
--- a/samples/golang/pgx/go.sum
+++ b/samples/golang/pgx/go.sum
@@ -87,8 +87,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
 golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -100,8 +100,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
 golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -113,8 +113,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
diff --git a/samples/java/cloud-run/pom.xml b/samples/java/cloud-run/pom.xml
index 747b3fc5..80293d3d 100644
--- a/samples/java/cloud-run/pom.xml
+++ b/samples/java/cloud-run/pom.xml
@@ -36,7 +36,7 @@
       <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-dependencies</artifactId>
-        <version>3.2.4</version>
+        <version>3.2.5</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
diff --git a/samples/java/hibernate/pom.xml b/samples/java/hibernate/pom.xml
index 6083af67..741e904f 100644
--- a/samples/java/hibernate/pom.xml
+++ b/samples/java/hibernate/pom.xml
@@ -57,7 +57,7 @@
     <dependency>
       <groupId>io.hypersistence</groupId>
       <artifactId>hypersistence-utils-hibernate-63</artifactId>
-      <version>3.7.3</version>
+      <version>3.7.4</version>
     </dependency>
     <!-- Add testcontainers, so we can run a Docker container with PGAdapter+Spanner Emulator -->
     <dependency>
diff --git a/samples/java/liquibase/pom.xml b/samples/java/liquibase/pom.xml
index c0b5a750..129115fc 100644
--- a/samples/java/liquibase/pom.xml
+++ b/samples/java/liquibase/pom.xml
@@ -37,7 +37,7 @@
   <parent>
     <groupId>com.google.cloud</groupId>
     <artifactId>google-cloud-shared-config</artifactId>
-    <version>1.7.6</version>
+    <version>1.7.7</version>
   </parent>
   <scm>
     <connection>scm:git:git@github.com:GoogleCloudPlatform/pgadapter.git</connection>
diff --git a/samples/java/spring-data-jpa/pom.xml b/samples/java/spring-data-jpa/pom.xml
index 5d209d6c..c51b5490 100644
--- a/samples/java/spring-data-jpa/pom.xml
+++ b/samples/java/spring-data-jpa/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
-    <version>3.2.4</version>
+    <version>3.2.5</version>
     <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.google.cloud.spanner</groupId>
@@ -54,7 +54,7 @@
     <dependency>
       <groupId>io.hypersistence</groupId>
       <artifactId>hypersistence-utils-hibernate-63</artifactId>
-      <version>3.7.3</version>
+      <version>3.7.4</version>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
diff --git a/samples/python/psycopg3/requirements.txt b/samples/python/psycopg3/requirements.txt
index 553878d3..a8cf7314 100644
--- a/samples/python/psycopg3/requirements.txt
+++ b/samples/python/psycopg3/requirements.txt
@@ -1,4 +1,4 @@
 psycopg~=3.1.18
-testcontainers~=4.3.3
+testcontainers~=4.4.0
 google~=3.0.0
 google.auth~=2.29.0
\ No newline at end of file
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/Server.java b/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
index 56b5d37c..edf41253 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
@@ -14,20 +14,25 @@
 
 package com.google.cloud.spanner.pgadapter;
 
+import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME;
+
 import com.google.auth.Credentials;
 import com.google.cloud.opentelemetry.metric.GoogleCloudMetricExporter;
 import com.google.cloud.opentelemetry.metric.MetricConfiguration;
 import com.google.cloud.opentelemetry.trace.TraceConfiguration;
 import com.google.cloud.opentelemetry.trace.TraceExporter;
+import com.google.cloud.spanner.pgadapter.logging.DefaultLogConfiguration;
 import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.cloudtrace.v2.AttributeValue;
 import com.google.devtools.cloudtrace.v2.TruncatableString;
 import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.common.Attributes;
 import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
 import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
 import io.opentelemetry.sdk.metrics.export.MetricExporter;
 import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
+import io.opentelemetry.sdk.resources.Resource;
 import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
 import io.opentelemetry.sdk.trace.export.SpanExporter;
 import io.opentelemetry.sdk.trace.samplers.Sampler;
@@ -38,6 +43,7 @@ import java.io.PrintStream;
 import java.time.Duration;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
 import org.apache.maven.model.Model;
 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 
@@ -50,6 +56,7 @@ public class Server {
    */
   public static void main(String[] args) {
     try {
+      DefaultLogConfiguration.configureLogging(args);
       OptionsMetadata optionsMetadata = extractMetadata(args, System.out);
       OpenTelemetry openTelemetry = setupOpenTelemetry(optionsMetadata);
       ProxyServer server = new ProxyServer(optionsMetadata, openTelemetry);
@@ -76,8 +83,9 @@ public class Server {
       System.setProperty("otel.logs.exporter", "none");
     }
     if (getOpenTelemetrySetting("otel.service.name") == null) {
-      System.setProperty("otel.service.name", "pgadapter");
+      System.setProperty("otel.service.name", "pgadapter-" + ThreadLocalRandom.current().nextInt());
     }
+    String serviceName = Objects.requireNonNull(getOpenTelemetrySetting("otel.service.name"));
 
     try {
       String projectId = optionsMetadata.getTelemetryProjectId();
@@ -95,14 +103,9 @@ public class Server {
         }
         builder.setFixedAttributes(
             ImmutableMap.of(
-                "service.name",
+                SERVICE_NAME.getKey(),
                 AttributeValue.newBuilder()
-                    .setStringValue(
-                        TruncatableString.newBuilder()
-                            .setValue(
-                                Objects.requireNonNull(
-                                    getOpenTelemetrySetting("otel.service.name")))
-                            .build())
+                    .setStringValue(TruncatableString.newBuilder().setValue(serviceName).build())
                     .build()));
         TraceConfiguration configuration = builder.build();
         SpanExporter traceExporter = TraceExporter.createWithConfiguration(configuration);
@@ -131,8 +134,10 @@ public class Server {
                     .build());
         openTelemetryBuilder.addMeterProviderCustomizer(
             (sdkMeterProviderBuilder, configProperties) ->
-                sdkMeterProviderBuilder.registerMetricReader(
-                    PeriodicMetricReader.builder(cloudMonitoringExporter).build()));
+                sdkMeterProviderBuilder
+                    .addResource(Resource.create(Attributes.of(SERVICE_NAME, serviceName)))
+                    .registerMetricReader(
+                        PeriodicMetricReader.builder(cloudMonitoringExporter).build()));
       }
       return openTelemetryBuilder.build().getOpenTelemetrySdk();
     } catch (IOException exception) {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfiguration.java b/src/main/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfiguration.java
new file mode 100644
index 00000000..14c31cdc
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfiguration.java
@@ -0,0 +1,44 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.logging;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.logging.LogManager;
+
+/** Util class for automatically setting up default logging. */
+public class DefaultLogConfiguration {
+
+  public static void configureLogging(String[] args) throws IOException {
+    if (shouldUseDefaultLogging(args)) {
+      LogManager.getLogManager()
+          .readConfiguration(
+              DefaultLogConfiguration.class
+                  .getClassLoader()
+                  .getResourceAsStream("default-logging.properties"));
+    }
+  }
+
+  static boolean shouldUseDefaultLogging(String[] args) {
+    return System.getProperty("java.util.logging.config.file") == null
+        && System.getProperty("java.util.logging.config.class") == null
+        && (args == null
+            || Arrays.stream(args)
+                .noneMatch(
+                    arg -> arg.equals("--enable_legacy_logging") || arg.equals("-legacy_logging")));
+  }
+
+  private DefaultLogConfiguration() {}
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/logging/StderrHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/logging/StderrHandler.java
new file mode 100644
index 00000000..bc530c42
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/logging/StderrHandler.java
@@ -0,0 +1,40 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.logging;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
+
+/** Log handler that redirects all output to stderr as long as the log level is higher than INFO. */
+public class StderrHandler extends StreamHandler {
+  public StderrHandler() {
+    super(System.err, new SimpleFormatter());
+  }
+
+  @Override
+  public void publish(LogRecord record) {
+    if (record.getLevel().intValue() > Level.INFO.intValue()) {
+      super.publish(record);
+      flush();
+    }
+  }
+
+  @Override
+  public void close() {
+    flush();
+  }
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/logging/StdoutHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/logging/StdoutHandler.java
new file mode 100644
index 00000000..b2e3425c
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/logging/StdoutHandler.java
@@ -0,0 +1,40 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.logging;
+
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.SimpleFormatter;
+import java.util.logging.StreamHandler;
+
+/** Log handler that redirects all output to stdout as long as the log level is INFO or lower. */
+public class StdoutHandler extends StreamHandler {
+  public StdoutHandler() {
+    super(System.out, new SimpleFormatter());
+  }
+
+  @Override
+  public void publish(LogRecord record) {
+    if (record.getLevel().intValue() <= Level.INFO.intValue()) {
+      super.publish(record);
+      flush();
+    }
+  }
+
+  @Override
+  public void close() {
+    flush();
+  }
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
index 16907617..6ded913a 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
@@ -589,6 +589,7 @@ public class OptionsMetadata {
   private static final String OPTION_SKIP_INTERNAL_DEBUG_MODE_WARNING =
       "skip_internal_debug_warning";
   private static final String OPTION_DEBUG_MODE = "debug";
+  private static final String OPTION_LEGACY_LOGGING = "legacy_logging";
 
   private final Map<String, String> environment;
   private final String osName;
@@ -1292,6 +1293,12 @@ public class OptionsMetadata {
             + "You most probably do not want to turn internal debug mode on. It is only intended for\n"
             + "internal test cases in PGAdapter that need to verify that it receives the correct \n"
             + "wire-protocol messages.");
+    options.addOption(
+        OPTION_LEGACY_LOGGING,
+        "enable_legacy_logging",
+        false,
+        "Enables legacy logging using the default java.util.logging configuration.\n"
+            + "This sends all log output to stderr.");
     CommandLineParser parser = new DefaultParser();
     HelpFormatter help = new HelpFormatter();
     help.setWidth(120);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
index f9f091b9..1c5709d6 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
@@ -945,6 +945,8 @@ public class BackendConnection {
     SpanBuilder builder =
         tracer.spanBuilder(name).setAttribute("pgadapter.connection_id", connectionId);
     if (statement != null) {
+      // Ignore deprecation for now, as there is no alternative offered (yet?).
+      //noinspection deprecation
       builder.setAttribute(SemanticAttributes.DB_STATEMENT, statement.getSql());
     }
     if (currentTransactionId != null) {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
index 29a4bda9..3c74fce1 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
@@ -66,7 +66,9 @@ public class ExtendedQueryProtocolHandler {
                 .getServer()
                 .getTracer(ConnectionHandler.class.getName(), getVersion()),
             connectionHandler.getServer().getMetrics(),
-            createMetricAttributes(connectionHandler.getDatabaseId()),
+            createMetricAttributes(
+                connectionHandler.getDatabaseId(),
+                connectionHandler.getTraceConnectionId().toString()),
             connectionHandler.getTraceConnectionId().toString(),
             connectionHandler::closeAllPortals,
             connectionHandler.getDatabaseId(),
@@ -95,8 +97,9 @@ public class ExtendedQueryProtocolHandler {
   }
 
   @VisibleForTesting
-  static Attributes createMetricAttributes(DatabaseId databaseId) {
+  static Attributes createMetricAttributes(DatabaseId databaseId, String connectionId) {
     AttributesBuilder attributesBuilder = Attributes.builder();
+    attributesBuilder.put("pgadapter.connection_id", connectionId);
     attributesBuilder.put("database", databaseId.getDatabase());
     attributesBuilder.put("instance_id", databaseId.getInstanceId().getInstance());
     attributesBuilder.put("project_id", databaseId.getInstanceId().getProject());
@@ -138,6 +141,8 @@ public class ExtendedQueryProtocolHandler {
    * received.
    */
   public void buffer(AbstractQueryProtocolMessage message) {
+    // Ignore deprecation for now, as there is no alternative offered (yet?).
+    //noinspection deprecation
     addEvent(
         "Received message: '" + message.getIdentifier() + "'",
         Attributes.of(SemanticAttributes.DB_STATEMENT, message.getSql()));
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleQueryStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleQueryStatement.java
index c4d8eb9c..c0b692bf 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleQueryStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleQueryStatement.java
@@ -137,6 +137,8 @@ public class SimpleQueryStatement {
   static ParsedStatement translatePotentialMetadataCommand(
       ParsedStatement parsedStatement, ConnectionHandler connectionHandler) {
     Tracer tracer = connectionHandler.getExtendedQueryProtocolHandler().getTracer();
+    // Ignore deprecation for now, as there is no alternative offered (yet?).
+    //noinspection deprecation
     Span span =
         tracer
             .spanBuilder("translatePotentialMetadataCommand")
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
index 71e362a8..9a2042f3 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
@@ -318,6 +318,8 @@ public abstract class ControlMessage extends WireMessage {
   SendResultSetState sendResultSet(
       IntermediateStatement describedResult, QueryMode mode, long maxRows) throws Exception {
     Tracer tracer = connection.getExtendedQueryProtocolHandler().getTracer();
+    // Ignore deprecation for now, as there is no alternative offered (yet?).
+    //noinspection deprecation
     Span span =
         tracer
             .spanBuilder("send_result_set")
@@ -403,20 +405,19 @@ public abstract class ControlMessage extends WireMessage {
         describedResult instanceof CopyToStatement && ((CopyToStatement) describedResult).isBinary()
             ? new CountDownLatch(1)
             : new CountDownLatch(0);
-    for (int i = 0; i < partitions.size(); i++) {
+    for (Partition partition : partitions) {
       futures.add(
           executorService.submit(
               context.wrap(
                   SendResultSetRunnable.forPartition(
                       describedResult,
                       batchReadOnlyTransaction,
-                      partitions.get(i),
+                      partition,
                       mode,
                       binaryCopyHeaderSentLatch))));
     }
     executorService.shutdown();
     try {
-      @SuppressWarnings("UnstableApiUsage")
       List<Long> rowCounts = Futures.allAsList(futures).get();
       long rowCount = rowCounts.stream().reduce(Long::sum).orElse(0L);
       logger.log(Level.INFO, String.format("Sent %d rows from partitioned query", rowCount));
diff --git a/src/main/resources/default-logging.properties b/src/main/resources/default-logging.properties
new file mode 100644
index 00000000..1cbe35f8
--- /dev/null
+++ b/src/main/resources/default-logging.properties
@@ -0,0 +1,5 @@
+handlers=com.google.cloud.spanner.pgadapter.logging.StdoutHandler,com.google.cloud.spanner.pgadapter.logging.StderrHandler
+.level=INFO
+io.grpc.internal.level = WARNING
+
+java.util.logging.SimpleFormatter.format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL] [%4$s] (%2$s): %5$s%6$s%n
diff --git a/wrappers/golang/go.mod b/wrappers/golang/go.mod
index 157c9809..f2056d64 100644
--- a/wrappers/golang/go.mod
+++ b/wrappers/golang/go.mod
@@ -38,8 +38,8 @@ require (
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
 	golang.org/x/mod v0.11.0 // indirect
-	golang.org/x/net v0.17.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/net v0.23.0 // indirect
+	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/tools v0.10.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
diff --git a/wrappers/golang/go.sum b/wrappers/golang/go.sum
index bec75657..eec16269 100644
--- a/wrappers/golang/go.sum
+++ b/wrappers/golang/go.sum
@@ -88,8 +88,8 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
 golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -101,12 +101,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=


Test patch:
diff --git a/src/test/golang/pgadapter_pgx5_tests/pgx.go b/src/test/golang/pgadapter_pgx5_tests/pgx.go
index df74318e..d943d098 100644
--- a/src/test/golang/pgadapter_pgx5_tests/pgx.go
+++ b/src/test/golang/pgadapter_pgx5_tests/pgx.go
@@ -713,6 +713,76 @@ func TestBatchExecutionError(connString string) *C.char {
 	return nil
 }
 
+//export TestDdlBatch
+func TestDdlBatch(connString string) *C.char {
+	ctx := context.Background()
+	conn, err := pgx.Connect(ctx, connString)
+	if err != nil {
+		return C.CString(err.Error())
+	}
+	defer conn.Close(ctx)
+
+	batch := &pgx.Batch{}
+	batch.Queue("CREATE SEQUENCE IF NOT EXISTS seq_merchants bit_reversed_positive")
+	batch.Queue("CREATE TABLE IF NOT EXISTS merchants (" +
+		"  merchant_id   varchar(36) DEFAULT spanner.generate_uuid() NOT NULL," +
+		"  seq_id bigint DEFAULT nextval('seq_merchants')," +
+		"  name  varchar(255) NOT NULL," +
+		"  created   timestamptz DEFAULT CURRENT_TIMESTAMP," +
+		"  created_by varchar(36)," +
+		"  modified  timestamptz DEFAULT CURRENT_TIMESTAMP," +
+		"  modified_by varchar(36)," +
+		"  PRIMARY KEY(merchant_id)" +
+		")")
+	batch.Queue("CREATE UNIQUE INDEX idx_uq_email ON users(email);")
+	br := conn.SendBatch(context.Background(), batch)
+	if _, err := br.Exec(); err != nil {
+		return C.CString(fmt.Sprintf("executing DDL batch returned error: %v", err.Error()))
+	}
+
+	return nil
+}
+
+//export TestDdlBatchInTransaction
+func TestDdlBatchInTransaction(connString string) *C.char {
+	ctx := context.Background()
+	conn, err := pgx.Connect(ctx, connString)
+	if err != nil {
+		return C.CString(err.Error())
+	}
+	defer conn.Close(ctx)
+
+	// Start a transaction and then try to execute a DDL batch.
+	tx, err := conn.Begin(ctx)
+
+	batch := &pgx.Batch{}
+	batch.Queue("CREATE SEQUENCE IF NOT EXISTS seq_merchants bit_reversed_positive")
+	batch.Queue("CREATE TABLE IF NOT EXISTS merchants (" +
+		"  merchant_id   varchar(36) DEFAULT spanner.generate_uuid() NOT NULL," +
+		"  seq_id bigint DEFAULT nextval('seq_merchants')," +
+		"  name  varchar(255) NOT NULL," +
+		"  created   timestamptz DEFAULT CURRENT_TIMESTAMP," +
+		"  created_by varchar(36)," +
+		"  modified  timestamptz DEFAULT CURRENT_TIMESTAMP," +
+		"  modified_by varchar(36)," +
+		"  PRIMARY KEY(merchant_id)" +
+		")")
+	batch.Queue("CREATE UNIQUE INDEX idx_uq_email ON users(email);")
+	br := conn.SendBatch(context.Background(), batch)
+	_, err = br.Exec()
+	if err == nil {
+		return C.CString("missing expected error for DDL batch in transaction")
+	}
+	// The batch execution should return an error indicating that DDL batches are not supported in transactions.
+	if g, w := err.Error(), "ERROR: DDL statements are not allowed in mixed batches or transactions. (SQLSTATE 25000)"; g != w {
+		return C.CString(fmt.Sprintf("error mismatch\n Got: %v\nWant: %v", g, w))
+	}
+
+	_ = tx.Rollback(ctx)
+
+	return nil
+}
+
 func insertBatch(batch *pgx.Batch, connString string, batchSize int) error {
 	sql := "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"
 	numeric := pgtype.Numeric{}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ITOpenTelemetryTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ITOpenTelemetryTest.java
index 9fdb2c63..ede12afc 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ITOpenTelemetryTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITOpenTelemetryTest.java
@@ -104,6 +104,8 @@ public class ITOpenTelemetryTest implements IntegrationTest {
             client.listTraces(
                 ListTracesRequest.newBuilder()
                     .setProjectId(testEnv.getProjectId())
+                    // Ignore deprecation for now, as there is no alternative offered (yet?).
+                    //noinspection deprecation
                     .setFilter(SemanticAttributes.DB_STATEMENT + ":\"" + sql + "\"")
                     .build());
         int size = Iterables.size(response.iterateAll());
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/Pgx5MockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/Pgx5MockServerTest.java
index b82099e6..efeb5139 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/Pgx5MockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/Pgx5MockServerTest.java
@@ -37,6 +37,7 @@ import com.google.protobuf.AbstractMessage;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.ListValue;
 import com.google.protobuf.Value;
+import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest;
 import com.google.spanner.v1.BeginTransactionRequest;
 import com.google.spanner.v1.CommitRequest;
 import com.google.spanner.v1.ExecuteBatchDmlRequest;
@@ -1009,6 +1010,31 @@ public class Pgx5MockServerTest extends AbstractMockServerTest {
     }
   }
 
+  @Test
+  public void testDdlBatch() {
+    addDdlResponseToSpannerAdmin();
+
+    String res = pgxTest.TestDdlBatch(createConnString());
+
+    assertNull(res);
+    assertEquals(1, mockDatabaseAdmin.getRequests().size());
+    UpdateDatabaseDdlRequest request =
+        (UpdateDatabaseDdlRequest) mockDatabaseAdmin.getRequests().get(0);
+    assertEquals(3, request.getStatementsCount());
+  }
+
+  @Test
+  public void testDdlBatchInTransaction() {
+    addDdlResponseToSpannerAdmin();
+
+    String res = pgxTest.TestDdlBatchInTransaction(createConnString());
+
+    assertNull(res);
+    // No DDL statements should be sent to Spanner, as the DDL batch was being sent in a
+    // transaction.
+    assertEquals(0, mockDatabaseAdmin.getRequests().size());
+  }
+
   @Test
   public void testInsertNullsAllDataTypes() {
     String sql =
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
index acc88b1f..3a3dc337 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
@@ -49,6 +49,10 @@ public interface PgxTest extends Library {
 
   String TestBatchExecutionError(GoString connString);
 
+  String TestDdlBatch(GoString connString);
+
+  String TestDdlBatchInTransaction(GoString connString);
+
   String TestWrongDialect(GoString connString);
 
   String TestCopyIn(GoString connString);
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfigurationTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfigurationTest.java
new file mode 100644
index 00000000..d85fff73
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/logging/DefaultLogConfigurationTest.java
@@ -0,0 +1,63 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.logging;
+
+import static com.google.cloud.spanner.pgadapter.logging.DefaultLogConfiguration.shouldUseDefaultLogging;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DefaultLogConfigurationTest {
+
+  @Test
+  public void testShouldUseDefaultLogging() {
+    assumeTrue(System.getProperty("java.util.logging.config.file") == null);
+    assumeTrue(System.getProperty("java.util.logging.config.class") == null);
+
+    assertTrue(shouldUseDefaultLogging(null));
+    assertTrue(shouldUseDefaultLogging(new String[0]));
+    assertTrue(shouldUseDefaultLogging(new String[] {"foo"}));
+
+    assertFalse(shouldUseDefaultLogging(new String[] {"-legacy_logging"}));
+    assertFalse(shouldUseDefaultLogging(new String[] {"--enable_legacy_logging"}));
+    runWithSystemProperty(
+        "java.util.logging.config.file",
+        "foo",
+        () -> assertFalse(shouldUseDefaultLogging(new String[0])));
+    runWithSystemProperty(
+        "java.util.logging.config.class",
+        "TestClass",
+        () -> assertFalse(shouldUseDefaultLogging(new String[0])));
+  }
+
+  void runWithSystemProperty(String key, String value, Runnable runnable) {
+    String original = System.getProperty(key);
+    try {
+      System.setProperty(key, value);
+      runnable.run();
+    } finally {
+      if (original == null) {
+        System.clearProperty(key);
+      } else {
+        System.setProperty(key, original);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
index d1809527..f56f53e2 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
@@ -85,7 +85,7 @@ public class BackendConnectionTest {
   private static final Runnable DO_NOTHING = () -> {};
   private static final DatabaseId DATABASE_ID = DatabaseId.of("p", "i", "d");
   private static final Attributes METRIC_ATTRIBUTES =
-      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID);
+      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID, "test-id");
 
   @Test
   public void testExtractDdlUpdateCounts() {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
index a5513ef5..9cac0270 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
@@ -97,7 +97,7 @@ public class StatementTest {
   private static final Runnable DO_NOTHING = () -> {};
   private static final DatabaseId DATABASE_ID = DatabaseId.of("p", "i", "d");
   private static final Attributes METRIC_ATTRIBUTES =
-      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID);
+      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID, "test-connection");
 
   private static ParsedStatement parse(String sql) {
     return PARSER.parse(Statement.of(sql));
@@ -521,7 +521,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,
@@ -629,7 +629,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,
@@ -688,7 +688,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,