diff --git a/CHANGES.md b/CHANGES.md index ad3f8e55..44848272 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ * Change the `volumes` subcommand to handle sizes in GB. * Add `--checks-grace-period` to set the grace period for health checks, for example with `koyeb service update app/service --checks 8000:http:/healtcheck --checks-grace-period 8000=10`. +* Add `--since` to `koyeb service logs`, `koyeb deployment logs` and `koyeb instance logs`. ## v4.2.0 diff --git a/docs/reference.md b/docs/reference.md index 80fb4406..f985a677 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1616,10 +1616,11 @@ koyeb services logs NAME [flags] ### Options ``` - -a, --app string Service application - -h, --help help for logs - --instance string Instance - -t, --type string Type (runtime, build) + -a, --app string Service application + -h, --help help for logs + --instance string Instance + --since HumanFriendlyDate Only return logs after this specific date (default 0001-01-01 00:00:00 +0000 UTC) + -t, --type string Type (runtime, build) ``` ### Options inherited from parent commands @@ -2035,8 +2036,9 @@ koyeb deployments logs NAME [flags] ### Options ``` - -h, --help help for logs - -t, --type string Type of log (runtime, build) + -h, --help help for logs + --since HumanFriendlyDate Only return logs after this specific date (default 0001-01-01 00:00:00 +0000 UTC) + -t, --type string Type of log (runtime, build) ``` ### Options inherited from parent commands @@ -2274,7 +2276,8 @@ koyeb instances logs NAME [flags] ### Options ``` - -h, --help help for logs + -h, --help help for logs + --since HumanFriendlyDate Only return logs after this specific date (default 0001-01-01 00:00:00 +0000 UTC) ``` ### Options inherited from parent commands diff --git a/pkg/koyeb/dates/cobra.go b/pkg/koyeb/dates/cobra.go new file mode 100644 index 00000000..4acffb4e --- /dev/null +++ b/pkg/koyeb/dates/cobra.go @@ -0,0 +1,38 @@ +package dates + +import ( + "time" + + "github.com/araddon/dateparse" +) + +// HumanFriendlyDate implements the pflag.Value interface to allow parsing dates in a human-friendly format. +type HumanFriendlyDate struct { + Time time.Time +} + +func (t *HumanFriendlyDate) String() string { + return t.Time.String() +} + +func (t *HumanFriendlyDate) Set(value string) error { + // Try to parse the date using RFC3339 format + if parsed, err := time.Parse(time.RFC3339, value); err == nil { + t.Time = parsed + return nil + } + + // Fall back to the dateparse library. Use dateparse.ParseStrict instead of + // dateparse.ParseAny to return an error in case of ambiguity, e.g. + // "01/02/03" could be interpreted as "January 2, 2003" or "February 1, 2003". + parsed, err := dateparse.ParseStrict(value) + if err != nil { + return err + } + t.Time = parsed + return nil +} + +func (t *HumanFriendlyDate) Type() string { + return "HumanFriendlyDate" +} diff --git a/pkg/koyeb/deployments.go b/pkg/koyeb/deployments.go index 00b7708e..6f8699d3 100644 --- a/pkg/koyeb/deployments.go +++ b/pkg/koyeb/deployments.go @@ -1,6 +1,7 @@ package koyeb import ( + "github.com/koyeb/koyeb-cli/pkg/koyeb/dates" "github.com/spf13/cobra" ) @@ -46,15 +47,19 @@ func NewDeploymentCmd() *cobra.Command { } deploymentCmd.AddCommand(cancelDeploymentCmd) + var since dates.HumanFriendlyDate logDeploymentCmd := &cobra.Command{ Use: "logs NAME", Aliases: []string{"l", "log"}, Short: "Get deployment logs", Args: cobra.ExactArgs(1), - RunE: WithCLIContext(h.Logs), + RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error { + return h.Logs(ctx, cmd, since.Time, args) + }), } deploymentCmd.AddCommand(logDeploymentCmd) logDeploymentCmd.Flags().StringP("type", "t", "", "Type of log (runtime, build)") + logDeploymentCmd.Flags().Var(&since, "since", "Only return logs after this specific date") return deploymentCmd } diff --git a/pkg/koyeb/deployments_logs.go b/pkg/koyeb/deployments_logs.go index 1a10abdb..20909243 100644 --- a/pkg/koyeb/deployments_logs.go +++ b/pkg/koyeb/deployments_logs.go @@ -2,12 +2,13 @@ package koyeb import ( "fmt" + "time" "github.com/koyeb/koyeb-cli/pkg/koyeb/errors" "github.com/spf13/cobra" ) -func (h *DeploymentHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []string) error { +func (h *DeploymentHandler) Logs(ctx *CLIContext, cmd *cobra.Command, since time.Time, args []string) error { deployment, err := h.ResolveDeploymentArgs(ctx, args[0]) if err != nil { return err @@ -27,6 +28,7 @@ func (h *DeploymentHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []str "", deploymentDetail.Deployment.GetId(), "", + since, GetBoolFlags(cmd, "full"), ) if err != nil { diff --git a/pkg/koyeb/instances.go b/pkg/koyeb/instances.go index cb717f5f..2e3aa384 100644 --- a/pkg/koyeb/instances.go +++ b/pkg/koyeb/instances.go @@ -1,6 +1,7 @@ package koyeb import ( + "github.com/koyeb/koyeb-cli/pkg/koyeb/dates" "github.com/spf13/cobra" ) @@ -57,13 +58,17 @@ func NewInstanceCmd() *cobra.Command { } instanceCmd.AddCommand(cpInstanceCmd) + var since dates.HumanFriendlyDate logInstanceCmd := &cobra.Command{ Use: "logs NAME", Aliases: []string{"l", "log"}, Short: "Get instance logs", Args: cobra.ExactArgs(1), - RunE: WithCLIContext(instanceHandler.Logs), + RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error { + return instanceHandler.Logs(ctx, cmd, since.Time, args) + }), } + logInstanceCmd.Flags().Var(&since, "since", "Only return logs after this specific date") instanceCmd.AddCommand(logInstanceCmd) return instanceCmd diff --git a/pkg/koyeb/instances_logs.go b/pkg/koyeb/instances_logs.go index f8a38737..cc6d50e2 100644 --- a/pkg/koyeb/instances_logs.go +++ b/pkg/koyeb/instances_logs.go @@ -2,12 +2,13 @@ package koyeb import ( "fmt" + "time" "github.com/koyeb/koyeb-cli/pkg/koyeb/errors" "github.com/spf13/cobra" ) -func (h *InstanceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []string) error { +func (h *InstanceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, since time.Time, args []string) error { instance, err := h.ResolveInstanceArgs(ctx, args[0]) if err != nil { return err @@ -26,6 +27,7 @@ func (h *InstanceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []strin "", "", instanceDetail.Instance.GetId(), + since, GetBoolFlags(cmd, "full"), ) if err != nil { diff --git a/pkg/koyeb/logs.go b/pkg/koyeb/logs.go index d6cf98cf..6f1f3e32 100644 --- a/pkg/koyeb/logs.go +++ b/pkg/koyeb/logs.go @@ -48,19 +48,21 @@ type WatchLogsQuery struct { serviceId string deploymentId string instanceId string + since time.Time conn *websocket.Conn ticker *time.Ticker full bool // Whether to display full IDs } func (client *LogsAPIClient) NewWatchLogsQuery( - logType string, serviceId string, deploymentId string, instanceId string, full bool, + logType string, serviceId string, deploymentId string, instanceId string, since time.Time, full bool, ) (*WatchLogsQuery, error) { query := &WatchLogsQuery{ client: client, serviceId: serviceId, deploymentId: deploymentId, instanceId: instanceId, + since: since, full: full, } switch logType { @@ -134,6 +136,9 @@ func (query *WatchLogsQuery) Execute() (chan WatchLogsEntry, error) { if query.instanceId != "" { queryParams.Add("instance_id", query.instanceId) } + if !query.since.IsZero() { + queryParams.Add("start", query.since.Format(time.RFC3339)) + } query.client.url.RawQuery = queryParams.Encode() conn, _, err := websocket.DefaultDialer.Dial(query.client.url.String(), query.client.header) diff --git a/pkg/koyeb/services.go b/pkg/koyeb/services.go index 2dbe4f71..9bf55bba 100644 --- a/pkg/koyeb/services.go +++ b/pkg/koyeb/services.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/koyeb/koyeb-api-client-go/api/v1/koyeb" + "github.com/koyeb/koyeb-cli/pkg/koyeb/dates" "github.com/koyeb/koyeb-cli/pkg/koyeb/errors" "github.com/koyeb/koyeb-cli/pkg/koyeb/flags_list" "github.com/sirupsen/logrus" @@ -75,16 +76,20 @@ $> koyeb service create myservice --app myapp --docker nginx --port 80:tcp getServiceCmd.Flags().StringP("app", "a", "", "Service application") serviceCmd.AddCommand(getServiceCmd) + var since dates.HumanFriendlyDate logsServiceCmd := &cobra.Command{ Use: "logs NAME", Aliases: []string{"l", "log"}, Short: "Get the service logs", Args: cobra.ExactArgs(1), - RunE: WithCLIContext(h.Logs), + RunE: WithCLIContext(func(ctx *CLIContext, cmd *cobra.Command, args []string) error { + return h.Logs(ctx, cmd, since.Time, args) + }), } logsServiceCmd.Flags().StringP("app", "a", "", "Service application") logsServiceCmd.Flags().String("instance", "", "Instance") logsServiceCmd.Flags().StringP("type", "t", "", "Type (runtime, build)") + logsServiceCmd.Flags().Var(&since, "since", "Only return logs after this specific date") serviceCmd.AddCommand(logsServiceCmd) listServiceCmd := &cobra.Command{ diff --git a/pkg/koyeb/services_logs.go b/pkg/koyeb/services_logs.go index d700cef5..36949726 100644 --- a/pkg/koyeb/services_logs.go +++ b/pkg/koyeb/services_logs.go @@ -2,6 +2,7 @@ package koyeb import ( "fmt" + "time" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -9,7 +10,7 @@ import ( "github.com/koyeb/koyeb-cli/pkg/koyeb/errors" ) -func (h *ServiceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []string) error { +func (h *ServiceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, since time.Time, args []string) error { serviceName, err := h.parseServiceName(cmd, args[0]) if err != nil { return err @@ -95,6 +96,7 @@ func (h *ServiceHandler) Logs(ctx *CLIContext, cmd *cobra.Command, args []string serviceId, deploymentId, instanceId, + since, GetBoolFlags(cmd, "full"), ) if err != nil {