Skip to content

Commit a0138c5

Browse files
committed
Add service log command
1 parent 6552315 commit a0138c5

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.14
55
require (
66
github.com/ghodss/yaml v1.0.0
77
github.com/go-openapi/runtime v0.19.11
8+
github.com/gorilla/websocket v1.4.0
89
github.com/iancoleman/strcase v0.1.3
910
github.com/koyeb/koyeb-api-client-go v0.0.0-20210612053838-a50d2c0e7f5a
1011
github.com/logrusorgru/aurora v2.0.3+incompatible

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
188188
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
189189
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
190190
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
191+
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
191192
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
192193
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
193194
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=

pkg/koyeb/service.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ package koyeb
33
import (
44
"context"
55
"fmt"
6+
"github.com/gorilla/websocket"
67
"github.com/koyeb/koyeb-api-client-go/api/v1/koyeb"
78
"github.com/logrusorgru/aurora"
89
"github.com/pkg/errors"
10+
log "github.com/sirupsen/logrus"
911
"github.com/spf13/cobra"
1012
"github.com/spf13/pflag"
13+
"net/http"
14+
"net/url"
1115
"strconv"
1216
"strings"
17+
"time"
1318
)
1419

1520
func addServiceDefinitionFlags(flags *pflag.FlagSet) {
@@ -162,6 +167,17 @@ func NewServiceCmd() *cobra.Command {
162167
}
163168
serviceCmd.AddCommand(getServiceCmd)
164169

170+
logsServiceCmd := &cobra.Command{
171+
Use: "logs [name]",
172+
Aliases: []string{"l", "log"},
173+
Short: "Get the service logs",
174+
Args: cobra.ExactArgs(1),
175+
RunE: h.Log,
176+
}
177+
serviceCmd.AddCommand(logsServiceCmd)
178+
logsServiceCmd.Flags().Bool("stderr", false, "Get stderr stream")
179+
logsServiceCmd.Flags().String("instance", "", "Instance")
180+
165181
listServiceCmd := &cobra.Command{
166182
Use: "list",
167183
Short: "List services",
@@ -260,6 +276,110 @@ func (h *ServiceHandler) Get(cmd *cobra.Command, args []string) error {
260276
return h.getFormat(cmd, args, format)
261277
}
262278

279+
func (h *ServiceHandler) Log(cmd *cobra.Command, args []string) error {
280+
client := getApiClient()
281+
ctx := getAuth(context.Background())
282+
app := getSelectedApp()
283+
_, _, err := client.ServicesApi.GetService(ctx, app, args[0]).Execute()
284+
if err != nil {
285+
fatalApiError(err)
286+
}
287+
revDetail, _, err := client.ServicesApi.GetRevision(ctx, app, args[0], "_latest").Execute()
288+
if err != nil {
289+
fatalApiError(err)
290+
}
291+
instances := revDetail.Revision.State.GetInstances()
292+
if len(instances) == 0 {
293+
log.Fatal("Unable to attach to instance")
294+
}
295+
instance := instances[0].GetId()
296+
selectedInstance, _ := cmd.Flags().GetString("instance")
297+
if selectedInstance != "" {
298+
instance = selectedInstance
299+
}
300+
done := make(chan struct{})
301+
stream := "stdout"
302+
stderr, _ := cmd.Flags().GetBool("stderr")
303+
if stderr {
304+
stream = "stderr"
305+
}
306+
return watchLog(app, args[0], revDetail.Revision.GetId(), instance, stream, done, "")
307+
}
308+
309+
type LogMessageResult struct {
310+
Msg string
311+
}
312+
313+
type LogMessage struct {
314+
Result LogMessageResult
315+
}
316+
317+
func (l LogMessage) String() string {
318+
return l.Result.Msg
319+
}
320+
321+
func watchLog(app string, service string, revision string, instance string, stream string, done chan struct{}, filter string) error {
322+
path := fmt.Sprintf("/v1/apps/%s/services/%s/revisions/%s/instances/%s/logs/%s/tail", app, service, revision, instance, stream)
323+
324+
u, err := url.Parse(apiurl)
325+
if err != nil {
326+
er(err)
327+
}
328+
329+
u.Path = path
330+
if u.Scheme == "https" {
331+
u.Scheme = "wss"
332+
} else {
333+
u.Scheme = "ws"
334+
}
335+
336+
if filter != "" {
337+
u.RawQuery = filter
338+
}
339+
340+
log.Debugf("Gettings logs from %v", u.String())
341+
342+
h := http.Header{"Sec-Websocket-Protocol": []string{fmt.Sprintf("Bearer, %s", token)}}
343+
c, _, err := websocket.DefaultDialer.Dial(u.String(), h)
344+
if err != nil {
345+
log.Fatal("dial:", err)
346+
}
347+
defer c.Close()
348+
349+
readDone := make(chan struct{})
350+
351+
go func() {
352+
defer close(done)
353+
for {
354+
msg := LogMessage{}
355+
err := c.ReadJSON(&msg)
356+
if err != nil {
357+
log.Println("error:", err)
358+
return
359+
}
360+
log.Printf("%s", msg)
361+
}
362+
}()
363+
364+
ticker := time.NewTicker(10 * time.Second)
365+
defer ticker.Stop()
366+
367+
for {
368+
select {
369+
case <-done:
370+
return nil
371+
case <-readDone:
372+
return nil
373+
case t := <-ticker.C:
374+
err := c.WriteMessage(websocket.PingMessage, []byte(t.String()))
375+
if err != nil {
376+
log.Println("write:", err)
377+
return err
378+
}
379+
}
380+
}
381+
}
382+
263383
func (h *ServiceHandler) Describe(cmd *cobra.Command, args []string) error {
264384
format := getFormat("detail")
265385
if len(args) == 0 {

0 commit comments

Comments
 (0)