View a markdown version of this page

Troubleshooting and monitoring Functions - AWS Elemental MediaTailor

Troubleshooting and monitoring Functions

This page helps you diagnose common function errors and monitor function performance in production. The troubleshooting section is organized by symptom — start with what you observe, then follow the cause and fix.

Monitoring

CloudWatch metrics

MediaTailor publishes metrics for function execution to Amazon CloudWatch. No opt-in is required.

Hook-level metrics — one data point per lifecycle hook execution:

MetricDescriptionDimensions
PreSessionInitHook.InvocationsCount of hook executionsConfigurationName
PreSessionInitHook.ErrorsCount of hook errorsConfigurationName
PreSessionInitHook.LatencyHook execution time (ms)ConfigurationName
PreAdsRequestHook.InvocationsCount of hook executionsConfigurationName
PreAdsRequestHook.ErrorsCount of hook errorsConfigurationName
PreAdsRequestHook.LatencyHook execution time (ms)ConfigurationName
PostAdsResponseHook.InvocationsCount of hook executionsConfigurationName
PostAdsResponseHook.ErrorsCount of hook errorsConfigurationName
PostAdsResponseHook.LatencyHook execution time (ms)ConfigurationName
PreManifestInsertionHook.InvocationsCount of hook executionsConfigurationName
PreManifestInsertionHook.ErrorsCount of hook errorsConfigurationName
PreManifestInsertionHook.LatencyHook execution time (ms)ConfigurationName

Function-level metrics — one data point per individual function execution:

MetricDescriptionDimensions
Function.InvocationsCount of function executionsConfigurationName, FunctionId, FunctionType, HookType
Function.ErrorsCount of function errorsConfigurationName, FunctionId, FunctionType, HookType
Function.LatencyFunction execution time (ms)ConfigurationName, FunctionId, FunctionType, HookType

For more details on setting up alarms and working with these metrics, see Monitoring AWS Elemental MediaTailor with Amazon CloudWatch metrics.

Log events

MediaTailor emits log events for function execution. Error events are emitted by default. Completed and summary events are opt-in.

Event typeDefault / Opt-inLog groupDescription
PRE_SESSION_INIT_HOOK_SUMMARYOpt-inManifest LogHook execution summary (success/error)
PRE_SESSION_INIT_HOOK_ERRORDefaultManifest LogHook failure with errorType and cause
PRE_SESSION_INIT_FUNCTION_COMPLETEDOpt-inManifest LogIndividual function completed with input/output
PRE_SESSION_INIT_FUNCTION_ERRORDefaultManifest LogIndividual function failure
PRE_ADS_REQUEST_HOOK_SUMMARYOpt-inADS Interaction LogHook execution summary (success/error)
PRE_ADS_REQUEST_HOOK_ERRORDefaultADS Interaction LogHook failure with errorType and cause
PRE_ADS_REQUEST_FUNCTION_COMPLETEDOpt-inADS Interaction LogIndividual function completed with input/output
PRE_ADS_REQUEST_FUNCTION_ERRORDefaultADS Interaction LogIndividual function failure
POST_ADS_RESPONSE_HOOK_SUMMARYOpt-inADS Interaction LogHook execution summary (success/error)
POST_ADS_RESPONSE_HOOK_ERRORDefaultADS Interaction LogHook failure with errorType and cause
POST_ADS_RESPONSE_FUNCTION_COMPLETEDOpt-inADS Interaction LogIndividual function completed with input/output
POST_ADS_RESPONSE_FUNCTION_ERRORDefaultADS Interaction LogIndividual function failure
PRE_MANIFEST_INSERTION_HOOK_SUMMARYOpt-inADS Interaction LogHook execution summary (success/error)
PRE_MANIFEST_INSERTION_HOOK_ERRORDefaultADS Interaction LogHook failure with errorType and cause
PRE_MANIFEST_INSERTION_FUNCTION_COMPLETEDOpt-inADS Interaction LogIndividual function completed with input/output
PRE_MANIFEST_INSERTION_FUNCTION_ERRORDefaultADS Interaction LogIndividual function failure

To enable opt-in log events, see Monitoring AWS Elemental MediaTailor with Amazon CloudWatch metrics.

Use the eventId field to correlate hook-level and function-level events for the same execution.

The following Amazon CloudWatch Logs Insights query filters function error events by eventId to trace a single execution:

fields @timestamp, eventType, functionId, errorType, cause | filter eventId = "5dc6f040-0f72-4e8c-a64e-25eeef62708c" | sort @timestamp asc

Troubleshooting

When a function fails, MediaTailor logs an errorType field in the error event. Use this field to identify the class of failure:

