By: Atul Gaikwad | Updated: 2016-10-07 | Comments (7) | Related: > Log Shipping
Problem
We have a few reporting servers where we have SQL Server Log Shipping setup and we are responsible for making sure the data is in sync for reporting users to generate reports. If there is an issue we need to be notified in order to fix the issue ASAP. In this tip I will demonstrate how we can automate SQL Server Log Shipping monitoring and keep the stakeholders informed.
Solution
Let's jump right in and walk through how to setup SQL Server Log Shipping monitoring.
Setup SQL Server Linked Servers for Log Shipping Monitoring
Step 1
Create a Linked Server to each server which is setup for Log Shipping on your monitoring server and ensure you test each Linked Server.
Setup SQL Server Table for Log Shipping Monitoring
Step 2
Create a table for centralized data and monitoring.
CREATE TABLE [dbo].[LSServers]( [SN] [int] IDENTITY(1,1) NOT NULL, [PRIMARY_SERVER] [sysname] NULL, [SECONDARY_SERVER] [sysname] NULL, [SQLVersion] [varchar](20) NULL ) ON [PRIMARY] GO
Insert Records into the SQL Server Log Shipping Monitoring Table
Step 3: Insert records of all your PRIMARY and SECONDARY servers in the monitoring table as shown below:
SQL Server Stored Procedure to Capture Log Shipping Status
Step 4: Create the below stored procedure on your monitoring server to pull the SQL Server Log Shipping status from the respective primary and secondary servers based on the SQL Server version. At a high level, this code captures the Log Shipping status based on the SQL Server version from the corresponding system tables in the MSDB database then inserts the data into the centralized monitoring table created in step 2. Then the status for each record in the monitoring table is updated. The code finishes with sending a status email.
USE [DBA] GO CREATE PROCEDURE [dbo].[usp_GetLogShippingStatus] @mode BIT = 0 AS BEGIN SET NOCOUNT ON SET XACT_ABORT ON DECLARE @Recipients VARCHAR(275) DECLARE @MailSubject VARCHAR(275) DECLARE @Xml VARCHAR(MAX) DECLARE @Mailtext VARCHAR(MAX) DECLARE @Server VARCHAR(25) DECLARE @Curdate DATETIME DECLARE @MailString VARCHAR(MAX) DECLARE @Note NVARCHAR(1000) DECLARE @Filedate DATETIME DECLARE @Dbname SYSNAME DECLARE @Latency SYSNAME DECLARE @Filename NVARCHAR (500) DECLARE @Tempname NVARCHAR (500) DECLARE @Primary SYSNAME DECLARE @Secondary SYSNAME DECLARE @SQLVersion VARCHAR(20) DECLARE @Sql VARCHAR(500) SET @Curdate = GETDATE() SET @Server = @@SERVERNAME SET @Recipients = '[email protected]' IF OBJECT_ID('tempdb..#TABLE_LS_MONITOR') IS NOT NULL DROP TABLE #TABLE_LS_MONITOR CREATE TABLE #TABLE_LS_MONITOR ( SN INT IDENTITY(1,1) ,PRIMARY_SERVER SYSNAME NULL ,PRIMARY_DATABASE SYSNAME NULL ,SECONDARY_SERVER SYSNAME NULL ,SECONDARY_DATABASE SYSNAME NULL ,LSSTATUS VARCHAR(10) ,LAST_BACKUP_FILE NVARCHAR(500) NULL ,BACKUP_THRESHOLD INT NULL ,LAST_BACKUP_TIME DATETIME ,TIME_SINCE_LAST_BACKUP INT NULL ,LAST_RESTORED_FILE NVARCHAR(500) NULL ,RESTORE_THRESHOLD INT NULL ,LAST_RESTORE_TIME DATETIME ,TIME_SINCE_LAST_RESTORE INT NULL ,LAST_RESTORED_LATENCY INT NULL ) DECLARE LSServers CURSOR FOR SELECT PRIMARY_SERVER, SECONDARY_SERVER, SQLVersion FROM LSServers; OPEN LSServers FETCH NEXT FROM LSServers INTO @Primary, @Secondary, @SQLVersion WHILE @@FETCH_STATUS = 0 BEGIN IF @SQLVersion in ('SQL2016','SQL2014','SQL2012','SQL2008','SQL2008R2','SQL2005') BEGIN SET @Sql = 'SELECT p.primary_server,p.primary_database, s.secondary_server, s.secondary_database, p.last_backup_file, p.backup_threshold, p.last_backup_date, s.last_restored_file, s.restore_threshold, s.last_restored_date, s.last_restored_latency FROM ' + @Primary + '.msdb.dbo.log_shipping_monitor_primary p INNER JOIN ' + @Secondary + '.msdb.dbo.log_shipping_monitor_secondary s ON p.primary_server = s.primary_server and p.primary_database = s.primary_database' INSERT INTO #TABLE_LS_MONITOR (PRIMARY_SERVER, PRIMARY_DATABASE, SECONDARY_SERVER, SECONDARY_DATABASE, LAST_BACKUP_FILE, BACKUP_THRESHOLD, LAST_BACKUP_TIME, LAST_RESTORED_FILE, RESTORE_THRESHOLD, LAST_RESTORE_TIME, LAST_RESTORED_LATENCY) EXEC(@Sql) END IF @SQLVersion = 'SQL2000' BEGIN SET @Sql = 'SELECT p.primary_server_name,p.primary_database_name, s.secondary_server_name, s.secondary_database_name, p.last_backup_filename, p.backup_threshold, p.last_updated, s.last_loaded_filename, s.out_of_sync_threshold, s.last_loaded_last_updated FROM ' + @Primary + '.msdb.dbo.log_shipping_primaries p INNER JOIN ' + @Primary + '.msdb.dbo.log_shipping_secondaries s ON p.primary_id = s.primary_id' INSERT INTO #TABLE_LS_MONITOR (PRIMARY_SERVER, PRIMARY_DATABASE, SECONDARY_SERVER, SECONDARY_DATABASE, LAST_BACKUP_FILE, BACKUP_THRESHOLD, LAST_BACKUP_TIME, LAST_RESTORED_FILE, RESTORE_THRESHOLD, LAST_RESTORE_TIME) EXEC(@Sql) END FETCH NEXT FROM LSServers INTO @Primary, @Secondary, @SQLVersion END CLOSE LSServers; DEALLOCATE LSServers; UPDATE #TABLE_LS_MONITOR SET TIME_SINCE_LAST_BACKUP = DATEDIFF(mi, LAST_BACKUP_TIME, @Curdate), TIME_SINCE_LAST_RESTORE = DATEDIFF(mi, LAST_RESTORE_TIME, @Curdate) UPDATE #TABLE_LS_MONITOR SET LAST_BACKUP_FILE = SUBSTRING(LAST_BACKUP_FILE, LEN(LAST_BACKUP_FILE) - CHARINDEX('\', REVERSE(LAST_BACKUP_FILE))+2, LEN(LAST_BACKUP_FILE)), LAST_RESTORED_FILE = SUBSTRING(LAST_RESTORED_FILE, LEN(LAST_RESTORED_FILE) - CHARINDEX('\', REVERSE(LAST_RESTORED_FILE))+2, LEN(LAST_RESTORED_FILE)) WHERE CHARINDEX(N'\',LAST_BACKUP_FILE)!=0 OR CHARINDEX(N'\',LAST_RESTORED_FILE)!=0 DECLARE LSCURSOR CURSOR FOR SELECT PRIMARY_DATABASE, LAST_RESTORED_FILE FROM #TABLE_LS_MONITOR WHERE LAST_RESTORED_LATENCY IS NULL; OPEN LSCURSOR FETCH NEXT FROM LSCURSOR INTO @Dbname, @Filename WHILE @@FETCH_STATUS = 0 BEGIN SELECT @Tempname = RIGHT (@Filename, LEN (@Filename) - (LEN(@Dbname) + LEN ('_tlog_'))) IF (CHARINDEX ('.',@Tempname,0) > 0) SELECT @Tempname = LEFT (@Tempname, CHARINDEX ('.',@Tempname,0) - 1) SELECT @Filedate = CONVERT (DATETIME,SUBSTRING (@Tempname, 1,8),112) IF (LEN (@Tempname) = 12) BEGIN SELECT @Filedate = DATEADD (hh, CONVERT (INT, SUBSTRING (@Tempname,9,2)),@Filedate) SELECT @Filedate = DATEADD (mi, CONVERT (INT, SUBSTRING (@Tempname,11,2)),@Filedate) END UPDATE #TABLE_LS_MONITOR SET LAST_RESTORED_LATENCY = datediff(mi, @Filedate, LAST_RESTORE_TIME) WHERE LAST_RESTORED_LATENCY IS NULL FETCH NEXT FROM LSCURSOR INTO @Dbname, @Filename END CLOSE LSCURSOR; DEALLOCATE LSCURSOR; UPDATE #TABLE_LS_MONITOR SET LSSTATUS = CASE WHEN TIME_SINCE_LAST_BACKUP > BACKUP_THRESHOLD THEN 'BAD' WHEN TIME_SINCE_LAST_RESTORE > RESTORE_THRESHOLD THEN 'BAD' WHEN LAST_RESTORED_LATENCY > RESTORE_THRESHOLD THEN 'BAD' ELSE 'GOOD' END SET @Mailtext ='<html> <style type="text/css"> table.gridtable { font-family: verdana,arial,sans-serif; font-size:12px; color:#000000; border-width: 1px; border-color: #666666; border-collapse: collapse; } table.gridtable th { border-width: 1px; font-size:11px; border-style: solid; border-color: #666666; } table.gridtable td { border-width: 1px; font-size:11px; border-style: solid; border-color: #666666; } </style> <body> <table class="gridtable"> <tr bgcolor = ''''#808080''''> <th> Primary_Server </th> <th> Primary_DB </th> <th> Secondary_Server </th> <th> Secondary_DB </th> <th> Status </th> <th> Last Backup File</th> <th> Backup Threshold </th> <th> TimeSince LastBackup </th> <th> Last Restored File </th> <th> Restore Threshold </th> <th> TimeSince LastRestore </th> <th> LastRestored Latency</th> </tr>' SET @Note = '<b>Note:</b><br/> Time unit is minute for all of the time measures used here.<br/> </body></html>' IF @mode = 0 BEGIN SET @Xml = CAST(( SELECT 'tr/@bgcolor'= CASE LSSTATUS WHEN 'GOOD' THEN '#90EE90' ELSE '#FF4500' END, td = PRIMARY_SERVER, '', td = PRIMARY_DATABASE , '', td = SECONDARY_SERVER, '', td = SECONDARY_DATABASE, '', td = LSSTATUS, '', td = LAST_BACKUP_FILE, '', td = BACKUP_THRESHOLD, '', td = TIME_SINCE_LAST_BACKUP, '', td = LAST_RESTORED_FILE, '', td = RESTORE_THRESHOLD, '', td = TIME_SINCE_LAST_RESTORE, '', td = LAST_RESTORED_LATENCY, '' FROM #TABLE_LS_MONITOR FOR XML PATH('tr'), ELEMENTS ) AS NVARCHAR(MAX)) SET @Mailtext = @Mailtext + @Xml +'</table><br/>' + @Note SET @MailSubject = 'Transaction Log Shipping Status' SET @MailString = 'SQLDBAExperts.msdb.dbo.sp_send_dbmail @profile_name = ''sqldbmail'', @recipients = ''' + @Recipients + ''',@subject = ''' + @MailSubject + ''', @body = ''' + @Mailtext + ''', @body_format = ''HTML'' ' EXEC (@MailString) END ELSE BEGIN IF EXISTS(SELECT 1 FROM #TABLE_LS_MONITOR WHERE LSSTATUS = 'BAD') BEGIN SET @Xml = CAST(( SELECT 'tr/@bgcolor'= CASE LSSTATUS WHEN 'GOOD' THEN '#90EE90' ELSE '#FF4500' END, td = PRIMARY_SERVER, '', td = PRIMARY_DATABASE , '', td = SECONDARY_SERVER, '', td = SECONDARY_DATABASE, '', td = LSSTATUS, '', td = LAST_BACKUP_FILE, '', td = BACKUP_THRESHOLD, '', td = TIME_SINCE_LAST_BACKUP, '', td = LAST_RESTORED_FILE, '', td = RESTORE_THRESHOLD, '', td = TIME_SINCE_LAST_RESTORE, '', td = LAST_RESTORED_LATENCY, '' FROM #TABLE_LS_MONITOR WHERE LSSTATUS = 'BAD' FOR XML PATH('tr'), ELEMENTS ) AS NVARCHAR(MAX)) SET @Mailtext = @Mailtext + @Xml +'</table><br/>' + @Note SET @MailSubject = 'Transaction Log Shipping is out of sync' SET @MailString = 'SQLDBAExperts.msdb.dbo.sp_send_dbmail @profile_name = ''sqldbmail'', @recipients = ''' + @Recipients + ''', @subject = ''' + @MailSubject + ''', @body = ''' + @Mailtext + ''', @body_format = ''HTML'' ' EXEC (@MailString) END END DROP TABLE #TABLE_LS_MONITOR END GO
SQL Server Agent Job for Log Shipping Notification
Step 5: Create a SQL Server Agent Job for sending Log Shipping notifications to all stakeholders.
USE [msdb] GO /****** Object: Job [Monitor Logshipping - Shiftwise] Script Date: 04/22/2016 03:53:50 ******/ BEGIN TRANSACTION DECLARE @ReturnCode INT SELECT @ReturnCode = 0 /****** Object: JobCategory [2 - DBA - On-Demand] Script Date: 04/22/2016 03:53:50 ******/ IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'2 - DBA - On-Demand' AND category_class=1) BEGIN EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'2 - DBA - On-Demand' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback END DECLARE @jobId BINARY(16) EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name= N'Monitor Logshipping - Shiftwise', @enabled=1, @notify_level_eventlog=0, @notify_level_email=0, @notify_level_netsend=0, @notify_level_page=0, @delete_level=0, @description=N'This job will send Logshipping status report to DBA team for all the databases configured.', @category_name=N'2 - DBA - On-Demand', @owner_login_name=N'sa', @job_id = @jobId OUTPUT IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback /****** Object: Step [Logshipping status] Script Date: 04/22/2016 03:53:50 ******/ EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Logshipping status', @step_id=1, @cmdexec_success_code=0, @on_success_action=1, @on_success_step_id=0, @on_fail_action=2, @on_fail_step_id=0, @retry_attempts=0, @retry_interval=0, @os_run_priority=0, @subsystem=N'TSQL', @command=N'exec usp_GetLogshippingStatus 0', @database_name=N'DBA', @flags=0 IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'Logshipping Status Schedule', @enabled=1, @freq_type=4, @freq_interval=1, @freq_subday_type=8, @freq_subday_interval=8, @freq_relative_interval=0, @freq_recurrence_factor=0, @active_start_date=20120920, @active_end_date=99991231, @active_start_time=0, @active_end_time=235959, @schedule_uid=N'a4b8a45f-5b47-40f1-86e6-3a55dc395052' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)' IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback COMMIT TRANSACTION GOTO EndSave QuitWithRollback: IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION EndSave: GO
Review the SQL Server Agent Job in Management Studio
Step 6: Refer to the below screen in SQL Server Management Studio to review the General properties for the job.
Review the Job Step to call the Log Shipping monitoring stored procedure.
SQL Server Agent Job Step details.
Schedule the SQL Server Agent Job as per your requirements. In this example, the schedule is every 8 hours for 24x7 coverage.
SQL Server Agent Log Shipping Monitoring Email
Step 6: With your SQL Server Log Shipping monitoring in place, you will receive email notifications similar to the below screen shot. Keep in mind if any Log Shipping configuration has issues the email will highlight that row in RED.
Next Steps
- Keep this process in mind as you need to deploy or inherit new SQL Server instances with Log Shipping.
- Check out these related resources:
About the author
This author pledges the content of this article is based on professional experience and not AI generated.
View all my tips
Article Last Updated: 2016-10-07