diff --git a/changelog/24.0/24.0.0/summary.md b/changelog/24.0/24.0.0/summary.md
index bf7a89b9959..05ef0ba8b32 100644
--- a/changelog/24.0/24.0.0/summary.md
+++ b/changelog/24.0/24.0.0/summary.md
@@ -7,6 +7,8 @@
- **[New Support](#new-support)**
- [Window function pushdown for sharded keyspaces](#window-function-pushdown)
- **[Minor Changes](#minor-changes)**
+ - **[Logging](#minor-changes-logging)**
+ - [Structured Logging](#structured-logging)
- **[VTGate](#minor-changes-vtgate)**
- [New default for `--legacy-replication-lag-algorithm` flag](#vtgate-new-default-legacy-replication-lag-algorithm)
- [New "session" mode for `--vtgate-balancer-mode` flag](#vtgate-session-balancer-mode)
@@ -36,6 +38,39 @@ For examples and more details, see the [documentation](https://vitess.io/docs/24
## Minor Changes
+### Logging
+
+#### Structured Logging
+
+Opt-in structured JSON logging has been added. By default, Vitess will continue to use unstructured logging through `glog`. To opt-in to structured logging, use these new flags:
+
+- `--structured-logging`: Enables structured logging.
+- `--structured-logging-level`: Minimum log level: trace, debug, info, warn, error, fatal, panic, disabled (default: info)
+- `--structured-logging-pretty`: Enable pretty, human-readable output (default: false)
+- `--structured-logging-file`: Log to a file instead of stdout
+
+The existing `--log-rotate-max-size` flag controls the max size of the log file before rotation. Otherwise, the new structured logging flags are mutually exclusive with the old `glog` flags.
+
+Example JSON output:
+
+```console
+$ vttablet --structured-logging
+
+{"level":"info","caller":"/Users/mhamza/dev/vitess/go/vt/servenv/servenv_unix.go:57","time":"2026-01-06T09:22:36-05:00","message":"Version: 24.0.0-SNAPSHOT (Git revision branch '') built on by @ using go1.25.5 darwin/arm64"}
+{"level":"fatal","caller":"/Users/mhamza/dev/vitess/go/vt/topo/server.go:257","time":"2026-01-06T09:22:36-05:00","message":"topo-global-server-address must be configured"}
+```
+
+Example pretty output:
+
+```console
+$ vttablet --structured-logging --structured-logging-pretty
+
+2026-01-06T09:23:13-05:00 INF go/vt/servenv/servenv_unix.go:57 > Version: 24.0.0-SNAPSHOT (Git revision branch '') built on by @ using go1.25.5 darwin/arm64
+2026-01-06T09:23:13-05:00 FTL go/vt/topo/server.go:257 > topo-global-server-address must be configured
+```
+
+In v25, structured logging will become the default and `glog` and its flags will be deprecated. In v26, `glog` will be removed.
+
### VTGate
#### New default for `--legacy-replication-lag-algorithm` flag
diff --git a/go.mod b/go.mod
index 0cee85073b1..1a279536a07 100644
--- a/go.mod
+++ b/go.mod
@@ -102,6 +102,7 @@ require (
github.com/kr/pretty v0.3.1
github.com/kr/text v0.2.0
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249
+ github.com/rs/zerolog v1.34.0
github.com/shirou/gopsutil/v4 v4.25.8
github.com/spf13/afero v1.15.0
github.com/spf13/jwalterweatherman v1.1.0
@@ -110,6 +111,7 @@ require (
golang.org/x/exp v0.0.0-20250911091902-df9299821621
golang.org/x/sync v0.18.0
gonum.org/v1/gonum v0.16.0
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1
modernc.org/sqlite v1.39.0
)
diff --git a/go.sum b/go.sum
index 749f9c722ad..58f8b37de18 100644
--- a/go.sum
+++ b/go.sum
@@ -180,6 +180,7 @@ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
+github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
@@ -261,6 +262,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -410,6 +412,7 @@ github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDh
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
@@ -417,6 +420,8 @@ github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL
github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
@@ -544,6 +549,9 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
+github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
@@ -819,8 +827,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -906,6 +916,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/go/flags/endtoend/mysqlctl.txt b/go/flags/endtoend/mysqlctl.txt
index e252218fd95..952d5b7bc3b 100644
--- a/go/flags/endtoend/mysqlctl.txt
+++ b/go/flags/endtoend/mysqlctl.txt
@@ -89,6 +89,10 @@ Flags:
--service-map strings comma separated list of services to enable (or disable if prefixed with '-') Example: grpc-queryservice
--socket-file string Local unix socket file to listen on
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid.
--tablet-uid uint32 Tablet UID. (default 41983)
diff --git a/go/flags/endtoend/mysqlctld.txt b/go/flags/endtoend/mysqlctld.txt
index d5c977ce411..1ffc1d0763c 100644
--- a/go/flags/endtoend/mysqlctld.txt
+++ b/go/flags/endtoend/mysqlctld.txt
@@ -119,6 +119,10 @@ Flags:
--shutdown-wait-time duration How long to wait for mysqld shutdown (default 5m0s)
--socket-file string Local unix socket file to listen on
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid.
--tablet-uid uint32 Tablet UID (default 41983)
diff --git a/go/flags/endtoend/topo2topo.txt b/go/flags/endtoend/topo2topo.txt
index 1586e4cae12..5cbd8357df6 100644
--- a/go/flags/endtoend/topo2topo.txt
+++ b/go/flags/endtoend/topo2topo.txt
@@ -37,6 +37,10 @@ Flags:
--purge-logs-interval duration how often try to remove old logs (default 1h0m0s)
--security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--to-implementation string topology implementation to copy data to
--to-root string topology server root to copy data to
--to-server string topology server address to copy data to
diff --git a/go/flags/endtoend/vtaclcheck.txt b/go/flags/endtoend/vtaclcheck.txt
index 9253aec9b01..9c88e5d803a 100644
--- a/go/flags/endtoend/vtaclcheck.txt
+++ b/go/flags/endtoend/vtaclcheck.txt
@@ -26,6 +26,10 @@ Flags:
--security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
--static-auth-file string The path of the auth_server_static JSON file to check
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--v Level log level for V logs
-v, --version print binary version
--vmodule vModuleFlag comma-separated list of pattern=N settings for file-filtered logging
diff --git a/go/flags/endtoend/vtbackup.txt b/go/flags/endtoend/vtbackup.txt
index 87bb94fd43c..781efaa1dea 100644
--- a/go/flags/endtoend/vtbackup.txt
+++ b/go/flags/endtoend/vtbackup.txt
@@ -226,6 +226,10 @@ Flags:
--stats-drop-variables string Variables to be dropped from the list of exported variables.
--stats-emit-period duration Interval between emitting stats to all registered backends (default 1m0s)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--tablet-manager-grpc-ca string the server ca to use to validate servers when connecting
--tablet-manager-grpc-cert string the cert to use to connect
--tablet-manager-grpc-concurrency int concurrency to use to talk to a vttablet server for performance-sensitive RPCs (like ExecuteFetchAs{Dba,App}, CheckThrottler and FullStatus) (default 8)
diff --git a/go/flags/endtoend/vtbench.txt b/go/flags/endtoend/vtbench.txt
index f1449e468e1..e399f078167 100644
--- a/go/flags/endtoend/vtbench.txt
+++ b/go/flags/endtoend/vtbench.txt
@@ -92,6 +92,10 @@ Flags:
--sql-max-length-errors int truncate queries in error logs to the given length (default unlimited)
--sql-max-length-ui int truncate queries in debug UIs to the given length (default 512) (default 512)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--tablet-grpc-ca string the server ca to use to validate servers when connecting
--tablet-grpc-cert string the cert to use to connect
--tablet-grpc-crl string the server crl to use to validate server certificates when connecting
diff --git a/go/flags/endtoend/vtclient.txt b/go/flags/endtoend/vtclient.txt
index b79139b1aad..982aee55abc 100644
--- a/go/flags/endtoend/vtclient.txt
+++ b/go/flags/endtoend/vtclient.txt
@@ -56,6 +56,10 @@ Flags:
--server string vtgate server to connect to
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
--streaming use a streaming query
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--target string keyspace:shard@tablet_type
--timeout duration timeout for queries (default 30s)
--tracer string tracing service to use (default "noop")
diff --git a/go/flags/endtoend/vtcombo.txt b/go/flags/endtoend/vtcombo.txt
index edfb1058bf4..01f2a8b54a3 100644
--- a/go/flags/endtoend/vtcombo.txt
+++ b/go/flags/endtoend/vtcombo.txt
@@ -350,6 +350,10 @@ Flags:
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
--stream-buffer-size int the number of bytes sent from vtgate for each stream call. It's recommended to keep this value in sync with vttablet's query-server-config-stream-buffer-size. (default 32768)
--stream-health-buffer-size uint max streaming health entries to buffer per streaming health client (default 20)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-gc-lifecycle string States for a DROP TABLE garbage collection cycle. Default is 'hold,purge,evac,drop', use any subset ('drop' implicitly always included) (default "hold,purge,evac,drop")
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid.
diff --git a/go/flags/endtoend/vtctlclient.txt b/go/flags/endtoend/vtctlclient.txt
index ac6b76fcf42..e56238616d2 100644
--- a/go/flags/endtoend/vtctlclient.txt
+++ b/go/flags/endtoend/vtctlclient.txt
@@ -37,6 +37,10 @@ Usage of vtctlclient:
--security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
--server string server to use for connection
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--tracer string tracing service to use (default "noop")
--tracing-enable-logging whether to enable logging in the tracing service
--tracing-sampling-rate float sampling rate for the probabilistic jaeger sampler (default 0.1)
diff --git a/go/flags/endtoend/vtctld.txt b/go/flags/endtoend/vtctld.txt
index 5f3706db4da..9f618c5d32d 100644
--- a/go/flags/endtoend/vtctld.txt
+++ b/go/flags/endtoend/vtctld.txt
@@ -136,6 +136,10 @@ Flags:
--stats-drop-variables string Variables to be dropped from the list of exported variables.
--stats-emit-period duration Interval between emitting stats to all registered backends (default 1m0s)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid.
--tablet-grpc-ca string the server ca to use to validate servers when connecting
diff --git a/go/flags/endtoend/vtctldclient.txt b/go/flags/endtoend/vtctldclient.txt
index 97214868b41..d4c3a1df3ab 100644
--- a/go/flags/endtoend/vtctldclient.txt
+++ b/go/flags/endtoend/vtctldclient.txt
@@ -145,6 +145,10 @@ Flags:
--security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
--server string server to use for the connection (required)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--tablet-manager-grpc-ca string the server ca to use to validate servers when connecting
--tablet-manager-grpc-cert string the cert to use to connect
--tablet-manager-grpc-concurrency int concurrency to use to talk to a vttablet server for performance-sensitive RPCs (like ExecuteFetchAs{Dba,App}, CheckThrottler and FullStatus) (default 8)
diff --git a/go/flags/endtoend/vtexplain.txt b/go/flags/endtoend/vtexplain.txt
index 5c2aeab0ffa..7be6cf5a9a6 100644
--- a/go/flags/endtoend/vtexplain.txt
+++ b/go/flags/endtoend/vtexplain.txt
@@ -76,6 +76,10 @@ Flags:
--sql-max-length-errors int truncate queries in error logs to the given length (default unlimited)
--sql-max-length-ui int truncate queries in debug UIs to the given length (default 512) (default 512)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--v Level log level for V logs
-v, --version print binary version
--vmodule vModuleFlag comma-separated list of pattern=N settings for file-filtered logging
diff --git a/go/flags/endtoend/vtgate.txt b/go/flags/endtoend/vtgate.txt
index 2e2cc5b5cd6..439effda2a3 100644
--- a/go/flags/endtoend/vtgate.txt
+++ b/go/flags/endtoend/vtgate.txt
@@ -204,6 +204,10 @@ Flags:
--statsd-sample-rate float Sample rate for statsd metrics (default 1)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
--stream-buffer-size int the number of bytes sent from vtgate for each stream call. It's recommended to keep this value in sync with vttablet's query-server-config-stream-buffer-size. (default 32768)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-filter-tags StringMap Specifies a comma-separated list of tablet tags (as key:value pairs) to filter the tablets to watch.
--tablet-filters strings Specifies a comma-separated list of 'keyspace|shard_name or keyrange' values to filter the tablets to watch.
diff --git a/go/flags/endtoend/vtgateclienttest.txt b/go/flags/endtoend/vtgateclienttest.txt
index 1e7c624939e..60bb4aa6a65 100644
--- a/go/flags/endtoend/vtgateclienttest.txt
+++ b/go/flags/endtoend/vtgateclienttest.txt
@@ -65,6 +65,10 @@ Flags:
--security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
--service-map strings comma separated list of services to enable (or disable if prefixed with '-') Example: grpc-queryservice
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--v Level log level for V logs
-v, --version print binary version
diff --git a/go/flags/endtoend/vtorc.txt b/go/flags/endtoend/vtorc.txt
index fd125a2f878..cb3f00c7192 100644
--- a/go/flags/endtoend/vtorc.txt
+++ b/go/flags/endtoend/vtorc.txt
@@ -82,6 +82,10 @@ Flags:
--stats-drop-variables string Variables to be dropped from the list of exported variables.
--stats-emit-period duration Interval between emitting stats to all registered backends (default 1m0s)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-manager-grpc-ca string the server ca to use to validate servers when connecting
--tablet-manager-grpc-cert string the cert to use to connect
diff --git a/go/flags/endtoend/vttablet.txt b/go/flags/endtoend/vttablet.txt
index d9a27b62ea6..924b4795158 100644
--- a/go/flags/endtoend/vttablet.txt
+++ b/go/flags/endtoend/vttablet.txt
@@ -349,6 +349,10 @@ Flags:
--statsd-sample-rate float Sample rate for statsd metrics (default 1)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
--stream-health-buffer-size uint max streaming health entries to buffer per streaming health client (default 20)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-acl-config string path to table access checker config file; send SIGHUP to reload this file
--table-acl-config-reload-interval duration Ticker to reload ACLs. Duration flag, format e.g.: 30s. Default: do not reload
--table-gc-lifecycle string States for a DROP TABLE garbage collection cycle. Default is 'hold,purge,evac,drop', use any subset ('drop' implicitly always included) (default "hold,purge,evac,drop")
diff --git a/go/flags/endtoend/vttestserver.txt b/go/flags/endtoend/vttestserver.txt
index d41a6b2c2bc..f685db4e3ca 100644
--- a/go/flags/endtoend/vttestserver.txt
+++ b/go/flags/endtoend/vttestserver.txt
@@ -128,6 +128,10 @@ Flags:
--sql-max-length-errors int truncate queries in error logs to the given length (default unlimited)
--sql-max-length-ui int truncate queries in debug UIs to the given length (default 512) (default 512)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--table-refresh-interval int interval in milliseconds to refresh tables in status page with refreshRequired class
--tablet-dir string The directory within the vtdataroot to store vttablet/mysql files. Defaults to being generated by the tablet uid.
--tablet-hostname string The hostname to use for the tablet otherwise it will be derived from OS' hostname (default "localhost")
diff --git a/go/flags/endtoend/zk.txt b/go/flags/endtoend/zk.txt
index 91d0c053f63..0cdd1ab9975 100644
--- a/go/flags/endtoend/zk.txt
+++ b/go/flags/endtoend/zk.txt
@@ -30,12 +30,16 @@ Available Commands:
zip Store a zk tree in a zip archive.
Flags:
- -h, --help help for zk
- --keep-logs duration keep logs for this long (using ctime) (zero to keep forever)
- --keep-logs-by-mtime duration keep logs for this long (using mtime) (zero to keep forever)
- --log-rotate-max-size uint size in bytes at which logs are rotated (glog.MaxSize) (default 1887436800)
- --purge-logs-interval duration how often try to remove old logs (default 1h0m0s)
- --security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
- --server string server(s) to connect to
+ -h, --help help for zk
+ --keep-logs duration keep logs for this long (using ctime) (zero to keep forever)
+ --keep-logs-by-mtime duration keep logs for this long (using mtime) (zero to keep forever)
+ --log-rotate-max-size uint size in bytes at which logs are rotated (glog.MaxSize) (default 1887436800)
+ --purge-logs-interval duration how often try to remove old logs (default 1h0m0s)
+ --security-policy string the name of a registered security policy to use for controlling access to URLs - empty means allow all for anyone (built-in policies: deny-all, read-only)
+ --server string server(s) to connect to
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
Use "zk [command] --help" for more information about a command.
diff --git a/go/flags/endtoend/zkctl.txt b/go/flags/endtoend/zkctl.txt
index 178d552fd8f..b5d7230bfff 100644
--- a/go/flags/endtoend/zkctl.txt
+++ b/go/flags/endtoend/zkctl.txt
@@ -31,6 +31,10 @@ Flags:
--pprof-http enable pprof http endpoints
--purge-logs-interval duration how often try to remove old logs (default 1h0m0s)
--stderrthreshold severityFlag logs at or above this threshold go to stderr (default 1)
+ --structured-logging Enable structured logging
+ --structured-logging-file string Path to log file for structured logging with rotation (empty means stdout)
+ --structured-logging-level string Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled (default "info")
+ --structured-logging-pretty Enable pretty console output for structured logging
--v Level log level for V logs
-v, --version print binary version
--vmodule vModuleFlag comma-separated list of pattern=N settings for file-filtered logging
diff --git a/go/vt/log/glog.go b/go/vt/log/glog.go
new file mode 100644
index 00000000000..a79e40348cc
--- /dev/null
+++ b/go/vt/log/glog.go
@@ -0,0 +1,174 @@
+/*
+Copyright 2026 The Vitess Authors.
+
+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 log
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/golang/glog"
+ "github.com/rs/zerolog"
+)
+
+// glogWriter implements zerolog.LevelWriter and routes log output to glog. This allows zerolog to
+// build structured JSON log entries while still outputting through glog when structured logging
+// is disabled.
+type glogWriter struct{}
+
+// Write implements io.Writer. It delegates to WriteLevel with NoLevel.
+func (w *glogWriter) Write(p []byte) (int, error) {
+ return w.WriteLevel(zerolog.NoLevel, p)
+}
+
+// exitLevel is a custom level used to mimic glog's Exit, which is like Fatal except it does not
+// print a stack trace.
+const exitLevel = zerolog.Level(8)
+
+// WriteLevel implements zerolog.LevelWriter. It receives the complete JSON log line from zerolog
+// and routes it to the appropriate glog function based on log level.
+func (w *glogWriter) WriteLevel(level zerolog.Level, p []byte) (int, error) {
+ // Remove trailing newline to avoid double newlines since glog adds its own.
+ s := strings.TrimSuffix(string(p), "\n")
+
+ const depth = 4
+
+ switch level {
+ case zerolog.DebugLevel, zerolog.TraceLevel, zerolog.InfoLevel, zerolog.NoLevel:
+ glog.InfoDepth(depth, s)
+ case zerolog.WarnLevel:
+ glog.WarningDepth(depth, s)
+ case zerolog.ErrorLevel:
+ glog.ErrorDepth(depth, s)
+ case zerolog.FatalLevel:
+ glog.FatalDepth(depth, s)
+ case zerolog.PanicLevel:
+ glog.ErrorDepth(depth, s)
+ panic(s)
+ case exitLevel:
+ glog.ExitDepth(depth, s)
+ default:
+ glog.InfoDepth(depth, s)
+ }
+
+ return len(p), nil
+}
+
+// glogInfo logs at info level, routing to structured logging or glog based on mode.
+func glogInfo(depth int, args ...any) {
+ if structuredLogging {
+ SInfo(depth + 2).Msg(fmt.Sprint(args...))
+ } else {
+ glog.InfoDepth(depth+1, args...)
+ }
+}
+
+// glogInfof logs at info level with formatting, routing to structured logging or glog based on
+// mode.
+func glogInfof(depth int, format string, args ...any) {
+ if structuredLogging {
+ SInfo(depth+2).Msgf(format, args...)
+ } else {
+ glog.InfoDepthf(depth+1, format, args...)
+ }
+}
+
+// glogWarning logs at warning level, routing to structured logging or glog based on mode.
+func glogWarning(depth int, args ...any) {
+ if structuredLogging {
+ SWarn(depth + 2).Msg(fmt.Sprint(args...))
+ } else {
+ glog.WarningDepth(depth+1, args...)
+ }
+}
+
+// glogWarningf logs at warning level with formatting, routing to structured logging or glog based
+// on mode.
+func glogWarningf(depth int, format string, args ...any) {
+ if structuredLogging {
+ SWarn(depth+2).Msgf(format, args...)
+ } else {
+ glog.WarningDepth(depth+1, fmt.Sprintf(format, args...))
+ }
+}
+
+// glogError logs at error level, routing to structured logging or glog based on mode.
+func glogError(depth int, args ...any) {
+ if structuredLogging {
+ SError(depth + 2).Msg(fmt.Sprint(args...))
+ } else {
+ glog.ErrorDepth(depth+1, args...)
+ }
+}
+
+// glogErrorf logs at error level with formatting, routing to structured logging or glog based on
+// mode.
+func glogErrorf(depth int, format string, args ...any) {
+ if structuredLogging {
+ SError(depth+2).Msgf(format, args...)
+ } else {
+ glog.ErrorDepth(depth+1, fmt.Sprintf(format, args...))
+ }
+}
+
+// glogExit logs at fatal level and exits, routing to structured logging or glog based on mode.
+func glogExit(depth int, args ...any) {
+ if structuredLogging {
+ SExit(depth + 2).Msg(fmt.Sprint(args...))
+ os.Exit(1)
+ } else {
+ glog.ExitDepth(depth+1, args...)
+ }
+}
+
+// glogExitf logs at fatal level with formatting and exits, routing to structured logging or glog
+// based on mode.
+func glogExitf(depth int, format string, args ...any) {
+ if structuredLogging {
+ SExit(depth+2).Msgf(format, args...)
+ os.Exit(1)
+ } else {
+ glog.ExitDepth(depth+1, fmt.Sprintf(format, args...))
+ }
+}
+
+// glogFatal logs at fatal level and terminates, routing to structured logging or glog based on
+// mode.
+func glogFatal(depth int, args ...any) {
+ if structuredLogging {
+ SFatal(depth + 2).Msg(fmt.Sprint(args...))
+ } else {
+ glog.FatalDepth(depth+1, args...)
+ }
+}
+
+// glogFatalf logs at fatal level with formatting and terminates, routing to structured logging or
+// glog based on mode.
+func glogFatalf(depth int, format string, args ...any) {
+ if structuredLogging {
+ SFatal(depth+2).Msgf(format, args...)
+ } else {
+ glog.FatalDepth(depth+1, fmt.Sprintf(format, args...))
+ }
+}
+
+// glogFlush flushes any buffered log entries.
+func glogFlush() {
+ if !structuredLogging {
+ glog.Flush()
+ }
+}
diff --git a/go/vt/log/log.go b/go/vt/log/log.go
index 111479c785f..77987b99b5c 100644
--- a/go/vt/log/log.go
+++ b/go/vt/log/log.go
@@ -36,57 +36,130 @@ type Level = glog.Level
var (
// V quickly checks if the logging verbosity meets a threshold.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SDebug() or log.STrace().
V = glog.V
// Flush ensures any pending I/O is written.
- Flush = glog.Flush
+ //
+ // Note: This function is deprecated. Use the new structured logging API. Structured logging does not require explicit flushing.
+ Flush = glogFlush
// Info formats arguments like fmt.Print.
- Info = glog.Info
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SInfo().Msg().
+ Info = func(args ...any) { glogInfo(1, args...) }
+
// Infof formats arguments like fmt.Printf.
- Infof = glog.Infof
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SInfo().Msgf().
+ Infof = func(format string, args ...any) { glogInfof(1, format, args...) }
+
// InfoDepth formats arguments like fmt.Print and uses depth to choose which call frame to log.
- InfoDepth = glog.InfoDepth
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SInfo().Msg().
+ InfoDepth = func(depth int, args ...any) { glogInfo(depth+1, args...) }
+
+ // InfofDepth formats arguments like fmt.Printf and uses depth to choose which call frame to log.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SInfo().Msgf().
+ InfofDepth = func(depth int, format string, args ...any) { glogInfof(depth+1, format, args...) }
// Warning formats arguments like fmt.Print.
- Warning = glog.Warning
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SWarn().Msg().
+ Warning = func(args ...any) { glogWarning(1, args...) }
+
// Warningf formats arguments like fmt.Printf.
- Warningf = glog.Warningf
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SWarn().Msgf().
+ Warningf = func(format string, args ...any) { glogWarningf(1, format, args...) }
+
// WarningDepth formats arguments like fmt.Print and uses depth to choose which call frame to log.
- WarningDepth = glog.WarningDepth
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SWarn().Msg().
+ WarningDepth = func(depth int, args ...any) { glogWarning(depth+1, args...) }
+
+ // WarningfDepth formats arguments like fmt.Printf and uses depth to choose which call frame to log.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SWarn().Msgf().
+ WarningfDepth = func(depth int, format string, args ...any) { glogWarningf(depth+1, format, args...) }
// Error formats arguments like fmt.Print.
- Error = glog.Error
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SError().Msg().
+ Error = func(args ...any) { glogError(1, args...) }
+
// Errorf formats arguments like fmt.Printf.
- Errorf = glog.Errorf
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SError().Msgf().
+ Errorf = func(format string, args ...any) { glogErrorf(1, format, args...) }
+
// ErrorDepth formats arguments like fmt.Print and uses depth to choose which call frame to log.
- ErrorDepth = glog.ErrorDepth
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SError().Msg().
+ ErrorDepth = func(depth int, args ...any) { glogError(depth+1, args...) }
+
+ // ErrorfDepth formats arguments like fmt.Printf and uses depth to choose which call frame to log.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SError().Msgf().
+ ErrorfDepth = func(depth int, format string, args ...any) { glogErrorf(depth+1, format, args...) }
// Exit formats arguments like fmt.Print.
- Exit = glog.Exit
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msg().
+ Exit = func(args ...any) { glogExit(1, args...) }
+
// Exitf formats arguments like fmt.Printf.
- Exitf = glog.Exitf
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msgf().
+ Exitf = func(format string, args ...any) { glogExitf(1, format, args...) }
+
// ExitDepth formats arguments like fmt.Print and uses depth to choose which call frame to log.
- ExitDepth = glog.ExitDepth
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msg().
+ ExitDepth = func(depth int, args ...any) { glogExit(depth+1, args...) }
+
+ // ExitfDepth formats arguments like fmt.Printf and uses depth to choose which call frame to log.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msgf().
+ ExitfDepth = func(depth int, format string, args ...any) { glogExitf(depth+1, format, args...) }
// Fatal formats arguments like fmt.Print.
- Fatal = glog.Fatal
- // Fatalf formats arguments like fmt.Printf
- Fatalf = glog.Fatalf
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msg().
+ Fatal = func(args ...any) { glogFatal(1, args...) }
+
+ // Fatalf formats arguments like fmt.Printf.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msgf().
+ Fatalf = func(format string, args ...any) { glogFatalf(1, format, args...) }
+
// FatalDepth formats arguments like fmt.Print and uses depth to choose which call frame to log.
- FatalDepth = glog.FatalDepth
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msg().
+ FatalDepth = func(depth int, args ...any) { glogFatal(depth+1, args...) }
+
+ // FatalfDepth formats arguments like fmt.Printf and uses depth to choose which call frame to log.
+ //
+ // Note: This function is deprecated. Use the new structured logging API: log.SFatal().Msgf().
+ FatalfDepth = func(depth int, format string, args ...any) { glogFatalf(depth+1, format, args...) }
)
// RegisterFlags installs log flags on the given FlagSet.
//
-// `go/cmd/*` entrypoints should either use servenv.ParseFlags(WithArgs)? which
-// calls this function, or call this function directly before parsing
-// command-line arguments.
+// `go/cmd/*` entrypoints should either use servenv.ParseFlags(WithArgs)? which calls this function,
+// or call this function directly before parsing command-line arguments.
func RegisterFlags(fs *pflag.FlagSet) {
flagVal := logRotateMaxSize{
val: strconv.FormatUint(atomic.LoadUint64(&glog.MaxSize), 10),
}
utils.SetFlagVar(fs, &flagVal, "log-rotate-max-size", "size in bytes at which logs are rotated (glog.MaxSize)")
+
+ fs.BoolVar(&structuredLogging, "structured-logging", true, "Enable structured logging")
+ fs.StringVar(&structuredLoggingLevel, "structured-logging-level", "info", "Log level for structured logging: trace, debug, info, warn, error, fatal, panic, disabled")
+ fs.BoolVar(&structuredLoggingPretty, "structured-logging-pretty", false, "Enable pretty console output for structured logging")
+ fs.StringVar(&structuredLoggingFile, "structured-logging-file", "", "Path to log file for structured logging with rotation (empty means stdout)")
}
// logRotateMaxSize implements pflag.Value and is used to
@@ -131,75 +204,70 @@ func (pl *PrefixedLogger) Flush() {
func (pl *PrefixedLogger) Info(args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- Info(args...)
+ InfoDepth(1, args...)
}
func (pl *PrefixedLogger) Infof(format string, args ...any) {
- args = append([]interface{}{pl.prefix}, args...)
- Infof("%s"+format, args...)
+ InfofDepth(1, pl.prefix+format, args...)
}
func (pl *PrefixedLogger) InfoDepth(depth int, args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- InfoDepth(depth, args...)
+ InfoDepth(depth+1, args...)
}
func (pl *PrefixedLogger) Warning(args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- Warning(args...)
+ WarningDepth(1, args...)
}
func (pl *PrefixedLogger) Warningf(format string, args ...any) {
- args = append([]interface{}{pl.prefix}, args...)
- Warningf("%s"+format, args...)
+ WarningfDepth(1, pl.prefix+format, args...)
}
func (pl *PrefixedLogger) WarningDepth(depth int, args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- WarningDepth(depth, args...)
+ WarningDepth(depth+1, args...)
}
func (pl *PrefixedLogger) Error(args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- Error(args...)
+ ErrorDepth(1, args...)
}
func (pl *PrefixedLogger) Errorf(format string, args ...any) {
- args = append([]interface{}{pl.prefix}, args...)
- Errorf("%s"+format, args...)
+ ErrorfDepth(1, pl.prefix+format, args...)
}
func (pl *PrefixedLogger) ErrorDepth(depth int, args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- ErrorDepth(depth, args...)
+ ErrorDepth(depth+1, args...)
}
func (pl *PrefixedLogger) Exit(args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- Exit(args...)
+ ExitDepth(1, args...)
}
func (pl *PrefixedLogger) Exitf(format string, args ...any) {
- args = append([]interface{}{pl.prefix}, args...)
- Exitf("%s"+format, args...)
+ ExitfDepth(1, pl.prefix+format, args...)
}
func (pl *PrefixedLogger) ExitDepth(depth int, args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- ExitDepth(depth, args...)
+ ExitDepth(depth+1, args...)
}
func (pl *PrefixedLogger) Fatal(args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- Fatal(args...)
+ FatalDepth(1, args...)
}
func (pl *PrefixedLogger) Fatalf(format string, args ...any) {
- args = append([]interface{}{pl.prefix}, args...)
- Fatalf("%s"+format, args...)
+ FatalfDepth(1, pl.prefix+format, args...)
}
func (pl *PrefixedLogger) FatalDepth(depth int, args ...any) {
args = append([]interface{}{pl.prefix}, args...)
- FatalDepth(depth, args...)
+ FatalDepth(depth+1, args...)
}
diff --git a/go/vt/log/zlog.go b/go/vt/log/zlog.go
new file mode 100644
index 00000000000..4326f3f86a9
--- /dev/null
+++ b/go/vt/log/zlog.go
@@ -0,0 +1,200 @@
+/*
+Copyright 2026 The Vitess Authors.
+
+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 log
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "sync/atomic"
+ "time"
+
+ "github.com/golang/glog"
+ "github.com/rs/zerolog"
+ "github.com/spf13/pflag"
+ "gopkg.in/natefinch/lumberjack.v2"
+)
+
+var (
+ structuredLogging bool
+ structuredLoggingLevel string
+ structuredLoggingPretty bool
+ structuredLoggingFile string
+)
+
+// slogger is the default structured logger. It defaults to glog unless structured logging is
+// enabled with --structured-logging.
+var slogger = zerolog.New(&glogWriter{}).With().Timestamp().Logger()
+
+// Init configures the global logger based on the --structured-logging flag. In structured mode,
+// logs are written as JSON to stdout or to a file with rotation. In glog mode, zerolog builds JSON
+// but routes it through glog for output.
+func Init(fs *pflag.FlagSet) error {
+ if err := validateFlags(fs); err != nil {
+ return fmt.Errorf("%w", err)
+ }
+
+ // exitLevel is a custom level used to mimic glog's Exit, which is like Fatal except it does
+ // not print a stack trace. By default, zerolog will print the number since it is an unknown level
+ // (i.e. {"level": 8}). We override that here so that it prints out "fatal" instead.
+ zerolog.LevelFieldMarshalFunc = func(l zerolog.Level) string {
+ if l == exitLevel {
+ return "fatal"
+ }
+
+ return l.String()
+ }
+
+ if structuredLogging {
+ level, err := zerolog.ParseLevel(structuredLoggingLevel)
+ if err != nil {
+ return fmt.Errorf("invalid --structured-logging-level %q: %w", structuredLoggingLevel, err)
+ }
+ zerolog.SetGlobalLevel(level)
+
+ // Build the appropriate output writer depending on the configured flags.
+ output := structuredLoggingOutput()
+
+ slogger = zerolog.New(output).With().Timestamp().Logger()
+ return nil
+ }
+
+ slogger = zerolog.New(&glogWriter{}).With().Timestamp().Logger()
+ return nil
+}
+
+// structuredLoggingOutput returns the appropriate io.Writer for structured logging based on the
+// configured flags. If a file path is specified, it returns a logger with rotation, configured
+// with the --log-rotate-max-size flag. Otherwise, it returns stdout.
+func structuredLoggingOutput() io.Writer {
+ var out io.Writer = os.Stdout
+ if structuredLoggingFile != "" {
+ // Convert glog.MaxSize from bytes to megabytes. Lumberjack requires at least 1 MB.
+ maxSizeMB := int(atomic.LoadUint64(&glog.MaxSize) / (1024 * 1024))
+ if maxSizeMB < 1 {
+ maxSizeMB = 1
+ }
+
+ out = &lumberjack.Logger{
+ Filename: structuredLoggingFile,
+ MaxSize: maxSizeMB,
+ }
+ }
+
+ if structuredLoggingPretty {
+ return zerolog.ConsoleWriter{
+ Out: out,
+ TimeFormat: time.RFC3339Nano,
+ }
+ }
+
+ return out
+}
+
+// validateFlags checks for conflicting flag combinations. When structured logging is enabled, glog
+// flags should not be set. When structured logging is disabled, structured logging flags should not
+// be set.
+func validateFlags(fs *pflag.FlagSet) error {
+ if structuredLogging {
+ // If structured logging is enabled, glog flags should not be set.
+ glogFlags := []string{"logtostderr", "alsologtostderr", "stderrthreshold", "log_dir", "log_backtrace_at", "vmodule", "v"}
+
+ for _, name := range glogFlags {
+ if fs.Changed(name) {
+ return fmt.Errorf("cannot use --%s with --structured-logging; glog flags are ignored when structured logging is enabled", name)
+ }
+ }
+
+ return nil
+ }
+
+ // If structured logging is not enabled, other structured logging flags should not be set.
+ structuredFlags := []string{
+ "structured-logging-level",
+ "structured-logging-pretty",
+ "structured-logging-file",
+ }
+
+ for _, name := range structuredFlags {
+ if fs.Changed(name) {
+ return fmt.Errorf("--%s requires --structured-logging", name)
+ }
+ }
+
+ return nil
+}
+
+// callerSkip returns the skip value to use, defaulting to 1 if not provided.
+func callerSkip(skip []int) int {
+ if len(skip) > 0 {
+ return skip[0]
+ }
+ return 1
+}
+
+// STrace starts a new message with trace level. Optional skip adjusts caller frame depth.
+func STrace(skip ...int) *zerolog.Event { return slogger.Trace().Caller(callerSkip(skip)) }
+
+// SDebug starts a new message with debug level. Optional skip adjusts caller frame depth.
+func SDebug(skip ...int) *zerolog.Event { return slogger.Debug().Caller(callerSkip(skip)) }
+
+// SInfo starts a new message with info level. Optional skip adjusts caller frame depth.
+func SInfo(skip ...int) *zerolog.Event { return slogger.Info().Caller(callerSkip(skip)) }
+
+// SWarn starts a new message with warn level. Optional skip adjusts caller frame depth.
+func SWarn(skip ...int) *zerolog.Event { return slogger.Warn().Caller(callerSkip(skip)) }
+
+// SError starts a new message with error level. Optional skip adjusts caller frame depth.
+func SError(skip ...int) *zerolog.Event { return slogger.Error().Caller(callerSkip(skip)) }
+
+// SExit starts a new message with exit level. Optional skip adjusts caller frame depth.
+// This is a custom level used to mimic glog's Exit, which is like Fatal except it does not
+// print a stack trace.
+func SExit(skip ...int) *zerolog.Event {
+ return slogger.WithLevel(zerolog.Level(exitLevel)).Caller(callerSkip(skip))
+}
+
+// SFatal starts a new message with fatal level. Optional skip adjusts caller frame depth.
+func SFatal(skip ...int) *zerolog.Event { return slogger.Fatal().Caller(callerSkip(skip)) }
+
+// SPanic starts a new message with panic level. Optional skip adjusts caller frame depth.
+func SPanic(skip ...int) *zerolog.Event { return slogger.Panic().Caller(callerSkip(skip)) }
+
+// SLog starts a new message with no level. Optional skip adjusts caller frame depth.
+func SLog(skip ...int) *zerolog.Event { return slogger.Log().Caller(callerSkip(skip)) }
+
+// SErr starts a new message with error level with err as a field if not nil or with info level if
+// err is nil. Optional skip adjusts caller frame depth.
+func SErr(err error, skip ...int) *zerolog.Event { return slogger.Err(err).Caller(callerSkip(skip)) }
+
+// SWithLevel starts a new message with the specified level. Optional skip adjusts caller frame
+// depth.
+func SWithLevel(level zerolog.Level, skip ...int) *zerolog.Event {
+ return slogger.WithLevel(level).Caller(callerSkip(skip))
+}
+
+// SWith creates a child logger with the field added to its context.
+func SWith() zerolog.Context { return slogger.With() }
+
+// SLevel creates a child logger with the minimum accepted level set to level.
+func SLevel(lvl zerolog.Level) zerolog.Logger { return slogger.Level(lvl) }
+
+// SSample returns a logger with the s sampler.
+func SSample(s zerolog.Sampler) zerolog.Logger { return slogger.Sample(s) }
+
+// SHook returns a logger with the hooks.
+func SHook(hooks ...zerolog.Hook) zerolog.Logger { return slogger.Hook(hooks...) }
diff --git a/go/vt/servenv/servenv.go b/go/vt/servenv/servenv.go
index d7ddccf1890..e3b64d93c43 100644
--- a/go/vt/servenv/servenv.go
+++ b/go/vt/servenv/servenv.go
@@ -319,6 +319,10 @@ func ParseFlags(cmd string) {
loadViper(cmd)
+ if err := log.Init(fs); err != nil {
+ log.Exitf("%s: %v", cmd, err)
+ }
+
logutil.PurgeLogs()
}
@@ -331,6 +335,10 @@ func ParseFlagsForTests(cmd string) {
pflag.Parse()
viperutil.BindFlags(fs)
loadViper(cmd)
+
+ if err := log.Init(fs); err != nil {
+ log.Exitf("%s: %v", cmd, err)
+ }
}
// MoveFlagsToCobraCommand moves the servenv-registered flags to the flagset of
@@ -371,6 +379,11 @@ func moveFlags(name string, fs *pflag.FlagSet) {
// functions.
func CobraPreRunE(cmd *cobra.Command, args []string) error {
_flag.TrickGlog()
+
+ if err := log.Init(cmd.Flags()); err != nil {
+ return fmt.Errorf("%s: %v", cmd.Name(), err)
+ }
+
// Register logging on config file change.
ch := make(chan struct{})
viperutil.NotifyConfigReload(ch)
@@ -428,6 +441,10 @@ func ParseFlagsWithArgs(cmd string) []string {
loadViper(cmd)
+ if err := log.Init(fs); err != nil {
+ log.Exitf("%s: %v", cmd, err)
+ }
+
logutil.PurgeLogs()
return args