2016-11-28 51 views
3

我試圖實現一個系統範圍內的日誌記錄,它將記錄我們的dabatase中所有失敗的存儲過程執行,並且正在查看擴展事件。SQL Server日誌記錄失敗的查詢

我做了一些研究,它似乎很容易捕獲失敗使用下面的代碼語句:

--Create an extended event session 
CREATE EVENT SESSION what_queries_are_failing ON SERVER 
ADD EVENT sqlserver.error_reported (
    ACTION (sqlserver.sql_text 
     , sqlserver.tsql_stack 
     , sqlserver.database_id 
     , sqlserver.username 
     ) 
    WHERE ([severity] > 10) 
    ) 
ADD TARGET package0.asynchronous_file_target (
    SET filename = 'C:\XEventSessions\what_queries_are_failing.xel' 
    , metadatafile = 'C:\XEventSessions\what_queries_are_failing.xem' 
    , max_file_size = 5 
    , max_rollover_files = 5 
    ) 
    WITH (MAX_DISPATCH_LATENCY = 5 SECONDS) 
GO 

-- Start the session 
ALTER EVENT SESSION what_queries_are_failing ON SERVER STATE = START 
GO 

;WITH events_cte 
AS (
    SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), CURRENT_TIMESTAMP), xevents.event_data.value('(event/@timestamp)[1]', 'datetime2')) AS [err_timestamp] 
     , xevents.event_data.value('(event/data[@name="severity"]/value)[1]', 'bigint') AS [err_severity] 
     , xevents.event_data.value('(event/data[@name="error_number"]/value)[1]', 'bigint') AS [err_number] 
     , xevents.event_data.value('(event/data[@name="message"]/value)[1]', 'nvarchar(512)') AS [err_message] 
     , xevents.event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS [sql_text] 
     , xevents.event_data 
    FROM sys.fn_xe_file_target_read_file('S:\XEventSessions\what_queries_are_failing*.xel', 'S:\XEventSessions\what_queries_are_failing*.xem', NULL, NULL) 
    CROSS APPLY (
     SELECT CAST(event_data AS XML) AS event_data 
     ) AS xevents 
    ) 
SELECT * 
FROM events_cte 
ORDER BY err_timestamp; 

不過,我想立即失敗語句存儲到一個表,讓我們把它Logs.Errors但我找不到辦法做到這一點,上面的方法將不得不作爲一個計劃的工作。

眼下,我們的程序看起來像:

CREATE PROCEDURE [dbo].[MyProcedure] 
AS 
BEGIN 
    SET NOCOUNT ON; 
    BEGIN TRY 
     SELECT 1; 
    END TRY 
    BEGIN CATCH 
     EXECUTE Logs.PrintError; 
     EXECUTE Logs.LogError; 
    END CATCH 
END 

Logs.LogError過程使得DBCC INPUTBUFFER();使用,但它並沒有捕捉到的參數,只是在執行的具體程序。這就是我可以從它那裏得到:

+----------------------------+-----------+-----------+------------------------------+ 
|  ErrorMessage  | EventType | Parameter |   Statement   | 
+----------------------------+-----------+-----------+------------------------------+ 
| Incorrect syntax near '.'. | RPC Event |   0 | DbName.dbo.FailedProcedure;1 | 
+----------------------------+-----------+-----------+------------------------------+ 

我正在尋找一種方式,迫使它捕獲整個陳述或XE插入記錄直入一些表,如果可能的話要麼使DBCC INPUTBUFFER()工作。

任何問題 - 讓我知道。

+0

請你詳細說明'他的上級方法將不得不作爲一個計劃的工作。' – TheGameiswar

+0

@TheGameiswar當然。我記住擴展事件可以在後臺運行並將失敗的查詢信息存儲到給定的文件中。然後根據時間表(我們可以說每小時一次),我可以讀取該文件並將記錄插入Logs.Errors表中。現在它更有意義嗎? –

+0

你不需要運行擴展事件啓動和停止事件作爲作業,一旦你啓動它,它在後臺運行 – TheGameiswar

回答

2

我發現XEvents非常適合在事件發生時進行監視。但是,他們沒有提供「處理」觀察事件的機制。爲了填補這個空白,我使用了Event Notifications。我經常將它們描述爲異步DDL觸發器。我會讓你決定這個tl;dr的定義是否準確。

如果你想給事件通知一個嘗試,這裏是一個腳本,你可以開始(抱歉,它真的很長)。如果您有任何疑問/問題,請告知我。我會盡力盡可能地回覆。

--Create these objects within a database that has service broker enabled. 
USE DbaData 
GO 

--Drop objects first before trying to create them (in specific sequence). 
IF EXISTS (
    SELECT * 
    FROM sys.services 
    WHERE name = 'svcUserErrorReportedNotification' 
) 
    DROP SERVICE svcUserErrorReportedNotification; 
GO 

