Author: Oliver Keyes
Date: 2016-01-23

# Introduction to webreadr

#### Oliver Keyes

#### 2016-01-23

# Reading web access logs

R, as a language, is used for analysing pretty much everything from genomic data to financial information. It’s also used to analyse website access logs, and R lacks a good framework for doing that; the URL decoder isn’t vectorised, the file readers don’t have convenient defaults, and good luck normalising IP addresses at scale.

Enter `webreadr`, which contains convenient wrappers and functions for reading, munging and formatting data from access logs and other sources of web request data.

## File reading

Base R has read.delim, which is convenient but much slower for file reading than Hadley’s new readr package. `webtools` defines a set of wrapper functions around readr’s `read_delim`, designed for common access log formats.

The most common historical log format is the Combined Log Format; this is used as one of the default formats for nginx and the Varnish caching system. `webtools` lets you read it in trivially with `read_combined`:

    library(webreadr)
    #read in an example file that comes with the webtools package
    data <- read_combined(system.file("extdata/combined_log.clf", package = "webreadr"))
    #And if we look at the format...
    str(data)

    ## Classes 'tbl_df', 'tbl' and 'data.frame':    12 obs. of  9 variables:
    ##  $ ip_address       : chr  "127.0.0.1" "123.123.123.123" "123.123.123.123" "123.123.123.123" ...
    ##  $ remote_user_ident: chr  NA NA NA NA ...
    ##  $ local_user_ident : chr  "frank" NA NA NA ...
    ##  $ timestamp        : POSIXct, format: "2000-10-10 20:55:36" "2000-04-26 04:23:48" ...
    ##  $ request          : chr  "GET /apache_pb.gif HTTP/1.0" "GET /pics/wpaper.gif HTTP/1.0" "GET /asctortf/ HTTP/1.0" "GET /pics/5star2000.gif HTTP/1.0" ...
    ##  $ status_code      : int  200 200 200 200 200 200 200 200 200 200 ...
    ##  $ bytes_sent       : int  2326 6248 8130 4005 1031 4282 36 10801 11179 887 ...
    ##  $ referer          : chr  "http://www.example.com/start.html" "http://www.jafsoft.com/asctortf/" "http://search.netscape.com/Computers/Data_Formats/Document/Text/RTF" "http://www.jafsoft.com/asctortf/" ...
    ##  $ user_agent       : chr  "Mozilla/4.08 [en] (Win98; I ;Nav)" "Mozilla/4.05 (Macintosh; I; PPC)" "Mozilla/4.05 (Macintosh; I; PPC)" "Mozilla/4.05 (Macintosh; I; PPC)" ...

As you can see, the types have been appropriately set, the date/times have been parsed, and sensible header names have been set. The same thing can be done with the Common Log Format, used by Apache default configurations and as one of the defaults for Squid caching servers, using `read_clf`. The other squid default format can be read with `read_squid`.

Amazon’s AWS files are also supported, with `read_aws`, which includes automatic field detection, and S3 bucket access logs can be read with `read_s3`.

## Splitting combined fields

One of the things you’ll notice about the example above is the “request” field - it contains not only the actual asset requested, but also the HTTP method used and the protocol used. That’s pretty inconvenient for people looking to do something productive with the data.

Normally you’d split each field out into a list, and then curse and recombine them into a data.frame and hope that doing so didn’t hit R’s memory limit during the “unlist” stage, and it’d take an absolute age. Or, you could just split them up directly into a data frame using `split_clf`:

    requests <- split_clf(data$request)
    str(requests)

    ## 'data.frame':    12 obs. of  3 variables:
    ##  $ method  : chr  "GET" "GET" "GET" "GET" ...
    ##  $ asset   : chr  "/apache_pb.gif" "/pics/wpaper.gif" "/asctortf/" "/pics/5star2000.gif" ...
    ##  $ protocol: chr  "HTTP/1.0" "HTTP/1.0" "HTTP/1.0" "HTTP/1.0" ...

This is faster than manual splitting-and-data.frame-ing, easier on the end user, and less likely to end in unexpected segfaults with large datasets. It also works on the `uri` within S3 access logs.

A similar function, `split_squid`, exists for the `status\_code` field in files read in with `read_squid`, which suffer from a similar problem.

## Other ideas

If you have ideas for other URL handlers that would make access log processing easier, the best approach is to either request it or add it!