Detecting Runtime Errors and Exiting from Nested Macros Gracefully Stop the Madness! Detecting Runtime Errors and Exiting from Nested Macros Gracefully
Learning Objectives Demonstrate how to use SYSCC to detect macro runtime errors Demonstrate how to use SYSMacroName to identify currently executing macro Demonstrate how to use %GOTO to redirect program flow Create an error handling macro Create a call stack There are a LOT of great article and books on SAS error handling. What I am going to present is the system I found to be most useful in my daily work.
What’s In, What’s Out Conference white paper has a step-by-step walkthrough of all of the code Presentation will touch on the key concepts and syntax
SAS Don’t Care… Who has every kicked off a long running SAS program, only to return hours later and discover an error occurred early in processing. Or worse yet, not discover the error occurred until reviewing the results day or weeks later? SAS doesn’t care, it just keeps running. And running. And running…
Nested Macro Debugging Master macro Custom Macro 1 Reusable Macro A Reusable Macro B Reusable Macro C Custom Macro 2 Reusable Macro D The SAS log indicates Reusable Macro A failed, but when?
SAS Don’t Care… %Macro BrokenMacroNoErrorTrap(); %put Things seem fine at the start ; PROC I_Made_A_Typo; RUN; %put Things seem fine at the end ; %mend; %Macro HelpfulMacroWithoutErrorTrap(); *Do something dependent upon broken Macro; %Macro CallingMacroNoErrorTrap(); %BrokenMacroNoErrorTrap(); %HelpfulMacroWithoutErrorTrap(); %put Things seem fine at the end; %CallingMacroNoErrorTrap(); Things seem fine at the start ERROR: Procedure I_MADE_A_TYPO not found. Things seem fine at the end
How to make SAS Care Simple Robust Useful
Detecting Errors With &SYSCC Built in Macro Variable &SYSCC 0-4 warnings Above 4 error Check after each statement Can (and must) be reset programmatically %IF &SysCC>4 %THEN ...
Responding to Errors %GOTO Statement Creating a Label using % and : %RETURN Statement %GOTO allowed the program flow to be redirected to deal with the error The Label is used by the %GOTO statement to…well…tell the program were to go %Return statement exits the macro, returning control to the calling program/macro
Detecting Errors With &SYSCC %Macro CallingMacroWithErrorTrap(); %If &SysCC>4 %Then %GoTo ErrorTrap; %put Things seem fine at the start while SYSCC=*&SYSCC*; %BrokenMacroNoErrorTrap(); %HelpfulMacroWithoutErrorTrap(); %put Things seem fine at the end while SYSCC=*&SYSCC*; %Return; %ErrorTrap: %put Things went wrong SYSCC=*&SYSCC*; %mend; %LET SYSCC = 0; %CallingMacroWithErrorTrap(); After each macro call (or any statement for that matter), there is a IF THEN statement checking SYSCC macro variable If the value is an error, then %GOTO ErrorTrap. If no errors are encountered, the return statement end normal execution If there was an error, the error is printed to the log and the macro exits. Note that SYSCC is set to zero before executing, to reset it in case of previous error
Why Bother? The last error is at the end of the log No more endless scrolling or word searches More sophisticated error handling can be rolled into a macro and called in ErrorTrap
Fancy Error Handler &SYSMacroName Custom macro Control Log Built in variable of the currently executing macro Custom macro Single line of code to call Write to a persistent dataset in case of SAS session failure Create a call stack tracking progress Control Log Tracks Successful completion of each macro Logs any errors Creates a “Call Stack” when an error occurs Encapsulates error handling in a single reusable macro
FancyErrorHandler %Macro FancyErrorHandler(CallingMacroName=,ErrorMessage=); Proc sql NOPRINT OUTOBS = 1; SELECT WasError FROM ExecutionLog WHERE WasError=1; quit; %IF &SqlObs ne 0 %then %let ErrorMessage = Call Stack; %IF %Length(&ErrorMessage)=0 %then %let ErrorMessage = *%superq(&SYSErrorText)*; %AddLog( CallingMacroName=&CallingMacroName, Message=&ErrorMessage, WasError=1); %mend; Here is a stripped down version of the error handler in the paper First the log is checked to see if there were any previous errors If there were errors, track this entry as a call stack If no error message was provided, use the built in macro variable SYSErrorText Finally, add the modified message to the log
Output Dataset In the example above, the first four observations represent error free execution, as indicated by the WasError variable. After the VeryHelpfulMacro encounters an error, all subsequent macros only log “Call Stack”. By reading the log backwards from the bottom, the program can see the calling hierarchy. In this case, EvenBetterErrorHandler called VeryHelpfulMacro, which encountered the error. The call stack is most helpful when macros are extensively reused and macros call macros calling macros.
Important Limitation of SYSMacroName If SYSMacroName is passed as a parameter, it is rendered as the current macro Resulting in the value always being FancyErrorHandler %LET CalledBy = &SYSMacroName;
Macro Template %MACRO EveryMacro(); ***Error Handling Definitions****; %LOCAL Message; %LOCAL CalledBy; %LET CalledBy=&SYSMacroName; ***Status Check at the start and after every operation****; %IF &SysCC>=&GlobalErrorDetectionLevel %THEN %GOTO ErrorTrap ***End of the macro****; %AddLog(CallingMacroName=&CalledBy,Message=Success); %Return; %ErrorTrap: %ErrorHandler(CallingMacroName=&CalledBy, ErrorMessage=&Message); %mend;
Conclusions Error handlers can be implemented with a few lines of cut-and-paste code %IF &SysCC>4 %THEN %GOTO ErrorTrap; %GOTO, %RETURN, and labels can be used to redirect program flow &SYSMacroName can be used to create a call stack to make debugging easier Much more detail is provided in the white paper along with complete code samples
Contact Information Name: Ted D. Williams, PharmD, BCPS Company: Magellan Method City/State: Middletown, RI Phone:314.387.4305 Email:tdwilliams1@magellanhealth.com