| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package main includes the main function for running service as an executable. |
| package main |
| |
| import ( |
| "context" |
| "fmt" |
| "log/slog" |
| "os" |
| "os/signal" |
| "strconv" |
| "strings" |
| |
| "go.chromium.org/chromiumos/platform/btpeerd/server" |
| ) |
| |
| const defaultGRPCServerPort = 8100 |
| const defaultLogLevel = slog.LevelInfo |
| |
| // runArgs are runtime arguments that can be set with positional arguments in |
| // the format <key>=<value> and is case-insensitive. |
| type runArgs struct { |
| // ListenPort is the port the gRPC server shall listen on (ListenPort=8100). |
| ListenPort int |
| |
| // LogLevel is the level for the logger (LogLevel=INFO). |
| LogLevel slog.Level |
| } |
| |
| func main() { |
| parsedArgs, err := parseRunArgs() |
| if err != nil { |
| fmt.Printf("Failed to parse run args: %v\n", err) |
| os.Exit(1) |
| } |
| configureDefaultLogger(parsedArgs.LogLevel) |
| slog.Info("Starting btpeerd", "runArgs", parsedArgs) |
| // Create context that will cancel when a SIGINT signal is received. |
| ctx, cancel := context.WithCancel(context.Background()) |
| interruptSignalChannel := make(chan os.Signal, 1) |
| signal.Notify(interruptSignalChannel, os.Interrupt) |
| defer func() { |
| signal.Stop(interruptSignalChannel) |
| cancel() |
| }() |
| go func() { |
| select { |
| case <-interruptSignalChannel: |
| slog.Warn("Received SIGINT, shutting down server") |
| cancel() |
| case <-ctx.Done(): |
| } |
| }() |
| |
| // Start the gRPC server. |
| if err := server.RunGRPCServer(ctx, parsedArgs.ListenPort); err != nil { |
| slog.Error("Failed to run gRPC server", "err", err) |
| } |
| } |
| |
| // parseRunArgs parses simple CLI run arguments in the format <key>=<value>. |
| func parseRunArgs() (*runArgs, error) { |
| parsedArgs := &runArgs{ |
| ListenPort: defaultGRPCServerPort, |
| LogLevel: defaultLogLevel, |
| } |
| for _, arg := range os.Args[1:] { |
| argParts := strings.Split(arg, "=") |
| if len(argParts) != 2 { |
| return nil, fmt.Errorf("invalid argument %q, must be in format <key>=<value>", arg) |
| } |
| argKey := strings.ToLower(argParts[0]) |
| argKey = strings.TrimPrefix(argKey, "--") |
| argValue := argParts[1] |
| switch argKey { |
| case "listenport": |
| port, err := strconv.Atoi(argValue) |
| if err != nil { |
| return nil, fmt.Errorf("invalid argument %q: value %q is not an integer", argKey, argValue) |
| } |
| parsedArgs.ListenPort = port |
| case "loglevel": |
| argValue = strings.ToUpper(argValue) |
| switch argValue { |
| case "DEBUG": |
| parsedArgs.LogLevel = slog.LevelDebug |
| case "INFO": |
| parsedArgs.LogLevel = slog.LevelInfo |
| case "WARN": |
| parsedArgs.LogLevel = slog.LevelWarn |
| case "ERROR": |
| parsedArgs.LogLevel = slog.LevelError |
| default: |
| return nil, fmt.Errorf("invalid argument %q: value %q is not one of the supported log levels (DEBUG, INFO, WARN, or ERROR)", argKey, argValue) |
| } |
| } |
| } |
| return parsedArgs, nil |
| } |
| |
| func configureDefaultLogger(logLevel slog.Level) { |
| handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ |
| Level: logLevel, |
| }) |
| slog.SetDefault(slog.New(handler)) |
| } |