Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ Flags:
also accepted
--param-file=STRING YAML or JSON file of query parameters (name
to type/literal string)
--log-grpc="off" gRPC logging mode: off, metadata, or payload
(payload may include request and response
payloads in logs)
--log-grpc gRPC logging: --log-grpc means payload;
use --log-grpc=off|metadata|payload to select
a mode (payload may include request and
response payloads)
--experimental-trace-project=STRING
Export traces to Cloud Trace in the given
project.
Expand Down Expand Up @@ -327,6 +328,9 @@ $ execspansql $DATABASE_ID --query-mode=PROFILE --sql 'SELECT 1' --experimental-
```

Note: `--experimental-trace-stdout` writes to **stderr**, not stdout.

A bare `--log-grpc` retains its previous meaning: payload logging. Logging is off when the flag is omitted. Use `--log-grpc=off`, `--log-grpc=metadata`, or `--log-grpc=payload` to select a mode; an explicit value must use `=` so the next database argument is not consumed. Legacy boolean values such as `--log-grpc=true` and `--log-grpc=false` remain accepted.

Note: `--log-grpc=payload` can log request and response payloads (including bound parameters and row values) and should only be used in trusted environments.

![trace.png](docs/trace.png)
Expand Down
35 changes: 35 additions & 0 deletions log_grpc_flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"strings"

"github.com/alecthomas/kong"
)

// logGrpcFlag retains the old boolean flag syntax while accepting explicit
// logging modes. As with boolean flags, explicit values use "=" so a bare
// flag never consumes the following database argument.
type logGrpcFlag string

func (*logGrpcFlag) IsBool() bool { return true }

func (m *logGrpcFlag) Decode(ctx *kong.DecodeContext) error {
if ctx.Scan.Peek().Type != kong.FlagValueToken {
*m = logGrpcModePayload
return nil
}
var value string
if err := ctx.Scan.PopValueInto("logging mode", &value); err != nil {
return err
}
// Preserve Kong's previous boolean spellings as well as the bare flag.
// The enum tag validates explicit mode names after this normalization.
switch strings.ToLower(value) {
case "true", "1", "yes":
value = logGrpcModePayload
case "false", "0", "no":
value = logGrpcModeOff
}
*m = logGrpcFlag(value)
return nil
}
56 changes: 56 additions & 0 deletions log_grpc_flag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"strings"
"testing"

"github.com/alecthomas/kong"
)

func TestLogGrpcFlag(t *testing.T) {
for _, tt := range []struct {
name string
args []string
want string
wantDB string
wantErr bool
}{
{name: "omitted", args: []string{"db"}, want: "off", wantDB: "db"},
{name: "bare_before_database", args: []string{"--log-grpc", "db"}, want: "payload", wantDB: "db"},
{name: "bare_after_database", args: []string{"db", "--log-grpc"}, want: "payload", wantDB: "db"},
{name: "mode_named_database", args: []string{"--log-grpc", "metadata"}, want: "payload", wantDB: "metadata"},
{name: "off", args: []string{"db", "--log-grpc=off"}, want: "off", wantDB: "db"},
{name: "metadata", args: []string{"db", "--log-grpc=metadata"}, want: "metadata", wantDB: "db"},
{name: "payload", args: []string{"db", "--log-grpc=payload"}, want: "payload", wantDB: "db"},
{name: "legacy_true", args: []string{"db", "--log-grpc=true"}, want: "payload", wantDB: "db"},
{name: "legacy_yes", args: []string{"db", "--log-grpc=YES"}, want: "payload", wantDB: "db"},
{name: "legacy_one", args: []string{"db", "--log-grpc=1"}, want: "payload", wantDB: "db"},
{name: "legacy_false", args: []string{"db", "--log-grpc=false"}, want: "off", wantDB: "db"},
{name: "legacy_no", args: []string{"db", "--log-grpc=NO"}, want: "off", wantDB: "db"},
{name: "legacy_zero", args: []string{"db", "--log-grpc=0"}, want: "off", wantDB: "db"},
{name: "invalid", args: []string{"db", "--log-grpc=invalid"}, wantErr: true},
{name: "empty", args: []string{"db", "--log-grpc="}, wantErr: true},
} {
t.Run(tt.name, func(t *testing.T) {
var o opts
parser, err := kong.New(&o)
if err != nil {
t.Fatal(err)
}
args := append(append([]string{}, tt.args...), "--project=p", "--instance=i", "--sql=SELECT 1")
_, err = parser.Parse(args)
if tt.wantErr {
if err == nil || !strings.Contains(err.Error(), "--log-grpc") {
t.Fatalf("error = %v, want invalid log mode", err)
}
return
}
if err != nil {
t.Fatal(err)
}
if string(o.LogGrpc) != tt.want || o.Database != tt.wantDB {
t.Fatalf("mode=%q database=%q, want %q %q", o.LogGrpc, o.Database, tt.want, tt.wantDB)
}
})
}
}
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type opts struct {
JqInputMode string `name:"jq-input-mode" enum:"eager,lazy" default:"eager" help:"How query rows are passed to jq (json/yaml only): eager (full ResultSet), lazy (JQValue root)."`
ParamFlags []string `name:"param" help:"[name]=[type or literal]; legacy [name]:[...] also accepted"`
ParamFile string `name:"param-file" help:"YAML or JSON file of query parameters (name to type/literal string)"`
LogGrpc string `name:"log-grpc" enum:"off,metadata,payload" default:"off" help:"gRPC logging mode: off, metadata, or payload (payload may include request and response payloads in logs)"`
LogGrpc logGrpcFlag `name:"log-grpc" enum:"off,metadata,payload" default:"off" help:"gRPC logging: --log-grpc means payload; use --log-grpc=off|metadata|payload to select a mode (payload may include request and response payloads)"`
TraceProject string `name:"experimental-trace-project" xor:"trace" help:"Export traces to Cloud Trace in the given project."`
TraceStdout bool `name:"experimental-trace-stdout" xor:"trace" help:"Export spans to stderr as pretty JSON (local debugging)."`
TraceOTLP bool `name:"experimental-trace-otlp" xor:"trace" help:"Export spans via OTLP/gRPC to a local OpenTelemetry collector."`
Expand Down Expand Up @@ -474,7 +474,7 @@ func runCLI(clientOptions ...option.ClientOption) error {
}()
}

client, err := newClient(ctx, o.Project, o.Instance, o.Database, o.DatabaseRole, o.LogGrpc, tracingEnabled(o), clientOptions...)
client, err := newClient(ctx, o.Project, o.Instance, o.Database, o.DatabaseRole, string(o.LogGrpc), tracingEnabled(o), clientOptions...)
if err != nil {
return err
}
Expand Down
Loading