Error typeDescription
SYNTAX_ERRORExpression failed to compile or encountered a runtime type error
RESOURCE_LIMIT_ERRORExpression exceeded CPU time, memory, or stack depth limits
RESTRICTION_ERRORExpression used a blocked function, or input payload size limit exceeded
TIMEOUT_ERRORFunction execution exceeded its time limit
VALIDATION_ERROROutput path targets a field not writable in the current hook's scope
INTERNAL_ERRORInfrastructure failure unrelated to the function

The entries are organized by symptom and reference these error types where applicable.

Expression returns null unexpectedly

Symptom: An output value you expected to be populated is null or missing from player parameters.

Possible causes:

CauseHow to identifyFix
The input field does not exist at this lifecycle hook. You referenced adsRequest.url in a PRE_SESSION_INITIALIZATION function. ADS request data is not available at session start. Move the function to the PRE_ADS_REQUEST lifecycle hook, or use a different input field. See Lifecycle hooks.
The input field is missing from the session data. You referenced player_params.campaign_id, but the player did not pass that parameter at session initialization. Use $exists() to check before accessing: {%$exists(player_params.campaign_id) ? player_params.campaign_id : 'default'%}.
You wrote an object or array to player parameters or the ADS request. These namespaces accept only strings, numbers, and booleans. Objects and arrays are filtered out. Store complex data in temp.* and extract strings, numbers, or booleans in a subsequent step.

RESOURCE_LIMIT_ERROR: Stack overflow

Symptom: The function fails with errorType: "RESOURCE_LIMIT_ERROR" and cause: "Stack overflow error".

Cause: The expression exceeded the maximum stack depth of 100 levels. This typically happens with deeply nested conditional (if/then/else) expressions or complex variable assignments.

This means the expression has too many levels of nesting for MediaTailor to process.

Fix: Simplify the expression. Break complex logic into multiple output entries or multiple steps in a sequential executor.

RESOURCE_LIMIT_ERROR: CPU timeout

Symptom: The function fails with errorType: "RESOURCE_LIMIT_ERROR" and cause: "Expression evaluation timeout: Check for infinite loop".

Cause: The expression exceeded the 100 ms CPU time limit. This can happen with expressions that perform expensive computations over large data structures.

Fix: Reduce the complexity of the expression. If you are processing large arrays, consider moving that logic to an external service and calling it with an HTTP_REQUEST function.

RESTRICTION_ERROR: Function not allowed

Symptom: The function fails with errorType: "RESTRICTION_ERROR" and cause: "Function '<name>' is not allowed".

Cause: The expression calls a JSONata function that is not in the allowed list of 44 functions. Common examples include $eval, $assert, $error, $sift.

Fix: Check the cause field for the blocked function name. Replace it with an allowed alternative. See JSONata expression reference for the full list of 44 allowed functions.

Commonly used allowed functions include $string, $number, $substring, $contains, and $encodeUrlComponent.

RESTRICTION_ERROR: Expression too long

Symptom: The function fails to create or update with cause: "Expression length <actual> exceeds limit <limit>".

Cause: A single expression exceeds 1,000 characters.

Fix: Break the expression into smaller parts. Use multiple output entries, or split the logic across multiple steps in a sequential executor. Use variable binding (:=) to avoid repeating long subexpressions.

HTTP error: Status code is null

Symptom: In an HTTP_REQUEST function's output, response.statusCode is null.

Cause: The external API was unreachable, the connection timed out, or a network error occurred. When this happens, MediaTailor sets response.statusCode to null, response.body to null, and response.text to "Internal Error".

Fix: Always check response.statusCode before accessing response data:

{%response.statusCode = 200 ? response.body.value : 'default'%}

This expression checks whether the HTTP call returned a 200 status code. If it did, it uses the response value. Otherwise, it falls back to a default value.

If this happens frequently, check whether the external API is healthy. Consider increasing RequestTimeoutMilliseconds if the API is slow, or decreasing it if you want to fail fast.

HTTP error: Response body is null

Symptom: response.statusCode is 200 but response.body is null.

Cause: The response body is either not valid JSON or exceeds 20,000 characters. MediaTailor only parses JSON responses up to 20,000 characters into response.body.

Fix: Use response.text as a fallback. The response.text field contains the raw response body truncated to 20,000 characters:

{%response.statusCode = 200 ? ($exists(response.body.id) ? response.body.id : $substring(response.text, 0, 100)) : 'error'%}

If the data you need is beyond the 20,000-character limit, consider asking the external API to return a smaller response (for example, by requesting specific fields).

HTTP error: URL validation failure

Symptom: The function fails at runtime with a message about the URL being malformed, using an invalid scheme, or exceeding the maximum length.

Possible causes:

CauseFix
The URL does not use http or https. Ensure the URL expression produces a URL starting with http:// or https://.
The URL exceeds 2,048 characters after expression evaluation. Shorten the URL. Move large parameter values to the request body using a POST method.
The URL is malformed (not a valid URI). Check the expression for missing or extra characters. Use $encodeUrlComponent() for query parameter values that may contain special characters.

