Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
mp_abort.sas
Go to the documentation of this file.
1 /**
2  @file
3  @brief abort gracefully according to context
4  @details Configures an abort mechanism according to site specific policies or
5  the particulars of an environment. For instance, can stream custom
6  results back to the client in an STP Web App context, or completely stop
7  in the case of a batch run. For STP sessions
8 
9  The method used varies according to the context. Important points:
10 
11  @li should not use endsas or abort cancel in 9.4m3 environments as this can
12  cause hung multibridge sessions and result in a frozen STP server
13  @li should not use endsas in viya 3.5 as this destroys the session and cannot
14  fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
15  recognise this and fetch the log of the parent session instead)
16  @li STP environments must finish cleanly to avoid the log being sent to
17  _webout. To assist with this, we also run stpsrvset('program error', 0)
18  and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
19  but don't close it! This works everywhere EXCEPT inside a \%include inside
20  a macro. For that, we recommend you use mp_include.sas to perform the
21  include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
22  OUTSIDE of the top-parent macro).
23 
24 
25  @param mac= to contain the name of the calling macro
26  @param msg= message to be returned
27  @param iftrue= supply a condition under which the macro should be executed.
28  @param errds= (work.mp_abort_errds) There is no clean way to end a process
29  within a %include called within a macro. Furthermore, there is no way to
30  test if a macro is called within a %include. To handle this particular
31  scenario, the %include should be switched for the mp_include.sas macro.
32  This provides an indicator that we are running a macro within a \%include
33  (`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
34  values (msg, mac).
35  We can then run an abort cancel FILE to stop the include running, and pass
36  the dataset back to the calling program to run a regular \%mp_abort().
37  The dataset will contain the following fields:
38  @li iftrue (1=1)
39  @li msg (the message)
40  @li mac (the mac param)
41 
42  @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
43  an abort status.
44  Valid values:
45  @li REGULAR (default)
46  @li INCLUDE
47 
48  <h4> Related Macros </h4>
49  @li mp_include.sas
50 
51  @version 9.4
52  @author Allan Bowe
53  @cond
54 **/
55 
56 %macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
57  , errds=work.mp_abort_errds
58  , mode=REGULAR
59 )/*/STORE SOURCE*/;
60 
61  %global sysprocessmode sysprocessname;
62 
63  %if not(%eval(%unquote(&iftrue))) %then %return;
64 
65  %put NOTE: /// mp_abort macro executing //;
66  %if %length(&mac)>0 %then %put NOTE- called by &mac;
67  %put NOTE - &msg;
68 
69  %if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
70  %if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
71  data &errds;
72  iftrue='1=1';
73  length mac $100 msg $5000;
74  mac=symget('mac');
75  msg=symget('msg');
76  run;
77  data _null_;
78  abort cancel FILE;
79  run;
80  %return;
81  %end;
82  %end;
83 
84  /* Stored Process Server web app context */
85  %if %symexist(_metaperson)
86  or "&SYSPROCESSNAME "="Compute Server "
87  or &mode=INCLUDE
88  %then %do;
89  options obs=max replace nosyntaxcheck mprint;
90  %if &mode=INCLUDE %then %do;
91  %if %sysfunc(exist(&errds))=1 %then %do;
92  data _null_;
93  set &errds;
94  call symputx('iftrue',iftrue,'l');
95  call symputx('mac',mac,'l');
96  call symputx('msg',msg,'l');
97  putlog (_all_)(=);
98  run;
99  %if (&iftrue)=0 %then %return;
100  %end;
101  %else %do;
102  %put &sysmacroname: No include errors found;
103  %return;
104  %end;
105  %end;
106 
107  /* extract log errs / warns, if exist */
108  %local logloc logline;
109  %global logmsg; /* capture global messages */
110  %if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
111  %else %let logloc=%qsysfunc(getoption(LOG));
112  proc printto log=log;run;
113  %if %length(&logloc)>0 %then %do;
114  %let logline=0;
115  data _null_;
116  infile &logloc lrecl=5000;
117  input; putlog _infile_;
118  i=1;
119  retain logonce 0;
120  if (
121  _infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
122  ) and logonce=0 then
123  do;
124  call symputx('logline',_n_);
125  logonce+1;
126  end;
127  run;
128  /* capture log including lines BEFORE the err */
129  %if &logline>0 %then %do;
130  data _null_;
131  infile &logloc lrecl=5000;
132  input;
133  i=1;
134  stoploop=0;
135  if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
136  call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
137  input;
138  i+1;
139  stoploop=1;
140  end;
141  if stoploop=1 then stop;
142  run;
143  %end;
144  %end;
145 
146  %if %symexist(SYS_JES_JOB_URI) %then %do;
147  /* setup webout */
148  OPTIONS NOBOMFILE;
149  %if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
150  filename _webout temp lrecl=999999 mod;
151  %end;
152  %else %do;
153  filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
154  name="_webout.json" lrecl=999999 mod;
155  %end;
156  %end;
157 
158  /* send response in SASjs JSON format */
159  data _null_;
160  file _webout mod lrecl=32000 encoding='utf-8';
161  length msg $32767 ;
162  sasdatetime=datetime();
163  msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
164  /* escape the quotes */
165  msg=tranwrd(msg,'"','\"');
166  /* ditch the CRLFs as chrome complains */
167  msg=compress(msg,,'kw');
168  /* quote without quoting the quotes (which are escaped instead) */
169  msg=cats('"',msg,'"');
170  if symexist('_debug') then debug=quote(trim(symget('_debug')));
171  else debug='""';
172  put '>>weboutBEGIN<<';
173  put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
174  put ',"sasjsAbort" : [{';
175  put ' "MSG":' msg ;
176  put ' ,"MAC": "' "&mac" '"}]';
177  put ",""SYSUSERID"" : ""&sysuserid"" ";
178  put ',"_DEBUG":' debug ;
179  if symexist('_metauser') then do;
180  _METAUSER=quote(trim(symget('_METAUSER')));
181  put ",""_METAUSER"": " _METAUSER;
182  _METAPERSON=quote(trim(symget('_METAPERSON')));
183  put ',"_METAPERSON": ' _METAPERSON;
184  end;
185  if symexist('SYS_JES_JOB_URI') then do;
186  SYS_JES_JOB_URI=quote(trim(symget('SYS_JES_JOB_URI')));
187  put ',"SYS_JES_JOB_URI": ' SYS_JES_JOB_URI;
188  end;
189  _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
190  put ',"_PROGRAM" : ' _PROGRAM ;
191  put ",""SYSCC"" : ""&syscc"" ";
192  syserrortext=quote(trim(symget('syserrortext')));
193  put ",""SYSERRORTEXT"" : " syserrortext;
194  put ",""SYSHOSTNAME"" : ""&syshostname"" ";
195  put ",""SYSJOBID"" : ""&sysjobid"" ";
196  put ",""SYSSCPL"" : ""&sysscpl"" ";
197  put ",""SYSSITE"" : ""&syssite"" ";
198  sysvlong=quote(trim(symget('sysvlong')));
199  put ',"SYSVLONG" : ' sysvlong;
200  syswarningtext=quote(trim(symget('syswarningtext')));
201  put ",""SYSWARNINGTEXT"" : " syswarningtext;
202  put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
203  put "}" @;
204  put '>>weboutEND<<';
205  run;
206 
207  %put _all_;
208 
209  %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
210  data _null_;
211  putlog 'stpsrvset program err and syscc';
212  rc=stpsrvset('program error', 0);
213  call symputx("syscc",0,"g");
214  run;
215  /**
216  * endsas kills 9.4m3 deployments by orphaning multibridges.
217  * Abort variants are ungraceful (non zero return code)
218  * This approach lets SAS run silently until the end :-)
219  * Caution - fails when called within a %include within a macro
220  * Use mp_include() to handle this.
221  */
222  filename skip temp;
223  data _null_;
224  file skip;
225  put '%macro skip();';
226  comment '%mend skip; -> fix lint ';
227  put '%macro skippy();';
228  comment '%mend skippy; -> fix lint ';
229  run;
230  %inc skip;
231  %end;
232  %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
233  /* endsas kills the session making it harder to fetch results */
234  data _null_;
235  syswarningtext=symget('syswarningtext');
236  syserrortext=symget('syserrortext');
237  abort_msg=symget('msg');
238  syscc=symget('syscc');
239  sysuserid=symget('sysuserid');
240  iftrue=symget('iftrue');
241  put (_all_)(/=);
242  call symputx('syscc',0);
243  abort cancel nolist;
244  run;
245  %end;
246  %else %do;
247  %abort cancel;
248  %end;
249  %end;
250  %else %do;
251  %put _all_;
252  %abort cancel;
253  %end;
254 %mend mp_abort;
255 
256 /** @endcond */