ここでは、オープンソースのプロキシである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
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に取り込む機会はほとんどないかもしれません。そのため、いざ必要になったときに思い出しにくい可能性を考え、メモしておくことにしました。