Including SAS Logs in Documents
Doug Hemken
February 2017
Background
SAS is unusual software in that it generates two distinct output streams. In addition to the typical tables and graphs (in a variety of formats, including text and html), SAS echos your code and writes notes and messages in a separate log file. Most software directs all of this into just one document.
When using R Markdown and knitr to create simple SAS documentation, we can often skip showing the user the contents of the log file. The default output in knitr is the listing
(text) output.
However, from time to time it is useful to show the reader what they will find in a log file. How do we get knitr to include that?
There are several possibilities here, depending on just what we need to show the reader. Here I'll look at some ways to use SAS commands to save log output. In another document I'll look at how to rewrite knitr's "sas engine" to do this. The advantage of using SAS code is that a SAS programmer ought to know these commands anyway. The disadvantage is that if you need to show the reader a lot of logs, the setup will get repetitive.
For either approach, if you want to fine tune how much of the logs you show the reader, you need to know some R.
There are two options for using commands within SAS, PROC PRINTTO
or the PUT
and FILE
statements within a DATA
step. These use SAS to create separate files which can then be included in an R Markdown document.
Using PROC PRINTTO
There is a utility procedure in SAS, PROC PRINTTO
, that allows you to redirect the log, the listing output, or both.
```{r, engine='sas', engine.path=saspath, engine.opts=sasopts}
proc printto log="saslog.log" new;
proc means data=sashelp.class;
run;
```
The MEANS Procedure
Variable N Mean Std Dev Minimum Maximum
-------------------------------------------------------------------------
Age 19 13.3157895 1.4926722 11.0000000 16.0000000
Height 19 62.3368421 5.1270752 51.3000000 72.0000000
Weight 19 100.0263158 22.7739335 50.5000000 150.0000000
-------------------------------------------------------------------------
(The PROC PRINTTO
command could be hidden with the echo=-1
chunk option.)
The log is then shown by a separate code chunk.
```{r}
cat(readLines("saslog.log"), sep="\n")
unlink("saslog.log")
```
NOTE: PROCEDURE PRINTTO used (Total process time):
real time 0.00 seconds
cpu time 0.00 seconds
3 proc means data=sashelp.class;
4 run;
NOTE: There were 19 observations read from the data set SASHELP.CLASS.
NOTE: The PROCEDURE MEANS printed page 1.
NOTE: PROCEDURE MEANS used (Total process time):
real time 0.03 seconds
cpu time 0.03 seconds
(We could hide all the R code that reads and deletes the log file with the echo=FALSE
chunk option.)
We can hide parts of the log file itself, too, by including just selected lines from readLines()
. For simple manipulations, like removing several leading lines, the R code isn't too difficult:
```
cat(tail(readLines("saslog.log"), -5), sep="\n")
```
(Display the "tail" of the log from it's "head", minus the first 5 lines.)
DATA Step PUT Files
In additional to echoing code and giving notes and error messages, the SAS log is also used to capture data values from DATA steps. This is useful for debugging and even for very simple reports. Here we are using SAS's PUT and FILE statements (so technically we are sidestepping the SAS log, and there is no PROC PRINTTO required).
data class;
set sashelp.class;
file "sasput.txt";
if sex eq "M" then put name= age= sex=;
run;
And we can see the result as before:
cat(readLines("sasput.txt"), sep="\n")
Name=Alfred Age=14 Sex=M
Name=Henry Age=14 Sex=M
Name=James Age=12 Sex=M
Name=Jeffrey Age=13 Sex=M
Name=John Age=12 Sex=M
Name=Philip Age=16 Sex=M
Name=Robert Age=12 Sex=M
Name=Ronald Age=15 Sex=M
Name=Thomas Age=11 Sex=M
Name=William Age=15 Sex=M
Showing SAS Error Messages in Logs
For showing errors in logs, SAS error messages come in two varieties: DATA step errors, and PROC step errors.
DATA step errors are signaled by an internal _ERROR_ variable, and can be captured and shown to the reader by the PROC PRINTTO method.
data class;
set sashelp.class(obs=6);
if sex eq 1 then put name= age= sex=;
run;
And the log looks like this:
NOTE: Character values have been converted to numeric
values at the places given by: (Line):(Column).
5:6
NOTE: Invalid numeric data, Sex='M' , at line 5 column 6.
Name=Alfred Sex=M Age=14 Height=69 Weight=112.5 _ERROR_=1 _N_=1
NOTE: Invalid numeric data, Sex='F' , at line 5 column 6.
Name=Alice Sex=F Age=13 Height=56.5 Weight=84 _ERROR_=1 _N_=2
NOTE: Invalid numeric data, Sex='F' , at line 5 column 6.
Name=Barbara Sex=F Age=13 Height=65.3 Weight=98 _ERROR_=1 _N_=3
NOTE: Invalid numeric data, Sex='F' , at line 5 column 6.
Name=Carol Sex=F Age=14 Height=62.8 Weight=102.5 _ERROR_=1 _N_=4
NOTE: Invalid numeric data, Sex='M' , at line 5 column 6.
Name=Henry Sex=M Age=14 Height=63.5 Weight=102.5 _ERROR_=1 _N_=5
NOTE: Invalid numeric data, Sex='M' , at line 5 column 6.
Name=James Sex=M Age=12 Height=57.3 Weight=83 _ERROR_=1 _N_=6
NOTE: There were 6 observations read from the data set SASHELP.CLASS.
NOTE: The data set WORK.CLASS has 6 observations and 5 variables.
NOTE: DATA statement used (Total process time):
real time 0.01 seconds
cpu time 0.00 seconds
Although SAS notes that there are errors in processing individual observations, the DATA step still runs (a potential problem that any SAS programmer will find familiar).
To log PROC errors, you must use chunk option error=TRUE
. Here, SAS exits with an error code (although it still gives you output), and in the R world this is usually the signal to stop everything.
```{r, engine='sas', engine.path=saspath, engine.opts=sasopts, error=TRUE, echo=FALSE}
proc printto log="saslog.log" new;
proc means data=sashelp.class;
var gender; *ERROR HERE;
run;
```
Which gives us the log:
3 proc means data=sashelp.class;
4 var gender; *ERROR HERE;
ERROR: Variable GENDER not found.
5 run;
NOTE: The SAS System stopped processing this step because of errors.
NOTE: PROCEDURE MEANS used (Total process time):
real time 0.03 seconds
cpu time 0.00 seconds