-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathchecklog.sas
2072 lines (1669 loc) · 85.8 KB
/
checklog.sas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%macro CheckLog(log,ext=LOG,subdir=N,keyword=,exclude=,out=Log_issues,pm=Y,sound=N,relog=N,print=N,to=,cc=,logdef=LOG,dirext=N,shadow=Y,abort=N,test=)
/ des='Check the current log, an external log, or a directory of logs for issues';
/* Quick Argument Reference:
Input:
log - Item to check, either blank (current), a file, or a directory
ext= - Extension of files to identify for checking in a directory,
defaulted to LOG
subdir= - Search subdirectories within a directory (Yes, No) - uses
DirList macro (Windows only)
keyword= - Additional keyword(s) to search for
exclude= - A data set with messages (in a column called LogText) to
exclude
Reporting:
out= - Output data set name, defaulted to Log_issues
pm= - Pop-up message behavior control, either Yes, No, or E (when
issues found)
sound= - Sound behavior control, either Yes, No, or E (when issues
found)
relog= - Recreate the log in the current interactive SAS session
(Yes, No), defaulted to No
print= - Print the identified issues to the output, defaulted to No
to= - Email address to send a report to
cc= - Email address to copy a report to (TO= required first)
Operational:
logdef= - The setting for PROC PRINTTO log= when CheckLog turns the
log back on, defaulted to LOG, which is the "default"
dirext= - Use the full filename when checking a directory (Yes, No),
defaulted to No
shadow= - Copy the log before importing it (Yes, No), defaulted to Yes
abort= - Abort SAS if issues are detected (Yes, No), defaulted to No
test= - Set to 'test' to enter test mode
*/
/* Idea: Log_issues in directory mode contains all issues from all logs */
/********************************************************************************
BEGIN MACRO HEADER
********************************************************************************
Name: CheckLog
Author: Christopher A. Swenson
Contact: Chris@CSwenson.com
Created: 2009-08-28
Version: 3 (2012-04-02)
OS: Microsoft Windows, UNIX, OpenVMS, z/OS
SAS: 9.1.3+
Language: English
Sections: 0) Macro Header 4) Internal Programs
1) Log Settings 5) Determine Log(s) to Check
2) Macro Variables 6) Import and Check Log(s)
3) Check Arguments 7) Report Issues
Purpose: Check log(s) for issues, including lines with (e)rror, (w)arning,
(i)nvalid, or (u)ninitialized messages. Additionally, other
phrases associated with issues are checked, and specific phrases
are ignored. The macro can also be used with non-SAS logs, as
long as the log uses the same issue keywords.
Disclaimer: This program is provided "as-is". The user is responsible for
testing the software on their platform and selecting appropriate
recipients for email output. The user agrees that the author
will not, under any circumstances, be held accountable for any
damages of any type that result from using this software.
Usage: The output of the macro can provide information about where to
find issues in the log. When issues are identified, it is best
to open the original log and program to identify the source of
an issue. Otherwise, the Log data set contains all lines with the
Ignore and Found variables.
It is suggested that the user saves the log at about the same time
as using the CheckLog macro, especially if relying on the email
function for communication. The original log can then be reviewed
at a later time. This can be accomplished by using a DM statement
(see below) or copying the Work.log file from the work directory
to a permanent directory once CheckLog has run.
dm log "file 'C:\Example\Log_20100801.log' replace;" wpgm;
This macro can be set to a keyboard shortcut in SAS to check the
current log. It has worked most consistently on F5-F8 combinations.
I suggest setting it to SHF + F6, since F6 is the key for the
log window. Type the following text in the keyboard shortcut
definition: gsubmit '%checklog;'
Batch Mode: During batch mode, be sure to execute a DATA step or a procedure
before using CheckLog. Otherwise, the log will not be written out
when CheckLog tries to make a copy of it.
Enterprise: Using Enterprise Guide, the user must either use PROC PRINTTO to
export the log to a specific location or export the log as a step
in the project.
Log names: The names of the logs should be valid SAS names, so as to name
the data sets of the imported text.
Output: Data Sets:
Log - The imported log (named according to code
if reviewing a directory)
Log_Issues - The log filtered for identified issues.
Log_Keywords - The log with keywords, including ignored
messages. Only output during testing.
Log_Summary - A summmary of the issues in a log, generated
for the output email.
Logs_w_Issues - A list of all the logs checked in a directory
that contained issues.
Macro Variables:
logmsg - A message that conveys the status of what was checked
issues - A count of issues discovered
(C)aution: Do not directly use the issue words that the macro is looking for.
Mask these words by using parentheses, underscores, character
replacement, or some other technique. If a conditional (E)RROR
or (W)ARNING is necessary, use the %str() function. Here are some
examples:
* Comment: Check for (e)rrors before continuing. ;
* Sometimes w_arnings are generated here. ;
data _null_;
[other statements]
if problem=1 then put "%str(E)RROR: There are problems.";
run;
%put %str(E)RROR: This is a custom message.;
Arguments: All arguments are optional. If no options are specified, the
macro checks the current log and outputs issues to Log_issues.
Input:
log - Directory of logs or full pathname of a log. Do not use
single or double quotes. Note that the names of the
logs in a directory should be valid SAS names.
ext= - Extension to search for when looking for logs in a
directory of log, defaulted to LOG.
subdir= - Search subdirectories using the DirList macro. This
macro can only be used on Microsoft Windows.
N = Do not search subdirectories (default)
Y = Search subdirectories (requires DirList macro)
keyword=- A list of word or words to search for in the log text,
which identify issues beyond those already identified
in this macro. This can be used to add words for non-
SAS logs that may use additionally words to indicate
issues, for example, (C)AUTION or (A)LERT. This could
also be used to differentiate user-specified issues
from system-generated issues.
NOTE: The order of the words listed matters. The words
listed first will be searched for last, and will
overwrite any other words found prior. Thus, the
first word should have the highest priority. Of course,
the original keywords will overwrite user-specified
keywords.
exclude=- A data set containing issues to exclude. Include the
name of the variable after the data set name if it is
not LogText (e.g., work.exclusions list). This can be
used when non-issues are encountered that cannot be
filtered based on a simple rule (e.g., a data set name
unavoidably contains the word '(e)rror'). It is not
recommended that this feature be used to circumvent
code revision for easily-avoided issues. Note that data
set options can be applied with this argument, so a
master file with exclusions can be used and filtered.
Reporting:
out= - Name of the output issues data set (default: Log_issues).
Note: This is automatically set when checking a
directory of logs.
print= - Determines whether or not to print out the identified
issues to the output window:
N = No, do not print to the output window (default)
Y = Yes, print to the output window
pm= - Determines whether or not the pop-up message is used:
N = No, never display a pop-up message
Y = Yes, always display a pop-up message (default)
E = (E)rror only pop-up message
sound= - Determines whether or not a sound is played:
N = No, never play a sound (default)
Y = Yes, always play a sound
E = (E)rror only sound
to= - Email address(es) to send the results to.
Note: Email support must be set up in SAS to use
this function.
cc= - Email address(es) to carbon copy the results to.
Note: CC will not work without the TO argument.
relog= - Overwrites the current log with the imported log, after
the CheckLog outputs notes regarding the status of the
log. Best used for testing and reviewing logs.
N = Do not overwrite the current log
Y = Overwrite the current log with the imported log
E = (E)rror only overwrite
Operational:
dirext= - When checking a directory of logs, it adds the directory
and extension to the log name.
N = No, do not add directory and extension
Y = Yes, add directory and extension
shadow= - Make a copy of the log before checking it, useful when
the program is still running and generating the log.
N = No, do not make a copy of the log (default)
Y = Yes, make a copy of the log
abort= - Determines whether or not the following program is
aborted. This is useful to halt a long program and
(a)lert the user that there are issues.
N = No, do not abort program when issues occur (default)
Y = Yes, abort program when issues occur
(C)AUTION: This option may have undesirable results.
Please use it with care and thorough testing. In version
9.1.3, it has been identified that when the abort option
is specified and used in a program, then the CheckLog
macro is used from a keyboard shortcut, SAS crashes.
There is no known workaround and it appears to be a bug
in SAS 9.1.3.
MACROS: For use in macros, it is much more stable to
use the following statement to end the program
immediately after using the CheckLog macro:
%if &ISSUES>0 %then %return;
test= - Set to 'test' (i.e., test=test) to test CheckLog
without turning off the log within the macro.
Additionally, the data set log_keywords is output
for troubleshooting.
Issues: The following statements are considered issues. Note that the
statements have been broken up to avoid detecting them within
this program.
Keywords:
- (E)RROR (macro variable: iss1)
- (W)ARNING (macro variable: iss2)
- (I)NVALID (macro variable: iss3)
- (U)NINITIALIZED (macro variable: iss4)
Conversion:
- CHARACTER VALUES HAVE BEEN CON-VERTED TO NUMERIC VALUES
- NUMERIC VALUES HAVE BEEN CON-VERTED TO CHARACTER VALUES
- VARIABLES IN THE INPUT DATA SET WILL BE CON-VERTED TO
Format:
- AT LEAST ONE W.D FOR-MAT
Graph:
- OUTSIDE THE AX-IS RANGE
Input:
- LOST CA-RD (input/infile data step)
- NEW LI-NE WHEN INPUT
- ONE OR MORE LINES WERE TRUNC-ATED (PROC IMPORT issue)
Math:
- DIVISION BY ZE-RO DETECTED
- MATHEMATICAL OPER-ATIONS COULD NOT BE PERFORMED
- MIS-SING VALUES WERE GENERATED
Merge:
- ME-RGE STATEMENT HAS MORE THAN ONE DATA SET WITH RE-PEATS
OF BY VALUES
SQL:
- THE EXECUTION OF THIS QUERY INVOLVES PERFORMING ONE OR
MORE CAR-TESIAN PRODUCT JOINS THAT CAN NOT BE OPTIMIZED.
- THE QUERY REQUIRES RE-MERGING SUMMARY STATISTICS BACK WITH
THE ORIGINAL DATA
Syntax:
- A CASE EXPRE-SSION HAS NO ELSE CLAUSE
- THE MEANING OF AN IDENT-IFIER AFTER A QUOTED STRING MAY CHANGE
Additional phrases for review are listed below.
- "NO TOOLS DEFINED"
- "UNABLE TO ACCESS SPECIFIED PRINTER DRIVER"
- "UNABLE TO FIND THE PRINTER NAME"
- "HAS 0 OBSERVATIONS" (Note: This could be intended.)
- "INPUT DATA SET IS EMPTY" (Note: This could be intended.)
- "MULTIPLE LENGTH" (I think this is usually an (e)rror.)
- "A MISSING EQUAL SIGN HAS BEEN INSERTED"
- "A GROUP BY CLAUSE HAS BEEN DISCARDED"
- "DUPLICATE BY VARIABLE(S) SPECIFIED"
- "DUPLICATION OF BY VARIABLE" (Note: See phrase above.)
Check the following pages:
- http://tinyurl.com/3wrvozg (sas.com)
- http://tinyurl.com/3qe2ugs (sas.com)
- http://tinyurl.com/3bn7h3n (sas.com)
Ignored: The following statements are ignored. These statements are either
common or are not always issues. For example, issues generated
from libname statements may or may not affect the current code.
These issues are ignored since they will present other issues if
necessary for the current code. Note that the statements have been
broken up to avoid detecting them within this program.
Variable messages:
- _(E)RROR_=0
- IF _(E)RROR_
- SET (E)RROR DETECTION MACRO
- SET THE (E)RROR DETECTION MACRO
SAS messages:
- THE MACRO... COMPLETED COMPILATION WITHOUT (E)RRORS
- YOUR SYS-TEM IS SCHEDULED TO EXPIRE ON
- (E)RROR(S) PRINTED ON PAGE
- (U)nable to copy SASUSER registry to WORK registry. Because of this,
- you will not see (r)egistry customizations during this session.
Library messages:
- (E)RROR IN THE LIBNAME STATEMENT
- ONLY AVAILABLE TO USERS WITH RE-STRICTED SESSION PRIVILEGE
- THE AC-COUNT IS LOCKED
- UN-ABLE TO CLEAR OR RE-ASSIGN THE LIBRARY
- UNABLE TO COPY SAS-USER REGISTRY TO WORK REGISTRY
- USER DOES NOT HAVE APP-ROPRIATE AUTHORIZATION LEVEL FOR
References
-----------------------------------------------------------------------------
Augustine, Aaron. (2010). SAS® Code Validation: L.E.T.O Method [PDF].
http://support.sas.com/resources/papers/proceedings10/083-2010.pdf
Foley, Malachy J. (2005). MERGING vs. JOINING: Comparing the DATA Step with
SQL [PDF].
http://support.sas.com/resources/papers/proceedings09/036-2009.pdf
Kuligowski, Andrew T. (2005). In Search of the LOST CARD [PDF].
http://www2.sas.com/proceedings/sugi30/058-30.pdf
Slaughter, Susan J. & Delwiche, Lora D. (1997). (E)rrors, (W)arnings, and
Notes (Oh My): A Practical Guide to Debugging SAS Programs [PDF].
http://www2.sas.com/proceedings/sugi22/BEGTUTOR/PAPER68.PDF
Revisions
-----------------------------------------------------------------------------
Date Author Comments
---------- ------ --------
2010 CAS Revisions in 2010 were archived on 2012-03-22, but can be
found online at http://goo.gl/3Kg1i.
2011-04-15 CAS Added new argument for the extension to search for when
searching a directory for logs, defaulted to LOG.
2011-04-25 CAS Recalled that COUNTW is not available in SAS 9.1.3, so
I created a sub-macro of the same name to use to
dynamically choose the COUNTW function or a sub-process
that does the same thing.
2011-04-25 CAS Added additional information to the output email in case
the user needs to re-run the CheckLog macro in SAS.
2011-05-12 CAS Changed the categories of some of the issue statements and
updated documentation above.
2011-05-16 CAS Added issue regarding the meaning of and identifier after
quoted text. See Samples section of sas.cswenson.com for
an example.
2011-05-16 CAS Added excluded phrase regarding SAS license expiration.
This has nothing to do with the successful functioning
of the code.
2011-05-16 CAS Added exclusion for EXT= option when outputting the line
regarding replicating the CheckLog macro in the output
email.
2011-05-16 CAS Added exclusion for batch job message, which can be
misleading, especially if a user uses the - after the
issue word to hide if from being output in the log.
2011-05-17 CAS Added a new output macro variable called ISSUES, which
contains the number of issues detected in the log, 1 when
an issue occurs with using the macro (e.g., (i)nvalid
argument), or 0 when there are no issues. This can help
when writing dynamic code to continue or end depending
on whether issues are detected. Updated the documentation
above, using '%if &ISSUES>0 %then %return;' instead for
aborting macro programs.
2011-06-01 CAS Added exclusion for default OUT= option for email.
2011-06-29 CAS Revised how test and summary mode are handled in the macro
logic in the section where the various output data sets
are created.
2011-06-29 CAS Added new argument to add the directory and extension to
the output when checking a directory of logs, with the
default set to No. Set DIREXT=Y to add the directory and
extension to the LOGS_W_ISSUES data set.
2011-07-14 CAS Added the DIRDLM variable to switch the directory
delimiter in other operating systems.
2011-07-14 CAS Added a step to identify the type of log argument
specified (blank, log, directory).
2011-07-14 CAS Replaced SAS_EXECFILEPATH with LOG option during batch
mode to identify the location of the log.
2011-07-14 CAS Removed the PIPE method of accessing the directory in
favor of native SAS code.
2011-07-15 CAS Changed the way issues are handled after importing a log
to output more specific issue messages.
2011-07-15 CAS Excluded SYMBOLGEN messages from the checks.
2011-07-15 CAS Added checks for valid output names during directory mode.
2011-07-15 CAS Added RELOG argument to overwrite the current log with the
imported log. Also added it to the email sent.
2011-07-15 CAS Modified reporting in directory mode to use the original
filename.
2011-07-15 CAS Removed the upper-case function on the LogText variable
and instead applied the function during comparison. The
result is that the log looks like the original.
2011-07-18 CAS Removed delimiters on the import in favor of simpler code.
2011-07-19 CAS Added format for filename to avoid truncation.
2011-07-20 CAS Upgraded the macro to version 3. (Note: Version 2 was when
I added email capabilities and some other features.)
2011-08-08 CAS Corrected operating system macro variable reference.
2011-08-09 CAS Added exclusion for (e)rorrs= option and for (e)rror and
(w)arning options in PROC COMPARE.
2011-08-15 CAS Set the log exclusion messages to upper case.
2011-09-01 CAS Added another value for the RELOG argument: E - to
overwrite the log with the imported log only when issues
occur.
2011-09-01 CAS Moved section that drops tables to the first section in
order to avoid confusion when tables already exist. Also
revised RELOG section to only execute when the LOG data
set exists.
2011-09-09 CAS Added a new argument, SHADOW=, which forces CheckLog to
make a copy of the log before importing it. This helps
when another instance of SAS is still generating the log,
and it would cause problems to directly import it.
2011-09-09 CAS Added "else" to if/then section for processing messages.
This should help speed up really big logs.
2011-10-07 CAS Modified section that checks what type of log is specified
to work with shadow mode (copying the file first).
2011-10-19 CAS Moved the code to drop pre-existing tables again due to
macro variable issues. This code appears to be difficult to
find a place for.
2011-11-23 CAS Modified behavior during directory mode to ignore blank
files. This allows the search to continue with other files.
2012-02-24 CAS Set SHADOW=Y by default. This should result in better
processing at all times and not interfere when it really
is not necessary.
2012-03-20 CAS Fixed issues with long filenames by outputting the master
without PROC PRINTTO. Additionally, incorporated the
DirList macro to add subdirectories.
2012-03-22 CAS Removed the COUNTW internal macro program, since it appears
that SAS 9.1 did have the COUNTW function, so it is not
necessary.
2012-03-28 CAS Converted DM POSTMESSAGE statements to use the MessageBox
API for Windows users. This should help use the CheckLog
macro in Enterprise Guide.
2012-03-30 CAS Added an exclusion for the (w)arning received when opening
multiple SAS sessions without locking the user profile.
2012-04-05 CAS Minor edits including adding labels to the output data
sets, adding some documentation, and moving some local
variable declarations. Removed the deletion of the
Log_summary data set.
2012-05-17 CAS Added check for a note regarding values outside of a graph
axis range.
2014-07-08 CAS Moved some pieces of code from general execution to
Windows-only execution.
2014-07-08 CAS Added PRINT=Y/N argument to print log to output window.
2014-07-09 CAS Added reference to DIRDLM instead of hard-coded slash.
2014-07-09 CAS Varied DM command based on OS.
2014-07-10 CAS Added LOGDEF= option to specify default log argument in
PROC PRINTTO, defautled to "log".
2015-08-05 CAS Added check to verify log copy.
2015-08-05 CAS Added trim when outputting log for multiple logs.
2015-08-06 CAS Added a combined data set with all log issues during multi
log review (directory).
2015-08-19 CAS Checked whether data set exists before printing.
2016-04-11 CAS Removed a blank argument causing issues with SYSFUNC.
2016-05-12 CAS Added a process to add email headers when using commands
for the EMAILSYS option in Unix-like environments.
Tested with 'mail' and 'sendmail' commands.
2017-09-28 CAS Added a new phrase regarding conversion based on user
feedback. Thanks Benoit!
YYYY-MM-DD III Please use this format and insert new entries above.
*********************************************************************************
END MACRO HEADER
*********************************************************************************/
/* The following line tricks the editor into thinking the macro has ended,
allowing readers to see the rest of the code in the usual SAS colors. */
/*%macro dummy; %mend dummy;*/
/********************************************************************************
Section 1: Log Settings
********************************************************************************/
/* Turn off mprint, notes, and the log */
%if %lowcase(&TEST) ne test %then %do;
/* Obtain option and temporarily turn off */
%local user_mprint user_notes;
%let user_mprint=%sysfunc(getoption(mprint));
%let user_notes=%sysfunc(getoption(notes));
option nomprint;
option nonotes;
/* Completely turn off these options */
option nomlogic nosymbolgen nomacrogen;
/* Temporarily turn the log off */
filename junk dummy;
proc printto log=junk; run;
%end;
/********************************************************************************
Section 2: Macro Variables
********************************************************************************/
/* Manage macro variable scope */
%global
logmsg /* Log issues message */
issues /* Count of issues in log */
;
%local
_EFIERR_ /* (E)RROR detection macro (indirect reference) */
ervar /* (E)RROR variable for import (indirect reference) */
key_wrd /* Variable used to store scan of KEYWORD argument */
iss1 iss2 iss3 iss4 /* Issue word variables */
log_init /* Initial log argument */
logcount /* Count of logs to check*/
lognumber /* Index variable used to check multiple logs */
logsummary /* Flag to summarize multiple logs */
msg /* Pop-up message */
multi /* Multiple-log flag */
outobs /* Record count of output */
i /* Iteration variable */
excludeds /* Data set that contains phrases to exclude */
excludevar /* Variable in exclude data set that contains phrases */
excludecnt /* Variable counting the messages to exclude */
dirdlm /* Variable to set the directory delimiter */
filedir /* Retains type of log specified */
copylog /* Path and filename of the copied log (shadow mode) */
api /* Libref in which the Windows API is stored */
logpath /* Path used for multiple logs */
emailheader /* A flag to indicate whether to add email headers */
;
/* Set defaults */
/* Note: These variables are modified when a directory is submitted */
%let multi=0;
%let logcount=1;
%let logsummary=0;
%let outobs=1;
/* Set initial log argument */
%let log_init=%superq(LOG);
/* List of words to check */
/* Note: These are listed here to avoid writing them to the log upon inclusion
of the macro program. */
%let iss1=%str(E)RROR;
%let iss2=%str(W)ARNING;
%let iss3=%str(I)NVALID;
%let iss4=%str(U)NINITIALIZED;
/* Determine the directory delimiter */
/* Windows */
%if %upcase("&SYSSCP")="WIN" %then %let dirdlm=\;
/* z/OS and OpenVMS */
%else %if %upcase("&SYSSCP")="OS" or %upcase("&SYSSCPL")="OPENVMS" %then %let dirdlm=.;
/* Unix */
%else %if %index(*AIX*HP-UX*LINUX*OSF1*SUNOS*,%upcase(*&SYSSCPL*))>0
%then %let dirdlm=/;
/* Unknown */
%else %do;
%let msg=Unable to determine the operating system. Please update the macro for your environment.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Log Type */
/* Determine if argument is a directory or a file */
/* REVISION 2011-07-14 CAS: Added the following section to check log type */
/* REVISION 2011-07-18 CAS: Promoted section to Macro Variables */
/* REVISION 2011-10-07 CAS: Modified the process of checking the log type */
%if %superq(LOG)=%str() %then %let FILEDIR=BLANK;
%else %do;
%local fileref file id rc;
/* Check that the file or directory exists */
/* REVISION 2011-10-07 CAS: Added check to see if the file exists */
%if %sysfunc(fileexist(%superq(LOG)))=0 %then %do;
%let msg=The specified file/directory does not exist.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Attempt to assign a fileref */
%let fileref=fileref;
%let file=%sysfunc(filename(FILEREF, %superq(LOG)));
%if &FILE ne 0 %then %do;
%let msg=Filename association during check of directory/file status failed.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Attempt to open fileref as a directory */
%let id=%sysfunc(dopen(FILEREF));
%if &ID ne 0 %then %do;
%let rc=%sysfunc(dclose(&ID));
%let FILEDIR=DIR;
%end;
/* Otherwise, assume it is a file, especially since it exists and is not
a directory. */
%else %let FILEDIR=FILE;
%let file=%sysfunc(filename(FILEREF));
%symdel fileref file id rc / nowarn;
%end;
/* Set emailheader variable */
/* This is only relevant in Unix-like environments, where a script may be used */
/* The scripts (or binaries like mail/sendmail) do not appear to output To, */
/* CC, and Subject fields via the filename email statement. */
/* REVISION 2016-05-12 CAS: Added process for setting emailheader value */
%let emailheader = N;
%if %index(*AIX*HP-UX*LINUX*OSF1*SUNOS*,%upcase(*&SYSSCPL*))>0 %then %do;
%if %sysfunc(getoption(emailsys)) ne SMTP %then %do;
%let emailheader = Y;
%put NOTE: Setting emailheader to Y.;
%end;
%end;
/********************************************************************************
Section 3: Check Arguments
********************************************************************************/
/* Drop the log and output if they exist */
/* REVISION 2011-09-01 CAS: Moved to first section */
/* REVISION 2011-09-09 CAS: Set to be mode dependent for some tables */
/* REVISION 2011-10-19 CAS: Moved the drop code again to avoid macro variable issues */
proc sql;
%if %sysfunc(exist(log)) %then %do;
drop table log;
%end;
%if %sysfunc(exist(&OUT)) %then %do;
drop table &OUT;
%end;
%if %sysfunc(exist(log_keywords)) and %lowcase(&TEST)=test %then %do;
drop table log_keywords;
%end;
%if %sysfunc(exist(logs_w_issues)) and %superq(LOG)=%str() %then %do;
drop table logs_w_issues;
%end;
%if %sysfunc(exist(log_summary)) %then %do;
drop table log_summary;
%end;
%if %sysfunc(exist(_log_status)) %then %do;
drop table _log_status;
%end;
quit;
/* Check for the existence of the tables in case they could not be dropped */
%if %sysfunc(exist(log)) or %sysfunc(exist(&OUT)) %then %do;
%let msg=The log and/or &OUT table could not be dropped. Check whether either table is open.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Modify arguments */
/* Note: These are modified to avoid using upcase and lowcase repeatedly */
/* REVISION 2011-04-15 CAS: Added a new argument: ext */
/* REVISION 2011-09-09 CAS: Added a new argument: shadow */
/* REVISION 2012-03-20 CAS: Added a new argument: subdir */
/* REVISION 2016-12-03 CAS: Added a new argument: print */
%let pm=%substr(%upcase(&PM), 1, 1);
%let sound=%substr(%upcase(&SOUND), 1, 1);
%let keyword=%upcase(%superq(KEYWORD));
%let abort=%substr(%upcase(&ABORT), 1, 1);
%let test=%lowcase(&TEST);
%let ext=%upcase(&EXT);
%let dirext=%substr(%upcase(&DIREXT), 1, 1);
%let relog=%substr(%upcase(&RELOG), 1, 1);
%let shadow=%substr(%upcase(&SHADOW), 1, 1);
%let subdir=%substr(%upcase(&SUBDIR), 1, 1);
%let print=%substr(%upcase(&PRINT), 1, 1);
/* OUT */
%if %index(*0*1*2*3*4*5*6*7*8*9*, *%substr(&OUT, 1, 1)*) ne 0 %then %do;
%let msg=%str(I)nvalid value for the OUT argument. Use a data set name beginning with a non-numeric character.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
%if %length(&OUT)>32 %then %do;
%let msg=%str(I)nvalid value for the OUT argument. Use a data set name less than 32 characters.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* PM */
/* Note: Checks for Y (Yes), N (No), and E ([E]rror only) */
%if "&PM" ne "" and %index(*Y*N*E*,*&PM*)=0 %then %do;
%let msg=%str(I)nvalid value for the PM argument. Use Y (Yes), N (No), or E (%str(E)rror only).;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* DIREXT */
/* Note: Checks for Y (Yes) and N (No) */
%if %index(*Y*N*,*&DIREXT*)=0 %then %do;
%let msg=%str(I)nvalid argument specified for DIREXT. Use Y (Yes) or N (No).;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* ABORT */
/* Note: Checks for Y (Yes) and N (No) */
%if "&ABORT" ne "" and %index(*Y*N*,*&ABORT*)=0 %then %do;
%let msg=%str(I)nvalid value for the ABORT argument. Use Y (Yes) or N (No).;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* RELOG */
/* Check for argument values in (Y N) */
/* REVISION 2011-08-18 CAS: Added the following check */
/* REVISION 2011-09-01 CAS: Added new value for RELOG and corrected check */
%if %index(*Y*N*E*,*&RELOG*)=0 %then %do;
%let msg=%str(I)nvalid argument specified for RELOG. Use Y (Yes), N (No), or E (%str(E)rror only).;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Shadow */
/* REVISION 2011-09-09 CAS: Added check for new argument (shadow) */
%if %index(*Y*N*,*&SHADOW*)=0 %then %do;
%let msg=%str(I)nvalid argument specified for SHADOW. Use Y (Yes) or N (No).;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Subdir */
/* REVISION 2012-03-20 CAS: Added check for new argument (subdir) */
/* Check for argument values in (Y N) */
%if %index(*Y*N*,*&SUBDIR*)=0 %then %do;
%let msg=%str(I)nvalid argument specified for SUBDIR. Use Y (Yes) or N (No).;
%let logmsg=%str(E)RROR: &MSG;
%goto exit;
%end;
/* EXCLUDE */
%if %superq(EXCLUDE) ne %str() %then %do;
/* Check for data set options by looking for parentheses */
%if "%sysfunc( compress( %superq(EXCLUDE), %str(%(), %str(k) ) )" ne "" %then %do;
/* Define length and location of last end parenthesis */
/* REVISION 2016-4-11 CAS: Removed a blank argument causing issues with SYSFUNC */
%local excludelen excludepar;
%let excludelen=%length(%superq(EXCLUDE));
%let excludepar=%sysfunc( findc( %superq(EXCLUDE), %str(%)), -&EXCLUDELEN ) );
%let excludeds=%substr(%superq(EXCLUDE), 1, &EXCLUDEPAR);
/* If the length of the argument is the same as the position of the last
parenthesis, then no variable is specified so set it to the default */
%if &EXCLUDELEN=&EXCLUDEPAR %then %let excludevar=LOGTEXT;
/* Otherwise, find the next word after the parenthesis */
%else %let excludevar=%substr(%superq(EXCLUDE), %eval(&EXCLUDEPAR+1), %eval(&EXCLUDELEN-&EXCLUDEPAR));
%end;
/* Otherwise, simply scan the argument for the parts */
%else %do;
%let excludeds=%scan(%superq(EXCLUDE), 1, %str( ));
%let excludevar=%scan(%superq(EXCLUDE), -1, %str( ));
%end;
/* Check if the variable is blank or the same, change to default */
%if "&EXCLUDEVAR"="" or %superq(EXCLUDEDS)=%superq(EXCLUDEVAR) %then %let excludevar=LOGTEXT;
%put ;
%put NOTE: Exclude Data Set: %superq(EXCLUDEDS);
%put NOTE: Exclude Variable: &EXCLUDEVAR;
%put ;
/* Check if the data set exists */
%if %sysfunc(exist( %scan(%superq(EXCLUDE), 1, %str( %()) ))=0 %then %do;
%let msg=The specified data set for the EXCLUDE argument does not exist.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Check if variable exists */
%local open varnum close;
%let open=%sysfunc(open(%scan(%superq(EXCLUDE), 1, %str( %())));
%let varnum=%sysfunc(varnum(&OPEN, &EXCLUDEVAR));
%let close=%sysfunc(close(&OPEN));
%if &VARNUM=0 %then %do;
%let msg=The specified variable &EXCLUDEVAR in the data set %scan(%superq(EXCLUDE), 1, %str( %()) does not exist.;
%let logmsg=%str(E)RROR: &MSG;
%let issues=1;
%goto exit;
%end;
/* Set variables */
/* REVISION 2011-08-15 CAS: Set exclusion messages to upper case */
%else %do;
data _null_;
set &EXCLUDEDS end=end;
if missing(&EXCLUDEVAR) then delete;
varname = compress('exclude' || put(_n_, 8.));
&EXCLUDEVAR = trim(upcase(&EXCLUDEVAR));
call symputx(varname, &EXCLUDEVAR, 'L');
if end then call symputx('excludecnt', put(_n_, 8.), 'L');
/*put varname= &EXCLUDEVAR=;*/
run;
%end;
%end;
/********************************************************************************
Section 4: Internal Programs
********************************************************************************/
/* CheckLog NOBS macro: Returns the number of records in a SAS table. */
%macro CLNOBS(dsn) / des='Copy of NOBS macro for CheckLog macro';
/* Define these variables locally */
%local dsid anobs rc;
/* Check if the table exists */
%if %eval(%sysfunc(exist(&DSN, %str(DATA))) + %sysfunc(exist(&DSN, %str(VIEW))))=0 %then %do;
%put %str(W)ARNING: %sysfunc(compbl(The &DSN data set does not exist)).;
%let num=;
%end;
%else %do;
/* Open the data set */
%let dsid=%sysfunc(open(&DSN));
/* Check to see if SAS can obtain the observations */
%let anobs=%sysfunc(attrn(&DSID, anobs));
%if &ANOBS=0 %then %do;
%put %str(W)ARNING: The macro NOBS is unable to obtain the number of observations from this engine (e.g., Oracle, Sybase).;
%end;
/* ATTRN will not work correctly against Sybase and Oracle tables */
%let num=%sysfunc(attrn(&DSID, nobs));
/* Close the data set */
%let rc=%sysfunc(close(&DSID));
%end;
/* Lack of semi-colon makes this value the output from this macro. */
&NUM
%mend CLNOBS;
/* REVISION 2016-05-12 CAS: Added clemailheader macro to add email headers */
%macro clemailheader() / des = 'Write To, CC, and Subject email headers';
%if &EMAILHEADER = N %then %do;
%return;
%end;
/* To field */
put "To: "
%do i=1 %to %sysfunc(countw(%superq(TO), %str( )));
%if &I ne 1 %then %do; ", " %end;
"%scan(%superq(TO), &I, %str( ))"
%end;
;
/* CC field */
%if %superq(CC) ne %str() %then %do;
put "CC: "
%do i=1 %to %sysfunc(countw(%superq(CC), %str( )));
%if &I ne 1 %then %do; ", " %end;
"%scan(%superq(CC), &I, %str( ))"
%end;
;
%end;
/* Subject */
%if &OUTOBS>0 %then %do;
put "Subject: CheckLog: Issues Detected";
%end;
%else %do;
put "Subject: CheckLog: No Issues Detected";
%end;
%mend clemailheader;
/* Message box API */
/* REVISION 2012-03-29 CAS: Added API for use in Windows */
%let api=sasuser;
%if %upcase("&SYSSCP")="WIN" %then %do;
/* This variable is used to execute the API later in the code */
%local msgbox;
%if %sysfunc(fileref(sascbtbl)) ne 0 %then %do;
filename sascbtbl catalog "&API..winapi.msgbox.source";
%end;
%if %sysfunc(cexist(&API..winapi.msgbox.source))=0 %then %do;
data _null_;
file sascbtbl;
put 'ROUTINE MessageBoxA' /
' MODULE=USER32 ' /
' MINARG=4 ' /
' MAXARG=4 ' /
' STACKPOP=called ' /
' RETURNS=short ; ' /
' ARG 1 input num format=pib4. byvalue;' /
' ARG 2 input char format=$cstr200.; ' /
' ARG 3 input char format=$cstr200.; ' /
' ARG 4 input num format=pib4. byvalue;'
;
run;
%end;
%end;
/********************************************************************************
Section 5: Determine Log(s) to Check
********************************************************************************/
/***** Section 5a: Current Log *****/
/* Save the current log in the work directory */
%if &FILEDIR=BLANK %then %do;
/* Obtain the work directory */
%let log=%sysfunc(pathname(work))&DIRDLM.Work.log;
%let log_init=Work.log;
/* Delete the log if it already exists */
%if %sysfunc(fileexist(%superq(LOG)))=1 %then %do;
filename log "%superq(LOG)";
%local fd;
%let fd=%sysfunc(fdelete(log));