Find Lambda Functions Without X-Ray Tracing Enabled

Lines of code on a dark monitor in a dim office

Photo by Pankaj Patel on Unsplash

To check whether Lambda X-Ray tracing is enabled, read TracingConfig.Mode from ListFunctions: Active means Lambda sends traces for sampled requests, PassThrough (the default) means it doesn’t. Active tracing also needs xray:PutTraceSegments and xray:PutTelemetryRecords on the execution role, which the AWSXRayDaemonWriteAccess managed policy includes.

When a request is slow, a trace shows where the time went: Lambda initialization, your handler, or a downstream call. With PassThrough mode, that trace isn’t there when you need it. This example is for engineers running Lambda in production who want to know which functions have Lambda X-Ray tracing enabled, which don’t, and which are set to Active but can’t write traces because of their role.

The TypeScript script for the AWS SDK for JavaScript v3 lists functions in each Region, checks each execution role with the IAM policy simulator, and with --names and --apply turns on Active tracing for the functions you choose. When you’re investigating a specific failure rather than setting up tracing, start with the guide on how to investigate Lambda errors with CloudWatch.

What’s the difference between Active and PassThrough tracing?

Lambda supports two tracing modes, and the Lambda developer guide describes them like this:

  • Active. Lambda creates trace segments for sampled invocations and sends them to X-Ray. If an upstream service such as API Gateway already made a sampling decision and passed it in the tracing header, Lambda follows it.
  • PassThrough. Lambda forwards the tracing header to downstream services but doesn’t send traces, even when the header says to sample. If there’s no header, Lambda generates one with a decision not to sample.

That second point changed. AWS notes that Lambda used to send traces automatically when an upstream service added a tracing header. It no longer does, and AWS’s advice is to switch to Active if you depended on that behavior. If a service map that used to show your functions now has gaps, this is a likely cause.

Two limits are worth knowing. The sampling rate for Lambda is 1 request per second plus 5 percent of additional requests, and you can’t configure it for your functions. X-Ray tracing isn’t supported for functions triggered by Amazon MSK, self-managed Apache Kafka, Amazon MQ or Amazon DocumentDB event source mappings.

Which xray permissions does a Lambda execution role need for tracing?

The execution role needs xray:PutTraceSegments and xray:PutTelemetryRecords. When you turn tracing on in the Lambda console, Lambda adds them for you. When you turn it on through the API, CloudFormation or Terraform, you add them yourself, usually by attaching AWSXRayDaemonWriteAccess. That’s how functions end up in Active mode with no traces. The script calls SimulatePrincipalPolicy for each distinct role, which evaluates identity-based policies and service control policies; AWS notes that simulator results can differ from your live environment, so treat a “no” as a strong hint rather than proof. Roles that grant too much are a separate risk, covered by the script to find Lambda functions with over-privileged execution roles.

What does the script do?

  1. Lists functionspaginateListFunctions returns each function’s unpublished configuration, including TracingConfig.Mode, Runtime and Role.
  2. Checks each role onceSimulatePrincipalPolicy with both X-Ray actions. Results are cached per role ARN, so 200 functions sharing one role cost one call.
  3. Reports gapsFunctions in PassThrough mode, and Active functions whose role can’t write traces. Active functions with a working role are left out.
  4. Turns on tracing with --applyUpdateFunctionConfiguration with TracingConfig: { Mode: "Active" }, only for names in --names whose role already passes the check.

Prerequisites

  • Node.js 18 or later, npm, tsx, @aws-sdk/client-lambda and @aws-sdk/client-iam.
  • A read-only profile for the report and a deploy profile for --apply.
  • If your functions are defined in infrastructure code, plan to set TracingConfig there too, or the next deploy switches it back.

Which IAM permissions does it need?

lambda-tracing-audit-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListFunctions",
      "Effect": "Allow",
      "Action": "lambda:ListFunctions",
      "Resource": "*"
    },
    {
      "Sid": "SimulateExecutionRoles",
      "Effect": "Allow",
      "Action": "iam:SimulatePrincipalPolicy",
      "Resource": "arn:aws:iam::123456789012:role/*"
    },
    {
      "Sid": "EnableTracingWithApply",
      "Effect": "Allow",
      "Action": "lambda:UpdateFunctionConfiguration",
      "Resource": "arn:aws:lambda:*:123456789012:function:*"
    }
  ]
}

Remove the last statement for an audit-only role. The free IAM policy generator for TypeScript code builds a starting list from any version of the script.

The script to find Lambda functions without tracing

find-lambda-functions-without-tracing.ts

