Our application is Windows Service (native .EXE written in C++) that calls stored procedures in SQL Server. In most cases errors in stored procedures (in 90% of the cases these errors mean something was wrong in our business logic) are re-thrown as exception and caught by our service. They are then logged in Application Event Log on the computer where our service is running.
However, I now have a need to log some of the errors on the SQL Server itself within a stored procedure.
Following the paradigm we use for our service I think I can use xp_logevent to save error information in the event log.
Is this a recommended approach to log SQL Server errors?
FWIW I use SQL Server 2008
The How To
You can always use RAISEERROR() WITH LOG. Logs to both Windows Application log and the SQL error log.Please note that severity level is key here. There are some limitations and security considerations, but you get some other features also.
More details in BOL:
http://msdn.microsoft.com/en-us/library/ms178592.aspx
The Should you
My opinion is that you shouldn't log anything to SQL error log unless it's generated by SQL server itself. Multiple reasons:
If your IT or DBA uses log analyzer or any other tool, it may trip an alarm on an application issue, instead of the server issue (this is what they are trying to catch).
I never found parsing error logs enjoyable from within SQL server, and I'm not particularly in love with SSMS's way of doing it.
My suggestion
Use a generic logging stored procedure writing to some error log table. A very nice patter is
BEGIN TRY
...do your stuff
END TRY
BEGIN CATCH
get the ERROR_LINE(), ERROR_MESSAGE() and friends
execute generic logging procedure
END CATCH
As a bonus, you can use SSSB within the logging procedure to make it async and not impede the main logic flow
Here is a useful way I have found to keep track of SQL Server errors. First, create a table to store the errors:
CREATE TABLE utiliity.dbo.ProcedureLog
(
LogDate DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
DatabaseID INT,
ObjectID INT,
ProcedureName NVARCHAR(400),
ErrorLine INT,
ErrorMessage NVARCHAR(MAX),
AdditionalInfo NVARCHAR(MAX)
);
GO
CREATE CLUSTERED INDEX cx_LogDate ON dbo.utiliity.dbo.ProcedureLog(LogDate);
GO
Then create a stored procedure to call when the error occurs:
CREATE PROCEDURE sp_CallProcedureLog
#ObjectID INT,
#DatabaseID INT = NULL,
#AdditionalInfo NVARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#ProcedureName NVARCHAR(400);
SELECT
#DatabaseID = COALESCE(#DatabaseID, DB_ID()),
#ProcedureName = COALESCE
(
QUOTENAME(DB_NAME(#DatabaseID)) + '.'
+ QUOTENAME(OBJECT_SCHEMA_NAME(#ObjectID, #DatabaseID))
+ '.' + QUOTENAME(OBJECT_NAME(#ObjectID, #DatabaseID)),
ERROR_PROCEDURE()
);
INSERT utiliity.dbo.ProcedureLog
(
DatabaseID,
ObjectID,
ProcedureName,
ErrorLine,
ErrorMessage,
AdditionalInfo
)
SELECT
#DatabaseID,
#ObjectID,
#ProcedureName,
ERROR_LINE(),
ERROR_MESSAGE(),
#AdditionalInfo;
END
GO
Finally, in your stored procedures where you want to record the errors:
BEGIN TRY
... execute SQL commands here
END TRY
BEGIN CATCH
DECLARE #msg NVARCHAR(MAX);
SET #msg = 'Something went horribly wrong. Error number = ' + ERROR_NUMBER();
EXEC utiliity.dbo.sp_CallProcedureLog
#ObjectID = ##PROCID,
#AdditionalInfo = #msg;
DECLARE #ErrorMessage NVARCHAR(MAX);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
Here are my sources: http://www.mssqltips.com/sqlservertip/2003/simple-process-to-track-and-log-sql-server-stored-procedure-use/ and http://msdn.microsoft.com/en-us/library/ms178592(SQL.105).aspx. HTH.
You can call xp_logevent to log messages in the event log. But for logging exceptions it is better to use the RAISERROR () WITH LOG statement.
If you are concerned about performance you can pass the message through a SQL Server Service Broker queue and have an activation procedure log the messages in the eventlog.
The downside is that whoever has to find out the errors now needs permissions to get into the event log.
If you go with this, make sure your log has more size than the default 512K. Also set it to overwrite events as needed.
Also, the event log is not as fast as your SQL Server database so you may want to run a load test to figure out if it slows your application down.
Related
BEGIN TRY
EXEC N'EXEC sp_testlinkedserver N''[MyLinkedServer]'';';
END TRY
BEGIN CATCH
SELECT 'LinkedServerDown' AS Result
RETURN
END CATCH
SELECT TOP(1) FirstName FROM [MyLinkedServer].TestDatabase.dbo.Customer
My first experience with using a TRY...CATCH in SQL Server does not have me impressed so far.
I've stopped the SQL Service on my linked server to attempt to test a situation where our linked server is down, inaccessible, etc.
Instead of catching any error, this code just throws the "Login timeout expired" and "network-related or instance-specific error has occurred..." error and ceases execution of the rest of the code.
Is my SQL TRY...CATCH block not set up correctly?
As per the MSDN, what sp_testlinkedserver do is
Tests the connection to a linked server. If the test is unsuccessful
the procedure raises an exception with the reason of the failure.
So when you compile your code (SP), sp_testlinkedserver checks for connection. But you can defer this and capture it by using dynamic SQL.
Like this -
BEGIN TRY
EXEC sp_executesql N'EXEC sp_testlinkedserver [192.168.51.81];';
END TRY
BEGIN CATCH
SELECT 'LinkedServerDown' AS Result
END CATCH
From MSDN
Errors Unaffected by a TRY…CATCH Construct
TRY…CATCH constructs do not trap the following conditions:
Warnings or informational messages that have a severity of 10 or
lower.
Errors that have a severity of 20 or higher that stop the SQL Server
Database Engine task processing for the session. If an error occurs
that has severity of 20 or higher and the database connection is not
disrupted, TRY…CATCH will handle the error.
Attentions, such as client-interrupt requests or broken client
connections.
When the session is ended by a system administrator by using the
KILL statement.
The following types of errors are not handled by a CATCH block when
they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from
running.
Errors that occur during statement-level recompilation, such as
object name resolution errors that occur after compilation because
of deferred name resolution.
You need to create your end testlinkedserver stored procedure. This will also capture login time out errors.
exec dbo.USP_testlinkedserver 'myServerNameHere'
The definition is mentioned below:
CREATE PROCEDURE USP_testlinkedserver
#ServerName sysname
AS
BEGIN
SET NOCOUNT ON;
DECLARE #statement NVARCHAR(MAX), #errorMessage NVARCHAR(MAX)
SET #statement = N'SELECT * FROM OPENQUERY('+QUOTENAME(#ServerName,'[')+', ''SELECT 1'')'
BEGIN TRY
-- run the query
EXEC sp_executesql #stmt = #statement;
END TRY
BEGIN CATCH
-- show custom message
SET #errorMessage=QUOTENAME(#ServerName,'[') + ' linked server is not available. ' + ERROR_MESSAGE()
Raiserror(#errorMessage,16,1)
END CATCH;
END
I have a stored procedure which is runs automatically every morning in SQL Server 2008 R2, part of this stored procedure involves executing other stored procedures. The format can be summarised thus:
BEGIN TRY
-- Various SQL Commands
EXECUTE storedprocedure1
EXECUTE storedprocedure2
-- etc
END TRY
BEGIN CATCH
--This logs the error to a table
EXECUTE errortrappingprocedure
END CATCH
storedprocedure1 and storedprocedure2 basically truncate a table and select into it from another table. Something along the lines of:
BEGIN TRY
TRUNCATE Table1
INSERT INTO Table1 (A, B, C)
SELECT A, B, C FROM MainTable
END TRY
BEGIN CATCH
EXECUTE errortrappingprocedure
END CATCH
The error trapping procedure contains this:
INSERT INTO
[Internal].dbo.Error_Trapping
(
[Error_Number],
[Error_Severity],
[Error_State],
[Error_Procedure],
[Error_Line],
[Error_Message],
[Error_DateTime]
)
(
SELECT
ERROR_NUMBER(),
ERROR_SEVERITY(),
ERROR_STATE(),
ERROR_PROCEDURE(),
ERROR_LINE(),
ERROR_MESSAGE(),
GETDATE()
)
99% of the time this works, however occasionally we will find that storedprocedure1 hasn't completed, with Table1 only being part populated. However no errors are logged in our error table. I've tested the error trapping procedure and it does work.
When I later run storedprocedure1 manually it completes fine. No data in the source table will have changed by this point so it's obviously not a problem with the data, something else has happened in that instant which has caused the procedure to fail. Is there a better way for me to log errors here, or somewhere else within the database I can look to try and find out why it is failing?
Try to use SET ARITHABORT (see link). It must ROLLBACK in your case. Also the answer of #Kartic seem reasonable.
I recommned also to read about implicit and explicit transactions - I think that this is your problem. You have several implicit transactions and when error happeneds you are in the middle of the job - so only part is rollbackеd and you have some data in that tables.
There are some type of Errors that TRY..CATCH block will not handle them, look here for more information https://technet.microsoft.com/en-us/library/ms179296(v=sql.105).aspx . for such Errors you should handle them in your application.
also I think you might have transaction management problem in your application too.
I am not sure if I understood you completely. Below code is too big for comment. So posting as an answer for your reference. If this is not what you want, I'll delete it.
Can we add transaction handling part as well.
DECLARE #err_msg NVARCHAR(MAX)
BEGIN TRY
BEGIN TRAN
-- Your code goes here
COMMIT TRAN
END TRY
BEGIN CATCH
SET #err_msg = ERROR_MESSAGE()
SET #err_msg = REPLACE(#err_msg, '''', '''''')
ROLLBACK TRAN
-- Do something with #err_msg
END CATCH
I've found a way to fail a SQL Scheduled Job (with severity 16) that does not report failure (and so does not send email notifications). I've fixed my immediate issue, but I want to know why there is a failure case that does not report as failure, and if there are any other surprising ways to miss notification.
I've set up two linked servers and am attempting to run an hourly scheduled SQL Job on one that queries the other. I found this morning that the code in the SP had not been running, but the history on the Job was reporting success. The Job's only step is EXEC _testSP. If I ran EXEC _testSP in a query window from SSMS, I received this error message:
Msg 0, Level 11, State 0, Line 0 A severe error occurred on the
current command. The results, if any, should be discarded.
The SP's contents are wrapped in TRY ... CATCH. If I remove the TRY ... CATCH, executing the SP gives up this error message:
Msg 213, Level 16, State 7, Line 1
Insert Error: Column name or number of supplied values does not match table definition.
This made sense. The remote table was referenced with a SELECT * FROM and some columns had been added to it. I've removed the asterix and the job runs fine now, but I want to make sure that all future exceptions get logged either by the job failure notification, or the CATCH block in _testSP. I don't understand why this one didn't get logged, and I hope that someone can explain it to me.
The job runs and fails and notifies just as I would expect when the TRY ... CATCH wrapping is removed, but we have some important things in the TRY ... CATCH that need to be kept.
This is not a duplicate of this related question. The Microsoft BOL for TRY...CATCH says that some exceptions cannot be caught by TRY...CATCH. It may be related, but what I've found is an exception that is not caught by the Scheduled Job agent.
Reproduceable example: (also try removing the TRY...CATCH wrapper and see the change)
USE [RemoteServer].[Database]
CREATE TABLE [Tally](
[ID] [int] IDENTITY(0,1) NOT NULL,
[ID2] [int] NOT NULL
) ON [PRIMARY]
GO
USE [LocalServer]
-- Setup procedure
CREATE PROCEDURE _testSP
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
-- Create destination temp table
CREATE TABLE #tempb (a int)
-- Insert into temp table from remote Tally table
DECLARE #query nvarchar(4000)
SELECT #query = '
SELECT TOP 5 *
FROM [Database].[dbo].Tally
'
INSERT INTO #tempb
EXEC [RemoteServer].[master].[dbo].sp_executesql #query
END TRY BEGIN CATCH
-- Log the exception
-- ...
-- Rethrow the exception
DECLARE #ErrorMessage nvarchar(max), #ErrorSeverity int, #ErrorState int;
SELECT
#ErrorMessage = 'Handled Exception: ' + ERROR_MESSAGE() + ' line ' + CAST(ERROR_LINE() as nvarchar(5)),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, #ErrorSeverity, #ErrorState);
END CATCH
END
GO
-- Setup job
DECLARE #database varchar(100)
SELECT #database = DB_Name()
EXEC msdb.dbo.sp_add_job
#job_name = '_testSPJob'
EXEC msdb.dbo.sp_add_jobstep
#job_name = '_testSPJob',
#step_name = '_testSPJob',
#subsystem = N'TSQL',
#command = 'EXEC _testSP',
#database_name = #database
EXEC msdb.dbo.sp_add_jobserver
#job_name = '_testSPJob',
#server_name = ##SERVERNAME
GO
-- Manual execution fails
EXEC _testSP
GO
-- Run job
EXEC msdb.dbo.sp_start_job
#job_name = '_testSPJob'
WAITFOR DELAY '00:00:02'
GO
-- Select job history
SELECT * FROM msdb.dbo.sysjobhistory
WHERE step_name = '_testSPJob'
ORDER BY run_date, run_time
GO
I really need to convince the bosses to get off SQL 2000. Here are my software versions. Perhaps this is fixed in later versions of SQL?
SSMS Version: 2012 (11.0.5058.0)
Local DB: SQL 2005 (9.0.5069)
Remote DB: SQL 2000 (8.0.760)
I think that what is happening is correct, because you're handdling an exception, so the job is not failling. A solution could be a log so when the exception y catch you insert a row with the error description.
I have stored procedure which is for inserting data into a table. This procedure is called from asp.net application which handles the transaction start, commit and rollback functionality. Inside the stored procedure there is no transaction.
In this scenario my application is working fine and it is hosted in the live. Now, inside the store procedure, I have to add a new functionality to insert another table by linked server to another database and if error appears then I have to store it in the database.
We want to implement this insertion in such a way so that the previous sp will be working fine.
Noted that if error from the insertion of linked server comes then entire process is rolled back and also save point is not working. We can do this by dot net code but this is for more 40 modules, so I have to do this by sp . So, how can we implement this.
MSDN SAVE TRANSACTION
CREATE PROCEDURE [dbo].[SaveCustomer]
#Firstname nvarchar(50),
#Lastname nvarchar(50)
AS
DECLARE #ReturnCode int = 1 -- 1 - success
,#NewID int
---------------------------------------------- These variables are for TRY/CATCH RAISEERROR and BEGIN TRAN/SAVE POINT use
,#tranCounter int -- #TranCounter > 0 means an active transaction was started before the procedure was called.
,#errorMessage nvarchar(4000) -- echo error information to the caller. Message text.
,#errorSeverity int -- Severity.
,#errorState int; -- State
SET #tranCounter = ##TRANCOUNT;
IF #tranCounter > 0
SAVE TRANSACTION SaveCustomer_Tran;
ELSE
BEGIN TRANSACTION;
BEGIN TRY
INSERT dbo.Customer (
Firstname
,Lastname)
VALUES (
#Firstname
,#Lastname)
SET #NewID = scope_identity()
IF #tranCounter = 0
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF #tranCounter = 0
ROLLBACK TRANSACTION;
ELSE
IF XACT_STATE() <> -1
ROLLBACK TRANSACTION SaveCustomer_Tran;
SELECT #errorMessage = 'Error in [SaveCustomer]: ' + ERROR_MESSAGE(), #errorSeverity = ERROR_SEVERITY(), #errorState = ERROR_STATE();
RAISERROR (#errorMessage, #errorSeverity, #errorState);
SET #ReturnCode = -1
END CATCH
SELECT #ReturnCode as [ReturnCode], #NewID as [NewID]
GO
There are two accepted ways to escape the current transaction rollback tarpit.
One way is to use a looopback connection. Either via linked server, or from SQLCLR, connect back to the current server and write the INSERT, making sure DTC enrollment is not allowed. As the loopback connection is a different transaction, you can safely rollback in the original transaction and preserve the INSERT. This approach has the drawback that is very easy to deadlock yourself.
The other, less known, way is to use sp_trace_generateevent to fire off a user-configurable event. This may not seem much, but you can create event notifications for these events and then impelemnt handling that does the INSERT in processing the event. Eg. see Sql progress logging in transaction. This approach has the drawback of being difficult.
I have a Helper DB which is on 32bit sql server and i have added a linked server of 64bit,
to perform Excel Import operation as Jet.oledb driver is not supported on 64-bit sql server machine.
Everything is working fine, but i have to maintain transactions for insert ,update,delete that happens on linked server database,
I have configured DTC service on client machine and server machine,
Added Block on top of sp for transaction on stored procedure start like,
BEGIN TRY SET XACT_ABORT ON BEGIN TRANSACTION
BEGIN TRY
SET XACT_ABORT ON
BEGIN TRANSACTION
--Code
--Code
END TRY
BEGIN CATCH
IF ##TRANCOUNT<>0 ROLLBACK TRANSACTION
--// raise error to log in website.
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
But it throws an err saying,
The requested operation could not be performed because OLE DB provider "Microsoft.Jet.OLEDB.4.0" for linked server "(null)" does not support the required transaction interface.
It is throwing error on Openrowset function that i have used to read data from Excel sheet
i.e,
SELECT * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 5.0;HDR=Yes; IMEX=1;Database=C:\test.xls','SELECT EmployeeID FROM [Sheet$]
where EmployeeID is not null')
I m really stuck and getting Crazy as am not able to find any solution,
Thanks in Advance for Help,
I had a similar problem, importing from CSV. Solved it by naming my transaction (e.g. BEGIN TRAN mytran) and checking ##ROWCOUNT instead of ##TRANCOUNT.
I understand it's a long time since OP asked, but maybe it could help somebody googling for this issue. :)
What if you open the Excel file in a cursor, but before the Begin Trans. ?