SQLServerCentral Article

Re-throw Errors with Logging

,

Often times errors in stored procedures go unnoticed. If there is no proper error handling on the application side or if the application is not doing any logging it is sometimes difficult to identify database errors especially if there is a lot of dynamic SQL involved. When all errors are logged to a central table, one can review them periodically and identify any potential issues and proactively fix them.

The following error handling method provides a clean way to catch and re-throw errors to the calling application and also log errors to a table if required. Errors would be logged to a central location (a table), which makes troubleshooting easy.

The method invloves creating a stored procedure (pReThrowErrorWithLogging) that will be called in the catch block to display, log, and re-throw errors. The stored procedure takes two parameters: @Debug when set to 1 will print the formatted error message and @LogError when enabled logs the error to the SPErrorLog table in the current database.

Here is the stored procedure.

/****************************************************************************
DESCRIPTION: Re-raises the error to be caught in the calling process.
Logs the error if @logError = 1
****************************************************************************/CREATE PROCEDURE [dbo].[pReThrowErrorWithLogging] 
  @ProcedureName VARCHAR(500),
  @logError BIT = 0,
  @Debug BIT = 0
AS
BEGIN
SET NOCOUNT ON
DECLARE @ErrorNumber INT = ERROR_NUMBER()
DECLARE @ErrorSeverity INT = ERROR_SEVERITY()
DECLARE @ErrorState INT = ERROR_STATE()
DECLARE @ErrorLine INT = ERROR_LINE()
DECLARE @ErrorProcedureName SYSNAME = ERROR_PROCEDURE()
DECLARE @ErrorMessage NVARCHAR(MAX) = ERROR_MESSAGE()
DECLARE @FullErrorMessage NVARCHAR(MAX)
SET @ErrorProcedureName = ISNULL(@ErrorProcedureName, @ProcedureName)
IF @Debug = 1
 PRINT N'ErrorNumber = '
  + CONVERT(NVARCHAR(10), @ErrorNumber)
  + N'ErrorSeverity = '
  + CONVERT(NVARCHAR(10), @ErrorSeverity)
  + N'ErrorState = '
  + CONVERT(NVARCHAR(10), @ErrorState)
  + N'ErrorLine = '
  + CONVERT(NVARCHAR(10), @ErrorLine)
  + N'ErrorProcedure = '
  + @ErrorProcedureName
  + N'ErrorMessage = '
  + @ErrorMessage + N'
 '
;
IF( @logError = 1 )
 BEGIN
  BEGIN TRY
   IF NOT EXISTS (SELECT 1
                    FROM sys.tables
                    WHERE name = 'SPErrorLog'
                  AND type IN ( N'U' ))
    BEGIN
     -- You can truncate this table periodically
     CREATE TABLE [SPErrorLog]
     (
      [ErrorLogID] [INT] IDENTITY(1, 1) NOT NULL,
      [ErrorTime] [DATETIME] NOT NULL CONSTRAINT [DF_ErrorLog_ErrorTime] DEFAULT (getdate()),
      [UserName] [VARCHAR](500),
      [SPID] [INT],
      [ErrorNumber] [INT],
      [ErrorSeverity] [INT],
      [ErrorState] [INT],
      [ErrorProcedure] [NVARCHAR](400),
      [ErrorLine] [INT],
      [ErrorMessage] [NVARCHAR](4000),
      CONSTRAINT [PK_ErrorLog_ErrorLogID] PRIMARY KEY CLUSTERED ( [ErrorLogID] ASC )
      )
    END
   INSERT INTO [SPErrorLog]
     (ErrorTime,
      UserName,
      SPID,
      ErrorNumber,
      ErrorSeverity,
      ErrorState,
      ErrorProcedure,
      ErrorLine,  
      ErrorMessage
      )
     VALUES ( GETDATE(),
              SYSTEM_USER,
              @@SPID,
              @ErrorNumber,
              @ErrorSeverity,
              @ErrorState,
              @ErrorProcedureName,
              @ErrorLine,
              LEFT(@ErrorMessage, 4000) 
            );
   END TRY
  BEGIN CATCH
   -- Do nothing, just re-throw the error.
  END CATCH
