BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
Suzy_Cat
Pyrite | Level 9

Hi there,

I am trying to use the macro %checklogs as provided in paper below, but could not make it work, can anyone please help me out? 

Refer to https://support.sas.com/resources/papers/proceedings17/1173-2017.pdf

 

I have created some dummy logs with error messages and saved on the server.

After running the macro, there is no error message being picked up from the logs saved on the server.

Everything looks fine to me, just the column called line is empty for all obs where it supposed to pick up the lines from each log files.

 

i will attach a copy of the code together with the screenshot. 

Thanks for help in advance!

Capture_1.PNGCapture_2.PNG

 

 

 

 

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
Tom
Super User Tom
Super User

You should almost never be using the older MISSOVER option instead of the new TRUNCOVER option which was introduced only about 20 to 30 years ago.  Although since you also added the PAD option it probably doesn't matter.  But PAD only pads to the LRECL (logical record length) so it that was less than 1,000 bytes then the MISSOVER would still force everything to missing because you tried to read more bytes than existed on the line.

 

The $ informat removes leading spaces.  The $VARYING informat does not.  You could have more easily used the $CHAR informat instead.  Then you wouldn't need to know how long the line was.

 infile "&loc.&slash.&lg..log" truncover;
 input line $char1000.;

 

View solution in original post

11 REPLIES 11
Suzy_Cat
Pyrite | Level 9

/****************************************************************************************************************
CHECKLOGS MACRO

Refer to:
https://support.sas.com/resources/papers/proceedings17/1173-2017.pdf
Currently system: LIN X64
****************************************************************************************************************/



/* retrieve all the logs in the specified directory */
%macro checklogs(loc=/xxxxxx/xxx/build/xx-xx/log, /* location of where the log files are stored */
loc2=, /* location of where report is stored (optional) */
fnm=, /* which types of files to look at (optional) */
/* e.g., Tables – t_, Figures – f_, Listings – l_ */
/* separate types of files by delimiter indicated in*/
/* the delm macro parameter (e.g., t_@f_) */
delm=@,/* delimiter used to separate types of files (opt'l)*/
out= Check_report /* log report name (optional) */);

/* need to determine the environment in which this is executed */
/* syntax for some commands vary from environment to environment */
/* end macro call if environment not Windows or Linux/Unix */
%if &sysscp = WIN %then %do;
%let ppcmd = %str(dir);
%let slash = \;
%end;

%else %if &sysscp = LIN X64 %then %do;
%let ppcmd = %str(ls -l);
%let slash = /;
%end;

%else %do;
%put ENVIRONMENT NOT SPECIFIED;
abort abend;
%end;

/* if a filename is specified then build the where clause */
%if "&fnm" ne "" %then %do;
data _null_;
length fullwhr $2000.;
retain fullwhr;
/* read in each log file and check for undesired messages */
%let f = 1;
%let typ = %scan(&fnm, &f, "&delm");
/* loop through each type of filename to build the where clause */
/* embed &typ in double quotes in case filename has any special */
/* characters or spaces */
%do %while ("&typ" ne "");
partwhr = catt("index(flog, '", "&typ", "')");
fullwhr = catx(" or ", fullwhr, partwhr);
call symputx('fullwhr', fullwhr);
%let f = %eval(&f + 1);
%let typ = %scan(&fnm, &f, "&delm");
%end;
run;
%end;
/* need to build pipe directory statement as a macro var */
/* because the statement requires a series of single and */
/* double quotes - by building the directory statement */
/* this allows the user to determine the directory rather */
/* than it being hardcoded into the program */
/* macro var will be of the form:'dir "directory path" ' */
data _null_;
libnm = "&loc";
dirnm = catx(" ", "'", "&ppcmd", quote(libnm), "'");
call symputx('dirnm', dirnm);
run;

