refactor(tixel-watch): refactor watch-tool to enable type safety and better performance

This commit is contained in:
Patryk Hegenberg 2025-09-21 22:36:05 +02:00
parent 159df116c8
commit 25dffecb43
8 changed files with 367 additions and 201 deletions

View file

@ -136,17 +136,18 @@ func (jep *JournalEntryParser) Parse(jsonLine string) (LogEntry, error) {
}
if msg, ok := journalData["MESSAGE"].(string); ok {
entry.Message = msg
entry.LogMessage = msg
}
if priority, ok := journalData["PRIORITY"].(string); ok {
entry.Priority = priority
entry.Fields["priority_name"] = jep.getPriorityName(priority)
entry.PriorityName = jep.getPriorityName(priority)
}
if pidStr, ok := journalData["_PID"].(string); ok {
if pid, err := strconv.Atoi(pidStr); err == nil {
entry.PID = pid
entry.SyslogInfo.ProcessInfo = strconv.FormatInt(int64(pid), 10)
}
}
@ -197,7 +198,7 @@ func (jep *JournalEntryParser) extractSystemdFields(journalData map[string]any,
for _, field := range systemdFields {
if value, ok := journalData[field]; ok {
esFieldName := strings.ToLower(strings.TrimPrefix(field, "_"))
entry.Fields[esFieldName] = value
entry.SyslogInfo.Fields[esFieldName] = value
}
}
}
@ -237,115 +238,114 @@ var (
func parseTixstreamService(entry LogEntry) LogEntry {
newEntry := entry
fields := make(map[string]any)
var baseInfo TSBaseInfo
matches := tsServicePattern.FindStringSubmatch(newEntry.Message)
matches := tsServicePattern.FindStringSubmatch(newEntry.LogMessage)
if len(matches) > 0 {
timestamp := strings.Join(strings.Split(matches[2], " "), "T")
fields["log_level"] = strings.TrimSpace(matches[1])
fields["message_timestamp"] = timestamp
fields["log_message"] = strings.TrimSpace(matches[3])
} else {
fields["log_message"] = newEntry.Message
newEntry.LogLevel = strings.TrimSpace(matches[1])
if newEntry.Timestamp.IsZero() {
timeParsed, err := parseRFC3339WithOptionalZ(timestamp)
if err != nil {
slog.Error("cant parse time string", "error", err)
}
newEntry.Timestamp = timeParsed
}
newEntry.LogMessage = strings.TrimSpace(matches[3])
}
trNameMatch := tsTransferIDPattern.FindStringSubmatch(fields["log_message"].(string))
trNameMatch := tsTransferIDPattern.FindStringSubmatch(newEntry.LogMessage)
var transferID string
if len(trNameMatch) > 0 {
transferID = trNameMatch[1]
fields["log_message"] = trNameMatch[2]
newEntry.LogMessage = trNameMatch[2]
split := strings.Fields(trNameMatch[2])
switch split[0] {
case "in:":
fields["transfer_direction"] = "incoming"
baseInfo.Direction = "incoming"
case "out:":
fields["transfer_direction"] = "outgoing"
baseInfo.Direction = "outgoing"
}
}
msg := strings.ReplaceAll(newEntry.Message, " ", " ")
msg := strings.ReplaceAll(newEntry.LogMessage, " ", " ")
parts := strings.Fields(msg)
if len(parts) < 5 {
return newEntry
}
tsDetail := tsDetailPattern1.FindStringSubmatch(fields["log_message"].(string))
tsDetail := tsDetailPattern1.FindStringSubmatch(newEntry.LogMessage)
if len(tsDetail) > 0 {
threadInt, _ := strconv.Atoi(strings.Split(tsDetail[1], "/")[0])
fields["thread"] = threadInt
buffersInt, _ := strconv.Atoi(tsDetail[2])
fields["buffers"] = buffersInt
fileCountInt, _ := strconv.Atoi(tsDetail[3])
fields["file_count"] = fileCountInt
fileSizeInt, _ := strconv.Atoi(tsDetail[4])
fields["file_size"] = fileSizeInt
chunkSizeInt, _ := strconv.Atoi(tsDetail[5])
fields["chunksize"] = chunkSizeInt
streamsInt, _ := strconv.Atoi(tsDetail[6])
fields["streams"] = streamsInt
datarateFloat, _ := strconv.ParseFloat(tsDetail[7], 64)
fields["target_datarate"] = datarateFloat
fields["protocoll"] = tsDetail[8]
fields["destination"] = tsDetail[9]
fields["sender_id"] = tsDetail[10]
fileSizeFloat, _ := strconv.ParseFloat(tsDetail[4], 64)
baseInfo.Lane = threadInt
baseInfo.Buffers = buffersInt
baseInfo.FileCount = fileCountInt
baseInfo.FileSize = fileSizeFloat
baseInfo.ChunkSize = chunkSizeInt
baseInfo.Streams = streamsInt
baseInfo.TargetDatarate = datarateFloat
baseInfo.Protocoll = tsDetail[8]
baseInfo.Destination = tsDetail[9]
baseInfo.Sender = tsDetail[10]
}
tsDetail = tsDetailPattern2.FindStringSubmatch(fields["log_message"].(string))
tsDetail = tsDetailPattern2.FindStringSubmatch(newEntry.LogMessage)
if len(tsDetail) > 0 {
fields["transfer_target"] = tsDetail[1]
baseInfo.Target = tsDetail[1]
}
tsDetail = tsDetailPattern3.FindStringSubmatch(fields["log_message"].(string))
tsDetail = tsDetailPattern3.FindStringSubmatch(newEntry.LogMessage)
if len(tsDetail) > 0 {
threadInt, _ := strconv.Atoi(strings.Split(tsDetail[1], "/")[0])
fields["thread"] = threadInt
buffersInt, _ := strconv.Atoi(tsDetail[2])
fields["buffers"] = buffersInt
fileCountInt, _ := strconv.Atoi(tsDetail[3])
fields["file_count"] = fileCountInt
fileSizeInt, _ := strconv.Atoi(tsDetail[4])
fields["file_size"] = fileSizeInt
fileSizeFloat, _ := strconv.ParseFloat(tsDetail[4], 64)
chunkSizeInt, _ := strconv.Atoi(tsDetail[5])
fields["chunksize"] = chunkSizeInt
streamsInt, _ := strconv.Atoi(tsDetail[6])
fields["streams"] = streamsInt
datarateFloat, _ := strconv.ParseFloat(tsDetail[7], 64)
fields["target_datarate"] = datarateFloat
fields["protocoll"] = tsDetail[8]
fields["source"] = tsDetail[9]
fields["receiver_id"] = tsDetail[10]
baseInfo.Lane = threadInt
baseInfo.Buffers = buffersInt
baseInfo.FileCount = fileCountInt
baseInfo.FileSize = fileSizeFloat
baseInfo.ChunkSize = chunkSizeInt
baseInfo.Streams = streamsInt
baseInfo.TargetDatarate = datarateFloat
baseInfo.Protocoll = tsDetail[8]
baseInfo.Source = tsDetail[9]
baseInfo.Receiver = tsDetail[10]
}
tsDetail = tsDetailPattern4.FindStringSubmatch(fields["log_message"].(string))
tsDetail = tsDetailPattern4.FindStringSubmatch(newEntry.LogMessage)
if len(tsDetail) > 0 {
threadInt, _ := strconv.Atoi(strings.Split(tsDetail[1], "/")[0])
fields["thread"] = threadInt
fields["source"] = tsDetail[2]
fields["destination"] = tsDetail[3]
fields["item"] = tsDetail[4]
fields["count"] = tsDetail[5]
baseInfo.Lane = threadInt
baseInfo.Source = tsDetail[2]
baseInfo.Destination = tsDetail[3]
}
if strings.Contains(fields["log_message"].(string), "Transfer start") || strings.Contains(fields["log_message"].(string), "Transfer started,") {
fields["event"] = "transfer_started"
fields["start_time"] = fields["message_timestamp"]
if strings.Contains(newEntry.LogMessage, "Transfer start") || strings.Contains(newEntry.LogMessage, "Transfer started,") {
baseInfo.StartTime = newEntry.Timestamp
}
if strings.Contains(fields["log_message"].(string), "Transfer stopped local state=finished") {
fields["event"] = "transfer_stopped"
fields["end_time"] = fields["message_timestamp"]
if strings.Contains(newEntry.LogMessage, "Transfer stopped local state=finished") {
baseInfo.EndTime = newEntry.Timestamp
}
// value, ok := fields["transfer_id"]
if transferID != "" {
fields["transfer_identifier"] = transferID
baseInfo.TransferID = transferID
} else {
baseInfo.TransferID = "no_transfer_id"
}
if fields["transfer_identifier"] == nil {
fields["transfer_identifier"] = "unknown"
if !baseInfo.StartTime.IsZero() {
newEntry.BaseInformation = baseInfo
}
if fields["message_timestamp"] == nil {
fields["message_timestamp"] = newEntry.Timestamp
}
newEntry.Fields = fields
return newEntry
}
func parseTJMService(entry LogEntry) LogEntry {
newEntry := entry
logContent := entry.Message
var baseInfo TJMBaseInfo
logContent := entry.LogMessage
msg := strings.TrimSpace(logContent)
msg = strings.ReplaceAll(msg, " ", " ")
msg = strings.ReplaceAll(msg, "---", "")
@ -354,156 +354,160 @@ func parseTJMService(entry LogEntry) LogEntry {
if len(parts) < 4 {
return newEntry
}
fields := make(map[string]any)
matches := tjmServicePattern.FindStringSubmatch(logContent)
if len(matches) > 0 {
timestamp := strings.Join(strings.Split(matches[1], " "), "T")
fields["message_timestamp"] = timestamp
fields["log_level"] = strings.TrimSpace(matches[2])
fields["pid"] = strings.TrimSpace(matches[3])
fields["correlation_id"] = strings.TrimSpace(matches[4])
fields["username"] = strings.TrimSpace(matches[5])
fields["thread_id"] = strings.TrimSpace(matches[6])
fields["java_class"] = strings.TrimSpace(matches[7])
fields["log_message"] = strings.TrimSpace(matches[8])
timestamp := strings.Join(strings.Split(matches[2], " "), "T")
newEntry.LogLevel = strings.TrimSpace(matches[1])
if newEntry.Timestamp.IsZero() {
timeParsed, err := parseRFC3339WithOptionalZ(timestamp)
if err != nil {
slog.Error("cant parse time string", "error", err)
}
newEntry.Timestamp = timeParsed
}
newEntry.LogLevel = strings.TrimSpace(matches[2])
newEntry.LogMessage = strings.TrimSpace(matches[8])
baseInfo = TJMBaseInfo{
ProcessID: strings.TrimSpace(matches[3]),
CorrelationID: strings.TrimSpace(matches[4]),
Username: strings.TrimSpace(matches[5]),
ThreadID: strings.TrimSpace(matches[6]),
JavaClass: strings.TrimSpace(matches[7]),
}
} else {
fields["log_message"] = logContent
newEntry.LogMessage = logContent
}
trNameMatch := tjmTransferNamePattern.FindStringSubmatch(fields["log_message"].(string))
trNameMatch := tjmTransferNamePattern.FindStringSubmatch(newEntry.LogMessage)
var transferName string
var transferID string
if len(trNameMatch) > 0 {
transferName = trNameMatch[1]
fields["log_message"] = trNameMatch[2]
newEntry.LogMessage = trNameMatch[2]
if strings.Contains(trNameMatch[1], "-in") {
fields["transfer_direction"] = "incoming"
baseInfo.Direction = "incoming"
}
if strings.Contains(trNameMatch[1], "-out") {
fields["transfer_direction"] = "outgoing"
baseInfo.Direction = "outgoing"
}
}
trIDMatch := tjmTransferIDPattern1.FindStringSubmatch(fields["log_message"].(string))
trIDMatch := tjmTransferIDPattern1.FindStringSubmatch(newEntry.LogMessage)
if len(trIDMatch) > 0 {
transferID = trIDMatch[1]
}
trIDMatch = tjmTransferIDPattern2.FindStringSubmatch(fields["log_message"].(string))
trIDMatch = tjmTransferIDPattern2.FindStringSubmatch(newEntry.LogMessage)
if len(trIDMatch) > 0 {
transferID = trIDMatch[2]
}
// value, ok := fields["transfer_id"]
if transferID != "" {
fields["transfer_identifier"] = transferID
baseInfo.TransferID = transferID
} else if transferName != "" {
// value, ok := fields["transfer_name"]
// if ok {
fields["transfer_identifier"] = transferName
// }
baseInfo.TransferID = transferName
} else {
baseInfo.TransferID = "no_transfer_id"
}
if fields["transfer_identifier"] == nil {
fields["transfer_identifier"] = "unknown"
}
if fields["message_timestamp"] == nil {
fields["message_timestamp"] = newEntry.Timestamp
}
newEntry.Fields = fields
newEntry.BaseInformation = baseInfo
return newEntry
}
func parseAMService(entry LogEntry) LogEntry {
newEntry := entry
if newEntry.Fields == nil {
newEntry.Fields = make(map[string]any)
}
fields := make(map[string]any)
logContent := newEntry.LogMessage
matches := amServicePattern.FindStringSubmatch(strings.TrimSpace(entry.Message))
matches := amServicePattern.FindStringSubmatch(strings.TrimSpace(logContent))
if len(matches) != 7 {
return newEntry
}
timestamp := strings.Join(strings.Split(matches[1], " "), "T")
fields["message_timestamp"] = timestamp
fields["log_level"] = matches[2]
fields["process_id"] = matches[3]
fields["thread_id"] = strings.TrimSpace(matches[4])
fields["logger_name"] = matches[5]
fields["log_message"] = matches[6]
fields["transfer_identifier"] = "unknown"
if fields["message_timestamp"] == nil {
fields["message_timestamp"] = newEntry.Timestamp
timestampStr := strings.Join(strings.Split(matches[1], " "), "T")
if newEntry.Timestamp.IsZero() {
timeParsed, err := parseRFC3339WithOptionalZ(timestampStr)
if err != nil {
slog.Error("cant parse time string", "error", err)
}
newEntry.Timestamp = timeParsed
}
newEntry.Fields = fields
baseInfo := AMBaseInfo{
ProcessID: matches[3],
ThreadID: strings.TrimSpace(matches[4]),
LoggerName: matches[5],
}
newEntry.LogLevel = matches[2]
newEntry.LogMessage = matches[6]
newEntry.BaseInformation = baseInfo
return newEntry
}
func parseTCCService(entry LogEntry) LogEntry {
newEntry := entry
if newEntry.Fields == nil {
newEntry.Fields = make(map[string]any)
}
logContent := newEntry.LogMessage
fields := make(map[string]any)
matches := tccServicePattern.FindStringSubmatch(strings.TrimSpace(entry.Message))
matches := tccServicePattern.FindStringSubmatch(logContent)
if len(matches) != 7 {
return newEntry
}
timestamp := strings.Join(strings.Split(matches[1], " "), "T")
fields["message_timestamp"] = timestamp
fields["log_level"] = matches[2]
fields["process_id"] = matches[3]
fields["thread_id"] = strings.TrimSpace(matches[4])
fields["logger_name"] = matches[5]
fields["log_message"] = matches[6]
fields["transfer_identifier"] = "unknown"
if fields["message_timestamp"].(string) == "" {
fields["message_timestamp"] = newEntry.Timestamp
timestampStr := strings.Join(strings.Split(matches[1], " "), "T")
if newEntry.Timestamp.IsZero() {
timeParsed, err := parseRFC3339WithOptionalZ(timestampStr)
if err != nil {
slog.Error("cant parse time string", "error", err)
}
newEntry.Timestamp = timeParsed
}
newEntry.Fields = fields
newEntry.Fields["timestamp"] = matches[1]
newEntry.Fields["log_level"] = matches[2]
newEntry.Fields["process_id"] = matches[3]
newEntry.Fields["thread_name"] = strings.TrimSpace(matches[4])
newEntry.Fields["logger_name"] = matches[5]
newEntry.Fields["log_message"] = matches[6]
baseInfo := TCCBaseInfo{
ProcessID: matches[3],
ThreadID: strings.TrimSpace(matches[4]),
LoggerName: matches[5],
}
newEntry.LogLevel = matches[2]
newEntry.LogMessage = matches[6]
newEntry.BaseInformation = baseInfo
return newEntry
}
func parseNginxService(entry LogEntry) LogEntry {
newEntry := entry
if newEntry.Fields == nil {
newEntry.Fields = make(map[string]any)
}
matches := nginxAccessPattern.FindStringSubmatch(strings.TrimSpace(entry.Message))
matches := nginxAccessPattern.FindStringSubmatch(strings.TrimSpace(entry.LogMessage))
if len(matches) < 7 {
return newEntry
}
newEntry.Fields["client_ip"] = matches[1]
newEntry.Fields["remote_user"] = matches[2]
newEntry.Fields["timestamp"] = matches[3]
newEntry.Fields["request"] = matches[4]
newEntry.Fields["status_code"] = matches[5]
newEntry.Fields["bytes_sent"] = matches[6]
statusCode, err := strconv.ParseInt(matches[5], 10, 64)
if err != nil {
slog.Error("cant parse statuscode", "error", err)
}
bytesSend, err := strconv.ParseInt(matches[6], 10, 64)
if err != nil {
slog.Error("cant parse bytessend", "error", err)
}
baseInfo := NGinXBaseInfo{
ClientIP: matches[1],
RemoteUser: matches[2],
Request: matches[4],
StatusCode: int(statusCode),
BytesSend: int(bytesSend),
}
if len(matches) > 7 && matches[7] != "" {
newEntry.Fields["referer"] = matches[7]
baseInfo.Referer = matches[7]
}
if len(matches) > 8 && matches[8] != "" {
newEntry.Fields["user_agent"] = matches[8]
baseInfo.UserAgent = matches[8]
}
if requestParts := strings.Fields(matches[4]); len(requestParts) >= 3 {
newEntry.Fields["http_method"] = requestParts[0]
newEntry.Fields["request_uri"] = requestParts[1]
newEntry.Fields["http_version"] = requestParts[2]
baseInfo.HTTPMethod = requestParts[0]
baseInfo.RequestURI = requestParts[1]
baseInfo.HTTPVersion = requestParts[2]
}
return newEntry
}
func parseRFC3339WithOptionalZ(timeStr string) (time.Time, error) {
if !strings.HasSuffix(timeStr, "Z") && !strings.ContainsAny(timeStr[len(timeStr)-6:], "+-") {
timeStr += "Z"
}
return time.Parse(time.RFC3339Nano, timeStr)
}