2016-11-28 84 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

是否可以仅过滤特定严重性以上的失败过程? –