Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
mv_getjoblog.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Extract the log from a completed SAS Viya Job
4 @details Extracts log from a Viya job and writes it out to a fileref.
5
6 To query the job, you need the URI. Sample code for achieving this
7 is provided below.
8
9 ## Example
10
11 %* First, compile the macros;
12 filename mc url
13 "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
14 %inc mc;
15
16 %* Next, create a job (in this case, a web service);
17 filename ft15f001 temp;
18 parmcards4;
19 data ;
20 rand=ranuni(0)*1000;
21 do x=1 to rand;
22 y=rand*4;
23 output;
24 end;
25 run;
26 proc sort data=&syslast
27 by descending y;
28 run;
29 ;;;;
30 %mv_createwebservice(path=/Public/temp,name=demo)
31
32 %* Execute it;
33 %mv_jobexecute(path=/Public/temp
34 ,name=demo
35 ,outds=work.info
36 )
37
38 %* Wait for it to finish;
39 data work.info;
40 set work.info;
41 where method='GET' and rel='state';
42 run;
43 %mv_jobwaitfor(ALL,inds=work.info,outds=work.jobstates)
44
45 %* and grab the uri;
46 data _null_;
47 set work.jobstates;
48 call symputx('uri',uri);
49 run;
50
51 %* Finally, fetch the log;
52 %mv_getjoblog(uri=&uri,outref=mylog)
53
54 This macro is used by the mv_jobwaitfor.sas macro, which is generally a more
55 convenient way to wait for the job to finish before fetching the log.
56
57 If the remote session calls `endsas` then it is not possible to get the log
58 from the provided uri, and so the log from the parent session is fetched
59 instead. This happens for a 400 response, eg below:
60
61 ErrorResponse[version=2,status=400,err=5113,id=,message=The session
62 requested is currently in a failed or stopped state.,detail=[path:
63 /compute/sessions/LONGURI-ses0006/jobs/LONGURI/log/content, traceId: 63
64 51aa617d01fd2b],remediation=Correct the errors in the session request,
65 and create a new session.,targetUri=<null>,errors=[],links=[]]
66
67 @param [in] access_token_var= The global macro variable to contain the access
68 token
69 @param [in] mdebug= (0) Set to 1 to enable DEBUG messages
70 @param [in] grant_type= valid values:
71 @li password
72 @li authorization_code
73 @li detect - will check if access_token exists, if not will use sas_services
74 if a SASStudioV session else authorization_code. Default option.
75 @li sas_services - will use oauth_bearer=sas_services.
76 @param [in] uri= The uri of the running job for which to fetch the status,
77 in the format `/jobExecution/jobs/$UUID` (unquoted).
78 @param [out] outref= The output fileref to which to APPEND the log (is always
79 appended).
80
81
82 @version VIYA V.03.04
83 @author Allan Bowe, source: https://github.com/sasjs/core
84
85 <h4> SAS Macros </h4>
86 @li mp_abort.sas
87 @li mf_existvar.sas
88 @li mf_getplatform.sas
89 @li mf_existfileref.sas
90 @li mf_getuniquefileref.sas
91 @li mf_getuniquelibref.sas
92
93**/
94
95%macro mv_getjoblog(uri=0,outref=0
96 ,access_token_var=ACCESS_TOKEN
97 ,grant_type=sas_services
98 ,mdebug=0
99 );
100%local dbg libref1 libref2 loglocation fname1 fname2 jobstate err_httpcode
101 err_msg abortmsg;
102%if &mdebug=1 %then %do;
103 %put &sysmacroname entry vars:;
104 %put _local_;
105%end;
106%else %let dbg=*;
107
108%local oauth_bearer;
109%if &grant_type=detect %then %do;
110 %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
111 %else %let grant_type=sas_services;
112%end;
113%if &grant_type=sas_services %then %do;
114 %let oauth_bearer=oauth_bearer=sas_services;
115 %let &access_token_var=;
116%end;
117
118%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
119 and &grant_type ne sas_services
120 )
121 ,mac=&sysmacroname
122 ,msg=%str(Invalid value for grant_type: &grant_type)
123)
124
125/* validation in datastep for better character safety */
126%local errmsg errflg;
127data _null_;
128 uri=symget('uri');
129 if length(uri)<12 then do;
130 call symputx('errflg',1);
131 call symputx('errmsg',"URI is too short - "!!uri,'l');
132 end;
133 if scan(uri,-1)='state' or scan(uri,1) ne 'jobExecution' then do;
134 call symputx('errflg',1);
135 call symputx('errmsg',
136 "URI should be in format /jobExecution/jobs/$$$$UUID$$$$"
137 !!" but is actually like:"!!uri,'l');
138 end;
139run;
140
141%mp_abort(iftrue=(&errflg=1)
142 ,mac=&sysmacroname
143 ,msg=%str(&errmsg)
144)
145
146%mp_abort(iftrue=(&outref=0)
147 ,mac=&sysmacroname
148 ,msg=%str(Output fileref should be provided)
149)
150
151%if %mf_existfileref(&outref) ne 1 %then %do;
152 filename &outref temp;
153%end;
154
155options noquotelenmax;
156%local base_uri; /* location of rest apis */
157%let base_uri=%mf_getplatform(VIYARESTAPI);
158
159/* prepare request*/
160%let fname1=%mf_getuniquefileref();
161%let fname2=%mf_getuniquefileref();
162proc http method='GET' out=&fname1 &oauth_bearer
163 url="&base_uri&uri";
164 headers
165 %if &grant_type=authorization_code %then %do;
166 "Authorization"="Bearer &&&access_token_var"
167 %end;
168 ;
169run;
170%if &mdebug=1 %then %do;
171 %put &sysmacroname: fetching log loc from &uri;
172 data _null_;infile &fname1;input;putlog _infile_;run;
173%end;
174%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
175%do;
176 data _null_;infile &fname1;input;putlog _infile_;run;
177 %mp_abort(mac=&sysmacroname
178 ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
179 )
180%end;
181
182%let libref1=%mf_getuniquelibref();
183libname &libref1 JSON fileref=&fname1;
184data _null_;
185 set &libref1..root;
186 call symputx('loglocation',loglocation,'l');
187 %if %mf_existvar(&libref1..root,state) %then %do;
188 call symputx('jobstate',state,'l');
189 %end;
190 %else %do;
191 call symputx('jobstate','unknown','l');
192 %end;
193run;
194/* If root had zero observations, jobstate was never set - fall back to
195 "unknown" so the abort message below reads cleanly. */
196%if %str(&jobstate)= %then %let jobstate=unknown;
197
198/* A real loglocation is always a /files/files/<uuid> URI (48+ chars), so
199 length<2 unambiguously means the job has no log (empty or SAS missing
200 value) - the job likely failed before a compute session was created
201 (e.g. 403 on session creation). Read the error details from the job
202 response so the actual failure reason is reported instead of the
203 opaque "URI is too short" message. */
204%if &mdebug=1 %then %do;
205 %put &sysmacroname: jobstate=[&jobstate] loglocation=[&loglocation];
206%end;
207%if %length(&loglocation)<2 %then %do;
208 %let err_httpcode=;
209 %let err_msg=;
210 %if %sysfunc(exist(&libref1..error)) %then %do;
211 data _null_;
212 set &libref1..error;
213 call symputx('err_httpcode',httpStatusCode,'l');
214 call symputx('err_msg',message,'l');
215 putlog "&sysmacroname: Job &jobstate - error " httpStatusCode ": " message;
216 stop;
217 run;
218 %end;
219 /* Build the abort message so it reads cleanly whether or not error
220 details were available (err_msg stays empty if the table is absent
221 or has zero observations). Include the job URI so the full JSON
222 response can be fetched directly via a GET to &base_uri&uri. */
223 %let abortmsg=Job &jobstate, no log available. GET &uri;
224 %if %length(&err_msg)>0 %then %let abortmsg=Job &jobstate, no log available. Error &err_httpcode: &err_msg. GET &uri;
225 /* A canceled job does not always provide a loglocation (eg a job
226 that was aborted by design, or canceled by the server before a
227 compute session was created). Aborting here would terminate the
228 calling program, so instead record the reason in the outref and
229 return - the caller can decide how to handle the missing log. */
230 %if &jobstate=canceled %then %do;
231 %put &sysmacroname: &abortmsg;
232 data _null_;
233 file &outref mod;
234 put "&sysmacroname: &abortmsg";
235 run;
236 %return;
237 %end;
238 %mp_abort(iftrue=(1=1)
239 ,mac=&sysmacroname
240 ,msg=%str(&abortmsg)
241 )
242%end;
243
244/* validate log path*/
245%let errflg=1;
246%let errmsg=No loglocation entry in &fname1 fileref;
247data _null_;
248 uri=symget('loglocation');
249 if length(uri)<12 then do;
250 call symputx('errflg',1);
251 call symputx('errmsg',"URI is too short - "!!uri,'l');
252 end;
253 else if (scan(uri,1,'/') ne 'compute' or scan(uri,2,'/') ne 'sessions')
254 and (scan(uri,1,'/') ne 'files' or scan(uri,2,'/') ne 'files')
255 then do;
256 call symputx('errflg',1);
257 call symputx('errmsg',
258 "URI should be in format /compute/sessions/$$$$UUID$$$$/jobs/$$$$UUID$$$$"
259 !!" or /files/files/$$$$UUID$$$$"
260 !!" but is actually like:"!!uri,'l');
261 end;
262 else do;
263 call symputx('errflg',0,'l');
264 call symputx('logloc',uri,'l');
265 end;
266run;
267
268%mp_abort(iftrue=(%str(&errflg)=1)
269 ,mac=&sysmacroname
270 ,msg=%str(&errmsg)
271)
272
273/* we have a log uri - now fetch the log */
274%&dbg.put &sysmacroname: querying &base_uri&logloc/content;
275proc http method='GET' out=&fname2 &oauth_bearer
276 url="&base_uri&logloc/content?limit=10000";
277 headers
278 %if &grant_type=authorization_code %then %do;
279 "Authorization"="Bearer &&&access_token_var"
280 %end;
281 ;
282run;
283
284%if &mdebug=1 %then %do;
285 %put &sysmacroname: fetching log content from &base_uri&logloc/content;
286 data _null_;infile &fname2;input;putlog _infile_;run;
287%end;
288
289%if &SYS_PROCHTTP_STATUS_CODE=400 %then %do;
290 /* fetch log from parent session */
291 %let logloc=%substr(&logloc,1,%index(&logloc,%str(/jobs/))-1);
292 %&dbg.put &sysmacroname: Now querying &base_uri&logloc/log/content;
293 proc http method='GET' out=&fname2 &oauth_bearer
294 url="&base_uri&logloc/log/content?limit=10000";
295 headers
296 %if &grant_type=authorization_code %then %do;
297 "Authorization"="Bearer &&&access_token_var"
298 %end;
299 ;
300 run;
301 %if &mdebug=1 %then %do;
302 %put &sysmacroname: fetching log content from &base_uri&logloc/log/content;
303 data _null_;infile &fname2;input;putlog _infile_;run;
304 %end;
305%end;
306
307%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201
308%then %do;
309 %if &mdebug ne 1 %then %do; /* have already output above */
310 data _null_;infile &fname2;input;putlog _infile_;run;
311 %end;
312 %mp_abort(mac=&sysmacroname
313 ,msg=%str(logfetch: &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
314 )
315%end;
316
317%let libref2=%mf_getuniquelibref();
318libname &libref2 JSON fileref=&fname2;
319data _null_;
320 file &outref mod;
321 if _n_=1 then do;
322 put "/** SASJS Viya Job Log Extract start: &uri **/";
323 end;
324 set &libref2..items end=last;
325 %if &mdebug=1 %then %do;
326 putlog line;
327 %end;
328 put line;
329 if last then do;
330 put "/** SASJS Viya Job Log Extract end: &uri **/";
331 end;
332run;
333
334%if &mdebug=0 %then %do;
335 filename &fname1 clear;
336 filename &fname2 clear;
337 libname &libref1 clear;
338 libname &libref2 clear;
339%end;
340%else %do;
341 %put &sysmacroname exit vars:;
342 %put _local_;
343%end;
344%mend mv_getjoblog;
345
346
347