/* read in the contents of the directory containing the logs */
filename pdir pipe &dirnm lrecl=32727;
data logs (keep = flog fdat ftim filename numtok);
infile pdir truncover scanover;
input filename $char1000.;
length flog $25 fdat ftim $10;
/* keep only the logs */
if index(filename, ".log");
/* count the number of tokens (i.e., different parts of filename) */
/* if there are no spaces then there should be 5 tokens for WIN */
/* or 9 tokens for LIN X64 */
numtok = countw(filename,' ','q');
/* need to build the flog value based on number of tokens */
/* if there are spaces in the log name then need to grab */
/* each piece of the log name */
/* the first token that is retrieved will have '.log' and */
/* it needs to be removed by substituting a blank */
/* also need to parse out the date and time these are in */
/* specific spots within the filename so aren't based on */
/* number of tokens but will have different locations */
/* depending on environment - so parsing of each piece of */
/* information will be environment dependent */
/* note on the scan function a negative # scans from right*/
/* and a positive # scans from the left */
/*********** WINDOWS ENVIRONMENT ************/
/* the pipe will read in the information in */
/* the format of: date time am/pm size file */
/* e.g. 08/24/2015 09:08 PM 18,498 ae.log */
/* '08/24/2015' is first token from left */
/* 'ae.log' is first token from right */
%if &sysscp = WIN %then %do;
do j = 5 to numtok;
tlog = tranwrd(scan(filename, 4 - j, " "), ".log", "");
flog = catx(" ", tlog, flog);
end;
ftim = catx(" ", scan(filename, 2, " "), scan(filename, 3, " "));
fdat = put(input(scan(filename, 1, " "), mmddyy10.), date9.);
%end;
/***************************** UNIX ENVIRONMENT ******************************/
/* the pipe will read in the information in the format of: permissions, user,*/
/* system environment, file size, month, day, year or time, filename */
/* e.g. -rw-rw-r-- 1 userid sysenviron 42,341 Oct 22 2015 ad_adaapasi.log */
/* '-rw-rw-r--' is first token from left */
/* 'ad_adaapasi.log' is first token from right */
%else %if &sysscp = LIN X64 %then %do;
do j = 9 to numtok;
tlog = tranwrd(scan(filename, 8 - j, " "), ".log", "");
flog = catx(" ", tlog, flog);
end;
_ftim = scan(filename, 8, " ");
/* in Unix if year is current year then time stamp is displayed */
/* otherwise the year last modified is displayed */
/* so if no year is provided then default to today's year and if*/
/* no time is provided indicated 'N/A' */
if anypunct(_ftim) then do;
ftim = put(input(_ftim, time5.), timeampm8.);
yr = put(year(today()), Z4.);
end;
else do;
ftim = 'N/A';
yr = _ftim;
end;
fdat = cats(scan(filename, 7, " "), upcase(scan(filename, 6, " ")), yr);
%end;
run;
/* create a list of logs, dates, times and store in macro variables */
proc sql noprint;
select flog,
fdat,
ftim
into : currlogs separated by "&delm",
: currdats separated by " ",
: currtims separated by "@"
from logs
%if "&fnm" ne "" %then where &fullwhr; ; /* need to keep extra semicolon */
quit;

/* need to make sure the alllogs data set does not exist before getting into loop */
proc datasets;
delete alllogs;
quit;
/* read in each log file and check for undesired messages */
%let x = 1;
%let lg = %scan(&currlogs, &x, "&delm");
%let dt = %scan(&currdats, &x);
%let tm = %scan(&currtims, &x, '@');
/* loop through each log in the directory and look for undesirable messages */
/* embed &lg in double quotes in case filename has special characters/spaces*/
%do %while ("&lg" ne "");
/* read the log file into a SAS data set to parse the text */
data logck&x;
infile "&loc.&slash.&lg..log" missover pad;
input line $1000.;
/* keep only the records that had an undesirable message */
/* if index(upcase(line), "WARNING") or*/
/* index(upcase(line), "ERROR:") or*/
/* index(upcase(line), "UNINITIALIZED") or*/
/* index(upcase(line), "NOTE: MERGE") or*/
/* index(upcase(line), "MORE THAN ONE DATA SET WITH REPEATS OF BY") or*/
/* index(upcase(line), "VALUES HAVE BEEN CONVERTED") or*/
/* index(upcase(line), "MISSING VALUES WERE GENERATED AS A RESULT") or*/
/* index(upcase(line), "INVALID DATA") or*/
/* index(upcase(line), "INVALID NUMERIC DATA") or*/
/* index(upcase(line), "AT LEAST ONE W.D FORMAT TOO SMALL") or*/
/* index(upcase(line), "ORDERING BY AN ITEM THAT DOESN'T APPEAR IN") or*/
/* index(upcase(line), "OUTSIDE THE AXIS RANGE") or*/
/* index(upcase(line), "RETURNING PREMATURELY") or*/
/* index(upcase(line), "UNKNOWN MONTH FOR") or*/
/* index(upcase(line), "QUERY DATA") or*/
/* index(upcase(line), "??") or*/
/* index(upcase(line), "QUESTIONABLE");*/
/* create variables that will contain the log that is being scanned */
/* as well as the and date and time that the log file was created */
length lognm $25. logdt logtm $10.;
lognm = upcase("&lg");
logdt = "&dt";
logtm = "&tm";
/* create a dummy variable to create a column on the report that will allow */
/* users to enter a reason if the message is allowed */
logrs = ' ';
run;

