2019-01-12 04:58:27 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package logs
import (
2019-04-07 17:07:55 +00:00
"bufio"
2019-01-12 04:58:27 +00:00
"errors"
"fmt"
"io"
2019-04-07 17:07:55 +00:00
"sync"
2019-01-12 04:58:27 +00:00
"time"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest"
2019-09-27 21:51:53 +00:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2019-01-12 04:58:27 +00:00
)
const (
logsUsageStr = "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER]"
)
var (
logsExample = templates . Examples ( i18n . T ( `
# Return snapshot logs from pod nginx with only one container
kubectl logs nginx
# Return snapshot logs from pod nginx with multi containers
kubectl logs nginx -- all - containers = true
# Return snapshot logs from all containers in pods defined by label app = nginx
kubectl logs - lapp = nginx -- all - containers = true
# Return snapshot of previous terminated ruby container logs from pod web - 1
kubectl logs - p - c ruby web - 1
# Begin streaming the logs of the ruby container in pod web - 1
kubectl logs - f - c ruby web - 1
2019-04-07 17:07:55 +00:00
# Begin streaming the logs from all containers in pods defined by label app = nginx
kubectl logs - f - lapp = nginx -- all - containers = true
2019-01-12 04:58:27 +00:00
# Display only the most recent 20 lines of output in pod nginx
kubectl logs -- tail = 20 nginx
# Show all logs from pod nginx written in the last hour
kubectl logs -- since = 1 h nginx
# Return snapshot logs from first container of a job named hello
kubectl logs job / hello
# Return snapshot logs from container nginx - 1 of a deployment named nginx
kubectl logs deployment / nginx - c nginx - 1 ` ) )
selectorTail int64 = 10
logsUsageErrStr = fmt . Sprintf ( "expected '%s'.\nPOD or TYPE/NAME is a required argument for the logs command" , logsUsageStr )
)
const (
defaultPodLogsTimeout = 20 * time . Second
)
type LogsOptions struct {
Namespace string
ResourceArg string
AllContainers bool
Options runtime . Object
Resources [ ] string
2019-04-07 17:07:55 +00:00
ConsumeRequestFn func ( rest . ResponseWrapper , io . Writer ) error
2019-01-12 04:58:27 +00:00
// PodLogOptions
2019-08-30 18:33:25 +00:00
SinceTime string
SinceSeconds time . Duration
Follow bool
Previous bool
Timestamps bool
IgnoreLogErrors bool
LimitBytes int64
Tail int64
Container string
2019-01-12 04:58:27 +00:00
// whether or not a container name was given via --container
ContainerNameSpecified bool
Selector string
2019-09-27 21:51:53 +00:00
MaxFollowConcurrency int
2019-01-12 04:58:27 +00:00
Object runtime . Object
GetPodTimeout time . Duration
RESTClientGetter genericclioptions . RESTClientGetter
LogsForObject polymorphichelpers . LogsForObjectFunc
genericclioptions . IOStreams
2019-09-27 21:51:53 +00:00
TailSpecified bool
2019-01-12 04:58:27 +00:00
}
func NewLogsOptions ( streams genericclioptions . IOStreams , allContainers bool ) * LogsOptions {
return & LogsOptions {
2019-09-27 21:51:53 +00:00
IOStreams : streams ,
AllContainers : allContainers ,
Tail : - 1 ,
MaxFollowConcurrency : 5 ,
2019-01-12 04:58:27 +00:00
}
}
// NewCmdLogs creates a new pod logs command
func NewCmdLogs ( f cmdutil . Factory , streams genericclioptions . IOStreams ) * cobra . Command {
o := NewLogsOptions ( streams , false )
cmd := & cobra . Command {
Use : logsUsageStr ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Print the logs for a container in a pod" ) ,
Long : "Print the logs for a container in a pod or specified resource. If the pod has only one container, the container name is optional." ,
Example : logsExample ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . RunLogs ( ) )
} ,
}
2019-08-30 18:33:25 +00:00
cmd . Flags ( ) . BoolVar ( & o . AllContainers , "all-containers" , o . AllContainers , "Get all containers' logs in the pod(s)." )
2019-01-12 04:58:27 +00:00
cmd . Flags ( ) . BoolVarP ( & o . Follow , "follow" , "f" , o . Follow , "Specify if the logs should be streamed." )
cmd . Flags ( ) . BoolVar ( & o . Timestamps , "timestamps" , o . Timestamps , "Include timestamps on each line in the log output" )
cmd . Flags ( ) . Int64Var ( & o . LimitBytes , "limit-bytes" , o . LimitBytes , "Maximum bytes of logs to return. Defaults to no limit." )
cmd . Flags ( ) . BoolVarP ( & o . Previous , "previous" , "p" , o . Previous , "If true, print the logs for the previous instance of the container in a pod if it exists." )
cmd . Flags ( ) . Int64Var ( & o . Tail , "tail" , o . Tail , "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided." )
2019-08-30 18:33:25 +00:00
cmd . Flags ( ) . BoolVar ( & o . IgnoreLogErrors , "ignore-errors" , o . IgnoreLogErrors , "If watching / following pod logs, allow for any errors that occur to be non-fatal" )
2019-01-12 04:58:27 +00:00
cmd . Flags ( ) . StringVar ( & o . SinceTime , "since-time" , o . SinceTime , i18n . T ( "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used." ) )
cmd . Flags ( ) . DurationVar ( & o . SinceSeconds , "since" , o . SinceSeconds , "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used." )
cmd . Flags ( ) . StringVarP ( & o . Container , "container" , "c" , o . Container , "Print the logs of this container" )
cmdutil . AddPodRunningTimeoutFlag ( cmd , defaultPodLogsTimeout )
cmd . Flags ( ) . StringVarP ( & o . Selector , "selector" , "l" , o . Selector , "Selector (label query) to filter on." )
2019-09-27 21:51:53 +00:00
cmd . Flags ( ) . IntVar ( & o . MaxFollowConcurrency , "max-log-requests" , o . MaxFollowConcurrency , "Specify maximum number of concurrent logs to follow when using by a selector. Defaults to 5." )
2019-01-12 04:58:27 +00:00
return cmd
}
func ( o * LogsOptions ) ToLogOptions ( ) ( * corev1 . PodLogOptions , error ) {
logOptions := & corev1 . PodLogOptions {
Container : o . Container ,
Follow : o . Follow ,
Previous : o . Previous ,
Timestamps : o . Timestamps ,
}
if len ( o . SinceTime ) > 0 {
t , err := util . ParseRFC3339 ( o . SinceTime , metav1 . Now )
if err != nil {
return nil , err
}
logOptions . SinceTime = & t
}
if o . LimitBytes != 0 {
logOptions . LimitBytes = & o . LimitBytes
}
if o . SinceSeconds != 0 {
// round up to the nearest second
sec := int64 ( o . SinceSeconds . Round ( time . Second ) . Seconds ( ) )
logOptions . SinceSeconds = & sec
}
2019-09-27 21:51:53 +00:00
if len ( o . Selector ) > 0 && o . Tail == - 1 && ! o . TailSpecified {
2019-01-12 04:58:27 +00:00
logOptions . TailLines = & selectorTail
} else if o . Tail != - 1 {
logOptions . TailLines = & o . Tail
}
return logOptions , nil
}
func ( o * LogsOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
o . ContainerNameSpecified = cmd . Flag ( "container" ) . Changed
2019-09-27 21:51:53 +00:00
o . TailSpecified = cmd . Flag ( "tail" ) . Changed
2019-01-12 04:58:27 +00:00
o . Resources = args
switch len ( args ) {
case 0 :
if len ( o . Selector ) == 0 {
return cmdutil . UsageErrorf ( cmd , "%s" , logsUsageErrStr )
}
case 1 :
o . ResourceArg = args [ 0 ]
if len ( o . Selector ) != 0 {
return cmdutil . UsageErrorf ( cmd , "only a selector (-l) or a POD name is allowed" )
}
case 2 :
o . ResourceArg = args [ 0 ]
o . Container = args [ 1 ]
default :
return cmdutil . UsageErrorf ( cmd , "%s" , logsUsageErrStr )
}
var err error
o . Namespace , _ , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
o . ConsumeRequestFn = DefaultConsumeRequest
o . GetPodTimeout , err = cmdutil . GetPodRunningTimeoutFlag ( cmd )
if err != nil {
return err
}
o . Options , err = o . ToLogOptions ( )
if err != nil {
return err
}
o . RESTClientGetter = f
o . LogsForObject = polymorphichelpers . LogsForObjectFn
if o . Object == nil {
builder := f . NewBuilder ( ) .
WithScheme ( scheme . Scheme , scheme . Scheme . PrioritizedVersionsAllGroups ( ) ... ) .
NamespaceParam ( o . Namespace ) . DefaultNamespace ( ) .
SingleResourceType ( )
if o . ResourceArg != "" {
builder . ResourceNames ( "pods" , o . ResourceArg )
}
if o . Selector != "" {
builder . ResourceTypes ( "pods" ) . LabelSelectorParam ( o . Selector )
}
infos , err := builder . Do ( ) . Infos ( )
if err != nil {
return err
}
if o . Selector == "" && len ( infos ) != 1 {
return errors . New ( "expected a resource" )
}
o . Object = infos [ 0 ] . Object
}
return nil
}
func ( o LogsOptions ) Validate ( ) error {
if len ( o . SinceTime ) > 0 && o . SinceSeconds != 0 {
return fmt . Errorf ( "at most one of `sinceTime` or `sinceSeconds` may be specified" )
}
logsOptions , ok := o . Options . ( * corev1 . PodLogOptions )
if ! ok {
return errors . New ( "unexpected logs options object" )
}
if o . AllContainers && len ( logsOptions . Container ) > 0 {
return fmt . Errorf ( "--all-containers=true should not be specified with container name %s" , logsOptions . Container )
}
if o . ContainerNameSpecified && len ( o . Resources ) == 2 {
return fmt . Errorf ( "only one of -c or an inline [CONTAINER] arg is allowed" )
}
if o . LimitBytes < 0 {
return fmt . Errorf ( "--limit-bytes must be greater than 0" )
}
if logsOptions . SinceSeconds != nil && * logsOptions . SinceSeconds < int64 ( 0 ) {
return fmt . Errorf ( "--since must be greater than 0" )
}
2019-09-27 21:51:53 +00:00
if logsOptions . TailLines != nil && * logsOptions . TailLines < - 1 {
return fmt . Errorf ( "--tail must be greater than or equal to -1" )
2019-01-12 04:58:27 +00:00
}
return nil
}
// RunLogs retrieves a pod log
func ( o LogsOptions ) RunLogs ( ) error {
requests , err := o . LogsForObject ( o . RESTClientGetter , o . Object , o . Options , o . GetPodTimeout , o . AllContainers )
if err != nil {
return err
}
2019-04-07 17:07:55 +00:00
if o . Follow && len ( requests ) > 1 {
2019-09-27 21:51:53 +00:00
if len ( requests ) > o . MaxFollowConcurrency {
2019-04-07 17:07:55 +00:00
return fmt . Errorf (
2019-09-27 21:51:53 +00:00
"you are attempting to follow %d log streams, but maximum allowed concurrency is %d, use --max-log-requests to increase the limit" ,
len ( requests ) , o . MaxFollowConcurrency ,
2019-04-07 17:07:55 +00:00
)
}
return o . parallelConsumeRequest ( requests )
}
return o . sequentialConsumeRequest ( requests )
}
func ( o LogsOptions ) parallelConsumeRequest ( requests [ ] rest . ResponseWrapper ) error {
reader , writer := io . Pipe ( )
wg := & sync . WaitGroup { }
wg . Add ( len ( requests ) )
for _ , request := range requests {
go func ( request rest . ResponseWrapper ) {
2019-09-27 21:51:53 +00:00
defer wg . Done ( )
2019-04-07 17:07:55 +00:00
if err := o . ConsumeRequestFn ( request , writer ) ; err != nil {
2019-08-30 18:33:25 +00:00
if ! o . IgnoreLogErrors {
writer . CloseWithError ( err )
2019-04-07 17:07:55 +00:00
2019-08-30 18:33:25 +00:00
// It's important to return here to propagate the error via the pipe
return
}
fmt . Fprintf ( writer , "error: %v\n" , err )
2019-04-07 17:07:55 +00:00
}
} ( request )
}
go func ( ) {
wg . Wait ( )
writer . Close ( )
} ( )
_ , err := io . Copy ( o . Out , reader )
return err
}
func ( o LogsOptions ) sequentialConsumeRequest ( requests [ ] rest . ResponseWrapper ) error {
2019-01-12 04:58:27 +00:00
for _ , request := range requests {
if err := o . ConsumeRequestFn ( request , o . Out ) ; err != nil {
return err
}
}
return nil
}
2019-04-07 17:07:55 +00:00
// DefaultConsumeRequest reads the data from request and writes into
// the out writer. It buffers data from requests until the newline or io.EOF
// occurs in the data, so it doesn't interleave logs sub-line
// when running concurrently.
//
// A successful read returns err == nil, not err == io.EOF.
// Because the function is defined to read from request until io.EOF, it does
// not treat an io.EOF as an error to be reported.
func DefaultConsumeRequest ( request rest . ResponseWrapper , out io . Writer ) error {
2019-01-12 04:58:27 +00:00
readCloser , err := request . Stream ( )
if err != nil {
return err
}
defer readCloser . Close ( )
2019-04-07 17:07:55 +00:00
r := bufio . NewReader ( readCloser )
for {
bytes , err := r . ReadBytes ( '\n' )
if _ , err := out . Write ( bytes ) ; err != nil {
return err
}
if err != nil {
if err != io . EOF {
return err
}
return nil
}
}
2019-01-12 04:58:27 +00:00
}