AWS CloudTrail Lake is a managed audit data lake that ingests CloudTrail events into Apache ORC columnar storage and exposes them through a SQL query interface — without the operational overhead of S3 bucket partitioning, Glue crawlers, or custom ETL. For Bedrock workloads, it provides the only native path to retroactive, immutable, queryable records of every InvokeModel call: who called it, which model, from which identity, at what time. This matters for two distinct reasons: (1) FinOps teams that cannot explain per-team Bedrock spend without call-level attribution, and (2) compliance officers at financial services and healthcare organizations who need a tamper-evident, queryable audit trail meeting 7-year retention mandates.
Note on availability: As of June 1, 2026, CloudTrail Lake is available only to existing customers who enrolled before May 31, 2026. New accounts must use S3-delivered CloudTrail logs with Athena for equivalent analysis. Existing customers should evaluate lock-in carefully before expanding event data stores.
1. CloudTrail Lake vs. S3-Delivered CloudTrail Logs
Architecture comparison
| Dimension | S3 + Athena | CloudTrail Lake |
|---|---|---|
| Storage format | JSON (compressed, partitioned by account/region/date) | Apache ORC columnar (managed) |
| Retention model | S3 Lifecycle rules — your responsibility | Built-in: 1-year (extendable to 10) or 7-year tiers |
| Query interface | Athena SQL — requires Glue table, partition management | Native SQL via console/API/SDK — no table setup |
| Partition pruning | Manual: WHERE year=2025 AND month=06 AND day=15 against S3 prefix |
Automatic: time-range filters push down to ORC stripes |
| ETL requirement | Partition projection or Glue crawler required | None — events land query-ready |
| Cross-account aggregation | Requires S3 bucket policy + per-trail config | Organization-level event data store covers all member accounts in one query |
| Data integrity | S3 bucket owner controls deletion | Immutable by default; no delete API |
| Athena join capability | Native — CUR and CloudTrail in same Athena workspace | Via Lake query federation (Glue Data Catalog registration) |
| Maximum retention | Unlimited (S3 object lifecycle) | 10 years (1-year tier) or 7 years (7-year tier) |
When S3 + Athena wins
S3-delivered logs remain superior for organizations that already have a mature data lake, want to join CloudTrail events to non-AWS log sources in a single Athena query, or have existing Glue/Lake Formation governance infrastructure. The per-GB query cost is identical ($5/TB scanned). The operational difference is setup complexity, not query capability.
When CloudTrail Lake wins
CloudTrail Lake is the faster path to production for multi-account Bedrock auditing: one event data store covers the entire AWS Organization, queries run against pre-indexed ORC columns rather than raw JSON, and there is no partition maintenance to perform. The immutability guarantee matters for compliance functions that need evidence records that cannot be modified after creation.
2. CloudTrail Lake Pricing for Bedrock Data Events
Two retention tiers
One-year extendable retention (recommended below 25 TB/month ingested)
| Component | Price |
|---|---|
| Ingestion — CloudTrail management, data, and network activity events | $0.75/GB uncompressed |
| Ingestion — other AWS data sources (Config, non-AWS) | $0.50/GB uncompressed |
| Retention beyond year 1 (up to 10 years) | $0.023/GB/month |
| Query analysis | $0.005/GB scanned |
First year of retention is included in the ingestion price. ORC compression ratios are typically 8–12x over raw JSON, so 100 GB uncompressed Bedrock events costs $75 to ingest but occupies roughly 8–12 GB of managed storage.
Seven-year retention (recommended above 25 TB/month)
| Component | Price |
|---|---|
| Ingestion — first 5 TB/month | $2.50/GB uncompressed |
| Ingestion — next tiers | Volume tiering applies |
| Retention | 7 years included, no extension beyond 7 years |
| Query analysis | $0.005/GB scanned |
Bedrock data event volume estimation
InvokeModel data events are compact: the request body (prompt content) and response body are not included in the CloudTrail record for security reasons — only metadata fields are captured. A typical InvokeModel event serializes to approximately 1–3 KB uncompressed. An organization running 1 million Bedrock calls per month generates roughly 1–3 GB of uncompressed event data, costing $0.75–$2.25/month to ingest under the 1-year tier. This is low enough that ingestion cost is rarely the budget concern; retention extension for 7-year compliance mandates is the cost driver.
7-year retention cost example
For a financial services team ingesting 5 GB/month of Bedrock events under the 1-year tier, extending to year 7 costs:
Year 1: included in ingestion ($3.75/month at $0.75/GB × 5 GB)
Years 2–7: 5 GB/month × 12 months × $0.023/GB/month × 6 years
= 5 × 12 × 0.023 × 6 = $8.28 additional storage cost over 6 years
(this is cumulative, not monthly — volume grows with each month retained)
At steady state with 5 GB ingested monthly and 7 years retained, the total stored volume grows to ~420 GB. At $0.023/GB/month that is ~$9.66/month in extended retention costs — modest relative to the compliance value.
3. Configuring CloudTrail Lake to Capture Bedrock InvokeModel Data Events
Resource types available for Bedrock data events
CloudTrail requires explicit data event configuration — Bedrock runtime events are not captured by default. The relevant resource types are:
| Resource type | Events captured |
|---|---|
AWS::Bedrock::Model |
InvokeModel, InvokeModelWithResponseStream |
AWS::Bedrock::AgentAlias |
InvokeAgent |
AWS::Bedrock::KnowledgeBase |
Retrieve, RetrieveAndGenerate |
AWS::Bedrock::FlowAlias |
InvokeFlow |
AWS::Bedrock::Prompt |
RenderPrompt |
AWS::Bedrock::AsyncInvoke |
StartAsyncInvoke, GetAsyncInvoke |
AWS CLI: create a Bedrock-focused event data store
aws cloudtrail create-event-data-store \
--name "bedrock-ai-audit-store" \
--retention-period 365 \
--advanced-event-selectors '[
{
"Name": "CaptureBedrockInvokeModel",
"FieldSelectors": [
{
"Field": "eventCategory",
"Equals": ["Data"]
},
{
"Field": "resources.type",
"Equals": ["AWS::Bedrock::Model"]
}
]
},
{
"Name": "CaptureBedrockAgentInvocations",
"FieldSelectors": [
{
"Field": "eventCategory",
"Equals": ["Data"]
},
{
"Field": "resources.type",
"Equals": ["AWS::Bedrock::AgentAlias"]
}
]
},
{
"Name": "CaptureBedrockKnowledgeBases",
"FieldSelectors": [
{
"Field": "eventCategory",
"Equals": ["Data"]
},
{
"Field": "resources.type",
"Equals": ["AWS::Bedrock::KnowledgeBase"]
}
]
}
]' \
--region us-east-1
Filtering to specific model ARNs (cost control)
To capture only Anthropic Claude calls and exclude other providers:
{
"Name": "CaptureClaudeOnly",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::Bedrock::Model"] },
{ "Field": "resources.ARN", "StartsWith": ["arn:aws:bedrock:us-east-1::foundation-model/anthropic."] }
]
}
To include all models but exclude test/sandbox accounts by IAM identity prefix:
{
"Field": "userIdentity.arn",
"NotStartsWith": ["arn:aws:iam::123456789012:role/sandbox-"]
}
Organization-level deployment
For multi-account coverage, deploy the event data store at the AWS Organizations management account:
aws cloudtrail create-event-data-store \
--name "org-bedrock-audit-store" \
--organization-enabled \
--multi-region-enabled \
--advanced-event-selectors '[...]'
This creates a single queryable store covering all member accounts — the most efficient pattern for enterprise attribution.
4. CloudTrail Lake SQL Query Interface: Key Fields for Bedrock Events
Core event schema fields
CloudTrail Lake exposes events with a flat-plus-nested schema. The primary fields relevant to Bedrock:
| Field | Type | Content for InvokeModel |
|---|---|---|
eventID |
string | UUID — unique per API call |
eventTime |
timestamp | ISO 8601 UTC call timestamp |
eventName |
string | InvokeModel, InvokeModelWithResponseStream, Converse, ConverseStream |
eventSource |
string | bedrock-runtime.amazonaws.com |
awsRegion |
string | Region where model was invoked |
sourceIPAddress |
string | Caller IP (Lambda IPs for serverless callers) |
userAgent |
string | SDK version string — useful for identifying application sources |
errorCode |
string | Null on success; ThrottlingException, ValidationException on failure |
errorMessage |
string | Human-readable error detail |
userIdentity.type |
string | AssumedRole, IAMUser, AWSService |
userIdentity.arn |
string | Full caller ARN — key attribution field |
userIdentity.accountId |
string | AWS account ID of caller |
userIdentity.sessionContext.sessionIssuer.arn |
string | Role ARN when type=AssumedRole |
userIdentity.sessionContext.webIdFederationData |
struct | OIDC claims for federated identities |
requestParameters |
map | Model ID, request body metadata |
responseElements |
map | Typically null for InvokeModel (response is streaming) |
resources |
array | [{"type": "AWS::Bedrock::Model", "ARN": "arn:aws:bedrock:..."}] |
additionalEventData |
map | Token counts (see Section 5), bearer token flag |
tlsDetails.tlsVersion |
string | TLS negotiated version |
requestID |
string | X-Amzn-RequestId — correlates with application logs |
Basic event query
SELECT
eventTime,
eventName,
userIdentity.arn AS caller_arn,
userIdentity.accountId AS caller_account,
awsRegion,
element_at(requestParameters, 'modelId') AS model_id,
errorCode,
requestID
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName IN ('InvokeModel', 'InvokeModelWithResponseStream', 'Converse', 'ConverseStream')
AND eventTime > '2026-01-01 00:00:00'
AND eventTime < '2026-06-18 00:00:00'
ORDER BY eventTime DESC
LIMIT 1000
Replace <event-data-store-arn> with the ARN returned from create-event-data-store.
5. Token Count Extraction from additionalEventData
Where token counts live
For Bedrock InvokeModel and Converse calls, CloudTrail records token consumption in additionalEventData. The fields present depend on the model family:
additionalEventData.inputTokenCount -- prompt tokens consumed
additionalEventData.outputTokenCount -- completion tokens generated
additionalEventData.totalTokenCount -- sum (not always present)
These fields are populated at the CloudTrail layer by the Bedrock service — they are independent of any application-level logging. This makes them the canonical retroactive source for token attribution even if the application itself never logged token counts.
Token attribution query by team/role
SELECT
element_at(requestParameters, 'modelId') AS model_id,
userIdentity.sessionContext.sessionIssuer.arn AS assumed_role_arn,
userIdentity.accountId AS aws_account,
DATE_TRUNC('day', eventTime) AS invocation_day,
COUNT(*) AS call_count,
SUM(CAST(element_at(additionalEventData, 'inputTokenCount')
AS BIGINT)) AS total_input_tokens,
SUM(CAST(element_at(additionalEventData, 'outputTokenCount')
AS BIGINT)) AS total_output_tokens,
SUM(CAST(element_at(additionalEventData, 'inputTokenCount')
AS BIGINT) +
CAST(element_at(additionalEventData, 'outputTokenCount')
AS BIGINT)) AS total_tokens
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName IN ('InvokeModel', 'InvokeModelWithResponseStream', 'Converse')
AND eventTime > '2026-01-01 00:00:00'
AND errorCode IS NULL
GROUP BY 1, 2, 3, 4
ORDER BY total_tokens DESC
Applying model pricing to token counts
Token counts from additionalEventData can be multiplied by the Bedrock on-demand pricing table to reconstruct cost per caller. This is the retroactive attribution path when Bedrock invoices show only aggregate account-level spend.
WITH token_usage AS (
SELECT
element_at(requestParameters, 'modelId') AS model_id,
userIdentity.arn AS caller_arn,
userIdentity.accountId AS account_id,
SUM(CAST(element_at(additionalEventData, 'inputTokenCount') AS BIGINT)) AS input_tokens,
SUM(CAST(element_at(additionalEventData, 'outputTokenCount') AS BIGINT)) AS output_tokens
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName IN ('InvokeModel', 'Converse')
AND eventTime > '2026-05-01 00:00:00'
AND eventTime < '2026-06-01 00:00:00'
AND errorCode IS NULL
GROUP BY 1, 2, 3
)
SELECT
model_id,
caller_arn,
account_id,
input_tokens,
output_tokens,
-- Approximate cost using Claude 3.5 Sonnet pricing as example
-- Adjust price_per_1k_input and price_per_1k_output per model
ROUND((input_tokens / 1000.0) * 0.003, 4) AS estimated_input_cost_usd,
ROUND((output_tokens / 1000.0) * 0.015, 4) AS estimated_output_cost_usd,
ROUND((input_tokens / 1000.0) * 0.003 +
(output_tokens / 1000.0) * 0.015, 4) AS estimated_total_cost_usd
FROM token_usage
ORDER BY estimated_total_cost_usd DESC
Important caveat: Token counts in additionalEventData reflect what Bedrock billed, but the pricing multiplier must be maintained manually as AWS adjusts on-demand rates. The CUR join (Section 6) is more reliable for actual dollar reconciliation.
6. Joining CloudTrail Lake to CUR in Athena: Cross-Service Cost Attribution
Architecture
CUR data lives in S3 and is queryable via Athena. CloudTrail Lake events are queryable via native CloudTrail Lake SQL or via Athena federation. The join key is line_item_resource_id in CUR (which for Bedrock contains the model ARN or a request-level identifier) correlated against CloudTrail’s requestID or resources[].ARN.
Enabling CloudTrail Lake federation for Athena joins
aws cloudtrail enable-federation \
--event-data-store <event-data-store-arn> \
--federation-role-arn arn:aws:iam::123456789012:role/CloudTrailLakeFederationRole
This registers the event data store as a table in AWS Glue Data Catalog under database aws_cloudtrail_lake, making it queryable from Athena alongside CUR tables.
Cross-service join pattern
-- CUR table: cost_and_usage_report (partitioned by year/month in S3)
-- CloudTrail Lake federated table: aws_cloudtrail_lake.bedrock_events (via Athena federation)
WITH bedrock_calls AS (
SELECT
requestID,
eventTime,
userIdentity.arn AS caller_arn,
element_at(requestParameters, 'modelId') AS model_id,
CAST(element_at(additionalEventData, 'inputTokenCount')
AS BIGINT) AS input_tokens,
CAST(element_at(additionalEventData, 'outputTokenCount')
AS BIGINT) AS output_tokens
FROM aws_cloudtrail_lake.bedrock_events
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName IN ('InvokeModel', 'Converse')
AND eventTime > TIMESTAMP '2026-05-01'
AND errorCode IS NULL
),
cur_bedrock AS (
SELECT
line_item_resource_id,
line_item_usage_account_id,
line_item_unblended_cost,
line_item_usage_start_date,
product_servicename
FROM cost_and_usage_report
WHERE
product_servicename = 'Amazon Bedrock'
AND line_item_line_item_type = 'Usage'
AND year = '2026'
AND month = '05'
)
SELECT
bc.caller_arn,
bc.model_id,
DATE_TRUNC('day', bc.eventTime) AS usage_day,
COUNT(bc.requestID) AS api_calls,
SUM(bc.input_tokens) AS input_tokens,
SUM(bc.output_tokens) AS output_tokens,
SUM(cur.line_item_unblended_cost) AS actual_cost_usd
FROM bedrock_calls bc
LEFT JOIN cur_bedrock cur
ON bc.requestID = cur.line_item_resource_id
GROUP BY 1, 2, 3
ORDER BY actual_cost_usd DESC NULLS LAST
Join key reliability note
The requestID to CUR line_item_resource_id join works when CUR is configured at request-level granularity. By default, CUR reports aggregate Bedrock charges without request IDs. To enable request-level CUR entries, activate Bedrock model invocation logging (separate from CloudTrail) with cost allocation tags — then use tags as the join key. Without request-level CUR, the practical join is at the account + model + day level:
GROUP BY
bc.caller_account,
bc.model_id,
DATE_TRUNC('day', bc.eventTime)
Match this aggregate to CUR grouped at the same granularity. The result is an attribution estimate, not an exact reconciliation — sufficient for chargeback purposes but not for audit-grade financial reporting.
7. Compliance Use Cases: Who Invoked Which Model, When, From Where
Audit trail query for regulated industries
The canonical compliance query — “show me every person or service that invoked a foundation model between these dates” — runs directly against CloudTrail Lake:
SELECT
eventTime,
eventName,
userIdentity.type AS identity_type,
userIdentity.arn AS caller_arn,
userIdentity.accountId AS aws_account,
userIdentity.sessionContext.sessionIssuer.arn AS session_role,
userIdentity.sessionContext.attributes.mfaAuthenticated AS mfa_authenticated,
awsRegion AS invocation_region,
sourceIPAddress AS source_ip,
userAgent,
element_at(requestParameters, 'modelId') AS model_invoked,
element_at(resources[1], 'ARN') AS model_arn,
errorCode,
requestID
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName IN ('InvokeModel', 'InvokeModelWithResponseStream', 'Converse', 'ConverseStream')
AND eventTime BETWEEN '2026-01-01 00:00:00' AND '2026-06-18 00:00:00'
ORDER BY eventTime DESC
This output satisfies common financial services regulatory requirements (SOX, FINRA, MAS TRM) and healthcare mandates (HIPAA audit controls) that require a queryable record of who accessed AI inference systems and when.
Cross-region invocation detection
Financial services firms in regulated jurisdictions (EU AI Act, MAS, FCA) may need to verify that model invocations did not cross data residency boundaries:
SELECT
awsRegion,
userIdentity.accountId,
element_at(requestParameters, 'modelId') AS model_id,
COUNT(*) AS call_count
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventName = 'InvokeModel'
AND eventTime > '2026-01-01 00:00:00'
AND awsRegion NOT IN ('us-east-1', 'us-west-2') -- approved regions
GROUP BY 1, 2, 3
HAVING COUNT(*) > 0
ORDER BY call_count DESC
Failed invocation audit (access denials)
Denied calls that appear in CloudTrail are security-relevant events — an IAM policy may have been misconfigured, or an unauthorized identity attempted access:
SELECT
eventTime,
userIdentity.arn AS caller_arn,
errorCode,
errorMessage,
element_at(requestParameters, 'modelId') AS model_id,
sourceIPAddress
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND errorCode IS NOT NULL
AND errorCode IN ('AccessDeniedException', 'UnauthorizedException')
AND eventTime > '2026-06-01 00:00:00'
ORDER BY eventTime DESC
8. CloudTrail Lake Insights: Anomaly Detection for Bedrock Call Patterns
What Insights covers
CloudTrail Insights analyzes management event activity against a rolling baseline and emits an Insights event when a statistically anomalous pattern is detected. As of November 2025, AWS extended Insights to data events, enabling detection of unusual Bedrock invocation volume.
Insights detects:
- Unexpected call volume spikes (e.g., a role that normally calls
InvokeModel1,000 times/day suddenly calling 50,000 times/hour) - Unusual error rate increases (e.g.,
ThrottlingExceptionrates jump 10x above baseline) - Anomalous API call rates from new source IPs or rarely-used roles
Enabling Insights for a CloudTrail Lake event data store
Insights for CloudTrail Lake is configured at the trail level, not the event data store level. Enable it on the trail feeding the event data store:
aws cloudtrail put-insight-selectors \
--trail-name bedrock-audit-trail \
--insight-selectors '[
{"InsightType": "ApiCallRateInsight"},
{"InsightType": "ApiErrorRateInsight"}
]'
Insights events are written to both the configured S3 bucket and optionally to a separate CloudTrail Lake event data store. To query Insights events:
SELECT
eventTime,
eventName,
element_at(additionalEventData, 'insightType') AS insight_type,
element_at(additionalEventData, 'baselineAverage') AS baseline_avg_calls_per_min,
element_at(additionalEventData, 'insightAverage') AS anomaly_avg_calls_per_min,
element_at(additionalEventData, 'insightDuration') AS duration_minutes
FROM <insights-event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
ORDER BY eventTime DESC
Practical anomaly thresholds for Bedrock
Bedrock usage patterns vary significantly by application type. Batch inference jobs produce high sustained volume; interactive agents produce spiky, low-latency calls. Insights uses statistical baselines automatically, but operators should:
- Establish a 2-week baseline before treating Insights events as alerts
- Tag IAM roles by use case (
batch-inference,interactive-agent,test) so Insights baselines apply per use case rather than account-wide - Forward Insights events to EventBridge for automated alerting to security teams
9. 7-Year Retention: Cost Implications vs. 1-Year Default
Regulatory drivers for 7-year retention
| Regulation | Retention requirement | Applicability |
|---|---|---|
| SEC Rule 17a-4 | 6 years (3 immediately accessible) | US broker-dealers |
| FINRA Rule 4370 | 3–6 years depending on record type | US financial services |
| SOX Section 802 | 5–7 years for audit work papers | US public companies |
| EU AI Act Article 12 | Records “for the lifetime of the AI system, at least 10 years” for high-risk AI | EU-deployed high-risk AI |
| HIPAA 45 CFR § 164.530 | 6 years | US healthcare covered entities |
| MAS TRM 2021 | No fixed term; “adequate period” typically 5–7 years | Singapore financial services |
The EU AI Act Article 12 requirement for high-risk AI systems establishes a de facto 10-year standard for EU-deployed Bedrock workloads used in consequential decisions (credit, hiring, medical). The CloudTrail Lake 1-year extendable tier can reach 10 years via retention extension; the 7-year tier caps at 7.
Cost comparison: 1-year extendable vs. 7-year tier at scale
Scenario: 100 GB/month of Bedrock event data ingested, full 7-year retention needed.
1-year extendable tier
Ingestion: 100 GB/month × $0.75/GB = $75/month
Year 1 retention: included
Years 2–7 retention: cumulative stored volume grows by 100 GB each month
Month 13: 1,200 GB stored × $0.023/month = $27.60/month additional
Month 84: 8,400 GB stored × $0.023/month = $193.20/month additional
Average steady-state retention cost ≈ $100–$200/month (after year 3)
Total 7-year cost estimate: ~$6,300 ingestion + ~$4,200 retention ≈ $10,500
7-year tier
Ingestion: 100 GB/month × $2.50/GB = $250/month
7-year retention: included
Total 7-year ingestion cost: $250 × 84 months = $21,000
At 100 GB/month, the 1-year extendable tier is significantly cheaper (~$10,500 vs. ~$21,000 over 7 years) because Bedrock event data volumes are modest. The 7-year tier only breaks even at very high ingestion volumes (>25 TB/month) where the volume discounts on the 7-year tier outweigh the retention surcharge.
Decision rule for Bedrock-only deployments: Use the 1-year extendable tier and set retention to 84 months (7 years). The cost savings are substantial unless Bedrock is part of a broader CloudTrail Lake deployment ingesting many other event types that push monthly volume above 25 TB.
10. CloudTrail Lake Athena Federation vs. Native CloudTrail Lake Query
Two query paths
Native CloudTrail Lake SQL — queries submitted via cloudtrail:StartQuery API against the event data store ARN directly. Results are returned asynchronously; poll with GetQueryResults.
Athena federation — enables CloudTrail Lake event data stores as tables in the Glue Data Catalog, queryable from Athena alongside S3-resident data (CUR, application logs, etc.).
Performance comparison
| Dimension | Native CloudTrail Lake | Athena Federation |
|---|---|---|
| Setup | None — query ARN directly | Federation role + Glue table registration required |
| Scan optimization | Native ORC predicate pushdown; time-range filters map to internal indexes | Athena reads ORC via Glue connector; similar pushdown but adds federation overhead |
| Query latency | Typically 5–30s for month-scale scans | Similar, with ~2–5s additional federation overhead for metadata resolution |
| Cross-source joins | Not supported — only event data store data | Supported — join CloudTrail Lake to CUR, VPC Flow Logs, ALB access logs in one query |
| Result output | S3 or inline (small results) | Always written to S3 query results bucket |
| Pricing | $0.005/GB scanned (same) | $0.005/GB scanned + Athena query coordination cost (same $5/TB) |
| Max result rows | 1,000 rows inline; unlimited via S3 export | Unlimited |
| Tooling | AWS console, SDK, CLI | Any Athena-compatible tool (QuickSight, Grafana, dbt, Tableau) |
Recommendation
Use native CloudTrail Lake SQL for:
- Point-in-time audit lookups (e.g., “show me all calls from this ARN on this date”)
- Scheduled compliance reports against Bedrock-only data
- Ad-hoc security investigations
Use Athena federation for:
- Joining Bedrock call records to CUR for chargeback
- Dashboards in QuickSight or Managed Grafana
- Engineering teams with existing Athena workflows
The query cost is identical between both paths. The choice is driven by whether you need cross-source joins and what tooling the team already operates.
11. Practical: Bedrock-Focused Event Data Store Setup
Step-by-step with cost controls
Step 1: Estimate data volume before deploying
Before creating the event data store, enable a CloudTrail trail with Bedrock data events to S3 for 48 hours and measure the raw JSON volume. Multiply by your expected steady-state call volume to project monthly ingestion GB.
Step 2: Create the event data store with narrow selectors
Avoid capturing all data events account-wide. Scope to Bedrock specifically:
# Get current account and region
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION="us-east-1"
aws cloudtrail create-event-data-store \
--name "bedrock-finops-compliance" \
--retention-period 2557 \
--advanced-event-selectors '[
{
"Name": "BedrockModelInvocations",
"FieldSelectors": [
{"Field": "eventCategory", "Equals": ["Data"]},
{"Field": "resources.type", "Equals": ["AWS::Bedrock::Model"]}
]
},
{
"Name": "BedrockAgentInvocations",
"FieldSelectors": [
{"Field": "eventCategory", "Equals": ["Data"]},
{"Field": "resources.type", "Equals": ["AWS::Bedrock::AgentAlias"]}
]
}
]' \
--region $REGION
Retention of 2,557 days = 7 years. Adjust to 365 + extension if using 1-year tier.
Step 3: Tag the event data store for cost tracking
aws cloudtrail add-tags \
--resource-id <event-data-store-arn> \
--tags-list \
Key=CostCenter,Value=ai-platform \
Key=Compliance,Value=sox-finra \
Key=Environment,Value=production
Step 4: Enable management event capture for control-plane audit
Management events (CreateFoundationModel, DeleteModelInvocationLoggingConfiguration, etc.) are captured automatically on the default trail. Verify:
aws cloudtrail get-trail-status --name default \
--query '{IncludeManagementEvents: TrailStatus.IncludeManagementEvents}'
Step 5: Validate capture within 15 minutes
Run a test InvokeModel call and verify the event appears:
SELECT eventTime, eventName, requestID
FROM <event-data-store-arn>
WHERE
eventSource = 'bedrock-runtime.amazonaws.com'
AND eventTime > (NOW() - INTERVAL '1' HOUR)
ORDER BY eventTime DESC
LIMIT 10
Step 6: Set a CloudWatch alarm for ingestion anomalies
CloudTrail Lake does not expose a direct ingestion-GB metric. Proxy with call count:
aws cloudwatch put-metric-alarm \
--alarm-name "bedrock-call-volume-spike" \
--metric-name CallCount \
--namespace AWS/CloudTrail \
--period 3600 \
--evaluation-periods 2 \
--threshold 100000 \
--comparison-operator GreaterThanThreshold \
--alarm-actions <sns-topic-arn>
Common pitfalls
Pitfall 1: Management events only — no data events captured. The default CloudTrail trail captures management events. InvokeModel is a data event and is not captured unless explicitly configured. Verify with the validation query in Step 5.
Pitfall 2: additionalEventData token fields are null. Token counts are populated for synchronous InvokeModel and Converse calls. Streaming calls (InvokeModelWithResponseStream, ConverseStream) may have null token counts in additionalEventData — use CloudWatch Bedrock metrics or model invocation logs as a supplement for streaming attribution.
Pitfall 3: requestParameters.modelId contains the short ID, not the full ARN. Bedrock returns model IDs like anthropic.claude-3-5-sonnet-20241022-v2:0 in requestParameters. The full ARN appears in resources[].ARN. Use resources[].ARN for joins to CUR line_item_resource_id which uses ARN format.
Pitfall 4: Organization-level event data stores require Delegated Administrator setup. For multi-account CloudTrail Lake, the management account must designate a delegated administrator before member account events flow to the central store.
Pitfall 5: Athena federation IAM permissions. The federation role requires glue:CreateDatabase, glue:CreateTable, lakeformation:RegisterResource, and cloudtrail:GetQueryResults. Missing Lake Formation permissions produce silent failures with empty query results.
Sources
- Auditing generative AI workloads with AWS CloudTrail — AWS Cloud Operations Blog
- Monitor Amazon Bedrock API calls using CloudTrail — Amazon Bedrock docs
- AWS CloudTrail pricing
- Announcing AWS CloudTrail Lake one-year extendable retention pricing option
- AWS CloudTrail Lake data now available for zero-ETL analysis in Amazon Athena
- Enable Lake query federation — AWS CloudTrail docs
- CloudTrail Lake event data stores — AWS CloudTrail docs
- CloudTrail record contents for management, data, and network activity events
- AWS CloudTrail launches Insights for data events — November 2025
- Managing CloudTrail Lake costs — AWS CloudTrail docs
- Querying AWS CloudTrail — Athena vs CloudTrail Lake (LinkedIn)
- How to Use CloudTrail Lake for Advanced Event Analysis — OneUptime
- CloudTrail Data Events — AWS Cloud Operations Best Practices
- Audit and visualize EC2 instances using CloudTrail Lake as zero-ETL source in Athena