# AWS_EMF_ENVIRONMENT: valid values and missing EMF fixes

> AWS_EMF_ENVIRONMENT overrides EMF library detection. Check Node, Python and Java values separately, trace stdout or agent delivery, and fix missing metrics.

By Boris Tane, Founder of Polylane · Published October 6, 2026 · 7 min read
Canonical: https://polylane.com/learn/troubleshooting/AWS_EMF_ENVIRONMENT/

AWS_EMF_ENVIRONMENT overrides environment detection in AWS embedded-metrics client libraries. Node.js and Java document Local, Lambda, Agent, EC2 and ECS; Python's current resolver recognizes Local, Lambda, EC2 and Default, case-insensitively. Choose the value supported by your installed library and verify the full route from emitted EMF JSON to CloudWatch Logs and extracted metrics.

`AWS_EMF_ENVIRONMENT` overrides the environment selected by AWS's embedded-metrics client libraries. It controls how the library decorates and delivers metric log events; it is not the name of a CloudWatch namespace or a production/staging label. Check the value against your language library before investigating missing metrics, because the supported overrides differ.

The verified [Node.js configuration](https://github.com/awslabs/aws-embedded-metrics-node#configuration) and [Java configuration](https://github.com/awslabs/aws-embedded-metrics-java#configuration) list `Local`, `Lambda`, `Agent`, `EC2` and `ECS`. Python's [current resolver](https://github.com/awslabs/aws-embedded-metrics-python/blob/master/aws_embedded_metrics/environment/environment_detector.py) instead recognizes `Local`, `Lambda`, `EC2` and `Default`, case-insensitively. Follow the installed release, not an assumed universal AWS value list.

## What the variable actually changes

Embedded metric format, or EMF, is JSON that carries both log context and a `_aws.CloudWatchMetrics` directive. When a valid event reaches CloudWatch Logs, CloudWatch extracts the declared metrics. The [EMF specification](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) defines that extraction contract; the library's environment setting decides which local transport and metadata to use before the event reaches the service.

That gives you three separate checks:

1. Did the application emit and flush a valid metric event?
2. Did stdout collection or an agent deliver that event to CloudWatch Logs?
3. Did CloudWatch extract the expected metric identity and timestamp?

Setting the variable alone does not install the CloudWatch Agent, grant logging permissions or route a container's stdout. Nor does it change native Lambda metrics such as `Errors` or [AsyncEventsDropped](/learn/troubleshooting/AsyncEventsDropped/).

## Valid values, verified separately by library

The following reflects the AWS-owned library documentation and resolver source checked on 6 October 2026. Package releases can differ from the default branch, so inspect the installed release when debugging an older service.

| Library | Supported override spellings | Important behavior |
| --- | --- | --- |
| Node.js `aws-embedded-metrics` | `Local`, `Lambda`, `Agent`, `EC2`, `ECS` | Local/Lambda write stdout; Agent/EC2 use agent transport; ECS supports its documented FireLens path |
| Python `aws-embedded-metrics` | `Local`, `Lambda`, `EC2`, `Default` | Resolver lowercases input; Default selects the default agent environment; unknown values fall through to detection |
| Java `software.amazon.cloudwatchlogs:aws-embedded-metrics` | `Local`, `Lambda`, `Agent`, `EC2`, `ECS` | Documented stdout for Local/Lambda and TCP for agent environments; `AWS_EMF_WRITE_TO_STDOUT=true` can override agent output |

Node's [environment detector](https://github.com/awslabs/aws-embedded-metrics-node/blob/master/src/environment/EnvironmentDetector.ts) tests Lambda, then ECS, then EC2 when no valid override is supplied, and falls back to its default environment. It caches resolution and starts resolving at module load. Configure the process environment before importing the library rather than trying to switch modes halfway through a request.

Python's [resolver](https://github.com/awslabs/aws-embedded-metrics-python/blob/master/aws_embedded_metrics/environment/environment_detector.py) tests Lambda and then EC2, with a default-environment fallback. Its [configuration provider](https://github.com/awslabs/aws-embedded-metrics-python/blob/master/aws_embedded_metrics/config/environment_configuration_provider.py) reads `AWS_EMF_ENVIRONMENT` from the process environment. Python's README does not provide Node's override list; the resolver is the evidence for its distinct accepted values.

Use documented casing for Node and Java; do not assume Python's lowercase normalization exists everywhere. Do not invent values such as `EKS`, `Fargate`, `Production` or `CloudWatch`. EKS or an on-premises host can use an agent-based delivery path without a same-named environment override.

## Confirm the actual package and process configuration

Run these in the application's environment, not only on your development laptop:

```bash
# Node.js project
npm ls aws-embedded-metrics

# Python process environment
python -m pip show aws-embedded-metrics
python -c 'import os; print(os.getenv("AWS_EMF_ENVIRONMENT", "<unset>"))'

# Maven project
mvn dependency:tree -Dincludes=software.amazon.cloudwatchlogs:aws-embedded-metrics
```

For Lambda, inspect only relevant keys rather than dumping every environment variable, which may include secrets:

```bash
aws lambda get-function-configuration --function-name orders-worker \
  --query '{Runtime:Runtime,EMF:Environment.Variables.AWS_EMF_ENVIRONMENT,LogFormat:LoggingConfig.LogFormat}'
```

Check the version actually reached by the alias too. A changed `$LATEST` environment does not update an existing published version. For containers, inspect the running task/pod configuration and its log driver or collector, not just the Dockerfile used to build it.

In Python, you can inspect the installed resolver directly from an interactive session with `inspect.getsource`. That lets you establish which override values your wheel accepts without assuming the current repository matches the installed code.

## Produce one known-good metric before changing the pipeline

For Node.js, install a current compatible `aws-embedded-metrics` release. The [README](https://github.com/awslabs/aws-embedded-metrics-node) specifically requires versions 4.1.1+, 3.0.2+ or 2.0.7+ for Lambda JSON log format, warning of metric loss with earlier releases in that configuration.

A minimal Lambda handler using the library's scope wrapper is:

```javascript
const { metricScope, Unit } = require("aws-embedded-metrics");

exports.handler = metricScope(metrics => async () => {
  metrics.setNamespace("Orders/Diagnostics");
  metrics.setDimensions({ Service: "orders-worker" });
  metrics.putMetric("Probe", 1, Unit.Count);
  metrics.setProperty("Purpose", "emf-delivery-check");
  return { ok: true };
});
```

The wrapper handles flushing at the end of the scoped invocation. If you create a logger manually, `await metrics.flush()` before the Lambda handler returns. Do not launch the flush as unobserved background work.

For a local Python smoke check, set the environment before launching the interpreter:

```bash
AWS_EMF_ENVIRONMENT=Local python -c '
from aws_embedded_metrics import metric_scope
@metric_scope
def probe(metrics):
    metrics.set_namespace("Orders/Diagnostics")
    metrics.set_dimensions({"Service": "orders-worker"})
    metrics.put_metric("Probe", 1, "Count")
probe()
'
```

This proves local EMF serialization, not CloudWatch ingestion. In a real Lambda environment, use autodetection or the library's `Lambda` override and its supported logging path. For Python running with an agent, the accepted override is `Default`; for Node or Java it is `Agent`. Configure the agent endpoint and log group separately according to that library's configuration documentation.

## Missing EMF: trace the first broken step

### Nothing leaves the application

Confirm the metric-producing code actually ran, then inspect raw stdout or the agent-facing output. Check that the logger is flushed and errors from flush are observed. In Python, also check `AWS_EMF_DISABLE_METRIC_EXTRACTION`: the [library documentation](https://github.com/awslabs/aws-embedded-metrics-python#configuration) says it can deliberately omit EMF metadata. Plain log properties without that metadata are not extraction-ready metrics.

An invalid override can fall back to detection rather than failing deployment. That is why a successful startup is insufficient verification. Confirm the emitted event and transport selected by the installed package.

### EMF appears locally but not in CloudWatch Logs

`Local` writes stdout; it does not call the CloudWatch service directly. Follow the actual logging pipeline. On Lambda, check execution-role log permissions and the function's configured log group. In an agent-based environment, check the configured endpoint, agent listener, connectivity and the agent's own logs.

If you intentionally route stdout from an agent-based Java environment, the [Java README](https://github.com/awslabs/aws-embedded-metrics-java#configuration) documents `AWS_EMF_WRITE_TO_STDOUT=true`. That is a Java setting, not a portable instruction for all three clients. Non-Lambda Java agent output can also be asynchronously buffered; follow the README's graceful-shutdown sink procedure so a process exit does not abandon buffered events.

### Logs arrive but no metric appears

Open the raw CloudWatch log event, not just a rendered summary. The [specification](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) requires a JSON object with no prefixed or suffixed text. `_aws` and metric/dimension targets must have the correct case and root-level placement. A timestamp prefix, stringified JSON nested inside `message`, or a multiline formatting pipeline can alter the extraction contract.

Check the declared `Namespace`, metric name, dimension sets, numeric values and timestamp. The EMF timestamp is epoch milliseconds, no more than 14 days old or two hours in the future. A seconds timestamp is not interchangeable. Inspect parsing/validation failure metrics in `AWS/Logs` as AWS recommends, and do not troubleshoot serialization by changing the transport override alone.

For the probe above, locate the metric identity explicitly:

```bash
aws cloudwatch list-metrics --namespace Orders/Diagnostics \
  --metric-name Probe \
  --dimensions Name=Service,Value=orders-worker
```

The namespace and dimensions are case-sensitive identities. A metric with additional default dimensions is distinct from the single-dimension series in this probe. Allow ingestion and discovery time, then query a time range containing the event's timestamp. Old or incorrectly timestamped events may exist in logs while the current chart remains empty.

## Verification and prevention

Success means you can find all three pieces: a valid emitted JSON event, the same event in CloudWatch Logs, and a datapoint with the exact declared metric identity. Run this check in the real target environment after library, runtime, log-format or collector changes. Keep package versions pinned and document why an override is necessary; leave autodetection enabled when it already selects the correct route.

Keep high-cardinality request IDs as log properties rather than metric dimensions. The [EMF specification](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) explains that each unique dimension combination creates a custom metric, with corresponding cost. A successful extraction test should not generate an unbounded set of series.

For the separate AWS system collector, use the [LambdaInsightsExtension guide](/learn/troubleshooting/LambdaInsightsExtension/). For an automated investigator, the [AWS read-access guidance](/learn/ai-in-production/is-it-safe-to-give-an-ai-sre-read-access-to-production/) helps separate telemetry reads from the credentials that configure and deliver telemetry.

Polylane's [AWS integration](/integrations/aws/) connects it to AWS, so it can see when something breaks there and work out why. The [AWS overview](/for/aws/) has more. When EMF delivery breaks, the useful first result is evidence of which pipeline step failed; missing custom metrics are not evidence that the application is healthy.

Running on AWS? See [how Polylane monitors AWS in production](https://polylane.com/for/aws/).

## Common questions

**When should I set the override?**

Use it when the installed library's automatic detection selects the wrong metadata or delivery path, or when a controlled local test needs stdout. Set it before library initialization and verify the emitted event and actual destination; leave detection enabled when it already works.

**Is AWS_EMF_ENVIRONMENT the deployment stage?**

No. It selects a library environment and delivery behavior, such as Lambda stdout or an agent transport. Use an explicit low-cardinality dimension or property for production, staging or a service name.

**Can I set AWS_EMF_ENVIRONMENT=Agent in Python?**

The current Python environment resolver recognizes Default for its default agent environment, not Agent. It lowercases the supplied value and falls back to detection for unknown values. Check the resolver in your installed release rather than copying Node's value list.

**Does Local publish metrics directly to CloudWatch?**

No. Local writes EMF to stdout without cloud-environment decoration. Unless a supported pipeline delivers those JSON events into CloudWatch Logs for extraction, the metrics remain local output.

**Does the Lambda Insights layer use my application's EMF override?**

AWS_EMF_ENVIRONMENT is a setting for the embedded-metrics client library. Lambda Insights is a separate collector with its own installation and log delivery requirements. Do not assume changing the application's variable configures that extension.

## Sources

- [AWS embedded-metrics Node.js configuration](https://github.com/awslabs/aws-embedded-metrics-node#configuration)
- [Node.js environment detector](https://github.com/awslabs/aws-embedded-metrics-node/blob/master/src/environment/EnvironmentDetector.ts)
- [Python environment detector](https://github.com/awslabs/aws-embedded-metrics-python/blob/master/aws_embedded_metrics/environment/environment_detector.py)
- [Python environment configuration provider](https://github.com/awslabs/aws-embedded-metrics-python/blob/master/aws_embedded_metrics/config/environment_configuration_provider.py)
- [AWS embedded-metrics Java configuration](https://github.com/awslabs/aws-embedded-metrics-java#configuration)
- [CloudWatch EMF specification](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html)
- [Polylane AWS integration](https://docs.polylane.com/integrations/aws)

## About the author

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

## Related

- [LambdaInsightsExtension: setup and missing metrics fixes](https://polylane.com/learn/troubleshooting/LambdaInsightsExtension/): LambdaInsightsExtension is the AWS Lambda Insights layer name. Choose a verified regional ARN, grant collector log permissions, and diagnose missing metrics.
- [AsyncEventsDropped: diagnose Lambda async event loss](https://polylane.com/learn/troubleshooting/AsyncEventsDropped/): AsyncEventsDropped counts Lambda async events discarded after age or retry limits. Check throttling, destinations and DLQs, then verify processing recovery.
- [Is It Safe to Give an AI SRE Read Access to Production?](https://polylane.com/learn/ai-in-production/is-it-safe-to-give-an-ai-sre-read-access-to-production/): Read access for an AI SRE is a sound first step if you scope it. The risks are secrets, customer data and exfiltration. Here are the controls and a checklist.
- [Fix 403 Forbidden (Access Denied) from Amazon S3](https://polylane.com/learn/troubleshooting/aws-403-forbidden/): Why Amazon S3 returns 403 Forbidden: Block Public Access, missing s3:ListBucket, object ownership, clock skew, expired presigned URLs and CloudFront. Fix each.
- [Fix 502 Bad Gateway on AWS API Gateway and Load Balancers](https://polylane.com/learn/troubleshooting/aws-502-bad-gateway/): Why AWS returns 502 Bad Gateway from API Gateway or an Application Load Balancer, how to find which hop wrote it, and the fix for each cause.

Get started with one command: `curl -fsSL https://polylane.com/setup | bash` installs the CLI, connects your coding agents, and creates the account.