// find-lambda-functions-without-tracing.ts
// Lists Lambda functions whose X-Ray tracing mode is PassThrough, and functions with Active tracing
// whose execution role can't write traces. Report only unless you pass --apply with --names: then it
// sets TracingConfig Mode=Active on the named functions whose role already allows the X-Ray writes.
// Usage:
//   npx tsx find-lambda-functions-without-tracing.ts [--regions us-east-1,eu-west-1]
//   npx tsx find-lambda-functions-without-tracing.ts --regions us-east-1 --names orders-api,checkout --apply
import { IAMClient, SimulatePrincipalPolicyCommand } from "@aws-sdk/client-iam";
import { LambdaClient, UpdateFunctionConfigurationCommand, paginateListFunctions } from "@aws-sdk/client-lambda";

const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
  const i = args.indexOf(name);
  return i >= 0 ? args[i + 1] : undefined;
};
const list = (v: string | undefined) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
const regions = list(flag("--regions") ?? process.env.AWS_REGION ?? "us-east-1");
const names = new Set(list(flag("--names")));
const apply = args.includes("--apply");
const XRAY_ACTIONS = ["xray:PutTraceSegments", "xray:PutTelemetryRecords"];

interface Row {
  Region: string;
  Function: string;
  Runtime: string;
  Tracing: string;
  RoleCanTrace: string;
  Result: string;
}

const iam = new IAMClient({ region: process.env.AWS_REGION ?? "us-east-1" });
const roleCache = new Map<string, string>();

// Asks the IAM policy simulator whether the execution role allows both X-Ray write actions.
async function roleCanTrace(roleArn: string): Promise<string> {
  const cached = roleCache.get(roleArn);
  if (cached) return cached;
  let answer: string;
  try {
    const res = await iam.send(new SimulatePrincipalPolicyCommand({ PolicySourceArn: roleArn, ActionNames: XRAY_ACTIONS, ResourceArns: ["*"] }));
    const denied = (res.EvaluationResults ?? []).filter((r) => r.EvalDecision !== "allowed").map((r) => r.EvalActionName);
    answer = denied.length === 0 ? "yes" : `no (${denied.join(", ")})`;
  } catch (err) {
    answer = `unknown (${err instanceof Error ? err.name : String(err)})`;
  }
  roleCache.set(roleArn, answer);
  return answer;
}

async function scanRegion(region: string, rows: Row[]): Promise<void> {
  const lambda = new LambdaClient({ region });
  for await (const page of paginateListFunctions({ client: lambda }, {})) {
    for (const fn of page.Functions ?? []) {
      const name = fn.FunctionName ?? "?";
      const mode = fn.TracingConfig?.Mode ?? "PassThrough";
      const role = fn.Role ? await roleCanTrace(fn.Role) : "unknown (no role)";
      if (mode === "Active" && role === "yes") continue; // traced and able to write: nothing to report
      const row: Row = {
        Region: region,
        Function: name,
        Runtime: fn.Runtime ?? fn.PackageType ?? "?",
        Tracing: mode,
        RoleCanTrace: role,
        Result: mode === "Active" ? "Active, but the role can't write traces" : "",
      };
      if (apply && mode !== "Active" && names.has(name)) {
        if (role !== "yes") {
          row.Result = "skipped: add AWSXRayDaemonWriteAccess to the role first";
        } else {
          try {
            await lambda.send(new UpdateFunctionConfigurationCommand({ FunctionName: name, TracingConfig: { Mode: "Active" } }));
            row.Result = "set to Active";
          } catch (err) {
            row.Result = `error: ${err instanceof Error ? err.name : String(err)}`;
          }
        }
      } else if (apply && mode !== "Active") {
        row.Result = "skipped (not in --names)";
      }
      rows.push(row);
    }
  }
}

