我正在编写一个日志文件解析器,并编写了一些测试代码以在C中解析它。
要解析的字符串如下所示:
s := `10.0.0.1 Jan 11 2014 10:00:00 hello`
在C中,解析这个很容易。首先,我找到指向字符串中日期的指针,然后使用strptime()尽可能多地使用它。这是可能的,因为strptime()将在调用后返回字符串中的位置。
最终我决定使用Golang而不是C,但在移植代码时我遇到了一些问题。据我所知,time.Parse()没有给我任何选项来解析现有的字符串(虽然这可以通过切片解决)或指示它在解析日期时消耗了多少原始字符串从字符串中。
Go中是否有任何优雅的方式我可以直接解析字符串中的日期/时间,而无需先将日期时间提取到精确的切片中,例如通过返回解析后提取的字符数?
答案 0 :(得分:3)
不幸的是,time.Parse
方法无法告诉您解析了多少个字符,因此我们需要调查其他优雅的解决方案。在您解析日志语句的示例中,正如@ rob74建议的那样使用正则表达式是一种相当优雅的策略。以下示例为简洁起见忽略了错误:
var r = regexp.MustCompile(`^((?:\d{1,3}\.){3}\d{1,3}) ([a-zA-Z]{3} \d{1,2} \d{4} \d{1,2}:\d{2}:\d{2}) (.*)`)
const longForm = "Jan 02 2006 15:04:05"
func parseRegex(s string) (ip, msg string, t time.Time) {
m := r.FindStringSubmatch(s)
t, _ = time.Parse(longForm, m[2])
ip, msg = m[1], m[3]
return ip, msg, t
}
基准测试显示上面的正则表达式比我机器上的@ rob74示例效率高两倍左右,每秒解析大约100,000行:
BenchmarkParseRegex 100000 17130 ns/op
BenchmarkParseRegexRob74 50000 32788 ns/op
但是,如果我们使用strings.SplitN
,我们可以保持解决方案的简短和高效。例如:
func parseSplit(s string) (ip, msg string, t time.Time) {
parts := strings.SplitN(s, " ", 6)
t, _ = time.Parse(longForm, strings.Join(parts[1:5], " "))
ip, msg = parts[0], parts[5]
return ip, msg, t
}
这会将字符串拆分为前5个空格,并将剩余的字符串(消息部分)放在最终的parts
切片元素中。这不是很优雅,因为我们依赖于日期格式中的空格数,但我们可以通过编程方式计算日期格式字符串中的空格,以获得更通用的解决方案。让我们看看这与我们的正则表达式解决方案相比如何:
BenchmarkParseRegex 100000 17130 ns/op
BenchmarkParseSplit 500000 3557 ns/op
事实证明,它相当不错。使用SplitN
比使用正则表达式快大约五倍,并且仍然可以生成简洁易读的代码。这样做的代价是为切片分配使用稍多的内存。
答案 1 :(得分:0)
也许您应该考虑使用正则表达式来分割日志行,例如:
package main
import "fmt"
import "time"
import "regexp"
func main() {
s := "10.0.0.1 Jan 11 2014 10:00:00 hello"
r := regexp.MustCompile("^([^/w]+) ([a-zA-Z]+ [0-9]{1,2} [0-9]{4} [0-9]{1,2}:[0-9]{2}:[0-9]{2}) (.*)")
m := r.FindStringSubmatch(s)
if len(m) >= 4 {
fmt.Println("IP:", m[1])
fmt.Println("Timestamp:", m[2])
fmt.Println("Message:", m[3])
t, err := time.Parse("Jan 02 2006 15:04:05", m[2])
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("Parsed Time:",t)
}
} else {
fmt.Println("Regexp mismatch!")
}
}