Skip to content

Conversation

AbhiPrasad
Copy link
Member

@AbhiPrasad AbhiPrasad commented Oct 3, 2025

resolves https://linear.app/getsentry/issue/LOGS-389/add-vercel-log-drain-endpoint-to-relay

Building on top of the vercel log transform added in #5209, this PR adds an endpoint for the vercel log drain.

This endpoint is featured flagged, you can see the options automator PR here: https://github.com/getsentry/sentry-options-automator/pull/5367

@AbhiPrasad AbhiPrasad self-assigned this Oct 3, 2025
Copy link

linear bot commented Oct 3, 2025

@AbhiPrasad AbhiPrasad force-pushed the abhi-vercel-log-drain-endpoint branch from 837b7d7 to 1dac424 Compare October 5, 2025 02:20
@AbhiPrasad AbhiPrasad force-pushed the abhi-vercel-log-drain-integration branch from 3810607 to ec8cfbf Compare October 6, 2025 23:24
Base automatically changed from abhi-vercel-log-drain-integration to master October 8, 2025 18:02
@AbhiPrasad AbhiPrasad force-pushed the abhi-vercel-log-drain-endpoint branch 6 times, most recently from 771fc43 to c95f053 Compare October 9, 2025 12:29
@AbhiPrasad AbhiPrasad force-pushed the abhi-vercel-log-drain-endpoint branch from c95f053 to d25fc99 Compare October 9, 2025 12:30
@AbhiPrasad AbhiPrasad marked this pull request as ready for review October 9, 2025 12:30
@AbhiPrasad AbhiPrasad requested a review from a team as a code owner October 9, 2025 12:30
cursor[bot]

This comment was marked as outdated.

Comment on lines 22 to 26
fn parse_logs_data(payload: &[u8]) -> Result<Vec<VercelLog>> {
// Try parsing as JSON array first
if let Ok(logs) = serde_json::from_slice::<Vec<VercelLog>>(payload) {
return Ok(logs);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no way to differentiate the payload format based on the request, a header or something?

If we can't, you can peek at the first byte ([) to figure out if it is an array or not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we differentiate based on content type, done with a3db213

Comment on lines 37 to 40
let logs: Vec<VercelLog> = payload_str
.lines()
.filter_map(|line| serde_json::from_str::<VercelLog>(line.trim()).ok())
.collect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of collecting to a vector here, you could use the iterator and call produce for each item instead. This reduces the amount of necessary allocations.

Comment on lines 42 to 44
if logs.is_empty() {
relay_log::debug!("Failed to parse any logs from vercel log drain payload");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that correct? Maybe we just got an empty content? Also the log line is not emitted for [].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adjusted this in 8879201.

}

// Fall back to NDJSON parsing
let payload_str = std::str::from_utf8(payload).map_err(|e| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conversion to utf8 first isn't necessary and means scanning an extra time across the data. slice::split is probably enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, alongside the iterator improvements. Changed with 8879201


response = self.post(url, headers=headers, data=data)

response.raise_for_status()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
response.raise_for_status()
response.raise_for_status()
return response

Comment on lines 227 to 229
project_config["config"]["retentions"] = {
"log": {"standard": 30, "downsampled": 13 * 30},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine to omit, we can expect retentions to work for an integration if it works for the core logs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, generally moved some test items around in 488f625

headers={"Content-Type": "application/json"},
)

# Check that the items are properly processed via items_consumer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What else?

headers={"Content-Type": "application/x-ndjson"},
)

# Check that the items are properly processed via items_consumer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What else (again)?

Comment on lines 85 to 87
project_config["config"]["retentions"] = {
"log": {"standard": 30, "downsampled": 13 * 30},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also mentioned below, we can omit this.

},
]

# Check outcomes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As opposed to what?

count += 1;
let ourlog = relay_ourlogs::vercel_log_to_sentry_log(log);
produce(ourlog);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Inconsistent Error Handling in NDJSON Parsing

The NDJSON parser silently drops individual lines that fail to parse, unlike the JSON array parser which fails the entire payload on any error. This inconsistent error handling can lead to silent data loss and make debugging challenging.

Fix in Cursor Fix in Web

}

if count == 0 {
relay_log::debug!("Failed to parse any logs from vercel log drain payload");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does output when you get empty arrays, but this is unexpected from the log drain endpoint.

After implementing this I also realized that we could return the count from expand, and use that accordingly, we can make a similar refactor to the otlp integration.

// Vercel Log Drain data in a json array payload
Json,
// Vercel Log Drain data in a newline delimited json payload
NDJson,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
NDJson,
NdJson,

Even if it looks wrong, according to Rust code this is the proper capitalization.

Comment on lines +6 to +20
TEST_CONFIG = {
"outcomes": {
"emit_outcomes": True,
"batch_size": 1,
"batch_interval": 1,
"aggregator": {
"bucket_interval": 1,
"flush_interval": 1,
},
},
"aggregator": {
"bucket_interval": 1,
"initial_delay": 0,
},
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure, because you're just repeating the default config:

"outcomes": {
"batch_size": 1,
"batch_interval": 1,
"aggregator": {
"bucket_interval": 1,
"flush_interval": 0,
},
},
"aggregator": {
"bucket_interval": 1,
"initial_delay": 0,
},

This didn't used to be the default, I changed that 2 weeks ago, maybe you tried before that?

continue;
}

if let Ok(log) = serde_json::from_slice::<VercelLog>(line) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we wouldn't want to swallow errors here and instead emit a single error.

This would mean you need to pass in the RecordKeeper and emit an error. But we can also tackle this separately/afterwards.

We should then also make a test which has a single invalid line.

Then this maybe also addresses the issue with the count afterwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants