github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/cmd/wazero/wazero.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"runtime/pprof"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/bananabytelabs/wazero"
    19  	"github.com/bananabytelabs/wazero/api"
    20  	"github.com/bananabytelabs/wazero/experimental"
    21  	"github.com/bananabytelabs/wazero/experimental/gojs"
    22  	"github.com/bananabytelabs/wazero/experimental/logging"
    23  	"github.com/bananabytelabs/wazero/experimental/opt"
    24  	"github.com/bananabytelabs/wazero/experimental/sock"
    25  	"github.com/bananabytelabs/wazero/experimental/sysfs"
    26  	"github.com/bananabytelabs/wazero/imports/wasi_snapshot_preview1"
    27  	"github.com/bananabytelabs/wazero/internal/platform"
    28  	internalsys "github.com/bananabytelabs/wazero/internal/sys"
    29  	"github.com/bananabytelabs/wazero/internal/version"
    30  	"github.com/bananabytelabs/wazero/sys"
    31  )
    32  
    33  func main() {
    34  	os.Exit(doMain(os.Stdout, os.Stderr))
    35  }
    36  
    37  // doMain is separated out for the purpose of unit testing.
    38  func doMain(stdOut io.Writer, stdErr logging.Writer) int {
    39  	flag.CommandLine.SetOutput(stdErr)
    40  
    41  	var help bool
    42  	flag.BoolVar(&help, "h", false, "Prints usage.")
    43  
    44  	flag.Parse()
    45  
    46  	if help || flag.NArg() == 0 {
    47  		printUsage(stdErr)
    48  		return 0
    49  	}
    50  
    51  	if flag.NArg() < 1 {
    52  		fmt.Fprintln(stdErr, "missing path to wasm file")
    53  		printUsage(stdErr)
    54  		return 1
    55  	}
    56  
    57  	subCmd := flag.Arg(0)
    58  	switch subCmd {
    59  	case "compile":
    60  		return doCompile(flag.Args()[1:], stdErr)
    61  	case "run":
    62  		return doRun(flag.Args()[1:], stdOut, stdErr)
    63  	case "version":
    64  		fmt.Fprintln(stdOut, version.GetWazeroVersion())
    65  		return 0
    66  	default:
    67  		fmt.Fprintln(stdErr, "invalid command")
    68  		printUsage(stdErr)
    69  		return 1
    70  	}
    71  }
    72  
    73  func doCompile(args []string, stdErr io.Writer) int {
    74  	flags := flag.NewFlagSet("compile", flag.ExitOnError)
    75  	flags.SetOutput(stdErr)
    76  
    77  	var help bool
    78  	flags.BoolVar(&help, "h", false, "Prints usage.")
    79  
    80  	var count int
    81  	var cpuProfile string
    82  	var memProfile string
    83  	if version.GetWazeroVersion() != version.Default {
    84  		count = 1
    85  	} else {
    86  		flags.IntVar(&count, "count", 1,
    87  			"Number of times to perform the compilation. This is useful to benchmark performance of the wazero compiler.")
    88  
    89  		flags.StringVar(&cpuProfile, "cpuprofile", "",
    90  			"Enables cpu profiling and writes the profile at the given path.")
    91  
    92  		flags.StringVar(&memProfile, "memprofile", "",
    93  			"Enables memory profiling and writes the profile at the given path.")
    94  	}
    95  
    96  	cacheDir := cacheDirFlag(flags)
    97  
    98  	_ = flags.Parse(args)
    99  
   100  	if help {
   101  		printCompileUsage(stdErr, flags)
   102  		return 0
   103  	}
   104  
   105  	if flags.NArg() < 1 {
   106  		fmt.Fprintln(stdErr, "missing path to wasm file")
   107  		printCompileUsage(stdErr, flags)
   108  		return 1
   109  	}
   110  
   111  	if memProfile != "" {
   112  		defer writeHeapProfile(stdErr, memProfile)
   113  	}
   114  
   115  	if cpuProfile != "" {
   116  		stopCPUProfile := startCPUProfile(stdErr, cpuProfile)
   117  		defer stopCPUProfile()
   118  	}
   119  
   120  	wasmPath := flags.Arg(0)
   121  
   122  	wasm, err := os.ReadFile(wasmPath)
   123  	if err != nil {
   124  		fmt.Fprintf(stdErr, "error reading wasm binary: %v\n", err)
   125  		return 1
   126  	}
   127  
   128  	c := wazero.NewRuntimeConfig()
   129  	if rc, cache := maybeUseCacheDir(cacheDir, stdErr); rc != 0 {
   130  		return rc
   131  	} else if cache != nil {
   132  		c = c.WithCompilationCache(cache)
   133  	}
   134  
   135  	ctx := context.Background()
   136  	rt := wazero.NewRuntimeWithConfig(ctx, c)
   137  	defer rt.Close(ctx)
   138  
   139  	for count > 0 {
   140  		compiledModule, err := rt.CompileModule(ctx, wasm)
   141  		if err != nil {
   142  			fmt.Fprintf(stdErr, "error compiling wasm binary: %v\n", err)
   143  			return 1
   144  		}
   145  		if err := compiledModule.Close(ctx); err != nil {
   146  			fmt.Fprintf(stdErr, "error releasing compiled module: %v\n", err)
   147  			return 1
   148  		}
   149  		count--
   150  	}
   151  	return 0
   152  }
   153  
   154  func doRun(args []string, stdOut io.Writer, stdErr logging.Writer) int {
   155  	flags := flag.NewFlagSet("run", flag.ExitOnError)
   156  	flags.SetOutput(stdErr)
   157  
   158  	var help bool
   159  	flags.BoolVar(&help, "h", false, "Prints usage.")
   160  
   161  	var useInterpreter bool
   162  	flags.BoolVar(&useInterpreter, "interpreter", false,
   163  		"Interprets WebAssembly modules instead of compiling them into native code.")
   164  
   165  	var useOptimizingCompiler bool
   166  	flags.BoolVar(&useOptimizingCompiler, "optimizing-compiler", false,
   167  		"[Experimental] Compiles WebAssembly modules using the optimizing compiler.")
   168  
   169  	var envs sliceFlag
   170  	flags.Var(&envs, "env", "key=value pair of environment variable to expose to the binary. "+
   171  		"Can be specified multiple times.")
   172  
   173  	var envInherit bool
   174  	flags.BoolVar(&envInherit, "env-inherit", false,
   175  		"Inherits any environment variables from the calling process. "+
   176  			"Variables specified with the <env> flag are appended to the inherited list.")
   177  
   178  	var mounts sliceFlag
   179  	flags.Var(&mounts, "mount",
   180  		"Filesystem path to expose to the binary in the form of <path>[:<wasm path>][:ro]. "+
   181  			"This may be specified multiple times. When <wasm path> is unset, <path> is used. "+
   182  			"For example, -mount=/:/ or c:\\:/ makes the entire host volume writeable by wasm. "+
   183  			"For read-only mounts, append the suffix ':ro'.")
   184  
   185  	var listens sliceFlag
   186  	flags.Var(&listens, "listen",
   187  		"Open a TCP socket on the specified address of the form <host:port>. "+
   188  			"This may be specified multiple times. Host is optional, and port may be 0 to "+
   189  			"indicate a random port.")
   190  
   191  	var timeout time.Duration
   192  	flags.DurationVar(&timeout, "timeout", 0*time.Second,
   193  		"If a wasm binary runs longer than the given duration string, then exit abruptly. "+
   194  			"The duration string is an unsigned sequence of decimal numbers, "+
   195  			"each with optional fraction and a unit suffix, such as \"300ms\", \"1.5h\" or \"2h45m\". "+
   196  			"Valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\". "+
   197  			"If the duration is 0, the timeout is disabled. The default is disabled.")
   198  
   199  	var hostlogging logScopesFlag
   200  	flags.Var(&hostlogging, "hostlogging",
   201  		"A comma-separated list of host function scopes to log to stderr. "+
   202  			"This may be specified multiple times. Supported values: all,clock,filesystem,memory,proc,poll,random,sock")
   203  
   204  	var cpuProfile string
   205  	var memProfile string
   206  	if version.GetWazeroVersion() == version.Default {
   207  		flags.StringVar(&cpuProfile, "cpuprofile", "",
   208  			"Enables cpu profiling and writes the profile at the given path.")
   209  
   210  		flags.StringVar(&memProfile, "memprofile", "",
   211  			"Enables memory profiling and writes the profile at the given path.")
   212  	}
   213  
   214  	cacheDir := cacheDirFlag(flags)
   215  
   216  	_ = flags.Parse(args)
   217  
   218  	if help {
   219  		printRunUsage(stdErr, flags)
   220  		return 0
   221  	}
   222  
   223  	if flags.NArg() < 1 {
   224  		fmt.Fprintln(stdErr, "missing path to wasm file")
   225  		printRunUsage(stdErr, flags)
   226  		return 1
   227  	}
   228  
   229  	if memProfile != "" {
   230  		defer writeHeapProfile(stdErr, memProfile)
   231  	}
   232  
   233  	if cpuProfile != "" {
   234  		stopCPUProfile := startCPUProfile(stdErr, cpuProfile)
   235  		defer stopCPUProfile()
   236  	}
   237  
   238  	wasmPath := flags.Arg(0)
   239  	wasmArgs := flags.Args()[1:]
   240  	if len(wasmArgs) > 1 {
   241  		// Skip "--" if provided
   242  		if wasmArgs[0] == "--" {
   243  			wasmArgs = wasmArgs[1:]
   244  		}
   245  	}
   246  
   247  	// Don't use map to preserve order
   248  	var env []string
   249  	if envInherit {
   250  		envs = append(os.Environ(), envs...)
   251  	}
   252  	for _, e := range envs {
   253  		fields := strings.SplitN(e, "=", 2)
   254  		if len(fields) != 2 {
   255  			fmt.Fprintf(stdErr, "invalid environment variable: %s\n", e)
   256  			return 1
   257  		}
   258  		env = append(env, fields[0], fields[1])
   259  	}
   260  
   261  	rc, rootPath, fsConfig := validateMounts(mounts, stdErr)
   262  	if rc != 0 {
   263  		return rc
   264  	}
   265  
   266  	wasm, err := os.ReadFile(wasmPath)
   267  	if err != nil {
   268  		fmt.Fprintf(stdErr, "error reading wasm binary: %v\n", err)
   269  		return 1
   270  	}
   271  
   272  	wasmExe := filepath.Base(wasmPath)
   273  
   274  	var rtc wazero.RuntimeConfig
   275  	if useInterpreter {
   276  		rtc = wazero.NewRuntimeConfigInterpreter()
   277  	} else if useOptimizingCompiler {
   278  		rtc = opt.NewRuntimeConfigOptimizingCompiler()
   279  	} else {
   280  		rtc = wazero.NewRuntimeConfig()
   281  	}
   282  
   283  	ctx := maybeHostLogging(context.Background(), logging.LogScopes(hostlogging), stdErr)
   284  
   285  	if rc, cache := maybeUseCacheDir(cacheDir, stdErr); rc != 0 {
   286  		return rc
   287  	} else if cache != nil {
   288  		rtc = rtc.WithCompilationCache(cache)
   289  	}
   290  
   291  	if timeout > 0 {
   292  		newCtx, cancel := context.WithTimeout(ctx, timeout)
   293  		ctx = newCtx
   294  		defer cancel()
   295  		rtc = rtc.WithCloseOnContextDone(true)
   296  	} else if timeout < 0 {
   297  		fmt.Fprintf(stdErr, "timeout duration may not be negative, %v given\n", timeout)
   298  		printRunUsage(stdErr, flags)
   299  		return 1
   300  	}
   301  
   302  	if rc, sockCfg := validateListens(listens, stdErr); rc != 0 {
   303  		return rc
   304  	} else {
   305  		ctx = sock.WithConfig(ctx, sockCfg)
   306  	}
   307  
   308  	rt := wazero.NewRuntimeWithConfig(ctx, rtc)
   309  	defer rt.Close(ctx)
   310  
   311  	// Because we are running a binary directly rather than embedding in an application,
   312  	// we default to wiring up commonly used OS functionality.
   313  	conf := wazero.NewModuleConfig().
   314  		WithStdout(stdOut).
   315  		WithStderr(stdErr).
   316  		WithStdin(os.Stdin).
   317  		WithRandSource(rand.Reader).
   318  		WithFSConfig(fsConfig).
   319  		WithSysNanosleep().
   320  		WithSysNanotime().
   321  		WithSysWalltime().
   322  		WithArgs(append([]string{wasmExe}, wasmArgs...)...)
   323  	for i := 0; i < len(env); i += 2 {
   324  		conf = conf.WithEnv(env[i], env[i+1])
   325  	}
   326  
   327  	guest, err := rt.CompileModule(ctx, wasm)
   328  	if err != nil {
   329  		fmt.Fprintf(stdErr, "error compiling wasm binary: %v\n", err)
   330  		return 1
   331  	}
   332  
   333  	switch detectImports(guest.ImportedFunctions()) {
   334  	case modeWasi:
   335  		wasi_snapshot_preview1.MustInstantiate(ctx, rt)
   336  		_, err = rt.InstantiateModule(ctx, guest, conf)
   337  	case modeWasiUnstable:
   338  		// Instantiate the current WASI functions under the wasi_unstable
   339  		// instead of wasi_snapshot_preview1.
   340  		wasiBuilder := rt.NewHostModuleBuilder("wasi_unstable")
   341  		wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder)
   342  		_, err = wasiBuilder.Instantiate(ctx)
   343  		if err == nil {
   344  			// Instantiate our binary, but using the old import names.
   345  			_, err = rt.InstantiateModule(ctx, guest, conf)
   346  		}
   347  	case modeGo:
   348  		// Fail fast on multiple mounts with the deprecated GOOS=js.
   349  		// GOOS=js will be removed in favor of GOOS=wasip1 once v1.22 is out.
   350  		if count := len(mounts); count > 1 || (count == 1 && rootPath == "") {
   351  			fmt.Fprintf(stdErr, "invalid mount: only root mounts supported in GOOS=js: %v\n"+
   352  				"Consider switching to GOOS=wasip1.\n", mounts)
   353  			return 1
   354  		}
   355  
   356  		gojs.MustInstantiate(ctx, rt, guest)
   357  
   358  		config := gojs.NewConfig(conf)
   359  
   360  		// Strip the volume of the path, for example C:\
   361  		rootDir := rootPath[len(filepath.VolumeName(rootPath)):]
   362  
   363  		// If the user mounted the entire filesystem, try to inherit the CWD.
   364  		// This is better than introducing a flag just for GOOS=js, especially
   365  		// as removing flags breaks syntax compat.
   366  		if platform.ToPosixPath(rootDir) == "/" {
   367  			config = config.WithOSWorkdir()
   368  		}
   369  
   370  		err = gojs.Run(ctx, rt, guest, config)
   371  	case modeDefault:
   372  		_, err = rt.InstantiateModule(ctx, guest, conf)
   373  	}
   374  
   375  	if err != nil {
   376  		if exitErr, ok := err.(*sys.ExitError); ok {
   377  			exitCode := exitErr.ExitCode()
   378  			if exitCode == sys.ExitCodeDeadlineExceeded {
   379  				fmt.Fprintf(stdErr, "error: %v (timeout %v)\n", exitErr, timeout)
   380  			}
   381  			return int(exitCode)
   382  		}
   383  		fmt.Fprintf(stdErr, "error instantiating wasm binary: %v\n", err)
   384  		return 1
   385  	}
   386  
   387  	// We're done, _start was called as part of instantiating the module.
   388  	return 0
   389  }
   390  
   391  func validateMounts(mounts sliceFlag, stdErr logging.Writer) (rc int, rootPath string, config wazero.FSConfig) {
   392  	config = wazero.NewFSConfig()
   393  	for _, mount := range mounts {
   394  		if len(mount) == 0 {
   395  			fmt.Fprintln(stdErr, "invalid mount: empty string")
   396  			return 1, rootPath, config
   397  		}
   398  
   399  		readOnly := false
   400  		if trimmed := strings.TrimSuffix(mount, ":ro"); trimmed != mount {
   401  			mount = trimmed
   402  			readOnly = true
   403  		}
   404  
   405  		// TODO: Support wasm paths with colon in them.
   406  		var dir, guestPath string
   407  		if clnIdx := strings.LastIndexByte(mount, ':'); clnIdx != -1 {
   408  			dir, guestPath = mount[:clnIdx], mount[clnIdx+1:]
   409  		} else {
   410  			dir = mount
   411  			guestPath = dir
   412  		}
   413  
   414  		// Eagerly validate the mounts as we know they should be on the host.
   415  		if abs, err := filepath.Abs(dir); err != nil {
   416  			fmt.Fprintf(stdErr, "invalid mount: path %q invalid: %v\n", dir, err)
   417  			return 1, rootPath, config
   418  		} else {
   419  			dir = abs
   420  		}
   421  
   422  		if stat, err := os.Stat(dir); err != nil {
   423  			fmt.Fprintf(stdErr, "invalid mount: path %q error: %v\n", dir, err)
   424  			return 1, rootPath, config
   425  		} else if !stat.IsDir() {
   426  			fmt.Fprintf(stdErr, "invalid mount: path %q is not a directory\n", dir)
   427  		}
   428  
   429  		root := sysfs.DirFS(dir)
   430  		if readOnly {
   431  			root = &sysfs.ReadFS{FS: root}
   432  		}
   433  
   434  		config = config.(sysfs.FSConfig).WithSysFSMount(root, guestPath)
   435  
   436  		if internalsys.StripPrefixesAndTrailingSlash(guestPath) == "" {
   437  			rootPath = dir
   438  		}
   439  	}
   440  	return 0, rootPath, config
   441  }
   442  
   443  // validateListens returns a non-nil net.Config, if there were any listen flags.
   444  func validateListens(listens sliceFlag, stdErr logging.Writer) (rc int, config sock.Config) {
   445  	for _, listen := range listens {
   446  		idx := strings.LastIndexByte(listen, ':')
   447  		if idx < 0 {
   448  			fmt.Fprintln(stdErr, "invalid listen")
   449  			return rc, config
   450  		}
   451  		port, err := strconv.Atoi(listen[idx+1:])
   452  		if err != nil {
   453  			fmt.Fprintln(stdErr, "invalid listen port:", err)
   454  			return rc, config
   455  		}
   456  		if config == nil {
   457  			config = sock.NewConfig()
   458  		}
   459  		config = config.WithTCPListener(listen[:idx], port)
   460  	}
   461  	return
   462  }
   463  
   464  const (
   465  	modeDefault importMode = iota
   466  	modeWasi
   467  	modeWasiUnstable
   468  	modeGo
   469  )
   470  
   471  type importMode uint
   472  
   473  func detectImports(imports []api.FunctionDefinition) importMode {
   474  	for _, f := range imports {
   475  		moduleName, _, _ := f.Import()
   476  		switch moduleName {
   477  		case wasi_snapshot_preview1.ModuleName:
   478  			return modeWasi
   479  		case "wasi_unstable":
   480  			return modeWasiUnstable
   481  		case "go", "gojs":
   482  			return modeGo
   483  		}
   484  	}
   485  	return modeDefault
   486  }
   487  
   488  func maybeHostLogging(ctx context.Context, scopes logging.LogScopes, stdErr logging.Writer) context.Context {
   489  	if scopes != 0 {
   490  		return context.WithValue(ctx, experimental.FunctionListenerFactoryKey{}, logging.NewHostLoggingListenerFactory(stdErr, scopes))
   491  	}
   492  	return ctx
   493  }
   494  
   495  func cacheDirFlag(flags *flag.FlagSet) *string {
   496  	return flags.String("cachedir", "", "Writeable directory for native code compiled from wasm. "+
   497  		"Contents are re-used for the same version of wazero.")
   498  }
   499  
   500  func maybeUseCacheDir(cacheDir *string, stdErr io.Writer) (int, wazero.CompilationCache) {
   501  	if dir := *cacheDir; dir != "" {
   502  		if cache, err := wazero.NewCompilationCacheWithDir(dir); err != nil {
   503  			fmt.Fprintf(stdErr, "invalid cachedir: %v\n", err)
   504  			return 1, cache
   505  		} else {
   506  			return 0, cache
   507  		}
   508  	}
   509  	return 0, nil
   510  }
   511  
   512  func printUsage(stdErr io.Writer) {
   513  	fmt.Fprintln(stdErr, "wazero CLI")
   514  	fmt.Fprintln(stdErr)
   515  	fmt.Fprintln(stdErr, "Usage:\n  wazero <command>")
   516  	fmt.Fprintln(stdErr)
   517  	fmt.Fprintln(stdErr, "Commands:")
   518  	fmt.Fprintln(stdErr, "  compile\tPre-compiles a WebAssembly binary")
   519  	fmt.Fprintln(stdErr, "  run\t\tRuns a WebAssembly binary")
   520  	fmt.Fprintln(stdErr, "  version\tDisplays the version of wazero CLI")
   521  }
   522  
   523  func printCompileUsage(stdErr io.Writer, flags *flag.FlagSet) {
   524  	fmt.Fprintln(stdErr, "wazero CLI")
   525  	fmt.Fprintln(stdErr)
   526  	fmt.Fprintln(stdErr, "Usage:\n  wazero compile <options> <path to wasm file>")
   527  	fmt.Fprintln(stdErr)
   528  	fmt.Fprintln(stdErr, "Options:")
   529  	flags.PrintDefaults()
   530  }
   531  
   532  func printRunUsage(stdErr io.Writer, flags *flag.FlagSet) {
   533  	fmt.Fprintln(stdErr, "wazero CLI")
   534  	fmt.Fprintln(stdErr)
   535  	fmt.Fprintln(stdErr, "Usage:\n  wazero run <options> <path to wasm file> [--] <wasm args>")
   536  	fmt.Fprintln(stdErr)
   537  	fmt.Fprintln(stdErr, "Options:")
   538  	flags.PrintDefaults()
   539  }
   540  
   541  func startCPUProfile(stdErr io.Writer, path string) (stopCPUProfile func()) {
   542  	f, err := os.Create(path)
   543  	if err != nil {
   544  		fmt.Fprintf(stdErr, "error creating cpu profile output: %v\n", err)
   545  		return func() {}
   546  	}
   547  
   548  	if err := pprof.StartCPUProfile(f); err != nil {
   549  		f.Close()
   550  		fmt.Fprintf(stdErr, "error starting cpu profile: %v\n", err)
   551  		return func() {}
   552  	}
   553  
   554  	return func() {
   555  		defer f.Close()
   556  		pprof.StopCPUProfile()
   557  	}
   558  }
   559  
   560  func writeHeapProfile(stdErr io.Writer, path string) {
   561  	f, err := os.Create(path)
   562  	if err != nil {
   563  		fmt.Fprintf(stdErr, "error creating memory profile output: %v\n", err)
   564  		return
   565  	}
   566  	defer f.Close()
   567  	runtime.GC()
   568  	if err := pprof.WriteHeapProfile(f); err != nil {
   569  		fmt.Fprintf(stdErr, "error writing memory profile: %v\n", err)
   570  	}
   571  }
   572  
   573  type sliceFlag []string
   574  
   575  func (f *sliceFlag) String() string {
   576  	return strings.Join(*f, ",")
   577  }
   578  
   579  func (f *sliceFlag) Set(s string) error {
   580  	*f = append(*f, s)
   581  	return nil
   582  }
   583  
   584  type logScopesFlag logging.LogScopes
   585  
   586  func (f *logScopesFlag) String() string {
   587  	return logging.LogScopes(*f).String()
   588  }
   589  
   590  func (f *logScopesFlag) Set(input string) error {
   591  	for _, s := range strings.Split(input, ",") {
   592  		switch s {
   593  		case "":
   594  			continue
   595  		case "all":
   596  			*f |= logScopesFlag(logging.LogScopeAll)
   597  		case "clock":
   598  			*f |= logScopesFlag(logging.LogScopeClock)
   599  		case "filesystem":
   600  			*f |= logScopesFlag(logging.LogScopeFilesystem)
   601  		case "memory":
   602  			*f |= logScopesFlag(logging.LogScopeMemory)
   603  		case "proc":
   604  			*f |= logScopesFlag(logging.LogScopeProc)
   605  		case "poll":
   606  			*f |= logScopesFlag(logging.LogScopePoll)
   607  		case "random":
   608  			*f |= logScopesFlag(logging.LogScopeRandom)
   609  		case "sock":
   610  			*f |= logScopesFlag(logging.LogScopeSock)
   611  		default:
   612  			return errors.New("not a log scope")
   613  		}
   614  	}
   615  	return nil
   616  }