async function main(): Promise<void> {
  if (apply && names.size === 0) {
    console.error("--apply needs --names with the function names to change.");
    process.exit(1);
  }
  const rows: Row[] = [];
  for (const region of regions) {
    try {
      await scanRegion(region, rows);
    } catch (err) {
      console.error(`${region}: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}`);
    }
  }
  const passThrough = rows.filter((r) => r.Tracing !== "Active").length;
  console.log(`Functions without active X-Ray tracing: ${passThrough}; Active but role can't write traces: ${rows.length - passThrough}`);
  if (rows.length) console.table(rows);
  if (!apply) console.log("Report only. Pass --names and --apply to turn on active tracing.");
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

How do you run it?

Terminal

npm install @aws-sdk/client-lambda @aws-sdk/client-iam
npm install --save-dev tsx typescript @types/node

# Report only
AWS_PROFILE=readonly npx tsx find-lambda-functions-without-tracing.ts --regions us-east-1,eu-west-1

# Turn on active tracing for two functions
AWS_PROFILE=deploy npx tsx find-lambda-functions-without-tracing.ts --regions us-east-1 --names orders-api,checkout --apply

Sample output

Output

Functions without active X-Ray tracing: 2; Active but role can't write traces: 1
┌─────────┬─────────────┬──────────────┬──────────────┬───────────────┬────────────────────────────────────────────────────────┬───────────────────────────────────────────────────────────┐
│ (index) │ Region      │ Function     │ Runtime      │ Tracing       │ RoleCanTrace                                           │ Result                                                    │
├─────────┼─────────────┼──────────────┼──────────────┼───────────────┼────────────────────────────────────────────────────────┼───────────────────────────────────────────────────────────┤
│ 0       │ 'us-east-1' │ 'orders-api' │ 'nodejs22.x' │ 'PassThrough' │ 'yes'                                                  │ 'set to Active'                                           │
│ 1       │ 'us-east-1' │ 'checkout'   │ 'python3.13' │ 'PassThrough' │ 'no (xray:PutTraceSegments, xray:PutTelemetryRecords)' │ 'skipped: add AWSXRayDaemonWriteAccess to the role first' │
│ 2       │ 'us-east-1' │ 'reports'    │ 'java21'     │ 'Active'      │ 'no (xray:PutTraceSegments, xray:PutTelemetryRecords)' │ "Active, but the role can't write traces"                 │
└─────────┴─────────────┴──────────────┴──────────────┴───────────────┴────────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────┘

Names are illustrative. orders-api now has Active tracing. checkout was skipped because its role has no X-Ray permissions, and reports uses the same role, so it’s set to Active but can’t send traces.

What does Lambda X-Ray tracing cost?

X-Ray item Price as of September 2026 Free each month
Traces recorded $5.00 per million ($0.000005 each) First 100,000
Traces retrieved or scanned $0.50 per million ($0.0000005 each) First 1,000,000

Prices are for US East (N. Virginia) from the AWS Price List API file published 11 September 2026, and match the X-Ray section of the Amazon CloudWatch pricing page, where X-Ray pricing now lives.

Worked example. A function handling a steady 7.7 requests per second (about 20 million a month) with the Lambda sampling rate records roughly 1 + 0.05 × 6.7 = 1.335 traces per second. Over 30 days that’s 1.335 × 2,592,000 ≈ 3.46 million traces. After the 100,000 free traces, (3,460,000 − 100,000) × $0.000005 ≈ $16.80 a month, before retrieval. If an upstream service samples more heavily, Lambda follows its decision and the number rises. To estimate your own volume, get Lambda invocation counts for the last 24 hours and apply the same formula.

Troubleshooting

  • Active tracing is on but no traces appear. Check the role column first. Then check that requests are sampled at all: a function called once an hour produces few traces.
  • ResourceConflictException on --apply. Another update to the function is still in progress. Wait for it to finish and run the script again.
  • A published version still shows PassThrough. Tracing mode is part of a version’s configuration and can’t be changed on a published version. Publish a new version after the change and move your alias to it.
  • RoleCanTrace shows unknown. The profile lacks iam:SimulatePrincipalPolicy; the guide to troubleshoot AWS IAM access denied errors shows how to confirm it.

Should you add code-level spans, keep in mind that AWS put the X-Ray SDKs and daemon into maintenance mode on 25 February 2026, with end of support on 25 February 2027, and now recommends OpenTelemetry-based instrumentation such as AWS Distro for OpenTelemetry. The script only changes Lambda’s own tracing setting. For workflows, the check to find Step Functions state machines without logging or tracing does the same job, and for the entry point, find API Gateway stages without access logging.

Ask ChatWithCloud instead

Ask ChatWithCloud “Which Lambda functions in us-east-1 don’t have active tracing?” It writes AWS SDK for JavaScript v2 code, runs it on your machine with your profile and lists the functions. To go from a failing function to its errors, the guide to ask AI about Lambda errors in your AWS account shows the follow-up questions. Changes run without a confirmation step, so leave UpdateFunctionConfiguration to the script and keep ChatWithCloud on a read-only profile.

Frequently asked questions

How do I check if X-Ray tracing is enabled on a Lambda function?

Run aws lambda get-function-configuration --function-name my-function --query TracingConfig.Mode. Active means tracing is on; PassThrough means Lambda doesn’t send traces.

How do I enable X-Ray tracing on a Lambda function?

Run aws lambda update-function-configuration --function-name my-function --tracing-config Mode=Active, and make sure the execution role has AWSXRayDaemonWriteAccess or the two X-Ray write actions.

Can I change the X-Ray sampling rate for Lambda?

No. Lambda samples 1 request per second and 5 percent of additional requests, and AWS says you can’t configure it for your functions. An upstream service’s sampling decision is followed in Active mode.

Does Lambda send traces in PassThrough mode?

No. It forwards the tracing header downstream but doesn’t send traces, even if the header asks for sampling. Lambda used to do this, but no longer does.

Related guides

Ask your AWS account in plain English

Your first 15 runs are free, with no OpenAI key needed.

npx chatwithcloud