Skip to content

Commit 595713f

Browse files
authored
auto-decode for tdbg workflow show (#9294)
## What changed? Adds `--decode` flag to tdbg's `wofkflow show` to decode event history proto payloads to JSON. ## Why? Make on-call investigation easier. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [ ] covered by existing tests - [x] added new unit test(s) - [ ] added new functional test(s) ## Potential risks The decoder ignores errors; so if it fails, it behaves as before.
1 parent 4668ca5 commit 595713f

File tree

6 files changed

+351
-13
lines changed

6 files changed

+351
-13
lines changed

tools/tdbg/commands.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func AdminShowWorkflow(c *cli.Context, clientFactory ClientFactory) error {
5252
startEventVerion := int64(c.Int(FlagMinEventVersion))
5353
endEventVersion := int64(c.Int(FlagMaxEventVersion))
5454
outputFileName := c.String(FlagOutputFilename)
55+
decode := c.Bool(FlagDecode)
5556

5657
client := clientFactory.AdminClient(c)
5758
serializer := serialization.NewSerializer()
@@ -116,12 +117,21 @@ func AdminShowWorkflow(c *cli.Context, clientFactory ClientFactory) error {
116117
errs = append(errs, err)
117118
continue
118119
}
120+
if decode {
121+
data = decodePayloadsInJSON(data)
122+
}
119123
// nolint:errcheck // assuming that write will succeed.
120124
fmt.Fprintln(c.App.Writer, string(data))
121125
}
122126
// nolint:errcheck // assuming that write will succeed.
123127
fmt.Fprintf(c.App.Writer, "======== total batches %v, total blob len: %v ======\n", len(histories), totalSize)
124128

129+
// Show info to user about the option to decode payloads.
130+
if !decode {
131+
// nolint:errcheck // assuming that write will succeed.
132+
fmt.Fprintf(c.App.ErrWriter, "(use --%s to decode payloads to JSON)\n", FlagDecode)
133+
}
134+
125135
err = errors.Join(errs...)
126136
if err != nil {
127137
return err
@@ -133,6 +143,9 @@ func AdminShowWorkflow(c *cli.Context, clientFactory ClientFactory) error {
133143
if err != nil {
134144
return fmt.Errorf("unable to serialize History data: %s", err)
135145
}
146+
if decode {
147+
data = decodePayloadsInJSON(data)
148+
}
136149
if err := os.WriteFile(outputFileName, data, 0666); err != nil {
137150
return fmt.Errorf("unable to write History data file: %s", err)
138151
}
@@ -473,15 +486,15 @@ func AdminListShardTasks(c *cli.Context, clientFactory ClientFactory, registry t
473486

474487
ctx, cancel := newContext(c)
475488
defer cancel()
476-
paginationFunc := func(paginationToken []byte) ([]interface{}, []byte, error) {
489+
paginationFunc := func(paginationToken []byte) ([]any, []byte, error) {
477490
req.NextPageToken = paginationToken
478491
response, err := client.ListHistoryTasks(ctx, req)
479492
if err != nil {
480493
return nil, nil, err
481494
}
482495
token := response.NextPageToken
483496

484-
var items []interface{}
497+
var items []any
485498
for _, task := range response.Tasks {
486499
items = append(items, task)
487500
}

tools/tdbg/decode_commands.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ import (
1010
"github.com/urfave/cli/v2"
1111
"go.temporal.io/server/common/codec"
1212
"google.golang.org/protobuf/encoding/prototext"
13-
"google.golang.org/protobuf/proto"
14-
"google.golang.org/protobuf/reflect/protoreflect"
15-
"google.golang.org/protobuf/reflect/protoregistry"
1613
)
1714

1815
func AdminDecodeProto(c *cli.Context) error {
@@ -60,14 +57,9 @@ func AdminDecodeProto(c *cli.Context) error {
6057
return fmt.Errorf("missing required parameter data flag")
6158
}
6259

63-
messageType, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(protoType))
60+
message, err := unmarshalProtoByTypeName(protoType, protoData)
6461
if err != nil {
65-
return fmt.Errorf("unable to find %s type: %w", protoType, err)
66-
}
67-
68-
message := messageType.New().Interface()
69-
if err = proto.Unmarshal(protoData, message); err != nil {
70-
return fmt.Errorf("unable to unmarshal to %s", protoType)
62+
return err
7163
}
7264

7365
encoder := codec.NewJSONPBIndentEncoder(" ")

tools/tdbg/flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,5 @@ var (
7575
FlagMinPass = "min-pass"
7676
FlagVisibilityQuery = "query"
7777
FlagJobID = "job-id"
78+
FlagDecode = "decode"
7879
)

tools/tdbg/proto_decoder.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package tdbg
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
"strings"
8+
9+
"go.temporal.io/server/common/codec"
10+
"google.golang.org/protobuf/proto"
11+
"google.golang.org/protobuf/reflect/protoreflect"
12+
"google.golang.org/protobuf/reflect/protoregistry"
13+
)
14+
15+
// decodePayloadsInJSON parses JSON data, finds Payload objects, and makes them
16+
// human-readable: metadata bytes are decoded from base64 to strings, and data
17+
// is decoded based on the encoding (binary/protobuf, json/plain, etc.).
18+
// If data decoding fails, the data field is left as its original base64 string.
19+
func decodePayloadsInJSON(data []byte) []byte {
20+
var parsed any
21+
if err := json.Unmarshal(data, &parsed); err != nil {
22+
return data
23+
}
24+
encoder := codec.NewJSONPBEncoder()
25+
decodePayloadsRecursive(parsed, encoder)
26+
result, err := json.Marshal(parsed)
27+
if err != nil {
28+
return data
29+
}
30+
return result
31+
}
32+
33+
func decodePayloadsRecursive(v any, encoder codec.JSONPBEncoder) {
34+
switch val := v.(type) {
35+
case map[string]any:
36+
tryDecodePayloadJSON(val, encoder)
37+
for _, child := range val {
38+
decodePayloadsRecursive(child, encoder)
39+
}
40+
case []any:
41+
for _, item := range val {
42+
decodePayloadsRecursive(item, encoder)
43+
}
44+
}
45+
}
46+
47+
func tryDecodePayloadJSON(obj map[string]any, encoder codec.JSONPBEncoder) {
48+
metadata, ok := obj["metadata"].(map[string]any)
49+
if !ok {
50+
return
51+
}
52+
53+
// Decode all metadata bytes values from base64 to strings.
54+
for key, val := range metadata {
55+
b64, ok := val.(string)
56+
if !ok {
57+
continue
58+
}
59+
decoded, err := base64.StdEncoding.DecodeString(b64)
60+
if err != nil {
61+
continue
62+
}
63+
metadata[key] = string(decoded)
64+
}
65+
66+
encoding, ok := metadata["encoding"].(string)
67+
if !ok {
68+
encoding = ""
69+
}
70+
messageType, ok := metadata["messageType"].(string)
71+
if !ok {
72+
messageType = ""
73+
}
74+
75+
dataB64, ok := obj["data"].(string)
76+
if !ok {
77+
return
78+
}
79+
80+
// For binary/protobuf with a known messageType, unmarshal and re-encode as JSON.
81+
if messageType != "" && encoding == "binary/protobuf" {
82+
dataBytes, err := base64.StdEncoding.DecodeString(dataB64)
83+
if err != nil {
84+
return
85+
}
86+
msg, err := unmarshalProtoByTypeName(messageType, dataBytes)
87+
if err != nil {
88+
return
89+
}
90+
jsonBytes, err := encoder.Encode(msg)
91+
if err != nil {
92+
return
93+
}
94+
var decoded any
95+
if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
96+
return
97+
}
98+
obj["data"] = decoded
99+
return
100+
}
101+
102+
// For JSON-based encodings, base64-decode and parse as JSON.
103+
if strings.HasPrefix(encoding, "json/") {
104+
dataBytes, err := base64.StdEncoding.DecodeString(dataB64)
105+
if err != nil {
106+
return
107+
}
108+
var decoded any
109+
if err := json.Unmarshal(dataBytes, &decoded); err != nil {
110+
return
111+
}
112+
obj["data"] = decoded
113+
}
114+
}
115+
116+
// unmarshalProtoByTypeName resolves a proto type by its full name from the
117+
// global registry and unmarshals the given bytes into it.
118+
func unmarshalProtoByTypeName(typeName string, data []byte) (proto.Message, error) {
119+
mt, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(typeName))
120+
if err != nil {
121+
return nil, fmt.Errorf("unable to find %s type: %w", typeName, err)
122+
}
123+
msg := mt.New().Interface()
124+
if err := proto.Unmarshal(data, msg); err != nil {
125+
return nil, fmt.Errorf("unable to unmarshal to %s: %w", typeName, err)
126+
}
127+
return msg, nil
128+
}

0 commit comments

Comments
 (0)