Complete Solution for FireLens to Athena JSON Parsing
Your issue stems from FireLens wrapping container logs in metadata, creating nested JSON that Athena’s SerDe can’t parse by default. Here’s the comprehensive solution:
FireLens Log Format Issue:
FireLens outputs logs in this format:
{"log":"{level:ERROR,message:API timeout}","container_id":"abc123","container_name":"/ecs-app","source":"stdout","ecs_cluster":"prod"}
The actual application log is double-encoded inside the log field. Athena fails because it expects flat JSON or properly nested structures, not escaped JSON strings.
Solution 1: Custom Fluent Bit Configuration (Recommended)
Create a Fluent Bit config file that parses and flattens the logs. Upload this to S3:
[FILTER]
Name parser
Match *
Key_Name log
Parser json_parser
Reserve_Data On
[PARSER]
Name json_parser
Format json
Update your ECS task definition FireLens configuration:
"firelensConfiguration": {
"type": "fluentbit",
"options": {
"config-file-type": "s3",
"config-file-value": "arn:aws:s3:::my-bucket/fluent-bit.conf",
"enable-ecs-log-metadata": "false"
}
}
Setting enable-ecs-log-metadata to false reduces metadata overhead. The parser filter extracts JSON from the log field and promotes it to top-level fields.
Solution 2: Athena JSON Parsing with Nested Structure
If you can’t modify FireLens config, adjust your Athena table to handle nested JSON:
CREATE EXTERNAL TABLE ecs_logs (
log STRING,
container_id STRING,
container_name STRING,
source STRING,
ecs_cluster STRING
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://my-logs-bucket/firelens/';
Then parse the nested JSON in queries:
SELECT
json_extract_scalar(log, '$.level') as log_level,
json_extract_scalar(log, '$.message') as message,
container_name,
ecs_cluster
FROM ecs_logs
WHERE json_extract_scalar(log, '$.level') = 'ERROR';
S3 Event Notifications and Partitioning:
Your 40% failure rate might also indicate partitioning issues. FireLens typically writes logs with date-based prefixes like year=2025/month=06/day=03/.
Create a partitioned table:
CREATE EXTERNAL TABLE ecs_logs (
log STRING,
container_id STRING
)
PARTITIONED BY (year STRING, month STRING, day STRING)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://my-logs-bucket/firelens/';
Use partition projection for automatic partition discovery:
ALTER TABLE ecs_logs SET TBLPROPERTIES (
'projection.enabled' = 'true',
'projection.year.type' = 'integer',
'projection.year.range' = '2024,2026',
'projection.month.type' = 'integer',
'projection.month.range' = '1,12',
'projection.day.type' = 'integer',
'projection.day.range' = '1,31',
'storage.location.template' = 's3://my-logs-bucket/firelens/year=${year}/month=${month}/day=${day}'
);
This eliminates the need for MSCK REPAIR TABLE and makes new logs immediately queryable.
Validation and Troubleshooting:
- Test Fluent Bit config locally:
docker run --rm -v $(pwd)/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf \
amazon/aws-for-fluent-bit:latest /fluent-bit/bin/fluent-bit \
-c /fluent-bit/etc/fluent-bit.conf --dry-run
-
Check FireLens container logs in CloudWatch Logs group /ecs/ecs-firelens-container for parsing errors
-
Query S3 directly to verify log format:
aws s3 cp s3://my-logs-bucket/firelens/year=2025/month=06/day=03/logs.json - | head -n 5
- Test Athena queries on small date ranges first to isolate parsing issues
Recommended Approach:
Implement Solution 1 (custom Fluent Bit config) for clean, flat JSON that’s easier to query. Add partition projection for automatic partition handling. This combination provides the best query performance and eliminates parsing errors entirely. Monitor FireLens container logs during initial deployment to catch configuration issues early.