/* because there are sometimes issues with SAS certificate */
/* there will be warnings in the logs that are expected */
/* these need to be removed */
data logck&x._2;
set logck&x.;
if index(upcase(line), 'UNABLE TO COPY SASUSER') or
index(upcase(line), 'BASE PRODUCT PRODUCT') or
index(upcase(line), 'EXPIRE WITHIN') or
(index(upcase(line), 'BASE SAS SOFTWARE') and
index(upcase(line), 'EXPIRING SOON')) or
index(upcase(line), 'UPCOMING EXPIRATION') or
index(upcase(line), 'SCHEDULED TO EXPIRE') or
index(upcase(line), 'SETINIT TO OBTAIN MORE INFO') then delete;
run;

/* determine the number of undesired messages were in the log */
data _null_;
if 0 then set logck&x._2 nobs=final;
call symputx('numobs',left(put(final, 8.)));
run;

/* if there is no undesired messages in log create a dummy record for report */
%if &numobs = 0 %then %do;
data logck&x._2;
length lognm $25. line $1000. logdt logtm $10.;
line = "No undesired messages. Log is clean.";
lognm = upcase("&lg");
logdt = "&dt";
logtm = "&tm";
/* create a dummy variable to create a column on the report that will allow */
/* users to enter a reason if the message is allowed */
logrs = ' ';
output;
run;
%end;

/* append all the results into one data set */
%if &x = 1 %then %do;
data alllogs;
set logck&x._2;
run;
%end;
%else %do;
proc append base=alllogs
new=logck&x._2;
run;
%end;
%let x = %eval(&x + 1);
%let lg = %scan(&currlogs, &x, "&delm");
%let dt = %scan(&currdats, &x);
%let tm = %scan(&currtims, &x, '@');
%end;

/* since a list of files can be provided then the files may not be in order */
proc sort data=alllogs presorted;
by lognm line;
run;
/* if the name of the output file is not specified then default to the name */
%if "&out" = "" %then %do;
%let out=all_checklogs;
%end;

/* if the name of the output file is not specified then default to the name */
%if "&loc2" = "" %then %do;
data _null_;
call symputx("loc2", "&loc");
run;
%end;

/* create the report */
ods listing close;

options orientation=landscape;
ods rtf file="&loc2.&slash.&out..rtf";
proc report data=alllogs ls=140 ps=43 spacing=1 missing nowindows headline;
column lognm logdt logtm line logrs;
define lognm / order style(column)=[width=12%] "Log Name";
define logdt / display style(column)=[width=12%] "Log Date";
define logtm / display style(column)=[width=12%] "Log Time";
define line / display style(column)=[width=30%] flow "Log Message";
define logrs / display style(column)=[width=20%] flow "Reason Message is Allowed";
/* force a blank line after each file */
compute after lognm;
line " ";
endcomp;
run;
ods rtf close;

ods listing;
%mend checklogs;


%checklogs;
Amir
PROC Star

Hi @Suzy_Cat,

 

Not sure if I'm missing something, but the code you've posted has the line:

 

/*	index(upcase(line), "ERROR:") or*/

which is clearly commented out. Have you tried the code without this line commented out and make it something like:

 

if index(upcase(line), "ERROR:");

 

 

 

Kind regards,

Amir.

Shmuel
Garnet | Level 18

Just to expend @Amir 's answer:

 

Pay attention to the bold line - code taken from the macro:

data logck&x;
infile "&loc.&slash.&lg..log" missover pad;
input line $1000.;
/* keep only the records that had an undesirable message */
/* if index(upcase(line), "WARNING") or*/
/* index(upcase(line), "ERROR:") or*/
/* index(upcase(line), "UNINITIALIZED") or*/
/* index(upcase(line), "NOTE: MERGE") or*/

Suzy_Cat
Pyrite | Level 9
Hi Shmuel,

