Production Ready Macros for SAS Application Developers
https://github.com/sasjs/core
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_getplatform.sas
88 @li mf_existfileref.sas
89 @li ml_json.sas
90
91**/
92
93%macro mv_getjoblog(uri=0,outref=0
94 ,access_token_var=ACCESS_TOKEN
95 ,grant_type=sas_services
96 ,mdebug=0
97 );
98%local dbg;
99%if &mdebug=1 %then %do;
100 %put &sysmacroname entry vars:;
101 %put _local_;
102%end;
103%else %let dbg=*;
104
105%local oauth_bearer;
106%if &grant_type=detect %then %do;
107 %if %symexist(&access_token_var) %then %let grant_type=authorization_code;
108 %else %let grant_type=sas_services;
109%end;
110%if &grant_type=sas_services %then %do;
111 %let oauth_bearer=oauth_bearer=sas_services;
112 %let &access_token_var=;
113%end;
114
115%mp_abort(iftrue=(&grant_type ne authorization_code and &grant_type ne password
116 and &grant_type ne sas_services
117 )
118 ,mac=&sysmacroname
119 ,msg=%str(Invalid value for grant_type: &grant_type)
120)
121
122/* validation in datastep for better character safety */
123%local errmsg errflg;
124data _null_;
125 uri=symget('uri');
126 if length(uri)<12 then do;
127 call symputx('errflg',1);
128 call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
129 end;
130 if scan(uri,-1)='state' or scan(uri,1) ne 'jobExecution' then do;
131 call symputx('errflg',1);
132 call symputx('errmsg',
133 "URI should be in format /jobExecution/jobs/$$$$UUID$$$$"
134 !!" but is actually like:"!!uri,'l');
135 end;
136run;
137
138%mp_abort(iftrue=(&errflg=1)
139 ,mac=&sysmacroname
140 ,msg=%str(&errmsg)
141)
142
143%mp_abort(iftrue=(&outref=0)
144 ,mac=&sysmacroname
145 ,msg=%str(Output fileref should be provided)
146)
147
148%if %mf_existfileref(&outref) ne 1 %then %do;
149 filename &outref temp;
150%end;
151
152options noquotelenmax;
153%local base_uri; /* location of rest apis */
154%let base_uri=%mf_getplatform(VIYARESTAPI);
155
156/* prepare request*/
157%local fname1;
158%let fname1=%mf_getuniquefileref();
159proc http method='GET' out=&fname1 &oauth_bearer
160 url="&base_uri&uri";
161 headers
162 %if &grant_type=authorization_code %then %do;
163 "Authorization"="Bearer &&&access_token_var"
164 %end;
165 ;
166run;
167%if &mdebug=1 %then %do;
168 %put &sysmacroname: fetching log loc from &uri;
169 data _null_;infile &fname1;input;putlog _infile_;run;
170%end;
171%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201 %then
172%do;
173 data _null_;infile &fname1;input;putlog _infile_;run;
174 %mp_abort(mac=&sysmacroname
175 ,msg=%str(&SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
176 )
177%end;
178%local fname2 fname3 fpath1 fpath2 fpath3;
179%let fname2=%mf_getuniquefileref();
180%let fname3=%mf_getuniquefileref();
181%let fpath1=%sysfunc(pathname(&fname1));
182%let fpath2=%sysfunc(pathname(&fname2));
183%let fpath3=%sysfunc(pathname(&fname3));
184
185/* compile the lua JSON module */
186%ml_json()
187/* read using LUA - this allows the code to be of any length */
188data _null_;
189 file "&fpath3..lua";
190 put '
191 infile = io.open (sas.symget("fpath1"), "r")
192 outfile = io.open (sas.symget("fpath2"), "w")
193 io.input(infile)
194 local resp=json.decode(io.read())
195 local logloc=resp["logLocation"]
196 outfile:write(logloc)
197 io.close(infile)
198 io.close(outfile)
199 ';
200run;
201%inc "&fpath3..lua";
202/* get log path*/
203%let errflg=1;
204%let errmsg=No entry in &fname2 fileref;
205data _null_;
206 infile &fname2;
207 input;
208 uri=cats(_infile_);
209 if length(uri)<12 then do;
210 call symputx('errflg',1);
211 call symputx('errmsg',"URI is invalid (too short) - '&uri'",'l');
212 end;
213 else if (scan(uri,1,'/') ne 'compute' or scan(uri,2,'/') ne 'sessions')
214 and (scan(uri,1,'/') ne 'files' or scan(uri,2,'/') ne 'files')
215 then do;
216 call symputx('errflg',1);
217 call symputx('errmsg',
218 "URI should be in format /compute/sessions/$$$$UUID$$$$/jobs/$$$$UUID$$$$"
219 !!" or /files/files/$$$$UUID$$$$"
220 !!" but is actually like:"!!uri,'l');
221 end;
222 else do;
223 call symputx('errflg',0,'l');
224 call symputx('logloc',uri,'l');
225 end;
226run;
227
228%mp_abort(iftrue=(%str(&errflg)=1)
229 ,mac=&sysmacroname
230 ,msg=%str(&errmsg)
231)
232
233/* we have a log uri - now fetch the log */
234%&dbg.put &sysmacroname: querying &base_uri&logloc/content;
235proc http method='GET' out=&fname1 &oauth_bearer
236 url="&base_uri&logloc/content?limit=10000";
237 headers
238 %if &grant_type=authorization_code %then %do;
239 "Authorization"="Bearer &&&access_token_var"
240 %end;
241 ;
242run;
243
244%if &mdebug=1 %then %do;
245 %put &sysmacroname: fetching log content from &base_uri&logloc/content;
246 data _null_;infile &fname1;input;putlog _infile_;run;
247%end;
248
249%if &SYS_PROCHTTP_STATUS_CODE=400 %then %do;
250 /* fetch log from parent session */
251 %let logloc=%substr(&logloc,1,%index(&logloc,%str(/jobs/))-1);
252 %&dbg.put &sysmacroname: Now querying &base_uri&logloc/log/content;
253 proc http method='GET' out=&fname1 &oauth_bearer
254 url="&base_uri&logloc/log/content?limit=10000";
255 headers
256 %if &grant_type=authorization_code %then %do;
257 "Authorization"="Bearer &&&access_token_var"
258 %end;
259 ;
260 run;
261 %if &mdebug=1 %then %do;
262 %put &sysmacroname: fetching log content from &base_uri&logloc/log/content;
263 data _null_;infile &fname1;input;putlog _infile_;run;
264 %end;
265%end;
266
267%if &SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201
268%then %do;
269 %if &mdebug ne 1 %then %do; /* have already output above */
270 data _null_;infile &fname1;input;putlog _infile_;run;
271 %end;
272 %mp_abort(mac=&sysmacroname
273 ,msg=%str(logfetch: &SYS_PROCHTTP_STATUS_CODE &SYS_PROCHTTP_STATUS_PHRASE)
274 )
275%end;
276data _null_;
277 file "&fpath3..lua";
278 put '
279 infile = io.open (sas.symget("fpath1"), "r")
280 outfile = io.open (sas.symget("fpath2"), "w")
281 io.input(infile)
282 local resp=json.decode(io.read())
283 for i, v in pairs(resp["items"]) do
284 outfile:write(v.line,"\n")
285 end
286 io.close(infile)
287 io.close(outfile)
288 ';
289run;
290%inc "&fpath3..lua";
291
292/* write log out to the specified fileref */
293data _null_;
294 infile &fname2 end=last;
295 file &outref mod;
296 if _n_=1 then do;
297 put "/** SASJS Viya Job Log Extract start: &uri **/";
298 end;
299 input;
300 put _infile_;
301 %if &mdebug=1 %then %do;
302 putlog _infile_;
303 %end;
304 if last then do;
305 put "/** SASJS Viya Job Log Extract end: &uri **/";
306 end;
307run;
308
309%if &mdebug=0 %then %do;
310 filename &fname1 clear;
311 filename &fname2 clear;
312 filename &fname3 clear;
313%end;
314%else %do;
315 %put &sysmacroname exit vars:;
316 %put _local_;
317%end;
318%mend mv_getjoblog;
319
320
321