Skip to content

Commit

Permalink
parse syslog (#501)
Browse files Browse the repository at this point in the history
* parse syslog

* move syslog parser into syslog package
  • Loading branch information
aluedeke authored Oct 21, 2024
1 parent 33c4935 commit 55deb23
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 11 deletions.
59 changes: 59 additions & 0 deletions ios/syslog/syslog.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package syslog

import (
"bufio"
"fmt"
"io"
"regexp"
"strings"
"time"

"github.com/danielpaulus/go-ios/ios"
)
Expand Down Expand Up @@ -62,6 +66,61 @@ func (sysLogConn *Connection) ReadLogMessage() (string, error) {
return logmsg, nil
}

// LogEntry represents a parsed log entry
type LogEntry struct {
Timestamp string `json:"timestamp"`
Device string `json:"device"`
Process string `json:"process"`
PID string `json:"pid"`
Level string `json:"level"`
Message string `json:"message"`
}

func Parser() func(log string) (*LogEntry, error) {
pattern := `(?P<Timestamp>[A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2}) (?P<Device>\S+) (?P<Process>[^\[]+)\[(?P<PID>\d+)\] <(?P<Level>\w+)>: (?P<Message>.+)`
regexp := regexp.MustCompile(pattern)

return func(log string) (*LogEntry, error) {
// Match the log message against the regex pattern
match := regexp.FindStringSubmatch(log)
if match == nil {
return nil, fmt.Errorf("failed to parse syslog message: %s", log)
}

// Create a map of named capture groups
result := make(map[string]string)
for i, name := range regexp.SubexpNames() {
if i != 0 && name != "" {
result[name] = match[i]
}
}

// Parse the original timestamp
originalTimestamp := result["Timestamp"]
parsedTime, err := time.Parse("Jan 2 15:04:05", originalTimestamp)
// Set the year to the current year from the system (this might cause friction at year end)
parsedTime = parsedTime.AddDate(time.Now().Year()-parsedTime.Year(), 0, 0)
if err != nil {
return nil, fmt.Errorf("failed to parse syslog timestamp: %s", log)
}

// Convert to ISO 8601 format
isoTimestamp := parsedTime.Format("2006-01-02T15:04:05")

// Populate the LogEntry struct
entry := &LogEntry{
Timestamp: isoTimestamp,
Device: result["Device"],
Process: strings.TrimSpace(result["Process"]),
PID: result["PID"],
Level: result["Level"],
Message: result["Message"],
}

return entry, nil
}
}

// Close closes the underlying UsbMuxConnection
func (sysLogConn *Connection) Close() error {
return sysLogConn.closer.Close()
Expand Down
54 changes: 43 additions & 11 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Usage:
ios image mount [--path=<imagepath>] [options]
ios image unmount [options]
ios image auto [--basedir=<where_dev_images_are_stored>] [options]
ios syslog [options]
ios syslog [--parse] [options]
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>]
ios instruments notifications [options]
ios crash ls [<pattern>] [options]
Expand Down Expand Up @@ -167,7 +167,7 @@ The commands work as following:
ios image auto [--basedir=<where_dev_images_are_stored>] [options] Automatically download correct dev image from the internets and mount it.
> You can specify a dir where images should be cached.
> The default is the current dir.
ios syslog [options] Prints a device's log output
ios syslog [--parse] [options] Prints a device's log output, Use --parse to parse the fields from the log
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>] Takes a screenshot and writes it to the current dir or to <outfile> If --stream is supplied it
> starts an mjpeg server at 0.0.0.0:3333. Use --port to set another port.
ios instruments notifications [options] Listen to application state notifications
Expand Down Expand Up @@ -620,7 +620,9 @@ The commands work as following:

b, _ = arguments.Bool("syslog")
if b {
runSyslog(device)
parse, _ := arguments.Bool("--parse")

runSyslog(device, parse)
return
}

Expand Down Expand Up @@ -2063,36 +2065,66 @@ func printDeviceInfo(device ios.DeviceEntry) {
fmt.Println(convertToJSONString(allValues))
}

func runSyslog(device ios.DeviceEntry) {
func runSyslog(device ios.DeviceEntry, parse bool) {
log.Debug("Run Syslog.")

syslogConnection, err := syslog.New(device)
exitIfError("Syslog connection failed", err)

defer syslogConnection.Close()

var logFormatter func(string) string
if JSONdisabled {
logFormatter = rawSyslog
} else if parse {
logFormatter = parsedJsonSyslog()
} else {
logFormatter = legacyJsonSyslog()
}

go func() {
messageContainer := map[string]string{}
for {
logMessage, err := syslogConnection.ReadLogMessage()
if err != nil {
exitIfError("failed reading syslog", err)
}
logMessage = strings.TrimSuffix(logMessage, "\x00")
logMessage = strings.TrimSuffix(logMessage, "\x0A")
if JSONdisabled {
fmt.Println(logMessage)
} else {
messageContainer["msg"] = logMessage
fmt.Println(convertToJSONString(messageContainer))
}

fmt.Println(logFormatter(logMessage))
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
}

func rawSyslog(log string) string {
return log
}

func legacyJsonSyslog() func(log string) string {
messageContainer := map[string]string{}

return func(log string) string {
messageContainer["msg"] = log
return convertToJSONString(messageContainer)
}
}

func parsedJsonSyslog() func(log string) string {
parser := syslog.Parser()

return func(log string) string {
log_entry, err := parser(log)
if err != nil {
return convertToJSONString(map[string]string{"msg": log, "error": err.Error()})
}

return convertToJSONString(log_entry)
}
}

func pairDevice(device ios.DeviceEntry, orgIdentityP12File string, p12Password string) {
if orgIdentityP12File == "" {
err := ios.Pair(device)
Expand Down

0 comments on commit 55deb23

Please sign in to comment.