I used to work for a company where they had this 3rd party Data warehouse Solution. Obviously all the objects and tables were hidden in the Supporting database so I don't have clear idea on what exactly happened inside some of the stored procedure. I saw this interesting stored procedure there and want to replicate that in my own solution but I just can't get my head around on how it works. I am describing the stored procedure below and it will be really helpful if someone can give me some ideas on how to achieve this. Even better if you can suggest me how I can make this even better.
Stored procedure was called process log. It had Parameters like DBID, ObjectId, Step, Status, Remarks, Reads, Inserts, Updates, Deletes
What we had to do was, inside every stored procedure, we have to execute this stored procedure with Status as 2 (In Progress) During the process where can execute this stored procedure multiple time at the end of each step or section after increasing the value of variable step. based on the row counts of Insert update select and delete statements we were supposed to record values in respective stored procedure parameter variables.
In the end you can execute same stored procedure with Status of 3 (Completed) or if the procedure ended up in catch block, status will be 4 (Failed) in the remarks section we can copy SQL's error messages.
To see all this information, we were given access to a Report, For which obviously I didn't have the source code but the report showed what time stored procedure started when it finished, what was the status how many inserts updates deletes and reads it did. if it failed, what was the error message?
I already have few ideas for improvements to store, who started it?, what were the values of parameters? For who started the stored procedure part, I have a confusion. Most of these stored procedure run as part of different Jobs. All our jobs run as a Service Account user but jobs are started manually by various users. I need to find out which user started it, as inside stored procedure, as current user, it will always show service account. Also for Values of Paramters, is there a better dynamic way of finding this out? rather then setting a value for a variable manually. I was hopping to use output from INPUTBUFFER but it only shows the name of the parameter not values.
If someone can guide me regarding the back end table structure and script for this auditing SP, it will be really helpful. Also any more ideas for improvement are welcome as well.
My main confusion:
I believe they had some table where these stored procedure values were stored and if the SP is running already, they did update in the record then doing an Insert but how would they identify to do an insert instead of update in the scenario where stored procedure failed critically and catch block wasn't executed.
Best Answer
Here is a structure that is at least very close.
There is no programmatic way to get the parameters (unfortunately). You need to format them into XML to pass in.
The Login that initiates a SQL Agent job seems to only be logged in the
message
column ofmsdb.dbo.sysjobhistory
, forstep_id = 0
. This value can be extracted, but not during the execution of the job.You get ObjectID to pass in from
@@PROCID
.Below is the schema (2 tables) and stored procedures (3 procs). The concept is to separate "init", "in process", and "completed (success or error)" logging. This allows for setting certain columns only at the appropriate time (e.g. only need to set
DatabaseID
,StartedAt
, etc at the very beginning). Separating the type of event also makes it easier to have event-specific logic (yes, can have it even in a single proc, but then you still have all the input parameters when you only need a subset per each event-type).A "process" record get updated via its IDENTITY (and clustered PK) value. This is another benefit of having the "event type" separation: it makes it much easier to handle capturing the
SCOPE_IDENTITY()
and passing it back to be used for the two other logging stored procedures. If a stored procedure fails and doesn't go to theCATCH
block, then there is no need to worry about accidentally updating that process record as the next time any stored procedure (that is being logged) starts, it will get a new / unique ID to update.Cleanup (optional) and Schema
Tables and Indexes
Stored Procedure to call at the very beginning of "logged" stored procedures
Stored Procedure to call after all but the final step
Stored Procedure to call after the final step and/or in a CATCH block
Demo stored procedure (input parameters are formatted as XML)
The reason for putting "StepNumber" in a variable is so that the value can get passed to the
CATCH
block. The@StepNumber
variable is incremented prior to each operation. If the operation succeeds, that value is used to call the "Log" stored procedure that captures the number of rows affected for that step and the time it was called. If the operation fails, that same@StepNumber
value is used to call the "Stop" stored procedure that marks the process as "failed" and passes in the error message. This makes the data less confusing since theStep
column on failed records will be the step it was actually working on when the error occurred.NOTES:
With regards to getting the "invoked by" Login for SQL Server Agent jobs: the
step_id = 0
record (which is the only place that this info exists) does not exist until the job completes (success or failure). Hence, it isn't available while the stored procedure is running, let alone at the beginning. For now we capture theMAX(sjh.[instance_id]) FROM msdb.dbo.sysjobhistory sjh
for the currently executing job for the current session. Later (i.e. after the job completes), that can get replaced with the job invoker Login.I would generally recommend against adding this type of logging to Stored Procedures that are executed very frequently as the additional read and write operations will have a negative impact on performance.
ADDENDUM
Here is an Inline Table-Valued Function (ITVF) to get the job outcome info (including the "invoked by" user or schedule or whatever) based on the
instance_id
value that was captured into theProcessLog.StartedBy
column. Theinstance_id
value returned in the result set is the row forstep_id = 0
.