はじめに

ここでは、オープンソースのプロキシであるSquidのログをRのデータフレームに取り込む方法を記載します。今回はサンプルを「SecRepo」から借りてきました。

サンプル

ログは160万行ほどあります。先頭の5行を見てみましょう。それぞれの列の意味は、たとえばWebsenseのヘルプページを参照してください。

1157689312.049   5006 10.105.21.199 TCP_MISS/200 19763 CONNECT login.yahoo.com:443 badeyek DIRECT/209.73.177.115 -
1157689320.327   2864 10.105.21.199 TCP_MISS/200 10182 GET http://www.goonernews.com/ badeyek DIRECT/207.58.145.61 text/html
1157689320.343   1357 10.105.21.199 TCP_REFRESH_HIT/304 214 GET http://www.goonernews.com/styles.css badeyek DIRECT/207.58.145.61 -
1157689321.315      1 10.105.21.199 TCP_HIT/200 1464 GET http://www.goonernews.com/styles.css badeyek NONE/- text/css
1157689322.780   1464 10.105.21.199 TCP_HIT/200 5626 GET http://www.google-analytics.com/urchin.js badeyek NONE/- text/javascript

read_log()を使うために

tidyverseに含まれるreadrパッケージは、Common Log FormatやCombined Log Formatを扱えるread_log()という関数を備えています。しかし、上のサンプルログを取り込もうとすると、列数が合わないというWarningが表示されます。なぜでしょうか?

このサンプル、一見すると空白区切りであるものの、2列目だけは固定長になっている点に気づかされます。awkであれば連続した空白文字を1つの区切りとして扱ってくれるので、意識することはありません。しかしread_log()関数は、空白文字の数だけ列があると解釈してしまうのです。

よって、まずread_lines()で文字列ベクトルとして読み込んでから、連続した空白を1つの空白に置き換える前処理を加えます。正規表現で文字列置換してもよいのですが、stringrパッケージstr_squish()関数が用意されていますので、それを使ったのが下の例です。

library(tidyverse)

read_squid <- function(file){
  names <- c("timestamp", "time_elapsed", "ip_address", "result_codes", "bytes_sent",
             "http_method", "url", "username", "hierarchy", "mime_type")
  types <- "dicciccccc"
  data <- read_log(file = file, col_names = names, col_types = types)
  data$timestamp <- as.POSIXct(data$timestamp, origin = "1970-01-01", tz = "UTC")
  return(data)
}

squid_log <- read_lines("https://www.secrepo.com/squid/access.log.gz")
df_squid <- squid_log %>% 
  str_squish %>% 
  read_squid
## Warning: 1 parsing failure.
##   row col   expected     actual         file
## 82947  -- 10 columns 18 columns literal data
df_squid
## # A tibble: 1,643,519 x 10
##    timestamp           time_elapsed ip_address result_codes bytes_sent
##    <dttm>                     <int> <chr>      <chr>             <int>
##  1 2006-09-08 04:21:52         5006 10.105.21~ TCP_MISS/200      19763
##  2 2006-09-08 04:22:00         2864 10.105.21~ TCP_MISS/200      10182
##  3 2006-09-08 04:22:00         1357 10.105.21~ TCP_REFRESH~        214
##  4 2006-09-08 04:22:01            1 10.105.21~ TCP_HIT/200        1464
##  5 2006-09-08 04:22:02         1464 10.105.21~ TCP_HIT/200        5626
##  6 2006-09-08 04:22:03         3856 10.105.21~ TCP_MISS/200      30169
##  7 2006-09-08 04:22:04         1372 10.105.21~ TCP_MISS/200        399
##  8 2006-09-08 04:22:04         1457 10.105.21~ TCP_REFRESH~        215
##  9 2006-09-08 04:22:04         1465 10.105.21~ TCP_REFRESH~        215
## 10 2006-09-08 04:22:05         1452 10.105.21~ TCP_REFRESH~        214
## # ... with 1,643,509 more rows, and 5 more variables: http_method <chr>,
## #   url <chr>, username <chr>, hierarchy <chr>, mime_type <chr>

エラーハンドリング

1行(l.82497)パースに失敗していますね。前後の行を含めて中身を確認します。

