github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/pkg/util/log/eventpb/eventpbgen/gen.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package main
    12  
    13  import (
    14  	"bytes"
    15  	"flag"
    16  	"fmt"
    17  	"os"
    18  	"regexp"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  	"text/template"
    23  
    24  	"github.com/cockroachdb/cockroachdb-parser/pkg/cli/exit"
    25  	"github.com/cockroachdb/errors"
    26  	"github.com/cockroachdb/gostdlib/go/format"
    27  )
    28  
    29  func main() {
    30  	if err := run(); err != nil {
    31  		fmt.Fprintln(os.Stderr, "ERROR:", err)
    32  		exit.WithCode(exit.UnspecifiedError())
    33  	}
    34  }
    35  
    36  type reInfos struct {
    37  	reCnt    int
    38  	infos    []reInfo
    39  	reToName map[string]string
    40  }
    41  
    42  type reInfo struct {
    43  	ReName string
    44  	ReDef  string
    45  }
    46  
    47  type catInfo struct {
    48  	Title      string
    49  	Comment    string
    50  	LogChannel string
    51  	EventNames []string
    52  	Events     []*eventInfo
    53  }
    54  
    55  type enumInfo struct {
    56  	Comment string
    57  	GoType  string
    58  	Values  []enumValInfo
    59  }
    60  
    61  type enumValInfo struct {
    62  	Comment string
    63  	Name    string
    64  	Value   int
    65  }
    66  
    67  type eventInfo struct {
    68  	Comment         string
    69  	LogChannel      string
    70  	GoType          string
    71  	Type            string
    72  	Fields          []fieldInfo
    73  	InheritedFields []fieldInfo
    74  	AllFields       []fieldInfo
    75  }
    76  
    77  type fieldInfo struct {
    78  	Comment             string
    79  	FieldType           string
    80  	FieldName           string
    81  	AlwaysReportingSafe bool
    82  	ReportingSafeRe     string
    83  	MixedRedactable     bool
    84  	Inherited           bool
    85  	IsEnum              bool
    86  	AllowZeroValue      bool
    87  }
    88  
    89  var (
    90  	packageFlag      = flag.String("package", "eventpb", "package to use in generated go")
    91  	excludeEventFlag = flag.String("excluded-events", "", "regexp of events to exclude")
    92  )
    93  
    94  func run() error {
    95  	flag.Parse()
    96  
    97  	args := flag.CommandLine.Args()
    98  	if len(args) < 2 {
    99  		return errors.Newf("usage: %s <template> <protos...>\n", os.Args[0])
   100  	}
   101  	var excludedEvents *regexp.Regexp
   102  	if *excludeEventFlag != "" {
   103  		var err error
   104  		excludedEvents, err = regexp.Compile(*excludeEventFlag)
   105  		if err != nil {
   106  			return errors.Wrap(err, "invalid --excluded-events flag")
   107  		}
   108  	}
   109  
   110  	// Which template are we running?
   111  	tmplName := args[0]
   112  	tmplSrc, ok := templates[tmplName]
   113  	if !ok {
   114  		return errors.Newf("unknown template: %q", tmplName)
   115  	}
   116  	tmplFuncs := template.FuncMap{
   117  		// error produces an error.
   118  		"error": func(s string) string {
   119  			panic(errors.Newf("template error: %s", s))
   120  		},
   121  		// tableCell formats strings for use in a table cell. For example, it converts \n\n into <br>.
   122  		"tableCell": func(s string) string {
   123  			s = strings.TrimSpace(s)
   124  			if s == "" {
   125  				return ""
   126  			}
   127  			s = strings.ReplaceAll(s, "\r", "")
   128  			// Double newlines are paragraph breaks.
   129  			s = strings.ReplaceAll(s, "\n\n", "<br><br>")
   130  			// Other newlines are just width wrapping and should be converted to spaces.
   131  			s = strings.ReplaceAll(s, "\n", " ")
   132  			return s
   133  		},
   134  	}
   135  	tmpl, err := template.New(tmplName).Funcs(tmplFuncs).Parse(tmplSrc)
   136  	if err != nil {
   137  		return errors.Wrapf(err, "failed to parse template %q", tmplName)
   138  	}
   139  
   140  	// Read the input .proto file.
   141  	info := map[string]*eventInfo{}
   142  	enums := map[string]*enumInfo{}
   143  	cats := map[string]*catInfo{}
   144  	regexps := reInfos{reToName: map[string]string{}}
   145  	for i := 1; i < len(args); i++ {
   146  		if err := readInput(&regexps, enums, info, cats, args[i]); err != nil {
   147  			return err
   148  		}
   149  	}
   150  
   151  	var keys []string
   152  	for k := range cats {
   153  		keys = append(keys, k)
   154  	}
   155  	sort.Strings(keys)
   156  	var sortedInfos []*eventInfo
   157  	var sortedCats []*catInfo
   158  	for _, k := range keys {
   159  		cat := cats[k]
   160  		sort.Strings(cat.EventNames)
   161  		for _, evname := range cat.EventNames {
   162  			ev := info[evname]
   163  			cat.Events = append(cat.Events, ev)
   164  			sortedInfos = append(sortedInfos, ev)
   165  		}
   166  		sortedCats = append(sortedCats, cat)
   167  	}
   168  
   169  	keys = nil
   170  	for k := range info {
   171  		if excludedEvents != nil && excludedEvents.MatchString(k) {
   172  			continue
   173  		}
   174  		keys = append(keys, k)
   175  	}
   176  	sort.Strings(keys)
   177  	var allSortedInfos []*eventInfo
   178  	for _, k := range keys {
   179  		allSortedInfos = append(allSortedInfos, info[k])
   180  	}
   181  
   182  	keys = nil
   183  	for k := range enums {
   184  		keys = append(keys, k)
   185  	}
   186  	sort.Strings(keys)
   187  	var allSortedEnums []*enumInfo
   188  	for _, k := range keys {
   189  		allSortedEnums = append(allSortedEnums, enums[k])
   190  	}
   191  
   192  	// Render the template.
   193  	var src bytes.Buffer
   194  	if err := tmpl.Execute(&src, struct {
   195  		Package    string
   196  		AllRegexps []reInfo
   197  		Categories []*catInfo
   198  		Events     []*eventInfo
   199  		AllEvents  []*eventInfo
   200  		Enums      []*enumInfo
   201  	}{
   202  		*packageFlag,
   203  		regexps.infos,
   204  		sortedCats,
   205  		sortedInfos,
   206  		allSortedInfos,
   207  		allSortedEnums,
   208  	}); err != nil {
   209  		return err
   210  	}
   211  
   212  	// If we are generating a .go file, do a pass of gofmt.
   213  	newBytes := src.Bytes()
   214  	if strings.HasSuffix(tmplName, "_go") {
   215  		newBytes, err = format.Source(newBytes)
   216  		if err != nil {
   217  			return errors.Wrap(err, "gofmt")
   218  		}
   219  	}
   220  
   221  	// Write the output file.
   222  	w := os.Stdout
   223  	if _, err := w.Write(newBytes); err != nil {
   224  		return err
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func readInput(
   231  	regexps *reInfos,
   232  	enums map[string]*enumInfo,
   233  	infos map[string]*eventInfo,
   234  	cats map[string]*catInfo,
   235  	protoName string,
   236  ) error {
   237  	protoData, err := os.ReadFile(protoName)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	inMsg := false
   242  	inEnum := false
   243  	comment := ""
   244  	channel := ""
   245  	var curCat *catInfo
   246  	var curMsg *eventInfo
   247  	var curEnum *enumInfo
   248  	for _, line := range strings.Split(string(protoData), "\n") {
   249  		line = strings.TrimSpace(line)
   250  
   251  		if strings.HasPrefix(line, "//") {
   252  			comment += strings.TrimSpace(line[2:]) + "\n"
   253  			continue
   254  		}
   255  
   256  		if line == "" {
   257  			if strings.HasPrefix(comment, "Category:") {
   258  				lines := strings.SplitN(comment, "\n", 3)
   259  				if len(lines) < 3 || !strings.HasPrefix(lines[1], "Channel:") {
   260  					return errors.New("invalid category comment: missing Channel specification")
   261  				}
   262  				title := strings.TrimSpace(strings.SplitN(lines[0], ":", 2)[1])
   263  				channel = strings.TrimSpace(strings.SplitN(lines[1], ":", 2)[1])
   264  				if _, ok := channels[channel]; !ok {
   265  					return errors.Newf("unknown channel name: %q", channel)
   266  				}
   267  				curCat = &catInfo{
   268  					Title:      title,
   269  					Comment:    strings.TrimSpace(strings.Join(lines[2:], "\n")),
   270  					LogChannel: channel,
   271  				}
   272  				cats[title] = curCat
   273  			}
   274  
   275  			comment = ""
   276  			continue
   277  		}
   278  
   279  		if !inEnum && !inMsg && strings.HasPrefix(line, "enum ") {
   280  			inEnum = true
   281  
   282  			typ := strings.Split(line, " ")[1]
   283  			if _, ok := enums[typ]; ok {
   284  				return errors.Newf("duplicate enum type: %q", typ)
   285  			}
   286  
   287  			curEnum = &enumInfo{
   288  				Comment: comment,
   289  				GoType:  typ,
   290  			}
   291  			comment = ""
   292  			enums[typ] = curEnum
   293  			continue
   294  		}
   295  		if inEnum {
   296  			if strings.HasPrefix(line, "}") {
   297  				inEnum = false
   298  				comment = ""
   299  				continue
   300  			}
   301  
   302  			// At this point, we don't support definitions that don't fit on a single line.
   303  			if !strings.Contains(line, ";") {
   304  				return errors.Newf("enum value definition must not span multiple lines: %q", line)
   305  			}
   306  
   307  			if !enumValDefRe.MatchString(line) {
   308  				return errors.Newf("invalid enum value definition: %q", line)
   309  			}
   310  
   311  			tag := enumValDefRe.ReplaceAllString(line, "$tag")
   312  			val := enumValDefRe.ReplaceAllString(line, "$val")
   313  			vali, err := strconv.Atoi(val)
   314  			if err != nil {
   315  				return errors.Wrapf(err, "parsing %q", line)
   316  			}
   317  
   318  			comment = strings.TrimSpace(strings.TrimPrefix(comment, tag))
   319  
   320  			curEnum.Values = append(curEnum.Values, enumValInfo{
   321  				Comment: comment,
   322  				Name:    tag,
   323  				Value:   vali,
   324  			})
   325  			comment = ""
   326  		}
   327  
   328  		if !inMsg && !inEnum && strings.HasPrefix(line, "message ") {
   329  			inMsg = true
   330  
   331  			typ := strings.Split(line, " ")[1]
   332  			if _, ok := infos[typ]; ok {
   333  				return errors.Newf("duplicate message type: %q", typ)
   334  			}
   335  			snakeType := camelToSnake(typ)
   336  
   337  			if strings.HasPrefix(comment, typ) {
   338  				comment = "An event of type `" + snakeType + "`" + strings.TrimPrefix(comment, typ)
   339  			}
   340  			curMsg = &eventInfo{
   341  				Comment:    comment,
   342  				GoType:     typ,
   343  				Type:       snakeType,
   344  				LogChannel: channel,
   345  			}
   346  			comment = ""
   347  			infos[typ] = curMsg
   348  			if !strings.HasPrefix(typ, "Common") {
   349  				if curCat == nil {
   350  					return errors.New("missing category specification at top of file")
   351  				}
   352  
   353  				curCat.EventNames = append(curCat.EventNames, typ)
   354  			}
   355  
   356  			continue
   357  		}
   358  		if inMsg {
   359  			if strings.HasPrefix(line, "}") {
   360  				inMsg = false
   361  				comment = ""
   362  				continue
   363  			}
   364  
   365  			// At this point, we don't support definitions that don't fit on a single line.
   366  			if !strings.Contains(line, ";") {
   367  				return errors.Newf("field definition must not span multiple lines: %q", line)
   368  			}
   369  
   370  			// Skip reserved fields.
   371  			if reservedDefRe.MatchString(line) {
   372  				continue
   373  			}
   374  
   375  			// A field.
   376  			if strings.HasPrefix(line, "repeated") {
   377  				line = "array_of_" + strings.TrimSpace(strings.TrimPrefix(line, "repeated"))
   378  			}
   379  			if !fieldDefRe.MatchString(line) {
   380  				return errors.Newf("unknown field definition syntax: %q", line)
   381  			}
   382  
   383  			// Allow zero values if the field is annotated with 'includeempty'.
   384  			allowZeroValue := strings.Contains(line, "includeempty")
   385  
   386  			typ := fieldDefRe.ReplaceAllString(line, "$typ")
   387  			switch typ {
   388  			case "google.protobuf.Timestamp":
   389  				typ = "timestamp"
   390  			case "cockroach.sql.sqlbase.Descriptor":
   391  				typ = "protobuf"
   392  			}
   393  
   394  			if otherMsg, ok := infos[typ]; ok {
   395  				// Inline the fields from the other messages here.
   396  				curMsg.InheritedFields = append(curMsg.InheritedFields, otherMsg.Fields...)
   397  				curMsg.AllFields = append(curMsg.AllFields, fieldInfo{
   398  					FieldType: typ,
   399  					FieldName: typ,
   400  					Inherited: true,
   401  				})
   402  			} else {
   403  				_, isEnum := enums[typ]
   404  
   405  				name := snakeToCamel(fieldDefRe.ReplaceAllString(line, "$name"))
   406  				alwayssafe := false
   407  				mixed := false
   408  				if nameOverride := fieldDefRe.ReplaceAllString(line, "$noverride"); nameOverride != "" {
   409  					name = nameOverride
   410  				}
   411  				// redact:"nonsensitive" - always safe for reporting.
   412  				if reportingSafe := fieldDefRe.ReplaceAllString(line, "$reportingsafe"); reportingSafe != "" {
   413  					alwayssafe = true
   414  					if reportingSafe == "mixed" {
   415  						mixed = true
   416  					}
   417  				}
   418  				// Certain types are also always safe for reporting.
   419  				if !alwayssafe && isSafeType(typ) {
   420  					alwayssafe = true
   421  				}
   422  				// redact:"safeif:<regexp>" - safe for reporting if the string matches the regexp.
   423  				safeReName := ""
   424  				if re := fieldDefRe.ReplaceAllString(line, "$safeif"); re != "" {
   425  					var err error
   426  					// We're reading the regular expression from the .proto source, so we must
   427  					// take care of string un-escaping ourselves. If this code ever improves
   428  					// to apply as a protobuf plugin, this step can be removed.
   429  					re, err = strconv.Unquote(`"` + re + `"`)
   430  					if err != nil {
   431  						return errors.Wrapf(err, "error while unquoting regexp at %q", line)
   432  					}
   433  					safeRe := "^" + re + "$"
   434  					// Syntax check on regexp.
   435  					_, err = regexp.Compile(safeRe)
   436  					if err != nil {
   437  						return errors.Wrapf(err, "regexp %s is invalid (%q)", re, line)
   438  					}
   439  					// We want to reuse the regexp variables across fields if the regexps are the same.
   440  					if n, ok := regexps.reToName[safeRe]; ok {
   441  						safeReName = n
   442  					} else {
   443  						regexps.reCnt++
   444  						safeReName = fmt.Sprintf("safeRe%d", regexps.reCnt)
   445  						regexps.reToName[safeRe] = safeReName
   446  						regexps.infos = append(regexps.infos, reInfo{ReName: safeReName, ReDef: safeRe})
   447  					}
   448  				}
   449  				fi := fieldInfo{
   450  					Comment:             comment,
   451  					FieldType:           typ,
   452  					FieldName:           name,
   453  					AlwaysReportingSafe: alwayssafe,
   454  					ReportingSafeRe:     safeReName,
   455  					MixedRedactable:     mixed,
   456  					IsEnum:              isEnum,
   457  					AllowZeroValue:      allowZeroValue,
   458  				}
   459  				curMsg.Fields = append(curMsg.Fields, fi)
   460  				curMsg.AllFields = append(curMsg.AllFields, fi)
   461  			}
   462  			comment = ""
   463  		}
   464  	}
   465  
   466  	return nil
   467  }
   468  
   469  func isSafeType(typ string) bool {
   470  	switch typ {
   471  	case "timestamp", "int32", "int64", "int16", "uint32", "uint64", "uint16", "bool", "float", "double":
   472  		return true
   473  	}
   474  	return false
   475  }
   476  
   477  var enumValDefRe = regexp.MustCompile(`\s*(?P<tag>[_A-Z0-9]+)[^=]*=[^0-9]*(?P<val>[0-9]+).*;`)
   478  
   479  var fieldDefRe = regexp.MustCompile(`\s*(?P<typ>[a-z._A-Z0-9]+)` +
   480  	`\s+(?P<name>[a-z_]+)` +
   481  	`(;|` +
   482  	`\s+(.*customname\) = "(?P<noverride>[A-Za-z]+)")?` +
   483  	`(.*"redact:\\"(?P<reportingsafe>nonsensitive|mixed)\\"")?` +
   484  	`(.*"redact:\\"safeif:(?P<safeif>([^\\]|\\[^"])+)\\"")?` +
   485  	`).*$`)
   486  
   487  var reservedDefRe = regexp.MustCompile(`\s*(reserved ([1-9][0-9]*);)`)
   488  
   489  func camelToSnake(typeName string) string {
   490  	var res strings.Builder
   491  	res.WriteByte(typeName[0] + 'a' - 'A')
   492  	for i := 1; i < len(typeName); i++ {
   493  		if typeName[i] >= 'A' && typeName[i] <= 'Z' {
   494  			res.WriteByte('_')
   495  			res.WriteByte(typeName[i] + 'a' - 'A')
   496  		} else {
   497  			res.WriteByte(typeName[i])
   498  		}
   499  	}
   500  	return res.String()
   501  }
   502  
   503  func snakeToCamel(typeName string) string {
   504  	var res strings.Builder
   505  	res.WriteByte(typeName[0] + 'A' - 'a')
   506  	for i := 1; i < len(typeName); i++ {
   507  		if typeName[i] == '_' {
   508  			i++
   509  			res.WriteByte(typeName[i] + 'A' - 'a')
   510  		} else {
   511  			res.WriteByte(typeName[i])
   512  		}
   513  	}
   514  	return res.String()
   515  }
   516  
   517  var templates = map[string]string{
   518  	"json_encode_go": `// Code generated by gen.go. DO NOT EDIT.
   519  
   520  package {{ .Package }}
   521  
   522  import (
   523    "strconv"{{ if .AllRegexps }}
   524    "regexp"{{end}}
   525  
   526    "github.com/cockroachdb/redact"
   527    "github.com/cockroachdb/cockroachdb-parser/pkg/util/jsonbytes"
   528  	"github.com/gogo/protobuf/jsonpb"
   529  )
   530  
   531  {{range .AllRegexps}}
   532  var {{ .ReName }} = regexp.MustCompile(` + "`{{ .ReDef }}`" + `)
   533  {{end}}
   534  var _ = jsonpb.Marshaler{}
   535  
   536  {{range .AllEvents}}
   537  // AppendJSONFields implements the EventPayload interface.
   538  func (m *{{.GoType}}) AppendJSONFields(printComma bool, b redact.RedactableBytes) (bool, redact.RedactableBytes) {
   539  {{range .AllFields }}
   540     {{if .Inherited -}}
   541     printComma, b = m.{{.FieldName}}.AppendJSONFields(printComma, b)
   542     {{- else if eq .FieldType "string" -}}
   543     {{ if not .AllowZeroValue -}}
   544     if m.{{.FieldName}} != "" {
   545     {{- end }}
   546       if printComma { b = append(b, ',')}; printComma = true
   547       b = append(b, "\"{{.FieldName}}\":\""...)
   548       {{ if .AlwaysReportingSafe -}}
   549       b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(m.{{.FieldName}})))
   550       {{- else if ne .ReportingSafeRe "" }}
   551       if {{ .ReportingSafeRe }}.MatchString(m.{{.FieldName}}) {
   552         b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(m.{{.FieldName}})))))
   553       } else {
   554         b = append(b, redact.StartMarker()...)
   555         b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(m.{{.FieldName}})))))
   556         b = append(b, redact.EndMarker()...)
   557       }
   558       {{- else -}}
   559       b = append(b, redact.StartMarker()...)
   560       b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(m.{{.FieldName}})))))
   561       b = append(b, redact.EndMarker()...)
   562       {{- end }}
   563       b = append(b, '"')
   564     {{ if not .AllowZeroValue -}}
   565     }
   566     {{- end }}
   567     {{- else if eq .FieldType "array_of_string" -}}
   568     if len(m.{{.FieldName}}) > 0 {
   569       if printComma { b = append(b, ',')}; printComma = true
   570       b = append(b, "\"{{.FieldName}}\":["...)
   571       for i, v := range m.{{.FieldName}} {
   572         if i > 0 { b = append(b, ',') }
   573         b = append(b, '"')
   574         {{ if .AlwaysReportingSafe -}}
   575         b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), v))
   576         {{- else if ne .ReportingSafeRe "" }}
   577         if {{ .ReportingSafeRe }}.MatchString(v) {
   578           b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(v)))))
   579         } else {
   580           b = append(b, redact.StartMarker()...)
   581           b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(v)))))
   582           b = append(b, redact.EndMarker()...)
   583         }
   584         {{- else -}}
   585         b = append(b, redact.StartMarker()...)
   586         b = redact.RedactableBytes(jsonbytes.EncodeString([]byte(b), string(redact.EscapeMarkers([]byte(v)))))
   587         b = append(b, redact.EndMarker()...)
   588         {{- end }}
   589         b = append(b, '"')
   590       }
   591       b = append(b, ']')
   592     }
   593     {{- else if eq .FieldType "bool" -}}
   594     {{ if not .AllowZeroValue -}}
   595     if m.{{.FieldName}} {
   596     {{- end }}
   597       if printComma { b = append(b, ',')}; printComma = true
   598       b = append(b, "\"{{.FieldName}}\":true"...)
   599     {{ if not .AllowZeroValue -}}
   600     }
   601     {{- end }}
   602     {{- else if eq .FieldType "int16" "int32" "int64"}}
   603     {{ if not .AllowZeroValue -}}
   604     if m.{{.FieldName}} != 0 {
   605     {{- end }}
   606       if printComma { b = append(b, ',')}; printComma = true
   607       b = append(b, "\"{{.FieldName}}\":"...)
   608       b = strconv.AppendInt(b, int64(m.{{.FieldName}}), 10)
   609     {{ if not .AllowZeroValue -}}
   610     }
   611     {{- end }}
   612     {{- else if eq .FieldType "float"}}
   613     {{ if not .AllowZeroValue -}}
   614     if m.{{.FieldName}} != 0 {
   615     {{- end }}
   616       if printComma { b = append(b, ',')}; printComma = true
   617       b = append(b, "\"{{.FieldName}}\":"...)
   618       b = strconv.AppendFloat(b, float64(m.{{.FieldName}}), 'f', -1, 32)
   619     {{ if not .AllowZeroValue -}}
   620     }
   621     {{- end }}
   622     {{- else if eq .FieldType "double"}}
   623     {{ if not .AllowZeroValue -}}
   624     if m.{{.FieldName}} != 0 {
   625     {{- end }}
   626       if printComma { b = append(b, ',')}; printComma = true
   627       b = append(b, "\"{{.FieldName}}\":"...)
   628       b = strconv.AppendFloat(b, float64(m.{{.FieldName}}), 'f', -1, 64)
   629     {{ if not .AllowZeroValue -}}
   630     }
   631     {{- end }}
   632     {{- else if eq .FieldType "uint16" "uint32" "uint64"}}
   633     {{ if not .AllowZeroValue -}}
   634     if m.{{.FieldName}} != 0 {
   635     {{- end }}
   636       if printComma { b = append(b, ',')}; printComma = true
   637       b = append(b, "\"{{.FieldName}}\":"...)
   638       b = strconv.AppendUint(b, uint64(m.{{.FieldName}}), 10)
   639     {{ if not .AllowZeroValue -}}
   640     }
   641     {{- end }}
   642     {{- else if eq .FieldType "array_of_uint32" -}}
   643     if len(m.{{.FieldName}}) > 0 {
   644       if printComma { b = append(b, ',')}; printComma = true
   645       b = append(b, "\"{{.FieldName}}\":["...)
   646       for i, v := range m.{{.FieldName}} {
   647         if i > 0 { b = append(b, ',') }
   648         b = strconv.AppendUint(b, uint64(v), 10)
   649       }
   650       b = append(b, ']')
   651     }
   652   	{{- else if eq .FieldType "array_of_int32" -}}
   653     if len(m.{{.FieldName}}) > 0 {
   654       if printComma { b = append(b, ',')}; printComma = true
   655       b = append(b, "\"{{.FieldName}}\":["...)
   656       for i, v := range m.{{.FieldName}} {
   657         if i > 0 { b = append(b, ',') }
   658         b = strconv.AppendInt(b, int64(v), 10)
   659       }
   660       b = append(b, ']')
   661     }
   662     {{- else if .IsEnum }}
   663     {{ if not .AllowZeroValue -}}
   664     if m.{{.FieldName}} != 0 {
   665     {{- end }}
   666       if printComma { b = append(b, ',')}; printComma = true
   667       b = append(b, "\"{{.FieldName}}\":"...)
   668       // Enums are defined in our code, so are always safe to print without
   669       // redaction.
   670       b = append(b, '"')
   671       b = append(b, m.{{.FieldName}}.String()...)
   672       b = append(b, '"')
   673     {{ if not .AllowZeroValue -}}
   674     }
   675     {{- end }}
   676     {{- else if eq .FieldType "array_of_LevelStats"}}
   677     if len(m.{{.FieldName}}) > 0 {
   678       if printComma { b = append(b, ',')}; printComma = true
   679       b = append(b, "\"{{.FieldName}}\":["...)
   680       for i, l := range m.{{.FieldName}} {
   681         if i > 0 { b = append(b, ',') }
   682         b = append(b, '{')
   683  			 printComma, b = l.AppendJSONFields(false, b)
   684         b = append(b, '}')
   685       }
   686       b = append(b, ']')
   687     }
   688     {{- else if eq .FieldType "protobuf"}}
   689     if m.{{.FieldName}} != nil {
   690       if printComma { b = append(b, ',')}; printComma = true
   691       jsonEncoder := jsonpb.Marshaler{}
   692       if str, err := jsonEncoder.MarshalToString(m.{{.FieldName}}); err == nil {
   693         b = append(b, "\"{{.FieldName}}\":"...)
   694         b = append(b, []byte(str)...)
   695       }
   696     }
   697     {{- else}}
   698     {{ error  .FieldType }}
   699     {{- end}}
   700  {{end}}
   701     return printComma, b
   702  }
   703  {{end}}
   704  
   705  
   706  `,
   707  
   708  	"eventlog_channels_go": `// Code generated by gen.go. DO NOT EDIT.
   709  
   710  package {{ .Package }}
   711  
   712  import "github.com/cockroachdb/cockroachdb-parser/pkg/util/log/logpb"
   713  
   714  {{range .Events}}
   715  // LoggingChannel implements the EventPayload interface.
   716  func (m *{{.GoType}}) LoggingChannel() logpb.Channel { return logpb.Channel_{{.LogChannel}} }
   717  {{end}}
   718  `,
   719  
   720  	"eventlog.md": `Certain notable events are reported using a structured format.
   721  Commonly, these notable events are also copied to the table
   722  ` + "`system.eventlog`" + `, unless the cluster setting
   723  ` + "`server.eventlog.enabled`" + ` is unset.
   724  
   725  Additionally, notable events are copied to specific external logging
   726  channels in log messages, where they can be collected for further processing.
   727  
   728  The sections below document the possible notable event types
   729  in this version of CockroachDB. For each event type, a table
   730  documents the possible fields. A field may be omitted from
   731  an event if its value is empty or zero.
   732  
   733  A field is also considered "Sensitive" if it may contain
   734  application-specific information or personally identifiable information (PII). In that case,
   735  the copy of the event sent to the external logging channel
   736  will contain redaction markers in a format that is compatible
   737  with the redaction facilities in ` + "[`cockroach debug zip`](cockroach-debug-zip.html)" + `
   738  and ` + "[`cockroach debug merge-logs`](cockroach-debug-merge-logs.html)" + `,
   739  provided the ` + "`redactable`" + ` functionality is enabled on the logging sink.
   740  
   741  Events not documented on this page will have an unstructured format in log messages.
   742  
   743  {{range .Categories -}}
   744  ## {{.Title}}
   745  
   746  {{.Comment}}
   747  
   748  Events in this category are logged to the ` + "`" + `{{.LogChannel}}` + "`" + ` channel.
   749  
   750  {{range .Events}}
   751  ### ` + "`" + `{{.Type}}` + "`" + `
   752  
   753  {{.Comment}}
   754  
   755  {{if .Fields -}}
   756  | Field | Description | Sensitive |
   757  |--|--|--|
   758  {{range .Fields -}}
   759  | ` + "`" + `{{- .FieldName -}}` + "`" + ` | {{ .Comment | tableCell }}{{- if .IsEnum }} See below for possible values for type ` + "`" + `{{- .FieldType -}}` + "`" + `.{{- end }} | {{ if .MixedRedactable }}partially{{ else if .AlwaysReportingSafe }}no{{else if ne .ReportingSafeRe "" }}depends{{else}}yes{{end}} |
   760  {{end}}
   761  {{- end}}
   762  
   763  {{if .InheritedFields -}}
   764  #### Common fields
   765  
   766  | Field | Description | Sensitive |
   767  |--|--|--|
   768  {{range .InheritedFields -}}
   769  | ` + "`" + `{{- .FieldName -}}` + "`" + ` | {{ .Comment | tableCell }} | {{ if .MixedRedactable }}partially{{ else if .AlwaysReportingSafe }}no{{else if ne .ReportingSafeRe "" }}depends{{else}}yes{{end}} |
   770  {{end}}
   771  {{- end}}
   772  
   773  {{- end}}
   774  {{end}}
   775  
   776  {{if .Enums}}
   777  ## Enumeration types
   778  {{range .Enums}}
   779  ### ` + "`" + `{{ .GoType }}` + "`" + `
   780  
   781  {{ .Comment }}
   782  
   783  | Value | Textual alias in code or documentation | Description |
   784  |--|--|--|
   785  {{range .Values -}}
   786  | {{ .Value }} | {{ .Name }} | {{ .Comment | tableCell }} |
   787  {{end}}
   788  {{end}}
   789  {{end}}
   790  `,
   791  }