github.com/ovsinc/prototool@v1.3.0/internal/cmd/templates.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package cmd
    22  
    23  import (
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"strings"
    28  
    29  	wordwrap "github.com/mitchellh/go-wordwrap"
    30  	"github.com/spf13/cobra"
    31  	"github.com/spf13/pflag"
    32  	"github.com/uber/prototool/internal/exec"
    33  	"go.uber.org/zap"
    34  	"go.uber.org/zap/zapcore"
    35  )
    36  
    37  const wordWrapLength uint = 80
    38  
    39  var (
    40  	allCmdTemplate = &cmdTemplate{
    41  		Use:   "all [dirOrFile]",
    42  		Short: "Compile, then format and overwrite, then re-compile and generate, then lint, stopping if any step fails.",
    43  		Args:  cobra.MaximumNArgs(1),
    44  		Run: func(runner exec.Runner, args []string, flags *flags) error {
    45  			return runner.All(args, flags.disableFormat, flags.disableLint, flags.fix)
    46  		},
    47  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
    48  			flags.bindConfigData(flagSet)
    49  			flags.bindDisableFormat(flagSet)
    50  			flags.bindDisableLint(flagSet)
    51  			flags.bindJSON(flagSet)
    52  			flags.bindFix(flagSet)
    53  			flags.bindProtocURL(flagSet)
    54  			flags.bindProtocBinPath(flagSet)
    55  			flags.bindProtocWKTPath(flagSet)
    56  		},
    57  	}
    58  
    59  	binaryToJSONCmdTemplate = &cmdTemplate{
    60  		Use:   "binary-to-json [dirOrFile] messagePath data",
    61  		Short: "Convert the data from json to binary for the message path and data.",
    62  		Args:  cobra.RangeArgs(2, 3),
    63  		Run: func(runner exec.Runner, args []string, flags *flags) error {
    64  			return runner.BinaryToJSON(args)
    65  		},
    66  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
    67  			flags.bindConfigData(flagSet)
    68  		},
    69  	}
    70  
    71  	cleanCmdTemplate = &cmdTemplate{
    72  		Use:   "clean",
    73  		Short: "Delete the cache.",
    74  		Args:  cobra.NoArgs,
    75  		Run: func(runner exec.Runner, args []string, flags *flags) error {
    76  			return runner.Clean()
    77  		},
    78  	}
    79  
    80  	compileCmdTemplate = &cmdTemplate{
    81  		Use:   "compile [dirOrFile]",
    82  		Short: "Compile with protoc to check for failures.",
    83  		Long:  `Stubs will not be generated. To generate stubs, use the "gen" command. Calling "compile" has the effect of calling protoc with "-o /dev/null".`,
    84  		Args:  cobra.MaximumNArgs(1),
    85  		Run: func(runner exec.Runner, args []string, flags *flags) error {
    86  			return runner.Compile(args, flags.dryRun)
    87  		},
    88  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
    89  			flags.bindConfigData(flagSet)
    90  			flags.bindDryRun(flagSet)
    91  			flags.bindJSON(flagSet)
    92  			flags.bindProtocURL(flagSet)
    93  			flags.bindProtocBinPath(flagSet)
    94  			flags.bindProtocWKTPath(flagSet)
    95  		},
    96  	}
    97  
    98  	createCmdTemplate = &cmdTemplate{
    99  		Use:   "create files...",
   100  		Short: "Create the given Protobuf files according to a template that passes default prototool lint.",
   101  		Long: `Assuming the filename "example_create_file.proto", the file will look like the following:
   102  
   103    syntax = "proto3";
   104  
   105    package SOME.PKG;
   106  
   107    option go_package = "PKGpb";
   108    option java_multiple_files = true;
   109    option java_outer_classname = "ExampleCreateFileProto";
   110    option java_package = "com.SOME.PKG.pb";
   111  
   112  This matches what the linter expects. "SOME.PKG" will be computed as follows:
   113  
   114  - If "--package" is specified, "SOME.PKG" will be the value passed to
   115    "--package".
   116  - Otherwise, if there is no "prototool.yaml" or "prototool.json" that would
   117    apply to the new file, use "uber.prototool.generated".
   118  - Otherwise, if there is a "prototool.yaml" or "prototool.json" file, check if
   119    it has a "packages" setting under the "create" section. If it does, this
   120    package, concatenated with the relative path from the directory with the
   121   "prototool.yaml" or "prototool.json" will be used.
   122  - Otherwise, if there is no "packages" directive, just use the
   123    relative path from the directory with the "prototool.yaml" or
   124    "prototool.json" file. If the file is in the same directory as the
   125    "prototool.yaml" or "prototool.json" file, use "uber.prototool.generated".
   126  
   127  For example, assume you have the following file at "repo/prototool.yaml":
   128  
   129  create:
   130    packages:
   131  	- directory: idl
   132  	  name: uber
   133  	- directory: idl/baz
   134  	  name: special
   135  
   136  - "prototool create repo/idl/foo/bar/bar.proto" will have the package
   137    "uber.foo.bar".
   138  - "prototool create repo/idl/bar.proto" will have the package "uber".
   139  - "prototool create repo/idl/baz/baz.proto" will have the package "special".
   140  - "prototool create repo/idl/baz/bat/bat.proto" will have the package
   141    "special.bat".
   142  - "prototool create repo/another/dir/bar.proto" will have the package
   143    "another.dir".
   144  - "prototool create repo/bar.proto" will have the package
   145    "uber.prototool.generated".
   146  
   147  This is meant to mimic what you generally want - a base package for your idl directory, followed by packages matching the directory structure.
   148  
   149  Note you can override the directory that the "prototool.yaml" or "prototool.json" file is in as well. If we update our file at "repo/prototool.yaml" to this:
   150  
   151  create:
   152    packages:
   153  	- directory: .
   154  	  name: foo.bar
   155  
   156  Then "prototool create repo/bar.proto" will have the package "foo.bar", and "prototool create repo/another/dir/bar.proto" will have the package "foo.bar.another.dir".
   157  
   158  If Vim integration is set up, files will be generated when you open a new Protobuf file.`,
   159  		Args: cobra.MinimumNArgs(1),
   160  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   161  			return runner.Create(args, flags.pkg)
   162  		},
   163  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   164  			flags.bindConfigData(flagSet)
   165  			flags.bindPackage(flagSet)
   166  		},
   167  	}
   168  
   169  	descriptorProtoCmdTemplate = &cmdTemplate{
   170  		Use:   "descriptor-proto [dirOrFile] messagePath",
   171  		Short: "Get the descriptor proto for the message path.",
   172  		Args:  cobra.MaximumNArgs(2),
   173  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   174  			return runner.DescriptorProto(args)
   175  		},
   176  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   177  			flags.bindConfigData(flagSet)
   178  		},
   179  	}
   180  
   181  	downloadCmdTemplate = &cmdTemplate{
   182  		Use:   "download",
   183  		Short: "Download the protobuf artifacts to a cache.",
   184  		Args:  cobra.NoArgs,
   185  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   186  			return runner.Download()
   187  		},
   188  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   189  			flags.bindConfigData(flagSet)
   190  		},
   191  	}
   192  
   193  	fieldDescriptorProtoCmdTemplate = &cmdTemplate{
   194  		Use:   "field-descriptor-proto [dirOrFile] fieldPath",
   195  		Short: "Get the field descriptor proto for the field path.",
   196  		Args:  cobra.RangeArgs(1, 2),
   197  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   198  			return runner.FieldDescriptorProto(args)
   199  		},
   200  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   201  			flags.bindConfigData(flagSet)
   202  		},
   203  	}
   204  
   205  	filesCmdTemplate = &cmdTemplate{
   206  		Use:   "files [dirOrFile]",
   207  		Short: "Print all files that match the input arguments.",
   208  		Args:  cobra.MaximumNArgs(1),
   209  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   210  			return runner.Files(args)
   211  		},
   212  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   213  			flags.bindConfigData(flagSet)
   214  		},
   215  	}
   216  
   217  	formatCmdTemplate = &cmdTemplate{
   218  		Use:   "format [dirOrFile]",
   219  		Short: "Format a proto file and compile with protoc to check for failures.",
   220  		Args:  cobra.MaximumNArgs(1),
   221  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   222  			return runner.Format(args, flags.overwrite, flags.diffMode, flags.lintMode, flags.fix)
   223  		},
   224  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   225  			flags.bindConfigData(flagSet)
   226  			flags.bindDiffMode(flagSet)
   227  			flags.bindJSON(flagSet)
   228  			flags.bindLintMode(flagSet)
   229  			flags.bindOverwrite(flagSet)
   230  			flags.bindFix(flagSet)
   231  			flags.bindProtocURL(flagSet)
   232  			flags.bindProtocBinPath(flagSet)
   233  			flags.bindProtocWKTPath(flagSet)
   234  		},
   235  	}
   236  
   237  	generateCmdTemplate = &cmdTemplate{
   238  		Use:   "generate [dirOrFile]",
   239  		Short: "Generate with protoc.",
   240  		Args:  cobra.MaximumNArgs(1),
   241  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   242  			return runner.Gen(args, flags.dryRun)
   243  		},
   244  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   245  			flags.bindConfigData(flagSet)
   246  			flags.bindDryRun(flagSet)
   247  			flags.bindJSON(flagSet)
   248  			flags.bindProtocURL(flagSet)
   249  			flags.bindProtocBinPath(flagSet)
   250  			flags.bindProtocWKTPath(flagSet)
   251  		},
   252  	}
   253  
   254  	grpcCmdTemplate = &cmdTemplate{
   255  		Use:   "grpc [dirOrFile]",
   256  		Short: "Call a gRPC endpoint. Be sure to set required flags address, method, and either data or stdin.",
   257  		Long: `This command compiles your proto files with "protoc", converts JSON input to binary and converts the result from binary to JSON. All these steps take on the order of milliseconds. For example, the overhead for a file with four dependencies is about 30ms, so there is little overhead for CLI calls to gRPC.
   258  
   259  There is a full example for gRPC in the example directory of Prototool. Run "make init example" to make sure everything is installed and generated.
   260  
   261  Start the example server in a separate terminal by doing "go run example/cmd/excited/main.go".
   262  
   263  prototool grpc [dirOrFile] \
   264    --address serverAddress \
   265    --method package.service/Method \
   266    --data 'requestData'
   267  
   268  Either use "--data 'requestData'" as the the JSON data to input, or "--stdin" which will result in the input being read from stdin as JSON.
   269  
   270  $ make init example # make sure everything is built just in case
   271  
   272  $ prototool grpc example \
   273    --address 0.0.0.0:8080 \
   274    --method foo.ExcitedService/Exclamation \
   275    --data '{"value":"hello"}'
   276  {
   277    "value": "hello!"
   278  }
   279  
   280  $ prototool grpc example \
   281    --address 0.0.0.0:8080 \
   282    --method foo.ExcitedService/ExclamationServerStream \
   283    --data '{"value":"hello"}'
   284  {
   285    "value": "h"
   286  }
   287  {
   288    "value": "e"
   289  }
   290  {
   291    "value": "l"
   292  }
   293  {
   294    "value": "l"
   295  }
   296  {
   297    "value": "o"
   298  }
   299  {
   300    "value": "!"
   301  }
   302  
   303  $ cat input.json
   304  {"value":"hello"}
   305  {"value":"salutations"}
   306  
   307  $ cat input.json | prototool grpc example \
   308    --address 0.0.0.0:8080 \
   309    --method foo.ExcitedService/ExclamationClientStream \
   310    --stdin
   311  {
   312    "value": "hellosalutations!"
   313  }
   314  
   315  $ cat input.json | prototool grpc example \
   316    --address 0.0.0.0:8080 \
   317    --method foo.ExcitedService/ExclamationBidiStream \
   318    --stdin
   319  {
   320    "value": "hello!"
   321  }
   322  {
   323    "value": "salutations!"
   324  }`,
   325  		Args: cobra.MaximumNArgs(1),
   326  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   327  			return runner.GRPC(args, flags.headers, flags.address, flags.method, flags.data, flags.callTimeout, flags.connectTimeout, flags.keepaliveTime, flags.stdin)
   328  		},
   329  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   330  			flags.bindConfigData(flagSet)
   331  			flags.bindAddress(flagSet)
   332  			flags.bindCallTimeout(flagSet)
   333  			flags.bindConnectTimeout(flagSet)
   334  			flags.bindData(flagSet)
   335  			flags.bindHeaders(flagSet)
   336  			flags.bindKeepaliveTime(flagSet)
   337  			flags.bindMethod(flagSet)
   338  			flags.bindStdin(flagSet)
   339  			flags.bindProtocURL(flagSet)
   340  			flags.bindProtocBinPath(flagSet)
   341  			flags.bindProtocWKTPath(flagSet)
   342  		},
   343  	}
   344  
   345  	configInitCmdTemplate = &cmdTemplate{
   346  		Use:   "init [dirPath]",
   347  		Short: "Generate an initial config file in the current or given directory.",
   348  		Long:  `All available options will be generated and commented out except for "protoc.version". Pass the "--uncomment" flag to uncomment all options.`,
   349  		Args:  cobra.MaximumNArgs(1),
   350  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   351  			return runner.Init(args, flags.uncomment)
   352  		},
   353  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   354  			flags.bindUncomment(flagSet)
   355  		},
   356  	}
   357  
   358  	jsonToBinaryCmdTemplate = &cmdTemplate{
   359  		Use:   "json-to-binary [dirOrFile] messagePath data",
   360  		Short: "Convert the data from json to binary for the message path and data.",
   361  		Args:  cobra.RangeArgs(2, 3),
   362  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   363  			return runner.JSONToBinary(args)
   364  		},
   365  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   366  			flags.bindConfigData(flagSet)
   367  		},
   368  	}
   369  
   370  	lintCmdTemplate = &cmdTemplate{
   371  		Use:   "lint [dirOrFile]",
   372  		Short: "Lint proto files and compile with protoc to check for failures.",
   373  		Long:  `The default rule set follows the Style Guide at https://github.com/uber/prototool/blob/master/etc/style/uber/uber.proto. You can add or exclude lint rules in your configuration file. The default rule set is very strict and is meant to enforce consistent development patterns.`,
   374  		Args:  cobra.MaximumNArgs(1),
   375  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   376  			return runner.Lint(args, flags.listAllLinters, flags.listLinters)
   377  		},
   378  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   379  			flags.bindConfigData(flagSet)
   380  			flags.bindJSON(flagSet)
   381  			flags.bindListAllLinters(flagSet)
   382  			flags.bindListLinters(flagSet)
   383  			flags.bindProtocURL(flagSet)
   384  			flags.bindProtocBinPath(flagSet)
   385  			flags.bindProtocWKTPath(flagSet)
   386  		},
   387  	}
   388  
   389  	listAllLintGroupsCmdTemplate = &cmdTemplate{
   390  		Use:   "list-all-lint-groups",
   391  		Short: "List all the available lint groups.",
   392  		Args:  cobra.NoArgs,
   393  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   394  			return runner.ListAllLintGroups()
   395  		},
   396  	}
   397  
   398  	listLintGroupCmdTemplate = &cmdTemplate{
   399  		Use:   "list-lint-group group",
   400  		Short: "List the linters in the given lint group.",
   401  		Args:  cobra.ExactArgs(1),
   402  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   403  			return runner.ListLintGroup(args[0])
   404  		},
   405  	}
   406  
   407  	serviceDescriptorProtoCmdTemplate = &cmdTemplate{
   408  		Use:   "service-descriptor-proto [dirOrFile] servicePath",
   409  		Short: "Get the service descriptor proto for the service path.",
   410  		Args:  cobra.RangeArgs(1, 2),
   411  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   412  			return runner.ServiceDescriptorProto(args)
   413  		},
   414  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   415  			flags.bindConfigData(flagSet)
   416  		},
   417  	}
   418  
   419  	versionCmdTemplate = &cmdTemplate{
   420  		Use:   "version",
   421  		Short: "Print the version.",
   422  		Args:  cobra.NoArgs,
   423  		BindFlags: func(flagSet *pflag.FlagSet, flags *flags) {
   424  			flags.bindJSON(flagSet)
   425  		},
   426  		Run: func(runner exec.Runner, args []string, flags *flags) error {
   427  			return runner.Version()
   428  		},
   429  	}
   430  )
   431  
   432  // cmdTemplate contains the static parts of a cobra.Command such as
   433  // documentation that we want to store outside of runtime creation.
   434  //
   435  // We do not just store cobra.Commands as in theory they have fields
   436  // with types such as slices that if we were to return a blind copy,
   437  // would mean that both the global cmdTemplate and the runtime
   438  // cobra.Command would point to the same location. By making a new
   439  // struct, we can also do more fancy templating things like prepending
   440  // the Short description to the Long description for consistency, and
   441  // have our own abstractions for the Run command.
   442  type cmdTemplate struct {
   443  	// Use is the one-line usage message.
   444  	// This field is required.
   445  	Use string
   446  	// Short is the short description shown in the 'help' output.
   447  	// This field is required.
   448  	Short string
   449  	// Long is the long message shown in the 'help <this-command>' output.
   450  	// The Short field will be prepended to the Long field with a newline
   451  	// when applied to a *cobra.Command.
   452  	// This field is optional.
   453  	Long string
   454  	// Expected arguments.
   455  	// This field is optional.
   456  	Args cobra.PositionalArgs
   457  	// Run is the command to run given an exec.Runner, args, and flags.
   458  	// This field is required.
   459  	Run func(exec.Runner, []string, *flags) error
   460  	// BindFlags binds flags to the *pflag.FlagSet on Build.
   461  	// There is no corollary to this on *cobra.Command.
   462  	// This field is optional, although usually will be set.
   463  	// We need to do this before run as the flags are populated
   464  	// before Run is called.
   465  	BindFlags func(*pflag.FlagSet, *flags)
   466  }
   467  
   468  // Build builds a *cobra.Command from the cmdTemplate.
   469  func (c *cmdTemplate) Build(exitCodeAddr *int, stdin io.Reader, stdout io.Writer, stderr io.Writer, flags *flags) *cobra.Command {
   470  	command := &cobra.Command{}
   471  	command.Use = c.Use
   472  	command.Short = strings.TrimSpace(c.Short)
   473  	if c.Long != "" {
   474  		command.Long = wordwrap.WrapString(fmt.Sprintf("%s\n\n%s", strings.TrimSpace(c.Short), strings.TrimSpace(c.Long)), wordWrapLength)
   475  	}
   476  	command.Args = c.Args
   477  	command.Run = func(_ *cobra.Command, args []string) {
   478  		checkCmd(exitCodeAddr, stdin, stdout, stderr, args, flags, c.Run)
   479  	}
   480  	if c.BindFlags != nil {
   481  		c.BindFlags(command.PersistentFlags(), flags)
   482  	}
   483  	return command
   484  }
   485  
   486  func checkCmd(exitCodeAddr *int, stdin io.Reader, stdout io.Writer, stderr io.Writer, args []string, flags *flags, f func(exec.Runner, []string, *flags) error) {
   487  	runner, err := getRunner(stdin, stdout, stderr, flags)
   488  	if err != nil {
   489  		*exitCodeAddr = printAndGetErrorExitCode(err, stdout)
   490  		return
   491  	}
   492  	if err := f(runner, args, flags); err != nil {
   493  		*exitCodeAddr = printAndGetErrorExitCode(err, stdout)
   494  	}
   495  }
   496  
   497  func getRunner(stdin io.Reader, stdout io.Writer, stderr io.Writer, flags *flags) (exec.Runner, error) {
   498  	logger, err := getLogger(stderr, flags.debug)
   499  	if err != nil {
   500  		return nil, err
   501  	}
   502  	runnerOptions := []exec.RunnerOption{
   503  		exec.RunnerWithLogger(logger),
   504  	}
   505  	if flags.cachePath != "" {
   506  		runnerOptions = append(
   507  			runnerOptions,
   508  			exec.RunnerWithCachePath(flags.cachePath),
   509  		)
   510  	}
   511  	if flags.configData != "" {
   512  		runnerOptions = append(
   513  			runnerOptions,
   514  			exec.RunnerWithConfigData(flags.configData),
   515  		)
   516  	}
   517  	if flags.json {
   518  		runnerOptions = append(
   519  			runnerOptions,
   520  			exec.RunnerWithJSON(),
   521  		)
   522  	}
   523  	if flags.protocBinPath != "" {
   524  		runnerOptions = append(
   525  			runnerOptions,
   526  			exec.RunnerWithProtocBinPath(flags.protocBinPath),
   527  		)
   528  	}
   529  	if flags.protocWKTPath != "" {
   530  		runnerOptions = append(
   531  			runnerOptions,
   532  			exec.RunnerWithProtocWKTPath(flags.protocWKTPath),
   533  		)
   534  	}
   535  	if flags.printFields != "" {
   536  		runnerOptions = append(
   537  			runnerOptions,
   538  			exec.RunnerWithPrintFields(flags.printFields),
   539  		)
   540  	}
   541  	if flags.protocURL != "" {
   542  		runnerOptions = append(
   543  			runnerOptions,
   544  			exec.RunnerWithProtocURL(flags.protocURL),
   545  		)
   546  	}
   547  	workDirPath, err := os.Getwd()
   548  	if err != nil {
   549  		return nil, err
   550  	}
   551  	return exec.NewRunner(workDirPath, stdin, stdout, runnerOptions...), nil
   552  }
   553  
   554  func getLogger(stderr io.Writer, debug bool) (*zap.Logger, error) {
   555  	level := zapcore.InfoLevel
   556  	if debug {
   557  		level = zapcore.DebugLevel
   558  	}
   559  	return zap.New(
   560  		zapcore.NewCore(
   561  			zapcore.NewConsoleEncoder(
   562  				zap.NewDevelopmentEncoderConfig(),
   563  			),
   564  			zapcore.Lock(zapcore.AddSync(stderr)),
   565  			zap.NewAtomicLevelAt(level),
   566  		),
   567  	), nil
   568  }