squid_log[82496:82498]
## [1] "1157703543.011      1 10.105.41.238 TCP_DENIED/407 373 HEAD http://windowsupdate.microsoft.com/v5/redir/wuredir.cab? - NONE/- text/html"
## [2] "1157703543.016      1 10.105.41.238 TCP_DENIED/407 1784 GET http://windowsupdate.microsoft.com/v5/redir/wuredir.cab? - NONE/- text/html"
## [3] "1157703543.021      4 10.105.41.238 TCP_DENIED/407 1784 GET http://windowsupdate.microsoft.com/v5/redir/wuredir.cab? - NONE/- text/html"

データが壊れていることを予想していたのですが、まったく正常に見えます。これで読めないのは妙なので、そこだけ読み取ってデータフレームと結合させてみます。

squid_log[82497:82498] %>% 
  str_squish %>% 
  read_squid %>% 
  slice_head(n = 1) %>% 
  bind_rows(df_squid) %>% 
  arrange(timestamp)-> df_squid_a

df_squid_a
## # A tibble: 1,643,520 x 10
##    timestamp           time_elapsed ip_address result_codes bytes_sent
##    <dttm>                     <int> <chr>      <chr>             <int>
##  1 2002-10-19 20:30:37          221 210.8.79.~ TCP_MISS/302        380
##  2 2002-10-19 20:30:42         3140 210.8.79.~ TCP_MISS/200      34827
##  3 2002-10-19 20:31:20          499 210.8.79.~ TCP_MISS/200      12560
##  4 2002-10-19 20:31:21           13 210.8.79.~ TCP_IMS_HIT~        312
##  5 2002-10-19 20:31:21           16 210.8.79.~ TCP_HIT/200        6910
##  6 2002-10-19 20:31:23           14 210.8.79.~ TCP_IMS_HIT~        312
##  7 2002-10-19 20:31:23            1 210.8.79.~ TCP_IMS_HIT~        290
##  8 2002-10-19 20:31:27            9 210.8.79.~ TCP_IMS_HIT~        312
##  9 2002-10-19 20:31:28           17 210.8.79.~ TCP_IMS_HIT~        312
## 10 2002-10-19 20:31:28           10 210.8.79.~ TCP_IMS_HIT~        312
## # ... with 1,643,510 more rows, and 5 more variables: http_method <chr>,
## #   url <chr>, username <chr>, hierarchy <chr>, mime_type <chr>

普通にパースできてしまいましたね。なぜなんだろう。

後続処理

Result Codesのフィールドは、実際には分解しておくことが多いでしょう。Hierarchyも同様です。

df_squid_a %>% 
  separate(result_codes, into = c("squid_result", "http_response"), sep = "/") %>% 
  separate(hierarchy, into = c("hierarchy_string", "dest"), sep = "/") -> df_squid_b
  
df_squid_b  
## # A tibble: 1,643,520 x 12
##    timestamp           time_elapsed ip_address squid_result http_response
##    <dttm>                     <int> <chr>      <chr>        <chr>        
##  1 2002-10-19 20:30:37          221 210.8.79.~ TCP_MISS     302          
##  2 2002-10-19 20:30:42         3140 210.8.79.~ TCP_MISS     200          
##  3 2002-10-19 20:31:20          499 210.8.79.~ TCP_MISS     200          
##  4 2002-10-19 20:31:21           13 210.8.79.~ TCP_IMS_HIT  304          
##  5 2002-10-19 20:31:21           16 210.8.79.~ TCP_HIT      200          
##  6 2002-10-19 20:31:23           14 210.8.79.~ TCP_IMS_HIT  304          
##  7 2002-10-19 20:31:23            1 210.8.79.~ TCP_IMS_HIT  304          
##  8 2002-10-19 20:31:27            9 210.8.79.~ TCP_IMS_HIT  304          
##  9 2002-10-19 20:31:28           17 210.8.79.~ TCP_IMS_HIT  304          
## 10 2002-10-19 20:31:28           10 210.8.79.~ TCP_IMS_HIT  304          
## # ... with 1,643,510 more rows, and 7 more variables: bytes_sent <int>,
## #   http_method <chr>, url <chr>, username <chr>, hierarchy_string <chr>,
## #   dest <chr>, mime_type <chr>

おわりに

現場では、Squidのログを直接的にRに取り込む機会はほとんどないかもしれません。そのため、いざ必要になったときに思い出しにくい可能性を考え、メモしておくことにしました。