app_logs.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package v2
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "os"
  7. "os/signal"
  8. "strings"
  9. "syscall"
  10. "time"
  11. "github.com/fatih/color"
  12. api "github.com/porter-dev/porter/api/client"
  13. "github.com/porter-dev/porter/cli/cmd/config"
  14. )
  15. // AppLogsInput is the input for the AppLogs function
  16. type AppLogsInput struct {
  17. // CLIConfig is the CLI configuration
  18. CLIConfig config.CLIConfig
  19. // Client is the Porter API client
  20. Client api.Client
  21. // DeploymentTargetName is the name of deployment target where the app is deployed
  22. DeploymentTargetName string
  23. // AppName is the name of the app to get logs for
  24. AppName string
  25. // ServiceName is an optional service name filter
  26. ServiceName string
  27. }
  28. // LogLine represents a single line of log output
  29. type LogLine struct {
  30. Line string `json:"line"`
  31. }
  32. // ServiceName_AllServices is a special value for ServiceName that indicates all services should be included
  33. const ServiceName_AllServices = "all"
  34. // AppLogs gets logs for an app
  35. func AppLogs(ctx context.Context, inp AppLogsInput) error {
  36. ctx, cancel := context.WithCancel(ctx)
  37. defer cancel()
  38. termChan := make(chan os.Signal, 1)
  39. signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
  40. color.New(color.FgGreen).Printf("Streaming logs for app %s...\n\n", inp.AppName) // nolint:errcheck,gosec
  41. conn, err := inp.Client.AppLogsStream(ctx, api.AppLogsInput{
  42. ProjectID: inp.CLIConfig.Project,
  43. ClusterID: inp.CLIConfig.Cluster,
  44. AppName: inp.AppName,
  45. DeploymentTargetName: inp.DeploymentTargetName,
  46. ServiceName: inp.ServiceName,
  47. })
  48. if err != nil {
  49. return fmt.Errorf("error connecting to app logs stream: %w", err)
  50. }
  51. defer conn.Close() // nolint:errcheck
  52. go func() {
  53. select {
  54. case <-termChan:
  55. color.New(color.FgYellow).Println("Shutdown signal received, canceling processes") // nolint:errcheck,gosec
  56. // ReadMessage will block until the next message is received, so we need to set a read deadline
  57. conn.SetReadDeadline(time.Now()) // nolint:errcheck,gosec
  58. cancel()
  59. case <-ctx.Done():
  60. }
  61. }()
  62. for {
  63. select {
  64. case <-ctx.Done():
  65. return ctx.Err()
  66. default:
  67. _, message, err := conn.ReadMessage()
  68. if err != nil {
  69. return fmt.Errorf("error reading message from app logs stream: %w", err)
  70. }
  71. if len(message) == 0 {
  72. return nil
  73. }
  74. lines := strings.Split(string(message), "\n")
  75. for _, l := range lines {
  76. var line LogLine
  77. err = json.Unmarshal([]byte(l), &line)
  78. if err != nil {
  79. // silently fail in case output is not properly formatted
  80. continue
  81. }
  82. message = append([]byte(line.Line), '\n')
  83. if _, err = os.Stdout.Write(message); err != nil {
  84. return nil
  85. }
  86. }
  87. }
  88. }
  89. }