@@ -9,10 +9,10 @@ import (
9
9
"io"
10
10
"strings"
11
11
"text/template"
12
- "time"
13
12
14
13
"github.com/docker/docker/api/types/container"
15
14
dockerclient "github.com/docker/docker/client"
15
+ "github.com/docker/docker/errdefs"
16
16
"github.com/fatih/color"
17
17
"k8s.io/klog/v2"
18
18
)
@@ -23,16 +23,18 @@ type DockerTail struct {
23
23
ContainerName string
24
24
ComposeProject string
25
25
Tty bool
26
- StartedAt time.Time
27
- FinishedAt string
28
- SeenPreviously bool
29
26
composeColor * color.Color
30
27
containerColor * color.Color
31
28
Options * TailOptions
32
29
tmpl * template.Template
33
30
closed chan struct {}
34
- out io.Writer
35
- errOut io.Writer
31
+ last struct {
32
+ timestamp string // RFC3339 timestamp (not RFC3339Nano)
33
+ lines int // the number of lines seen during this timestamp
34
+ }
35
+ resumeRequest * ResumeRequest
36
+ out io.Writer
37
+ errOut io.Writer
36
38
}
37
39
38
40
func NewDockerTail (
@@ -41,9 +43,6 @@ func NewDockerTail(
41
43
containerName string ,
42
44
composeProject string ,
43
45
tty bool ,
44
- startedAt time.Time ,
45
- finishedAt string ,
46
- seenPreviously bool ,
47
46
tmpl * template.Template ,
48
47
out , errOut io.Writer ,
49
48
options * TailOptions ,
@@ -56,9 +55,6 @@ func NewDockerTail(
56
55
ContainerName : containerName ,
57
56
ComposeProject : composeProject ,
58
57
Tty : tty ,
59
- StartedAt : startedAt ,
60
- FinishedAt : finishedAt ,
61
- SeenPreviously : seenPreviously ,
62
58
Options : options ,
63
59
composeColor : composeColor ,
64
60
containerColor : containerColor ,
@@ -86,43 +82,6 @@ func (t *DockerTail) Start(ctx context.Context) error {
86
82
87
83
t .printStarting ()
88
84
89
- err := t .consumeRequest (ctx )
90
- if err != nil {
91
- klog .V (7 ).ErrorS (err , "Error fetching logs for container" , "name" , t .ContainerName , "id" , t .ContainerId )
92
- if errors .Is (err , context .Canceled ) {
93
- return nil
94
- }
95
- }
96
-
97
- return err
98
- }
99
-
100
- func (t * DockerTail ) Close () {
101
- t .printStopping ()
102
- close (t .closed )
103
- }
104
-
105
- // Since log streams end when containers terminate tailfin must keep track of when a container has been previously
106
- // tailed and use a "since"-time from last finish/start. Otherwise it will include logs from previous starts thus
107
- // printing logs already printed.
108
- func (t * DockerTail ) getSinceTime () time.Time {
109
- if ! t .SeenPreviously ||
110
- t .StartedAt .Before (t .Options .DockerSinceTime ) {
111
- return t .Options .DockerSinceTime
112
- }
113
-
114
- // If there's no finish time it should mean the container only started once so it's safe to use the options time.
115
- finished , err := time .Parse (time .RFC3339 , t .FinishedAt )
116
- if err != nil {
117
- return t .Options .DockerSinceTime
118
- } else if finished .Before (t .StartedAt ) {
119
- // Sometimes early logs are missing if StartedAt is used
120
- return finished
121
- }
122
- return t .StartedAt
123
- }
124
-
125
- func (t * DockerTail ) consumeRequest (ctx context.Context ) error {
126
85
logs , err := t .client .ContainerLogs (
127
86
ctx ,
128
87
t .ContainerId ,
@@ -131,7 +90,7 @@ func (t *DockerTail) consumeRequest(ctx context.Context) error {
131
90
ShowStderr : true ,
132
91
Follow : t .Options .Follow ,
133
92
Timestamps : true ,
134
- Since : t .getSinceTime (). Format ( time . RFC3339 ) ,
93
+ Since : t .Options . DockerSinceTime ,
135
94
Tail : t .Options .DockerTailLines ,
136
95
},
137
96
)
@@ -140,6 +99,30 @@ func (t *DockerTail) consumeRequest(ctx context.Context) error {
140
99
}
141
100
defer logs .Close ()
142
101
102
+ err = t .consumeStream (ctx , logs )
103
+ if err != nil {
104
+ klog .V (7 ).ErrorS (err , "Error fetching logs for container" , "name" , t .ContainerName , "id" , t .ContainerId )
105
+ if errors .Is (err , context .Canceled ) || errdefs .IsConflict (err ) {
106
+ return nil
107
+ }
108
+ }
109
+
110
+ return err
111
+ }
112
+
113
+ func (t * DockerTail ) Close () {
114
+ t .printStopping ()
115
+ close (t .closed )
116
+ }
117
+
118
+ func (t * DockerTail ) Resume (ctx context.Context , resumeRequest * ResumeRequest ) error {
119
+ t .resumeRequest = resumeRequest
120
+ t .Options .DockerSinceTime = resumeRequest .Timestamp
121
+ t .Options .DockerTailLines = "-1"
122
+ return t .Start (ctx )
123
+ }
124
+
125
+ func (t * DockerTail ) consumeStream (ctx context.Context , logs io.Reader ) error {
143
126
r := bufio .NewReader (logs )
144
127
for {
145
128
line , err := r .ReadBytes ('\n' )
@@ -162,6 +145,13 @@ func (t *DockerTail) consumeLine(line string) {
162
145
return
163
146
}
164
147
148
+ rfc3339 := removeSubsecond (rfc3339Nano )
149
+ t .rememberLastTimestamp (rfc3339 )
150
+ if t .resumeRequest .shouldSkip (rfc3339 ) {
151
+ return
152
+ }
153
+ t .resumeRequest = nil
154
+
165
155
if t .Options .IsExclude (content ) || ! t .Options .IsInclude (content ) {
166
156
return
167
157
}
@@ -236,6 +226,21 @@ func trimLeadingChars(line string, tty bool) string {
236
226
klog .V (7 ).InfoS ("Invalid log line format received" , "line" , line )
237
227
return ""
238
228
}
239
-
240
229
return line [8 :]
241
230
}
231
+
232
+ func (t * DockerTail ) rememberLastTimestamp (timestamp string ) {
233
+ if t .last .timestamp == timestamp {
234
+ t .last .lines ++
235
+ return
236
+ }
237
+ t .last .timestamp = timestamp
238
+ t .last .lines = 1
239
+ }
240
+
241
+ func (t * DockerTail ) GetResumeRequest () * ResumeRequest {
242
+ if t .last .timestamp == "" {
243
+ return nil
244
+ }
245
+ return & ResumeRequest {Timestamp : t .last .timestamp , LinesToSkip : t .last .lines }
246
+ }
0 commit comments