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