@@ -3,9 +3,7 @@ package aws
3
3
import (
4
4
"context"
5
5
"encoding/json"
6
- "fmt"
7
6
"io"
8
- "path"
9
7
"strings"
10
8
"time"
11
9
@@ -23,25 +21,22 @@ import (
23
21
type byocServerStream struct {
24
22
ctx context.Context
25
23
err error
26
- errCh <- chan error
27
24
etag string
28
25
response * defangv1.TailResponse
29
26
services []string
30
27
stream ecs.EventStream
31
- }
32
28
33
- func newByocServerStream (ctx context.Context , stream ecs.EventStream , etag string , services []string ) * byocServerStream {
34
- var errCh <- chan error
35
- if errch , ok := stream .(hasErrCh ); ok {
36
- errCh = errch .Errs ()
37
- }
29
+ ecsEventsHandler ECSEventHandler
30
+ }
38
31
32
+ func newByocServerStream (ctx context.Context , stream ecs.EventStream , etag string , services []string , ecsEventHandler ECSEventHandler ) * byocServerStream {
39
33
return & byocServerStream {
40
34
ctx : ctx ,
41
- errCh : errCh ,
42
35
etag : etag ,
43
36
stream : stream ,
44
37
services : services ,
38
+
39
+ ecsEventsHandler : ecsEventHandler ,
45
40
}
46
41
}
47
42
@@ -62,24 +57,20 @@ func (bs *byocServerStream) Msg() *defangv1.TailResponse {
62
57
return bs .response
63
58
}
64
59
65
- type hasErrCh interface {
66
- Errs () <- chan error
67
- }
68
-
69
60
func (bs * byocServerStream ) Receive () bool {
70
61
var evts []ecs.LogEvent
71
62
select {
72
63
case e := <- bs .stream .Events (): // blocking
64
+ if bs .stream .Err () != nil {
65
+ bs .err = bs .stream .Err ()
66
+ return false
67
+ }
73
68
var err error
74
69
evts , err = ecs .GetLogEvents (e )
75
70
if err != nil {
76
71
bs .err = err
77
72
return false
78
73
}
79
- case err := <- bs .errCh : // blocking (if not nil)
80
- bs .err = err
81
- return false // abort on first error?
82
-
83
74
case <- bs .ctx .Done (): // blocking (if not nil)
84
75
bs .err = context .Cause (bs .ctx )
85
76
return false
@@ -132,19 +123,21 @@ func (bs *byocServerStream) parseEvents(events []ecs.LogEvent) (*defangv1.TailRe
132
123
}
133
124
} else if strings .HasSuffix (* event .LogGroupIdentifier , "/ecs" ) || strings .HasSuffix (* event .LogGroupIdentifier , "/ecs:*" ) {
134
125
parseECSEventRecords = true
126
+ response .Etag = bs .etag
127
+ response .Service = "ecs"
135
128
}
136
129
137
130
// Client-side filtering
138
131
if bs .etag != "" && bs .etag != response .Etag {
139
132
return nil , nil // TODO: filter these out using the AWS StartLiveTail API
140
133
}
141
134
142
- if len (bs .services ) > 0 && ! pkg .Contains (bs .services , bs . response .GetService ()) {
135
+ if len (bs .services ) > 0 && ! pkg .Contains (bs .services , response .GetService ()) {
143
136
return nil , nil // TODO: filter these out using the AWS StartLiveTail API
144
137
}
145
138
146
- entries := make ([]* defangv1.LogEntry , len (events ))
147
- for i , event := range events {
139
+ entries := make ([]* defangv1.LogEntry , 0 , len (events ))
140
+ for _ , event := range events {
148
141
entry := & defangv1.LogEntry {
149
142
Message : * event .Message ,
150
143
Stderr : false ,
@@ -162,81 +155,36 @@ func (bs *byocServerStream) parseEvents(events []ecs.LogEvent) (*defangv1.TailRe
162
155
}
163
156
} else if parseECSEventRecords {
164
157
var err error
165
- if err = parseECSEventRecord (event , entry ); err != nil {
158
+ if err = bs . parseECSEventRecord (event , entry ); err != nil {
166
159
term .Debugf ("error parsing ECS event, output raw event log: %v" , err )
167
160
}
168
161
} else if response .Service == "cd" && strings .HasPrefix (entry .Message , " ** " ) {
169
162
entry .Stderr = true
170
163
}
171
- entries [i ] = entry
164
+ if entry .Etag != "" && bs .etag != "" && entry .Etag != bs .etag {
165
+ continue
166
+ }
167
+ if entry .Service != "" && len (bs .services ) > 0 && ! pkg .Contains (bs .services , entry .Service ) {
168
+ continue
169
+ }
170
+ entries = append (entries , entry )
171
+ }
172
+ if len (entries ) == 0 {
173
+ return nil , nil
172
174
}
173
175
response .Entries = entries
174
176
return & response , nil
175
177
}
176
178
177
- func parseECSEventRecord (event ecs.LogEvent , entry * defangv1.LogEntry ) error {
178
- var ecsEvt ecs.Event
179
- if err := json .Unmarshal ([]byte (* event .Message ), & ecsEvt ); err != nil {
180
- return fmt .Errorf ("error unmarshaling ECS event: %w" , err )
181
- }
182
-
183
- var buf strings.Builder
184
- fmt .Fprintf (& buf , "%s " , ecsEvt .DetailType )
185
- if len (ecsEvt .Resources ) > 0 {
186
- fmt .Fprintf (& buf , "%s " , path .Base (ecsEvt .Resources [0 ]))
187
- }
188
- switch ecsEvt .DetailType {
189
- case "ECS Task State Change" :
190
- var detail ecs.ECSTaskStateChange
191
- if err := json .Unmarshal (ecsEvt .Detail , & detail ); err != nil {
192
- return fmt .Errorf ("error unmarshaling ECS task state change: %w" , err )
193
- }
194
-
195
- // Container name is in the format of "service_etag"
196
- if len (detail .Containers ) < 1 {
197
- return fmt .Errorf ("error parsing ECS task state change: missing containers section" )
198
- }
199
- i := strings .LastIndex (detail .Containers [0 ].Name , "_" )
200
- if i < 0 {
201
- return fmt .Errorf ("error parsing ECS task state change: invalid container name %q" , detail .Containers [0 ].Name )
202
- }
203
- entry .Service = detail .Containers [0 ].Name [:i ]
204
- entry .Etag = detail .Containers [0 ].Name [i + 1 :]
205
- entry .Host = path .Base (ecsEvt .Resources [0 ])
206
- fmt .Fprintf (& buf , "%s %s" , path .Base (detail .ClusterArn ), detail .LastStatus )
207
- if detail .StoppedReason != "" {
208
- fmt .Fprintf (& buf , " : %s" , detail .StoppedReason )
209
- }
210
- case "ECS Service Action" , "ECS Deployment State Change" : // pretty much the same JSON structure for both
211
- var detail ecs.ECSDeploymentStateChange
212
- if err := json .Unmarshal (ecsEvt .Detail , & detail ); err != nil {
213
- return fmt .Errorf ("error unmarshaling ECS service/deployment event: %v" , err )
214
- }
215
- ecsSvcName := path .Base (ecsEvt .Resources [0 ])
216
- // TODO: etag is not available at service and deployment level, find a possible correlation, possibly task definition revision using the deploymentId
217
- snStart := strings .LastIndex (ecsSvcName , "_" ) // ecsSvcName is in the format "project_service-random", our validation does not allow '_' in service names
218
- snEnd := strings .LastIndex (ecsSvcName , "-" )
219
- if snStart < 0 || snEnd < 0 || snStart >= snEnd {
220
- return fmt .Errorf ("error parsing ECS service action: invalid service name %q" , ecsEvt .Resources [0 ])
221
- }
222
- entry .Service = ecsSvcName [snStart + 1 : snEnd ]
223
- entry .Host = detail .DeploymentId
224
- fmt .Fprintf (& buf , "%s" , detail .EventName )
225
- if detail .Reason != "" {
226
- fmt .Fprintf (& buf , " : %s" , detail .Reason )
227
- }
228
- default :
229
- entry .Service = "ecs"
230
- if len (ecsEvt .Resources ) > 0 {
231
- entry .Host = path .Base (ecsEvt .Resources [0 ])
232
- }
233
- // Print the unrecogonalized ECS event detail in prettry JSON format if possible
234
- raw , err := json .MarshalIndent (ecsEvt .Detail , "" , " " )
235
- if err != nil {
236
- raw = []byte (ecsEvt .Detail )
237
- }
238
- fmt .Fprintf (& buf , "\n %s" , raw )
179
+ func (bs * byocServerStream ) parseECSEventRecord (event ecs.LogEvent , entry * defangv1.LogEntry ) error {
180
+ evt , err := ecs .ParseECSEvent ([]byte (* event .Message ))
181
+ if err != nil {
182
+ return err
239
183
}
240
- entry .Message = buf .String ()
184
+ bs .ecsEventsHandler .HandleECSEvent (evt )
185
+ entry .Service = evt .Service ()
186
+ entry .Etag = evt .Etag ()
187
+ entry .Host = evt .Host ()
188
+ entry .Message = evt .Status ()
241
189
return nil
242
190
}
0 commit comments