Skip to content

Commit 557bc77

Browse files
joe4devclaude
andcommitted
fix(init): fix REPORT log format and add init timeout retry support
- Move Init Duration after Max Memory Used in REPORT line (matches AWS) - Add Status: timeout to REPORT line on invoke timeout - Fix timeout error message format to "RequestId: <id> Error: Task timed out after N.00 seconds" - Add ErrorType: "Sandbox.Timedout" to timeout error response - Track init start time and emit Init Duration on first non-retry invocation - Add is-init-retry field to InvokeRequest to suppress Init Duration on retry invokes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 5bb7698 commit 557bc77

3 files changed

Lines changed: 20 additions & 6 deletions

File tree

cmd/localstack/awsutil.go

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

95-
func PrintEndReports(invokeId string, initDuration string, memorySize string, invokeStart time.Time, timeoutDuration time.Duration, w io.Writer) {
95+
func PrintEndReports(invokeId string, initDuration string, status string, memorySize string, invokeStart time.Time, timeoutDuration time.Duration, w io.Writer) {
9696
// Calculate invoke duration
9797
invokeDuration := math.Min(float64(time.Now().Sub(invokeStart).Nanoseconds()),
9898
float64(timeoutDuration.Nanoseconds())) / float64(time.Millisecond)
@@ -102,11 +102,12 @@ func PrintEndReports(invokeId string, initDuration string, memorySize string, in
102102
// not a clean way to get this information from rapidcore
103103
_, _ = fmt.Fprintf(w,
104104
"REPORT RequestId: %s\t"+
105-
initDuration+
106105
"Duration: %.2f ms\t"+
107106
"Billed Duration: %.f ms\t"+
108107
"Memory Size: %s MB\t"+
109-
"Max Memory Used: %s MB\t\n",
108+
"Max Memory Used: %s MB\t"+
109+
initDuration+
110+
status+"\n",
110111
invokeId, invokeDuration, math.Ceil(invokeDuration), memorySize, memorySize)
111112
}
112113

cmd/localstack/custom_interop.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ type CustomInteropServer struct {
2828
localStackAdapter *LocalStackAdapter
2929
port string
3030
upstreamEndpoint string
31+
initStart time.Time
32+
warmStart bool
3133
}
3234

3335
type LocalStackAdapter struct {
@@ -113,6 +115,13 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
113115
functionVersion := GetEnvOrDie("AWS_LAMBDA_FUNCTION_VERSION") // default $LATEST
114116
_, _ = fmt.Fprintf(logCollector, "START RequestId: %s Version: %s\n", invokeR.InvokeId, functionVersion)
115117

118+
initDuration := ""
119+
if !server.warmStart && !invokeR.IsInitRetry {
120+
initTimeMS := float64(time.Since(server.initStart).Nanoseconds()) / float64(time.Millisecond)
121+
initDuration = fmt.Sprintf("Init Duration: %.2f ms\t", initTimeMS)
122+
}
123+
server.warmStart = true
124+
116125
invokeStart := time.Now()
117126
err = server.Invoke(invokeResp, &interop.Invoke{
118127
ID: invokeR.InvokeId,
@@ -134,15 +143,17 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
134143
})
135144
timeout := int(server.delegate.GetInvokeTimeout().Seconds())
136145
isErr := false
146+
status := ""
137147
if err != nil {
138148
switch {
139149
case errors.Is(err, rapidcore.ErrInvokeTimeout):
140150
log.Debugf("Got invoke timeout")
141151
isErr = true
152+
status = "Status: timeout"
142153
errorResponse := lsapi.ErrorResponse{
154+
ErrorType: "Sandbox.Timedout",
143155
ErrorMessage: fmt.Sprintf(
144-
"%s %s Task timed out after %d.00 seconds",
145-
time.Now().Format("2006-01-02T15:04:05Z"),
156+
"RequestId: %s Error: Task timed out after %d.00 seconds",
146157
invokeR.InvokeId,
147158
timeout,
148159
),
@@ -171,7 +182,7 @@ func NewCustomInteropServer(lsOpts *LsOpts, delegate interop.Server, logCollecto
171182
}
172183
timeoutDuration := time.Duration(timeout) * time.Second
173184
memorySize := GetEnvOrDie("AWS_LAMBDA_FUNCTION_MEMORY_SIZE")
174-
PrintEndReports(invokeR.InvokeId, "", memorySize, invokeStart, timeoutDuration, logCollector)
185+
PrintEndReports(invokeR.InvokeId, initDuration, status, memorySize, invokeStart, timeoutDuration, logCollector)
175186

176187
if err2 := server.localStackAdapter.SendLogs(invokeR.InvokeId, logCollector.getLogs()); err2 != nil {
177188
log.Error("failed to send logs to LocalStack: ", err2)
@@ -258,6 +269,7 @@ func (c *CustomInteropServer) SendRuntimeReady() error {
258269

259270
func (c *CustomInteropServer) Init(i *interop.Init, invokeTimeoutMs int64) error {
260271
log.Traceln("Init called")
272+
c.initStart = time.Now()
261273
return c.delegate.Init(i, invokeTimeoutMs)
262274
}
263275

internal/lsapi/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type InvokeRequest struct {
66
InvokedFunctionArn string `json:"invoked-function-arn"`
77
Payload string `json:"payload"`
88
TraceId string `json:"trace-id"`
9+
IsInitRetry bool `json:"is-init-retry,omitempty"`
910
}
1011

1112
// LogResponse is sent by the runtime to report logs for a completed invocation.

0 commit comments

Comments
 (0)