Thanks for your note.

the bold line is commented out as i want to see what have been picked up in the data logck&x, and have found the variable line are showing as empty for all obs. that is the reason why this macro yet to be fixed to flag those error message.

I have also tried to use variations for those key word, i.e. " index(upcase(line), "ERROR") or index(upcase(line), "ERROR:"), none of them working... since the line has nothing in it
Tom
Super User Tom
Super User

@Amir wrote:

Hi @Suzy_Cat,

 

Not sure if I'm missing something, but the code you've posted has the line:

 

/*	index(upcase(line), "ERROR:") or*/

which is clearly commented out. Have you tried the code without this line commented out and make it something like:

 

if index(upcase(line), "ERROR:");

 

 

 

Kind regards,

Amir.


Note that test will NOT work right.

It will NOT find all of the ERROR lines in a SAS log.  SAS will sometimes place an error number between the word ERROR and the colon.

ERROR 76-322: Syntax error, statement will be ignored

Also that test will get false positives when lines of code happen to have the text ERROR: in them

if missing(age) then put 'ERROR: Age is missing.';

SAS will always put the ERROR/WARNING/NOTE in the first column of the line.

So a better test is:

if line =: 'ERROR' and index(line,':') ....
ballardw
Super User

I agree with @Tom on the issue about user written code using "ERROR:" and "WARNING:" elements. I have some projects where I have to constantly check data supplied by my sources for content and have such code in many programs that read external data. I use the keywords that way for the same reason the SAS designers do: I can find the problem bits in the log with the highlighted text.

Suzy_Cat
Pyrite | Level 9

Hi Amir,

 

Thanks for helping.

I have commented it out to check what is in the input file and have discovered the reason the error message not being picked by running the macro is due to input lines are all empty, i.e. no log lines are input into the file to do the check.

Cheers

 

 

Suzy_Cat
Pyrite | Level 9

Hi,

 

Thank you all for the kind help:)

 

I have manipulated the data and can confirm the macro works the way it suppose to be.

 

The issue lined up with the input code where there is no text from any log being read to data logck&x;

 

Example:

data LOGCK3_1 reads all lines from the log which contains an error message "ERROR ..." for testing, but all obs read in are showing missing value. 

I then used below code to test next part and confirmed LOGCK3_1A is functional with the code "if index(upcase(line), 'ERROR 76-322:') or".

 

data LOGCK3_1;
set LOGCK3(obs=5);
line="ERROR 76-322: Syntax error, statement will be ignored";
run;


data LOGCK3_1A;
set LOGCK3_1;
if index(upcase(line), 'ERROR 76-322:') or
index(upcase(line), '') then delete;
run;

 

When i insert a line with the error message, data logck&x._2 will be 

 

Capture_1.PNGCapture_2.PNG

 

hope some ideas on how to read in the log info into the  data logck&x properly :_)

Suzy_Cat
Pyrite | Level 9

Hi All,

 

Thanks for all your help.

 

Now i have changed the code 

 

from:

 data logck&x;
 infile "&loc.&slash.&lg..log" missover pad;
 input line $1000.;

 

to:

data logck&x;
infile "&loc.&slash.&lg..log" missover pad length=len;
input @1 line & $varying200. len;

it works perfectly fine --- log file lines are now properly read in to file logck&x.

 

Although I don't understand why the previous code not working (all lines are empty) and why the new code works(reads the logs into the file).

 

Would appreciate if anyone can explain in details.

 

 

Tom
Super User Tom
Super User

You should almost never be using the older MISSOVER option instead of the new TRUNCOVER option which was introduced only about 20 to 30 years ago.  Although since you also added the PAD option it probably doesn't matter.  But PAD only pads to the LRECL (logical record length) so it that was less than 1,000 bytes then the MISSOVER would still force everything to missing because you tried to read more bytes than existed on the line.

 

The $ informat removes leading spaces.  The $VARYING informat does not.  You could have more easily used the $CHAR informat instead.  Then you wouldn't need to know how long the line was.

 infile "&loc.&slash.&lg..log" truncover;
 input line $char1000.;

 

Suzy_Cat
Pyrite | Level 9
Hi Tom, that is super helpful. Thank you very much for the explanation. I have done a bit test on that and it helped me to fully understand these bits. Legend!

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!
How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 11 replies
  • 2702 views
  • 5 likes
  • 5 in conversation