First of all, you need to know which query that ran longer than the patience of the application permitted. This is something you should be able to retrieve from the application. But it may be difficult to reproduce the issue, since the query plan you get the next day is better fitted for the query and its parameters. And, also, the long execution time might not have been due to a bad plan, but it could have been something else, for instance blocking.
But Query Store can help you! On SQL 2016 that is. It's not available in SQL 2008 R2. You need to enable Query Store for your database:
ALTER DATABASE db SET QUERY_STORE = ON (QUERY_CAPTURE_MODE = AUTO)
If you know that there was a timeout yesterday at 15:30. you can run this query:
SELECT *
FROM sys.query_store_runtime_stats rs
JOIN sys.query_store_runtime_stats_interval rsi
ON rs.runtime_stats_interval_id = rsi.runtime_stats_interval_id
WHERE '20250905 15:30+08:00' BETWEEN rsi.start_time AND rsi.end_time
AND rs.execution_type = 3
Be careful to specify the correct time zone. The timestamps in sys.runtime_stats_interval are stored as datetimeoffset.
execution_type = 3 is for aborted queries, that is the application sent an attention signal (which is what happens at a timeout.)
sys.query_store_runtime_stats has a plan_id which maps to sys.query_store_plan where you find the query plan and also a query_id which takes you to sys.query_store_query and sys.query_store_query_text to give you the text for the query.
You can also see in sys.query_store_plan see if there more plans for this query. It is also worth looking at the rows in sys.query_plan_runtime_stats for the same plan_id and execution_type 1 (normal executions). If these normal executions are swift, the long execution might be due to blocking. (But it could also be a funky parameter value).