-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathlog-to-gor.go
More file actions
140 lines (126 loc) · 4.12 KB
/
Copy pathlog-to-gor.go
File metadata and controls
140 lines (126 loc) · 4.12 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
package main
import (
"bufio"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"regexp"
"time"
parser "github.com/nekrassov01/access-log-parser"
)
// The delimiter used by goreplay to separate payloads.
const gorPayloadDelimiter = "🐵🙈🙉"
// CombinedLogFormat is the standard format string for Apache Combined Log files.
const CombinedLogFormat = `%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"`
func main() {
// 1. Check for command-line arguments
if len(os.Args) != 3 {
fmt.Println("Usage: log-to-gor <input_logfile> <output_gorfile>")
fmt.Println("Example: ./log-to-gor access.log requests.gor")
os.Exit(1)
}
inputFile := os.Args[1]
outputFile := os.Args[2]
log.Printf("Starting conversion from %s to %s", inputFile, outputFile)
// 2. Open the input log file for reading
in, err := os.Open(inputFile)
if err != nil {
log.Fatalf("Error opening input file %s: %v", inputFile, err)
}
defer in.Close()
// 3. Create the output .gor file for writing
out, err := os.Create(outputFile)
if err != nil {
log.Fatalf("Error creating output file %s: %v", outputFile, err)
}
defer out.Close()
// 4. Initialize the log parser for Apache Combined Log Format
ctx := context.Background()
parserInstance := parser.NewApacheCLFRegexParser(ctx, io.Discard, parser.Option{})
// 5. Process the files line by line
count, err := processLogs(in, out, parserInstance)
if err != nil {
log.Fatalf("Error during processing: %v", err)
}
log.Printf("✅ Success! Converted %d log entries.", count)
log.Printf("Output saved to %s", outputFile)
}
// processLogs reads from the reader, parses logs, and writes to the writer in .gor format.
func processLogs(r io.Reader, w io.Writer, p *parser.RegexParser) (int, error) {
scanner := bufio.NewScanner(r)
processedCount := 0
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
// Parse the log line
entry, err := p.ParseString(line)
if err != nil || len(entry.Errors) > 0 || entry.Matched == 0 {
log.Printf("⚠️ Skipping malformed line: %s (%v)", line, err)
continue
}
// Extract timestamp and request line
// Find the request line in the log
// For Combined Log Format, request line is in quotes after the date
// We'll use regex to extract it
var requestLine string
var timestamp int64
// Try to extract request line and timestamp from the parsed result
// The parser does not expose fields directly, so we use regex fallback
re := regexp.MustCompile(`"(GET|POST|PUT|DELETE|HEAD|OPTIONS|PATCH) ([^ ]+) ([^"]+)"`)
matches := re.FindStringSubmatch(line)
if len(matches) == 4 {
requestLine = fmt.Sprintf("%s %s %s", matches[1], matches[2], matches[3])
} else {
continue
}
// Extract timestamp from the log line
timeRe := regexp.MustCompile(`\[(\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2}) [^\]]+\]`)
timeMatch := timeRe.FindStringSubmatch(line)
if len(timeMatch) == 2 {
t, err := time.Parse("02/Jan/2006:15:04:05", timeMatch[1])
if err == nil {
timestamp = t.UnixNano()
}
}
// Generate request ID
reqID, err := generateRequestID()
if err != nil {
log.Printf("⚠️ Skipping line due to ID generation error: %v", err)
continue
}
// Write .gor format
reqType := "1"
latency := 0
_, err = fmt.Fprintf(w, "%s %s %d %d\n", reqType, reqID, timestamp, latency)
if err != nil {
return processedCount, fmt.Errorf("failed to write header: %w", err)
}
_, err = fmt.Fprintf(w, "%s\r\n\r\n\n", requestLine)
if err != nil {
return processedCount, fmt.Errorf("failed to write request line: %w", err)
}
_, err = fmt.Fprintf(w, "%s\n", gorPayloadDelimiter)
if err != nil {
return processedCount, fmt.Errorf("failed to write delimiter: %w", err)
}
processedCount++
}
if err := scanner.Err(); err != nil {
return processedCount, fmt.Errorf("error reading input file: %w", err)
}
return processedCount, nil
}
// generateRequestID creates a random 16-byte slice and returns it as a 32-character hex string.
func generateRequestID() (string, error) {
bytes := make([]byte, 12)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}