Here is a relatively simple yet effective stored procedure to quickly view those failed login attempts (an alternative to viewing the SQL Server Error log manually and filtering).
There are other ways of getting this information, but we liked the idea of having a stored procedure that we could run Ad hoc when we wanted.
Its always nice to have something in your Arsenal that allows you to be proactive and this allows you to proactively check the SQL server error log for failed logins whenever you please, or on a schedule if you use it within an Agent job of course.
So that’s my boring speech out of the way , lets get our hands dirty and show you the procedure:
If we run sp_FailedLogins without any Parameters specified it will return the results for any failed logins that have occured within the last 60 Minutes:
EXEC sp_FailedLogins
Example Output:

As we can see above this returns the following information:
Username
Number of Attempts by this login
The Last occurrence of a failed login attempt
The Last error produced for the failed login attempt
For searching within a given period we can use the following:
EXEC sp_FailedLogins @FromDate = '20170603 09:00', @ToDate = '20170605 17:00'
We can also Omit the @ToDate if we want to search FROM a date right up to the present time.
EXEC sp_FailedLogins @FromDate = '20170603 09:00'
The procedure has been written to query the current error log file only.
Please be aware that the performance of this procedure will be directly affected by the size of the log and potentially the frequency in which you cycle the log too (depending on log size) , as an example when I run this on our environments it runs in under a second, I would not expect it to take much more than that.
Here is the complete code:
USE [master]
GO
CREATE PROCEDURE [dbo].[sp_FailedLogins]
(
@FromDate DATETIME = NULL,
@ToDate DATETIME = NULL
)
AS
BEGIN
--Failed login attempts in the last 60 minutes
IF @FromDate IS NULL BEGIN SET @FromDate = DATEADD(MINUTE,-60,GETDATE()) END
IF @ToDate IS NULL BEGIN SET @ToDate = GETDATE() END
IF OBJECT_ID('Tempdb..#Errors') IS NOT NULL
DROP TABLE #Errors
CREATE TABLE #Errors
(
Logdate Datetime,
Processinfo Varchar(30),
Text Varchar (255)
)
INSERT INTO #Errors
EXEC xp_ReadErrorLog 0, 1, N'FAILED',N'login',@FromDate,@ToDate;
SELECT
REPLACE(LoginErrors.Username,'''','') AS Username,
CAST(LoginErrors.Attempts AS NVARCHAR(6)) AS Attempts,
LatestDate.Logdate,
Latestdate.LastError
from (
Select SUBSTRING(text,Patindex('%''%''%',Text),charindex('.',Text)-(Patindex('%''%''%',Text))) as Username,Count(*) AS Attempts
From #Errors Errors
GROUP BY SUBSTRING(text,Patindex('%''%''%',Text),charindex('.',Text)-(Patindex('%''%''%',Text)))
) LoginErrors
CROSS APPLY (SELECT TOP 1 Logdate,text as LastError
FROM #Errors LatestDate
where LoginErrors.Username = SUBSTRING(text,Patindex('%''%''%',Text),charindex('.',Text)-(Patindex('%''%''%',Text)))
ORDER by Logdate DESC) LatestDate
Order by LoginErrors.Attempts DESC
END
GO
This is part of the Undercover Toolbox, a set of useful scripts, procedures and functions for the discerning DBA.
