通过分隔符解析文本文件并使用R输出多个文件

Parsing a text file by a delimiter and outputting multiple files with R

我正在尝试将我的服务器日志分解成多个文件,这样我就可以 运行 对它们进行一些度量。我有这个 cronjob,它在每个月的第一天向我的服务器日志添加一个字符串和一个时间戳,该字符串看起来像这样的“每月断点,2020 年 3 月 1 日”。这个想法是我可以通过这个行分隔符将这个大的服务器日志文件分解成多个日志文件,然后 运行 每个文件的一些指标。我正在尝试编写一个脚本来为我创建这些输出文件,但我正在为此苦苦挣扎。到目前为止,我可以读取文件并循环遍历行并找到分隔符,但我不确定解决此类问题的最佳方法,也许我不应该使用 R,还有更简单的方法吗?

# server log
serverLog <- "server-out.log"

# Process File 
conn <- file( serverLog ,open="r")
linn <-readLines(conn)
for (i in 1:length(linn)){
  print( linn[i] )
  test <- grepl(  "Monthly", linn[i] )
  # print( paste("test: ", test, sep="" ) )
  if( test ) {
    print( "Found Monthly Breakpoint")
  }
}
close(conn)

# Example of the server-out.log file 

[0mGET /notifications [36m304 [0m9.439 ms - -[0m
[0mGET /user/status [36m304 [0m2.137 ms - -[0m
[0mGET /user/status [36m304 [0m5.675 ms - -[0m
[0mPOST /user/login [32m200 [0m19.960 ms - 30[0m
[0mGET /user/status [36m304 [0m9.518 ms - -[0m
[0mGET /user/status [32m200 [0m2.364 ms - 16[0m
[0mGET /user/status [36m304 [0m1.396 ms - -[0m
[0mGET /user/status [36m304 [0m1.087 ms - -[0m
[0mPOST /user/login [32m200 [0m300.214 ms - 30[0m
[0mGET /user/status [36m304 [0m4.374 ms - -[0m
[0mGET /localUser [32m200 [0m2.260 ms - 1045[0m

 Monthly Breakpoint, March 1 2020

[0mGET /user/status [32m200 [0m5.284 ms - 16[0m
[0mGET /user/status [36m304 [0m2.101 ms - -[0m
[0mGET /users [32m200 [0m2.387 ms - 36[0m
[0mGET /notifications [32m200 [0m30.395 ms - 2624[0m
[0mGET /user/status [36m304 [0m2.172 ms - -[0m
[0mGET /user/status [36m304 [0m1.424 ms - -[0m
[0mGET /user/status [36m304 [0m2.074 ms - -[0m
[0mGET /user/status [36m304 [0m0.920 ms - -[0m
[0mGET /users [36m304 [0m2.471 ms - -[0m
[0mGET /notifications [36m304 [0m8.416 ms - -[0m
[0mGET /user/status [36m304 [0m1.757 ms - -[0m
[0mGET /user/status [36m304 [0m1.114 ms - -[0m
[0mGET /favicon.ico [33m404 [0m2.218 ms - 150[0m
[0mGET /user/status [36m304 [0m2.003 ms - -[0m
[0mPOST /user/login [32m200 [0m175.473 ms - 30[0m
[0mGET /user/status [36m304 [0m3.893 ms - -[0m
csplit -z server-out.min /Monthly/ '{*}'

csplit: illegal option -- z
usage: csplit [-ks] [-f prefix] [-n number] file args ...

可能使用一些 UNIX 命令最 "natural"、awkcsplitwork in that regard

反正我有一个 R 解决方案给你。我不使用 readLines(),而是从 read.delim() 开始。这样您就可以从 data.frame 开始,然后可以使用任何工具进行 data.frame 操作。我最熟悉 tidyverse 命令,这就是为什么我会在这里使用它们。

# Process File 
library(tidyverse)
log_df <- read.delim(serverLog, header = FALSE) %>% 
  mutate(breakpoint = grepl("Monthly Breakpoint", V1),
         breakdate = ifelse(breakpoint, gsub("Monthly Breakpoint, ", "", V1), NA)) %>% 
  fill(breakdate) %>% 
  mutate(breakdate = ifelse(is.na(breakdate), "before first breakdate", breakdate)) %>% 
  filter(!breakpoint) %>% 
  select(-breakpoint)

# Save Files
log_df %>% 
  split(.$breakdate) %>% 
  lapply(function(x) write.csv(x, file = paste(x$breakdate[1], ".csv"), row.names = FALSE))

不过,我不知道将数据存储在单独的文件中是否是此处选择的最佳工作流程。为什么不只将数据保留在 R 中,将行拆分成几列并按月对分析进行分组。

编辑:这就是拆分成列和一些分析的样子。

# split / separate() into columns

log_sep_df <- 
  log_df %>%
  as_tibble() %>% 
  mutate(V1 = substr(V1, 2, nchar(as.character(V1)))) %>% 
  separate(V1, into = c(paste0("var", 1:10)), sep = "\[|  | ") %>% 
  mutate(http = ifelse(grepl("POST", var1), "POST", "GET")) %>% 
  mutate(var1 = gsub("POST|GET", "", var1))

# get month labels
library(lubridate)
log_sep_df <- 
log_sep_df %>% 
  mutate(date = as.Date(mdy(log_sep_df$breakdate)))

date_before_first_breakpoint <- min(log_sep_df$date, na.rm = TRUE) - 10

log_sep_df <- 
log_sep_df %>% 
  mutate(date = if_else(is.na(date), 
                        date_before_first_breakpoint, 
                        date),
         month = month(date, label = TRUE))


# grouped visiualization of logs
ggplot(log_sep_df, aes(http)) +
  geom_bar() +
  facet_wrap(~month)

这不是最优雅的答案,但它满足了我的需要。我会尝试其他答案,将数据保存在我的 R 环境中是个好主意,这样我就可以 运行 我的所有指标而无需读取不必要的文件。谢谢@Till

#~~~~~~~~~~~~~~~~~~~~~~#
#~~ Parse Server Log ~~#
#~~~~~~~~~~~~~~~~~~~~~~#

# Read File 
serverLog <- "server-out.min"
conn <- file( serverLog ,open="r")
linn <-readLines(conn)
num <- 1

# Loop through File 
for (i in 1:length(linn)){
  # print( linn[i] )

  # current output file
  file <- paste( "server-log-", num, sep = "")
  # write to file
  write(linn[i], file=file, append=TRUE)

  # Check for Monthly Delimiter, update num
  test <- grepl(  "Monthly", linn[i] )
  if( test ) {
    print( "Found Monthly Breakpoint")
    num <- num+1
  }
}
close(conn)

如果您想在 R 中完成,可以使用 data.table 解决方案以提高效率:

library(data.table)
DT <- fread("out.log", sep = NULL, header = FALSE)[V1 != ""]
DT[, Idx := rleid(grepl("Monthly Breakpoint", V1))]
DT <- DT[!grepl("Monthly Breakpoint", V1)]
DT.list <- split(DT, DT$Idx) ## or just operate by Idx