现在的任务是尝试提取一些数据用于分析,报告和从特定于应用程序的日志文件进行建模。这些文件的挑战在于它们具有某种“半结构化”性质,以及不同大小(几千字节到几兆字节),并且结构无法改变。
在分析了相当多的这些日志文件后,可以确定四个主要类别的条目。
日志文件的结构化部分包含如下所示的列。
实施例
<Date> <Time> <RefNo> <Class> <Endpoint> <Description>
2016-12-20 04:04:04.004 12345678 [my.java.class1] (host1-001) start pattern #1
2016-12-20 04:04:04.014 12345678 [my.java.class1] (host1-001) pattern #1-2
2016-12-20 04:04:04.104 12345679 [my.java.class1] (host1-001) pattern #1-3
2016-12-20 04:04:05.004 12345680 [my.java.class2] (host1-001) pattern #1-4
2016-12-20 04:04:06.004 12345681 [my.java.class2] (host1-001) end pattern #1
2016-12-21 05:05:04.005 12389741 [my.java.class3] (host2-003) start pattern #2
2016-12-21 05:05:04.016 12390000 [my.java.class3] (host2-003) pattern #2-2
2016-12-21 05:05:04.108 12390001 [my.java.class4] (host2-003) pattern #2-3
2016-12-21 05:05:04.110 12389741 [my.java.class7] (host3-004) start pattern #3
2016-12-21 05:05:04.115 12390000 [my.java.class7] (host3-004) pattern #3-2
2016-12-21 05:05:05.007 12390002 [my.java.class5] (host2-003) pattern #2-4
2016-12-21 05:05:06.003 12390010 [my.java.class6] (host2-003) end pattern #2
2016-12-21 05:05:06.012 12390010 [my.java.class8] (host3-004) end pattern #3
每个 *状态序列* 以特定的 *开始模式* 开头,随后以相应的 *结尾 - 但不一定相邻 - 结束图案* 。每当关注特定的 *状态序列* 时,保持时间顺序至关重要,同时选择匹配(相同) * *端点
仔细查看各种结构化状态消息的 * description / test * 可以发现20个不同的 *模式序列* ,每个都需要合并并转换为包含一系列变量的单行观测。
预期的目标结构包括以下变量:
通过过滤条目并应用于相关模式,所需信息将从 *状态序列* 的相应行中提取。
由于上述每个 *模式序列* 彼此不同,并且可以在单个日志文件中多次出现,因此每个 *模式序列的特定函数* 已创建。
clnPattern <- function(dt=NULL, cn=NA) {
pat <- c(startPattern,
patternOne,
patternTwo,
patternThree,
patternFour,
endPattern)
patLen <- length(pat)
p <- NULL
allEP <- NULL
currEP <- NULL
rowIdx <- list()
wipeIdx <- NULL
seqOk <- NULL
i <- NULL
j <- NULL
tmpDT <- data.table(NULL)
retDT <- data.table(NULL)
# cleanse pattern entries
if (length(dt) > 0) {
# cycle over each endpoint separately, but only if at
# least the starting pattern can be found
if ((length(allEP <- unique(dt[, Endpoint])) > 0) &
(sum(grepl(pat[1], dt[, Description], ignore.case=TRUE, perl=TRUE)) > 0)) {
for (currEP in allEP) {
p <- 1
if (length(rowIdx[[p]] <- grep(pat[1],
dt[Endpoint == currEP,Description],ignore.case=TRUE, perl=TRUE)) > 0) {
# search for each subsequent pattern respectively
for (p in 2:patLen) {
rowIdx[[p]] <- grep(pat[p], dt[Endpoint == currEP,
Description],
ignore.case=TRUE, perl=TRUE)
} # for
# check for pattern sequence & cleanse
for (i in 1:length(rowIdx[[1]])) {
seqOk <- TRUE
wipeIdx <- dt[Endpoint == currEP, RowIdx][rowIdx[[1]][i]]
for (p in 2:patLen) {
seqOk <- (seqOk &
((rowIdx[[1]][i] + p - 1) %in% rowIdx[[p]]))
wipeIdx <- c(wipeIdx,
dt[Endpoint == currEP, RowIdx][rowIdx[[1]][i] + p - 1])
} # for
# pattern sequence found
if (seqOk) {
# consolidate sequence into one single row
tmpDT <- dt[Endpoint == currEP][rowIdx[[1]][i]]
setColClass(tmpDT)
vTimeStamp1=as.POSIXct(strptime(paste(
dt[Endpoint == currEP, Date][rowIdx[[1]][i]],
gsub(",", ".",
dt[Endpoint == currEP, Time][rowIdx[[1]][i]],fixed=TRUE)),
"%Y-%m-%d %H:%M:%OS"))
vTimeStamp2=as.POSIXct(strptime(paste(
dt[Endpoint == currEP, Date][rowIdx[[1]][i] + patLen - 1],
gsub(",", ".",
dt[Endpoint == currEP, Time][rowIdx[[1]][i] + patLen - 1],fixed=TRUE)),
"%Y-%m-%d %H:%M:%OS"))
tmpDT[1, `:=`(
TimeStamp=vTimeStamp1,
Var1=getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i]],
"Description", startPattern, posVar1),
Var2=getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i]],
"Description", startPattern, posVar2),
Var3=as.integer(getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i]],
"Description", startPattern, posVar3)),
Var4=getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i] + 1],
"Description", patternOne, posVar4),
Var5=getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i] + 2],
"Description", patternTwo, posVar5),
Var6=as.double(getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i] + 3],
"Description", patternThree, posVar6)),
Var7=getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i] + 4],
"Description", patternThree, posVar7),
Var8=as.double(getFromVariable(dt[Endpoint == currEP][rowIdx[[1]][i] + 5],
"Description", patternFour, posVar8)),
TmTotal=as.numeric(vTimeStamp2 - vTimeStamp1)
)]
# add to resulting data.table
retDT <- rbindlist(list(retDT, tmpDT))
# mark rows as processed
if (length(wipeIdx) > 0) {
dt[(RowIdx %in% wipeIdx),
`:=`(Date=NA, Time=NA, RefNo=NA, Class=NA, Description=NA)]
} # if
} # if
} # for
} # if
} # for
} # if
# re-shape resulting data.table
if ((length(retDT) > 0) & (length(cn) > 0)) {
retDT <- retDT[, .SD, .SDcols=cn]
} # if
} # if
# return resulting data.table
return(retDT)
} # clnPattern
在此函数中,调用另一个用户定义的函数 - * getFromVariable * - 来提取 * descripion / text * 的相关部分,并将它们存储起来远离相应的变量/列。
getFromVariable <- function(dt=NULL, varName="", srchPat="", getPat="") {
# local variables & constants
retVal <- NULL
# search für matching observations/rows, and extract parts from variable
if ((length(dt) > 0) & (nchar(varName) > 0) &
(nchar(srchPat) > 0) & (nchar(getPat) > 0)) {
# prepare if not a data.table
if (!is.data.table(dt)) {
dt <- as.data.table(dt)
setnames(dt, 1, varName)
} # if
# search & extract
if (varName %in% colnames(dt)) {
retVal <- trimws(gsub(srchPat, getPat,
dt[grep(srchPat,
dt[, varName, with=FALSE][[1]],
ignore.case=TRUE, perl=TRUE),
varName, with=FALSE][[1]],
ignore.case=TRUE, perl=TRUE))
} # if
} # if
# return result vector
return(retVal)
} # getFromVariable
此功能基本上执行两项任务:
这样提取的数据将作为 * data.table * 返回到父级。父级 - 处理完所有已定义的 *模式序列* 后,将从原始 * dt *中删除所有标记的( * NA * )行 data.table,以及将每个返回的特定于模式的data.tables组合成一个单独的,组合的(实际数据分析和建模的基础)。
缺乏对 * R * 和 * data.tables * 的更全面的洞察力,所采取的方法之一是促进 * for-loops * ,充分意识到应尽可能防止这样的车辆被部署。
处理几千字节的日志文件只需几秒钟,但只要进入Mbytes领域,处理时间就会急剧增加,最多可达几分钟。由于为一个日志文件处理了20种不同的模式,平均每个日志文件的平均总时间约为1小时。
我有兴趣学习的是我可以改进所采用的方法,尽可能选择,也许回到绘图板,以便更好地掌握 * R * 和 * data.tables * ,以及显着缩短处理时间,内存使用和性能。
- Sil68