END
SET @FullErrorMessage = N'Unexpected error ocurred in Procedure: %s Line Number: %i Error Number: %i Severity: %i State: %i '
+ @ErrorMessage
RAISERROR (@FullErrorMessage,16,@ErrorState,@ErrorProcedureName,@ErrorLine,@ErrorNumber,@ErrorSeverity,@ErrorState)
RETURN @ErrorNumber
END
GO

In your stored procedures, use the following template for error handling  :

DECLARE @SPName NVARCHAR(392) = DB_NAME() + '.' + SCHEMA_NAME() + '.' + OBJECT_NAME(@@PROCID);
DECLARE @Result INT
BEGIN TRY
  -- Your code
  SET @Result = 0
END TRY
BEGIN CATCH 
 -- re-raise the error to be caught in the calling process
 EXECUTE @Result = dbo.pReThrowError @SPName = @SPName, @logError = 1, @Debug = 1
END CATCH;
RETURN @Result

Set @logError = 0 if you don’t want the error to be logged and use @Debug = 1 if you want to print the error.

Here is an example stored procedure that uses the pReThrowErrorWithLogging stored procedure to rethrow and log errors .

/*****************************************************************************************************
Object: [dbo].[pSimpleDivideToTestErrorLogging]
Description: Simple division sp to test pReThrowError with logging 
******************************************************************************************************/
ALTER PROCEDURE [dbo].[pSimpleDivideToTestErrorLogging]
  @Numerator INT
, @Denominator INT 
, @LogError BIT = 0 
, @Debug BIT = 0 
AS
BEGIN
SET NOCOUNT ON
DECLARE @Result INT
DECLARE @SPName NVARCHAR(392) = DB_NAME() + '.' + SCHEMA_NAME() + '.' + OBJECT_NAME(@@PROCID)
IF @Debug = 1 
 PRINT ': START OF ' + @SPName
 BEGIN TRY 
  SELECT @Numerator * 1.0/@Denominator;
  SET @Result = 0 ;
 END TRY
 BEGIN CATCH 
  -- re-raise the error to be caught in the calling process
  EXECUTE @Result = dbo.pReThrowErrorWithLogging @SPName,@LogError, @Debug
 END CATCH;
IF @Debug = 1 
 PRINT ': END OF '+ @SPName
RETURN @Result
END
GO

You can run the stored procedure with logging enabled by setting @LogError = 1 . This will log errors to the SPErrorLog table when an error is encountered.

EXEC [dbo].[pSimpleDivideToTestErrorLogging] 
  @Numerator = 1 
, @Denominator = 0
, @LogError = 1
, @Debug = 1;

Here is the output of the above run:

: START OF AIRProject.dbo.pSimpleDivideToTestErrorLogging

ErrorNumber = 8134

ErrorSeverity = 16

ErrorState = 1

ErrorLine = 24

ErrorProcedure = pSimpleDivideToTestErrorLogging

ErrorMessage = Divide by zero error encountered.

Msg 50000, Level 16, State 1, Procedure pReThrowErrorWithLogging, Line 96

Unexpected error ocurred in Procedure: pSimpleDivideToTestErrorLogging Line Number: 24 Error Number: 8134 Severity: 16 State: 1

Divide by zero error encountered.

: END OF AIRProject.dbo.pSimpleDivideToTestErrorLogging.

You can see the error logged in the SPError log table

SELECT *
 FROM SPErrorLog

If you want to make this error logging procedure a standard so that it can be used by all procs accessed by various applications, you have to create this application in each one of your databases and call the stored procedure in all of your error handling catch blocks.You can also create the stored procedure in the model database, so that the next time you create a new database this stored procedure is automatically there.

Some implementation options to consider :

  • In SQL Server 2012 , you can use THROW instead of RAISEERROR to rethrow an error.
  • You can have one error log table per database or have a central table per database or log errors to a remote central database via a linked server . Remember to use fully qualified name for the table if you want to use these options.

Rate

4.05 (21)

You rated this post out of 5. Change rating

Share

Share

Rate

4.05 (21)

You rated this post out of 5. Change rating