Skip to content

Commit

Permalink
Use Lambda-Runtime-Function-Error-Type for reporting errors in format…
Browse files Browse the repository at this point in the history
… "Runtime.<Error>" (#501)

* Introduce Runtime. error codes

* fix: apply linting

* Remove unused imports

---------

Co-authored-by: Alexander Smirnov <[email protected]>
  • Loading branch information
maxday and Alexander Smirnov authored Jul 31, 2024
1 parent ff5d9e0 commit f589b18
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
import com.amazonaws.services.lambda.runtime.api.client.logging.LambdaContextLogger;
import com.amazonaws.services.lambda.runtime.api.client.logging.LogSink;
import com.amazonaws.services.lambda.runtime.api.client.logging.StdOutLogSink;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClient;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.LambdaRuntimeApiClientImpl;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.RapidErrorType;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.LambdaErrorConverter;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters.XRayErrorCauseConverter;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause;
import com.amazonaws.services.lambda.runtime.api.client.util.LambdaOutputStream;
import com.amazonaws.services.lambda.runtime.api.client.util.UnsafeUtil;
import com.amazonaws.services.lambda.runtime.logging.LogFormat;
Expand Down Expand Up @@ -215,7 +215,9 @@ private static void startRuntime(String handler, LambdaContextLogger lambdaLogge
requestHandler = findRequestHandler(handler, customerClassLoader);
} catch (UserFault userFault) {
lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
LambdaError error = LambdaErrorConverter.fromUserFault(userFault);
LambdaError error = new LambdaError(
LambdaErrorConverter.fromUserFault(userFault),
RapidErrorType.BadFunctionCode);
runtimeClient.reportInitError(error);
System.exit(1);
return;
Expand Down Expand Up @@ -243,17 +245,20 @@ private static void startRuntime(String handler, LambdaContextLogger lambdaLogge
shouldExit = f.fatal;
userFault = f;
UserFault.filterStackTrace(f);

LambdaError error = LambdaErrorConverter.fromUserFault(f);
LambdaError error = new LambdaError(
LambdaErrorConverter.fromUserFault(f),
RapidErrorType.BadFunctionCode);
runtimeClient.reportInvocationError(request.getId(), error);
} catch (Throwable t) {
shouldExit = t instanceof VirtualMachineError || t instanceof IOError;
UserFault.filterStackTrace(t);
userFault = UserFault.makeUserFault(t);

LambdaError error = LambdaErrorConverter.fromThrowable(t);
XRayErrorCause xRayErrorCause = XRayErrorCauseConverter.fromThrowable(t);
runtimeClient.reportInvocationError(request.getId(), error, xRayErrorCause);
LambdaError error = new LambdaError(
LambdaErrorConverter.fromThrowable(t),
XRayErrorCauseConverter.fromThrowable(t),
RapidErrorType.UserException);
runtimeClient.reportInvocationError(request.getId(), error);
} finally {
if (userFault != null) {
lambdaLogger.log(userFault.reportableError(), lambdaLogger.getLogFormat() == LogFormat.JSON ? LogLevel.ERROR : LogLevel.UNDEFINED);
Expand All @@ -268,16 +273,18 @@ static void onInitComplete(final LambdaContextLogger lambdaLogger) throws IOExce
runtimeClient.restoreNext();
} catch (Exception e1) {
logExceptionCloudWatch(lambdaLogger, e1);
LambdaError error = LambdaErrorConverter.fromThrowable(e1);
runtimeClient.reportInitError(error);
runtimeClient.reportInitError(new LambdaError(
LambdaErrorConverter.fromThrowable(e1),
RapidErrorType.BeforeCheckpointError));
System.exit(64);
}
try {
Core.getGlobalContext().afterRestore(null);
} catch (Exception restoreExc) {
logExceptionCloudWatch(lambdaLogger, restoreExc);
LambdaError error = LambdaErrorConverter.fromThrowable(restoreExc);
runtimeClient.reportRestoreError(error);
runtimeClient.reportRestoreError(new LambdaError(
LambdaErrorConverter.fromThrowable(restoreExc),
RapidErrorType.AfterRestoreError));
System.exit(64);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;

import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause;
import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer;
import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory;
Expand All @@ -13,7 +13,7 @@

public class DtoSerializers {

public static byte[] serialize(LambdaError error) {
public static byte[] serialize(ErrorRequest error) {
return serialize(error, SingletonHelper.LAMBDA_ERROR_SERIALIZER);
}

Expand All @@ -36,7 +36,7 @@ private static <T> byte[] serialize(T pojo, PojoSerializer<T> serializer) {
* This way the serializers will be loaded lazily
*/
private static class SingletonHelper {
private static final PojoSerializer<LambdaError> LAMBDA_ERROR_SERIALIZER = GsonFactory.getInstance().getSerializer(LambdaError.class);
private static final PojoSerializer<ErrorRequest> LAMBDA_ERROR_SERIALIZER = GsonFactory.getInstance().getSerializer(ErrorRequest.class);
private static final PojoSerializer<XRayErrorCause> X_RAY_ERROR_CAUSE_SERIALIZER = GsonFactory.getInstance().getSerializer(XRayErrorCause.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;

import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause;

public class LambdaError {

public final ErrorRequest errorRequest;

public final XRayErrorCause xRayErrorCause;

public final RapidErrorType errorType;

public LambdaError(ErrorRequest errorRequest, XRayErrorCause xRayErrorCause, RapidErrorType errorType) {
this.errorRequest = errorRequest;
this.xRayErrorCause = xRayErrorCause;
this.errorType = errorType;
}

public LambdaError(ErrorRequest errorRequest, RapidErrorType errorType) {
this(errorRequest, null, errorType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;

import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause;
import java.io.IOException;

/**
Expand Down Expand Up @@ -40,14 +38,6 @@ public interface LambdaRuntimeApiClient {
*/
void reportInvocationError(String requestId, LambdaError error) throws IOException;

/**
* Report invocation error
* @param requestId request id
* @param error error to report
* @param xRayErrorCause X-Ray error cause
*/
void reportInvocationError(String requestId, LambdaError error, XRayErrorCause xRayErrorCause) throws IOException;

/**
* SnapStart endpoint to report that beforeCheckoint hooks were executed
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;

import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.XRayErrorCause;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand Down Expand Up @@ -46,7 +44,7 @@ public LambdaRuntimeApiClientImpl(String hostnameAndPort) {
@Override
public void reportInitError(LambdaError error) throws IOException {
String endpoint = this.baseUrl + "/2018-06-01/runtime/init/error";
reportLambdaError(endpoint, error, null);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
}

@Override
Expand All @@ -61,13 +59,8 @@ public void reportInvocationSuccess(String requestId, byte[] response) {

@Override
public void reportInvocationError(String requestId, LambdaError error) throws IOException {
reportInvocationError(requestId, error, null);
}

@Override
public void reportInvocationError(String requestId, LambdaError error, XRayErrorCause xRayErrorCause) throws IOException {
String endpoint = invocationEndpoint + requestId + "/error";
reportLambdaError(endpoint, error, xRayErrorCause);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
}

@Override
Expand All @@ -82,22 +75,21 @@ public void restoreNext() throws IOException {
@Override
public void reportRestoreError(LambdaError error) throws IOException {
String endpoint = this.baseUrl + "/2018-06-01/runtime/restore/error";
reportLambdaError(endpoint, error, null);
reportLambdaError(endpoint, error, XRAY_ERROR_CAUSE_MAX_HEADER_SIZE);
}

void reportLambdaError(String endpoint, LambdaError error, XRayErrorCause xRayErrorCause) throws IOException {
void reportLambdaError(String endpoint, LambdaError error, int maxXrayHeaderSize) throws IOException {
Map<String, String> headers = new HashMap<>();
if (error.errorType != null && !error.errorType.isEmpty()) {
headers.put(ERROR_TYPE_HEADER, error.errorType);
}
if (xRayErrorCause != null) {
byte[] xRayErrorCauseJson = DtoSerializers.serialize(xRayErrorCause);
if (xRayErrorCauseJson != null && xRayErrorCauseJson.length < XRAY_ERROR_CAUSE_MAX_HEADER_SIZE) {
headers.put(ERROR_TYPE_HEADER, error.errorType.getRapidError());

if (error.xRayErrorCause != null) {
byte[] xRayErrorCauseJson = DtoSerializers.serialize(error.xRayErrorCause);
if (xRayErrorCauseJson != null && xRayErrorCauseJson.length < maxXrayHeaderSize) {
headers.put(XRAY_ERROR_CAUSE_HEADER, new String(xRayErrorCauseJson));
}
}

byte[] payload = DtoSerializers.serialize(error);
byte[] payload = DtoSerializers.serialize(error.errorRequest);
int responseCode = doPost(endpoint, headers, payload);
if (responseCode != HTTP_ACCEPTED) {
throw new LambdaRuntimeClientException(endpoint, responseCode);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi;

public enum RapidErrorType {
BadFunctionCode,
UserException,
BeforeCheckpointError,
AfterRestoreError;

public String getRapidError() {
return "Runtime." + this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi.converters;

import com.amazonaws.services.lambda.runtime.api.client.UserFault;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.LambdaError;
import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.ErrorRequest;

public class LambdaErrorConverter {
private LambdaErrorConverter() {
}

public static LambdaError fromUserFault(UserFault userFault) {
public static ErrorRequest fromUserFault(UserFault userFault) {
// Not setting stacktrace for compatibility with legacy/native runtime
return new LambdaError(userFault.msg, userFault.exception, null);
return new ErrorRequest(userFault.msg, userFault.exception, null);
}

public static LambdaError fromThrowable(Throwable throwable) {
String errorMessage = throwable.getLocalizedMessage() == null
? throwable.getClass().getName() : throwable.getLocalizedMessage();
public static ErrorRequest fromThrowable(Throwable throwable) {
String errorMessage = throwable.getLocalizedMessage() == null
? throwable.getClass().getName()
: throwable.getLocalizedMessage();
String errorType = throwable.getClass().getName();

StackTraceElement[] trace = throwable.getStackTrace();
String[] stackTrace = new String[trace.length];
for (int i = 0; i < trace.length; i++) {
stackTrace[i] = trace[i].toString();
}
return new LambdaError(errorMessage, errorType, stackTrace);
return new ErrorRequest(errorMessage, errorType, stackTrace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
*/
package com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto;

public class LambdaError {
public class ErrorRequest {
public String errorMessage;
public String errorType;
public String[] stackTrace;

@SuppressWarnings("unused")
public LambdaError() {
public ErrorRequest() {
}

public LambdaError(String errorMessage, String errorType, String[] stackTrace) {
public ErrorRequest(String errorMessage, String errorType, String[] stackTrace) {
this.errorMessage = errorMessage;
this.errorType = errorType;
this.stackTrace = stackTrace;
Expand Down

0 comments on commit f589b18

Please sign in to comment.