Access CX-Data directly
This guide explains how to query your S3 Coralogix archive bucket (cx-data) using a third-party framework with the standard Apache Parquet reader provided by the relevant framework and required schema.
Folder structure
cx-data is stored in standard hive-like partitions, with the following partition fields:
-
team_id=<team-id>: Coralogix Team ID -
dt=YYYY-MM-DD: Date of the data in UTC -
hr=HH: Hour of the data in UTC
These fields can be defined as virtual columns inside the framework, serving as filters in a query.
Note:
-
Both
dtandhrare based on the event timestamp. -
The
team_id=<team-id>partition allows reusing the same bucket and prefix to write data from multiple Coralogix teams and query them all in one query.
Fields
Each Apache Parquet file has three fields with data as JSON-formatted strings:
-
src_obj__event_metadata: JSON object containing metadata related to the event -
src_obj__event_labels: JSON object containing the labels of the event (such as the Coralogix applicationName and subsystemName) -
src_obj__user_data: JSON object containing actual event data
Examples
Below is an example of each of the 3 payload fields.
src_obj__event_metadata:
{
"timestamp": "2022-03-28T08:50:57.946",
"severity": "Debug",
"priorityclass": "low",
"logid": "some-uuid"
}
src_obj__event_labels:
{
"applicationname": "some-app",
"subsystemname": "some-subsystem",
"category": "some-category",
"classname": "some-class",
"methodname": "some-method",
"computername": "some-computer",
"threadid": "some-thread-id",
"ipaddress": "some-ip-address"
}
src_obj__user_data:
{
"_container_id": "0f099482cf3b507462020e9052516554b65865fb761af8e076735312772352bf",
"host": "ip-10-1-11-144",
"short_message": "10.1.11.144 - - [28/Mar/2022:08:50:57 +0000] \\"GET /check HTTP/1.1\\" 200 16559 \\"-\\" \\"Consul Health Check\\" \\"-\\""
}
Reading cx-data files using a standard framework
Pandas
Loading cx-data files in Pandas can be done using the read_parquet method:
import pandas as pd
# Notice that only the three payload columns are passed eventually to read_parquet()
cx_columns = [
'src_obj__event_metadata',
'src_obj__event_labels',
'src_obj__user_data'
]
df = pd.read_parquet('s3://.../myfile.parquet',columns = cx_columns)
# The dataframe `df` contains all the data needed for further processing.
Here is the output of df.info() showing the expected schema of the DataFrame:
print(df.info())
### Output
RangeIndex: 161050 entries, 0 to 161049
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 src_obj__event_metadata 161050 non-null object
1 src_obj__event_labels 161050 non-null object
2 src_obj__user_data 161050 non-null object
dtypes: object(3)
Athena
To use cx-data directly in Athena, you’ll need to create an EXTERNAL table as follows:
CREATE EXTERNAL TABLE IF NOT EXISTS "my_table" (
`src_obj__event_labels` STRING,
`src_obj__event_metadata` STRING,
`src_obj__user_data` STRING
)
PARTITIONED BY (`team_id` string, `dt` string, `hr` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION 's3://<bucket>/cx/parquet/v1/'
After creating the external table, table partitions should be added to limit the scope of queries, reducing costs and improving performance.
Table partitions can be added manually:
ALTER TABLE "my_table" ADD PARTITION (team_id='23333', dt='2022-05-30', hr='01');
# Additional ALTER TABLE ... ADD PARTITION statements according to which dates and hours are needed
Table partitions can also be added automatically:
MSCK REPAIR TABLE "my_table"
Note! This command scans all of the files in the table location, which may be a time-consuming task when the amount of partitions detected is large. It may be easier to add the relevant new partitions manually. One option to expedite automatic partitioning is to limit the scope of the data by specifying a specific team_id and dt in the table definition LOCATION (e.g. modify the CREATE EXTERNAL TABLE command to include the team_id=<team-id>/dt=<date>/ subpath). This restricts the partition auto-detection to the relevant dates.
Permissions for adding partitions
ALTER TABLE ... ADD PARTITION and MSCK REPAIR TABLE write partitions to the AWS Glue Data Catalog. Without write access to the catalog, MSCK REPAIR TABLE can list the partitions it finds on S3 as "Partitions not in metastore" and still fail to add them.
Grant the IAM principal that runs the Athena queries, such as the user or the workgroup role, these actions on the Glue database and table:
| Action | Purpose |
|---|---|
glue:BatchCreatePartition | Add the partitions to the table. |
glue:GetDatabase | Read the database. |
glue:GetTable | Read the table definition. |
glue:GetPartition, glue:GetPartitions | Read the partitions already registered. |
If the Glue database or table is registered in AWS Lake Formation, Glue calls fail even with these IAM permissions. Grant the principal Lake Formation permissions on the database and table, including access to the underlying data, or deregister the resource from Lake Formation.
Partitions you add this way cover only the data that exists when you add them. Each new day and hour needs another MSCK REPAIR TABLE run or ADD PARTITION statement. For more information, see MSCK REPAIR TABLE troubleshooting in the AWS documentation.
To avoid adding partitions and the Glue write permissions altogether, use partition projection.
After adding the relevant partitions, a query can be executed on the table. Accessing a specific field inside one of the payload fields should be done using the Athena function json_extract_scalar, as in the following example:
SELECT
json_extract_scalar(src_obj__event_metadata,'$.severity') severity,
json_extract_scalar(src_obj__event_labels,'$.applicationName') app_name,
json_extract_scalar(src_obj__user_data,'$.my_obj.my_message_field') message_field
FROM "my_table"
Query with partition projection
With Athena partition projection, Athena works out the partition values at query time from the table properties, so you never add partitions to the Glue Data Catalog. You don't need MSCK REPAIR TABLE, ALTER TABLE ... ADD PARTITION, or Glue write permissions, and new days and hours are queryable as soon as they arrive.
Create the table with projection enabled:
CREATE EXTERNAL TABLE IF NOT EXISTS "my_table" (
`src_obj__event_labels` STRING,
`src_obj__event_metadata` STRING,
`src_obj__user_data` STRING
)
PARTITIONED BY (`team_id` string, `dt` string, `hr` string)
STORED AS PARQUET
LOCATION 's3://<bucket>/cx/parquet/v1/'
TBLPROPERTIES (
'projection.enabled'='true',
'projection.team_id.type'='enum',
'projection.team_id.values'='<team-id>',
'projection.dt.type'='date',
'projection.dt.format'='yyyy-MM-dd',
'projection.dt.range'='2026-01-01,NOW',
'projection.dt.interval'='1',
'projection.dt.interval.unit'='DAYS',
'projection.hr.type'='integer',
'projection.hr.range'='0,23',
'projection.hr.digits'='2',
'storage.location.template'='s3://<bucket>/cx/parquet/v1/team_id=${team_id}/dt=${dt}/hr=${hr}'
)
projection.team_id.values: your Team ID. If the bucket holds data from several teams, list them comma-separated, such as111111,222222.projection.dt.range: the first and last dates Athena considers. Set the start date to limit how far back queries can reach.storage.location.template: Athena builds each partition's S3 path from this template and ignoresLOCATION. KeepLOCATIONat the base prefix. Narrowing it doesn't limit what Athena reads.
Always filter on team_id, dt, and hr. Athena prunes only on projected columns, so a query without these filters lists every partition in the range and scans the full prefix. Use the same formats as the partitions: yyyy-MM-dd dates and zero-padded hours.
SELECT
json_extract_scalar(src_obj__event_metadata,'$.severity') severity,
json_extract_scalar(src_obj__user_data,'$.my_obj.my_message_field') message_field
FROM "my_table"
WHERE team_id = '<team-id>'
AND dt = '2026-10-22'
AND hr = '04'
AND json_extract_scalar(src_obj__event_labels,'$.applicationName') = 'my_application'
To cap the cost of a query that misses these filters, set a data-scanned limit on the Athena workgroup.