-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse.go
More file actions
148 lines (139 loc) · 5.26 KB
/
parse.go
File metadata and controls
148 lines (139 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
* Copyright 2024 Daniel C. Brotsky. All rights reserved.
* All the copyrighted work in this repository is licensed under the
* open source MIT License, reproduced in the LICENSE file.
*/
// Package tracker provides the caddy adobe_usage_tracker plugin.
package tracker
import (
"go.uber.org/zap/zapcore"
"regexp"
"strconv"
"time"
)
var (
regexMap = map[string]*regexp.Regexp{
"line": regexp.MustCompile(`SessionID=([^.]+\.([0-9]+)) Timestamp=([^ ]+) [^\r\n]*Description="([^\r\n]+)"`),
"os": regexp.MustCompile(`SetConfig:.+OS Name=([^\s,]+), OS Version=([^\s,]+)`),
"app": regexp.MustCompile(`SetConfig:.+AppID=([^,]+), AppVersion=([^\s,]+)`),
"ngl": regexp.MustCompile(`SetConfig:.+NGLLibVersion=([^\s,]+)`),
"locale": regexp.MustCompile(`SetAppRuntimeConfig:.+AppLocale=([^\s,]+)`),
"user": regexp.MustCompile(`LogCurrentUser:.+UserID=([^\s,]+)`),
}
)
// A logSession captures the information from a single log about
// a single launch of a single application.
//
// The sessionId is generated by the app and is unique to the launch.
// Although a single launch of a single application may generate more
// than one log, the session_id will be the same for all of them.
//
// The launchTime is taken from the last component of the
// sessionId, which by Adobe convention is the launch time of the
// app. This ensures that the launchTime is the same for all logs
// generated from the same launch.
//
// The launchDuration field measures the time between the last
// log line of the session and the launch time of the session.
// If a session's log gets split among multiple log files, this
// means that later files will create sessions with bigger
// launchDuration times.
type logSession struct {
sessionId string
launchTime time.Time
launchDuration time.Duration
clientIp string
appId string // NGL app ID
appVersion string
appLocale string
nglVersion string // version of the app's NGL library
osName string
osVersion string
userId string // a SHA1 of the logged-in Adobe user ID
}
func (l logSession) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("sessionId", l.sessionId)
enc.AddString("launchTime", l.launchTime.Format(time.RFC3339))
enc.AddString("launchDuration", l.launchDuration.String())
enc.AddString("clientIp", l.clientIp)
enc.AddString("appId", l.appId)
enc.AddString("appVersion", l.appVersion)
enc.AddString("appLocale", l.appLocale)
enc.AddString("nglVersion", l.nglVersion)
enc.AddString("osName", l.osName)
enc.AddString("osVersion", l.osVersion)
enc.AddString("userId", l.userId)
return nil
}
// parseLog reads every line of a log's contents, and returns
// a slice of the logSessions found in the log. It never fails,
// but it will return an empty slice on malformed input.
func parseLog(log string, ip string) (sessions []logSession) {
var session logSession
var lastTime time.Time
endSession := func() {
if session.sessionId != "" {
if lastTime.Compare(session.launchTime) > 0 {
session.launchDuration = lastTime.Sub(session.launchTime)
}
sessions = append(sessions, session)
}
}
for _, line := range regexMap["line"].FindAllStringSubmatch(log, -1) {
if sessionId := line[1]; sessionId != session.sessionId {
endSession()
session = logSession{sessionId: sessionId, launchTime: parseTimeMillis(line[2]), clientIp: ip}
}
lastTime = parseLogTimestamp(line[3])
parseLogDescription(line[4], &session)
}
endSession()
return
}
// parseLogDescription takes the description field of a log line and
// fills session parameters from values found in the description.
func parseLogDescription(description string, session *logSession) {
var match []string
if match = regexMap["os"].FindStringSubmatch(description); match != nil {
session.osName = match[1]
session.osVersion = match[2]
} else if match = regexMap["app"].FindStringSubmatch(description); match != nil {
session.appId = match[1]
session.appVersion = match[2]
} else if match = regexMap["ngl"].FindStringSubmatch(description); match != nil {
session.nglVersion = match[1]
} else if match = regexMap["locale"].FindStringSubmatch(description); match != nil {
session.appLocale = match[1]
} else if match = regexMap["user"].FindStringSubmatch(description); match != nil {
session.userId = match[1]
}
}
// parseTimeMillis is given a string representing a number of
// milliseconds since the Unix Epoch and returns a time.Time
// containing that value. If it's given malformed input, it
// returns the epoch.
func parseTimeMillis(s string) time.Time {
msec, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return time.UnixMilli(0)
}
return time.UnixMilli(msec)
}
// parseLogTiimestamp is given a date string in the unique format
// written by Adobe apps in their log, and returns a time.Time
// containing that value. If it's given malformed input, it
// returns the epoch.
func parseLogTimestamp(s string) time.Time {
// incoming format is "2024-02-15T10:54:21:732-0800"
// but we have to replace that last : with a . to get it to parse.
// Luckily, it's at a fixed offset in the timestring
if s[19] != ':' {
return time.UnixMilli(0)
}
valid := s[0:19] + "." + s[20:]
t, err := time.Parse("2006-01-02T15:04:05.999-0700", valid)
if err != nil {
return time.UnixMilli(0)
}
return t
}