IF EXISTS (
    SELECT * 
    FROM sys.service_queues 
    WHERE name = 'queUserErrorReportedNotification' 
) 
    DROP QUEUE queUserErrorReportedNotification; 
GO 

IF EXISTS (
    SELECT * 
    FROM sys.server_event_notifications 
    WHERE name = 'enUserErrorReportedEvents' 
) 
    DROP EVENT NOTIFICATION enUserErrorReportedEvents 
    ON SERVER 
GO 

--Create a queue just for user error events. 
CREATE QUEUE queUserErrorReportedNotification 
GO 

--Create a service just for user error events. 
CREATE SERVICE svcUserErrorReportedNotification 
ON QUEUE queUserErrorReportedNotification ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]) 
GO 

-- Create the event notification for user error events on the service. 
CREATE EVENT NOTIFICATION enUserErrorReportedEvents 
ON SERVER 
WITH FAN_IN 
FOR USER_ERROR_MESSAGE 
TO SERVICE 'svcUserErrorReportedNotification', 'current database'; 
GO 

IF EXISTS (
    SELECT * 
    FROM INFORMATION_SCHEMA.ROUTINES r 
    WHERE r.ROUTINE_SCHEMA = 'dbo' AND r.ROUTINE_NAME = 'ReceiveUserErrorReportedEvent' 
) 
    DROP PROCEDURE dbo.ReceiveUserErrorReportedEvent 
GO 

CREATE PROCEDURE dbo.ReceiveUserErrorReportedEvent 
/***************************************************************************** 
* Name  : dbo.ReceiveUserErrorReportedEvent 
* Purpose : Runs when there is a USER_ERROR_MESSAGE event. 
* Inputs : None 
* Outputs : None 
* Returns : Nothing 
****************************************************************************** 
* Change History 
* 11/28/2016 DMason Created 
******************************************************************************/ 
AS 
BEGIN 
    SET NOCOUNT ON 
    DECLARE @MsgBody XML 

    WHILE (1 = 1) 
    BEGIN 
     BEGIN TRANSACTION 

     -- Receive the next available message FROM the queue 
     WAITFOR (
      RECEIVE TOP(1) -- just handle one message at a time 
       @MsgBody = CAST(message_body AS XML) 
       FROM queUserErrorReportedNotification 
     ), TIMEOUT 1000 -- if the queue is empty for one second, give UPDATE and go away 
     -- If we didn't get anything, bail out 
     IF (@@ROWCOUNT = 0) 
     BEGIN 
      ROLLBACK TRANSACTION 
      BREAK 
     END 
     ELSE 
     BEGIN 
      --Grab some relevant items from the message body XML (it is EVENTDATA(), btw) 
      DECLARE @Login SYSNAME; 
      DECLARE @ErrMsgText VARCHAR(MAX); 
      DECLARE @ApplicationName VARCHAR(MAX); 
      DECLARE @Severity INT; 
      DECLARE @ErrorNumber INT; 
      DECLARE @DBName SYSNAME; 

      SET @Login = @MsgBody.value('(/EVENT_INSTANCE/LoginName)[1]', 'VARCHAR(128)'); 
      SET @ErrMsgText = @MsgBody.value('(/EVENT_INSTANCE/TextData)[1]', 'VARCHAR(MAX)'); 
      SET @ApplicationName = @MsgBody.value('(/EVENT_INSTANCE/ApplicationName)[1]', 'VARCHAR(MAX)'); 
      SET @Severity = @MsgBody.value('(/EVENT_INSTANCE/Severity)[1]', 'INT'); 
      SET @ErrorNumber = @MsgBody.value('(/EVENT_INSTANCE/Error)[1]', 'INT'); 
      SET @DBName = @MsgBody.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'VARCHAR(128)'); 

      --Do stuff here. 
      --Log to a table, etc. 

      /* 
       Commit the transaction. At any point before this, we 
       could roll back -- the received message would be back 
       on the queue AND the response wouldn't be sent. 
      */ 
      COMMIT TRANSACTION 
     END 
    END 
END 
GO 

ALTER QUEUE dbo.queUserErrorReportedNotification 
WITH 
STATUS = ON, 
ACTIVATION ( 
    PROCEDURE_NAME = dbo.ReceiveUserErrorReportedEvent, 
    STATUS = ON, 
    MAX_QUEUE_READERS = 1, 
    EXECUTE AS OWNER) 
GO 
+0

哦,男孩。這看起來正是我正在尋找的。我會在我的本地機器上嘗試,並在可以的情況下立即反饋。謝謝! –

+0

我剛想到的東西...... USER_ERROR_MESSAGE可能會產生很多「噪音」。它包含所有嚴重級別的錯誤(我認爲)。例如,您可能會發現大量針對嚴重性級別低於10的「錯誤」的事件,您可能不會關心這些事件。這可能會比SQL可以處理它的速度更快。 – DMason

+0

是否可以僅過濾特定嚴重性以上的失敗過程? –