Skip to content

Commit 4e2f91e

Browse files
joe4devclaude
andcommitted
refactor(init): format the REPORT line in one place
PrintEndReports took pre-formatted "Init Duration: ..." and "Status: ...\tError Type: ..." string fragments, splitting knowledge of the REPORT line format between custom_interop.go and awsutil.go. Pass the structured values (duration, status, error type) instead and render the line in PrintEndReports only. Output is byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4d352ef commit 4e2f91e

2 files changed

Lines changed: 28 additions & 16 deletions

File tree

cmd/localstack/awsutil.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,25 +92,38 @@ func getBootstrap(args []string) (interop.Bootstrap, string) {
9292
return NewSimpleBootstrap(bootstrapLookupCmd, currentWorkingDir), handler
9393
}
9494

95-
func PrintEndReports(invokeId string, initDuration string, status string, memorySize string, invokeStart time.Time, timeoutDuration time.Duration, w io.Writer) {
95+
// PrintEndReports emits the END and REPORT lines that close an invocation's log envelope.
96+
// initDurationMS is rendered when hasInitDuration is set (the successful on-demand cold-start
97+
// init duration reported via the first invocation, see TakeColdStartInitDuration). status is
98+
// "" for a plain successful invocation, or AWS's "timeout" / "error"; errorType (a scrubbed
99+
// fatal error type, e.g. Runtime.ExitError) is rendered only alongside status "error".
100+
func PrintEndReports(invokeId string, initDurationMS float64, hasInitDuration bool, status string, errorType string, memorySize string, invokeStart time.Time, timeoutDuration time.Duration, w io.Writer) {
96101
// Calculate invoke duration
97102
invokeDuration := math.Min(float64(time.Now().Sub(invokeStart).Nanoseconds()),
98103
float64(timeoutDuration.Nanoseconds())) / float64(time.Millisecond)
99104

100105
_, _ = fmt.Fprintln(w, "END RequestId: "+invokeId)
101106
// We set the Max Memory Used and Memory Size to be the same (whatever it is set to) since there is
102107
// not a clean way to get this information from rapidcore
103-
// initDuration and status are pre-formatted fragments: pass them as %s arguments, not as
104-
// part of the format string — status may embed a runtime-supplied error type, and a stray
105-
// formatting verb in it would corrupt the REPORT line.
106-
_, _ = fmt.Fprintf(w,
108+
report := fmt.Sprintf(
107109
"REPORT RequestId: %s\t"+
108110
"Duration: %.2f ms\t"+
109111
"Billed Duration: %.f ms\t"+
110112
"Memory Size: %s MB\t"+
111-
"Max Memory Used: %s MB\t"+
112-
"%s%s\n",
113-
invokeId, invokeDuration, math.Ceil(invokeDuration), memorySize, memorySize, initDuration, status)
113+
"Max Memory Used: %s MB\t",
114+
invokeId, invokeDuration, math.Ceil(invokeDuration), memorySize, memorySize)
115+
if hasInitDuration {
116+
report += fmt.Sprintf("Init Duration: %.2f ms\t", initDurationMS)
117+
}
118+
if status != "" {
119+
// Concatenated, not formatted: errorType is runtime-supplied, and a stray formatting
120+
// verb in it would corrupt the REPORT line.
121+
report += "Status: " + status
122+
if errorType != "" {
123+
report += "\tError Type: " + errorType
124+
}
125+
}
126+
_, _ = fmt.Fprintln(w, report)
114127
}
115128

116129
type Sandbox interface {

cmd/localstack/custom_interop.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,9 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
133133
// after an inline suppressed init's own logs) — see events.go.
134134

135135
// First invocation into a successfully initialized on-demand environment: REPORT
136-
// carries the Init phase duration as measured by rapid (take-once; empty on warm
136+
// carries the Init phase duration as measured by rapid (take-once; absent on warm
137137
// starts, failed/timed-out inits, and non-on-demand environments).
138-
initDuration := ""
139-
if initTimeMS, ok := server.eventsAPI.TakeColdStartInitDuration(); ok {
140-
initDuration = fmt.Sprintf("Init Duration: %.2f ms\t", initTimeMS)
141-
}
138+
initDurationMS, hasInitDuration := server.eventsAPI.TakeColdStartInitDuration()
142139

143140
invokeStart := time.Now()
144141
err = server.Invoke(invokeResp, &interop.Invoke{
@@ -162,12 +159,13 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
162159
timeout := int(server.delegate.GetInvokeTimeout().Seconds())
163160
isErr := false
164161
status := ""
162+
errorType := ""
165163
if err != nil {
166164
switch {
167165
case errors.Is(err, rapidcore.ErrInvokeTimeout):
168166
log.Debugf("Got invoke timeout")
169167
isErr = true
170-
status = "Status: timeout"
168+
status = "timeout"
171169
errorResponse := lsapi.ErrorResponse{
172170
ErrorType: "Sandbox.Timedout",
173171
ErrorMessage: fmt.Sprintf(
@@ -191,7 +189,8 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
191189
// scrubbed fatal error type (e.g. Runtime.Unknown).
192190
if errType := server.eventsAPI.InitErrorType(); errType != "" {
193191
isErr = true
194-
status = "Status: error\tError Type: " + errType
192+
status = "error"
193+
errorType = errType
195194
}
196195
default:
197196
log.Fatalln(err)
@@ -207,7 +206,7 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
207206
}
208207
timeoutDuration := time.Duration(timeout) * time.Second
209208
memorySize := GetEnvOrDie("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
210-
PrintEndReports(invokeR.InvokeId, initDuration, status, memorySize, invokeStart, timeoutDuration, logCollector)
209+
PrintEndReports(invokeR.InvokeId, initDurationMS, hasInitDuration, status, errorType, memorySize, invokeStart, timeoutDuration, logCollector)
211210

212211
if err2 := server.localStackAdapter.SendLogs(invokeR.InvokeId, logCollector.getLogs()); err2 != nil {
213212
log.Error("failed to send logs to LocalStack: ", err2)

0 commit comments

Comments
 (0)