VALIDATION_ERROR

Symptom: The function fails with errorType: "VALIDATION_ERROR". This error can occur at authoring time (when you create or update the function) or at execution time (when the function runs during a session).

Possible causes:

CauseExampleFix
Output key targets a namespace not writable at the current hook. Writing to adsRequest.url in a function attached to PRE_SESSION_INITIALIZATION. Check which output namespaces are allowed at your lifecycle hook. PRE_SESSION_INITIALIZATION only allows player_params.*. Either move the function to the correct hook or change the output key. See Lifecycle hooks.
Output key uses invalid characters. An output key like player_params.device type (with a space). Only letters, numbers, underscores, and hyphens are allowed. Rename the output key to use only valid characters. For example, use player_params.device_type instead.
Output key doesn't start with a valid prefix. An output key like custom.myValue instead of player_params.myValue or temp.myValue. Use a valid output prefix: player_params.*, temp.*, or adsRequest.*.
A JSONata expression has a syntax error. A missing closing quote or incomplete conditional: {%session.id & %}. Review the expression for missing quotes, unmatched parentheses, or unsupported operators like ?? or ?:.
A required field is missing in an HTTP_REQUEST function. The URL field is empty or the method is not specified. Ensure the URL and method fields are set. The method must be GET or POST.
The URL built by the expression is invalid. The evaluated URL uses an unsupported scheme like ftp://, exceeds 2,048 characters, or is malformed. Verify the URL expression produces a valid http:// or https:// URL. Use $encodeUrlComponent() for query parameter values that may contain special characters.
An HTTP header contains invalid characters or uses a restricted name. A header value contains line breaks, or the header name is host or transfer-encoding. Remove invalid characters from header values. Avoid restricted header names. See HTTP request for header limits.

Check the cause field in the error log event — it identifies which field or expression failed validation.

INTERNAL_ERROR

Symptom: The function fails with errorType: "INTERNAL_ERROR".

Cause: An infrastructure failure occurred that is unrelated to your function configuration.

Fix: Retry the request. If the error persists, contact AWS Support.

Ad list change has no effect

Symptom: Your function runs without errors at POST_ADS_RESPONSE or PRE_MANIFEST_INSERTION, but the ads in the stream are unchanged. A filter, reorder, or modification appears to be ignored.

Possible causes:

CauseHow to identifyFix
The filter expression produced zero or one result without array coercion, so the output key was omitted or misapplied. The expression is a predicate filter such as adsResponse.ads[adSystem != 'BLOCKED'] without a trailing [] or an enclosing array constructor. With one match it produces a single object; with zero matches it produces no value, and the output key is omitted, which the system treats as "no change". Always coerce filter results to an array: adsResponse.ads[adSystem != 'BLOCKED'][] or [adsResponse.ads[predicate]].
The ad break is not modifiable (PRE_MANIFEST_INSERTION). The ad break's mutable input field is false. Modifications to these ad breaks are discarded. Check mutable in your expression and only modify ad breaks where it is true.
The hook was skipped because the shared hook budget was exhausted or the input exceeded the size limit. No HOOK_SUMMARY event for the hook in the ADS interaction log for that request. Reduce time spent in earlier hooks, or reduce input size. See Limits.

To confirm what your function actually returned, opt in to the FUNCTION_COMPLETED event types for the hook and inspect the function output in the ADS interaction log.

Injected ad doesn't appear in the manifest

Symptom: Your PRE_MANIFEST_INSERTION function returns an injected ad and completes successfully, but the ad is not in the rendered manifest.

Injection checks run after your function returns, so the function's log events report success even when an injection is dropped. Check the following causes in order:

CauseFix
The creativeUrl creative is not yet transcoded. MediaTailor drops the injection for the current ad break rather than waiting, but the attempt initiates transcoding. Injecting the same URL into a later ad break succeeds after transcoding completes. For an immediate insertion, use a creative MediaTailor has already transcoded (a creative from a previous ad break, or a skippedAds[].creativeUrl value whose reason is not transcode-related), or inject by vastAdId with a VAST_REQUEST function in the same chain so MediaTailor handles media selection and transcode registration.
The entry has neither a usable creativeUrl nor a vastAdId that matches an ad parsed by a VAST_REQUEST call in the same hook invocation. Call VAST_REQUEST in the same function chain as the injection, and inject using the parsed ad's adId value as vastAdId.
The invocation exceeded the injection limit of 10 ads. Injections beyond the limit are dropped. Reduce the number of injected ads per invocation.
The target ad break is not modifiable (mutable: false). Only inject into ad breaks where mutable is true.
The injected ad's renditions don't match the stream's variants, or the added duration overflows the ad break. Injected ads go through the same variant matching and fill policy as ads from the ADS. Verify the creative's renditions match the stream, and keep total ad duration within the ad break.