First commit

This commit is contained in:
GRMrGecko 2024-03-07 11:56:15 -06:00
commit 4d168ce8b8
6 changed files with 248 additions and 0 deletions

29
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,29 @@
on:
release:
types: [created]
permissions:
contents: write
packages: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v4
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

30
.goreleaser.yaml Normal file
View File

@ -0,0 +1,30 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
# The lines below are called `modelines`. See `:help modeline`
# Feel free to remove those if you don't want/need to use them.
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
version: 1
before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy
# you may remove this if you don't need go generate
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: "{{ .ProjectName }}-{{ .Version }}.{{ .Os }}-{{ .Arch }}"
wrap_in_directory: true
strip_parent_binary_folder: false

19
LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2024 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# goreplay-http-logger
I needed a way to directly capture http traffic for use with [GoReplay](https://goreplay.org/), and there did not seem to be an official method. As such, I wrote my own quick server to do the job. I may as well share it with the world as it has been useful to me. I did not do anything fancy here, just a simple cli argument configuration.
```
$ ./goreplay-http-logger --help
http log server
-bind string
HTTP bind address
-log-file string
Log file name with date (default "http-%Y%m%d.log")
-port int
HTTP port (default 8080)
```

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/grmrgecko/goreplay-http-logger
go 1.20

153
main.go Normal file
View File

@ -0,0 +1,153 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// Separator between http requests.
var payloadSeparator = "\n🐵🙈🙉\n"
// Generate random hex.
func randByte(len int) []byte {
b := make([]byte, len/2)
rand.Read(b)
h := make([]byte, len)
hex.Encode(h, b)
return h
}
// Generate a uuid for a request.
func uuid() []byte {
return randByte(24)
}
// Generate a header with type 1 for a request.
func payloadHeader(uuid []byte, timing int64) (header []byte) {
return []byte(fmt.Sprintf("1 %s %d 0\n", uuid, timing))
}
// The log file structure for writing logs.
type LogFile struct {
sync.Mutex
file *os.File
currentName string
nextUpdate time.Time
}
// Write to log file.
func (l *LogFile) write(data []byte) (n int, err error) {
// Lock to prevent multiple writes at the same time.
l.Lock()
defer l.Unlock()
// Get current time for file name generator.
now := time.Now()
// If no file defined or we are after the next update time, update the tile name.
if l.file == nil || now.After(l.nextUpdate) {
// Set next update to a second later, truncating the nanoseconds.
l.nextUpdate = now.Truncate(time.Second).Add(time.Second)
// Generate the log file name based on config.
name := config.LogFile
// Year
name = strings.ReplaceAll(name, "%Y", now.Format("2006"))
// Month
name = strings.ReplaceAll(name, "%m", now.Format("01"))
// Day
name = strings.ReplaceAll(name, "%d", now.Format("02"))
// Hour
name = strings.ReplaceAll(name, "%H", now.Format("15"))
// Minute
name = strings.ReplaceAll(name, "%M", now.Format("04"))
// Second
name = strings.ReplaceAll(name, "%S", now.Format("05"))
l.currentName = filepath.Clean(name)
// If new name generated is different from existing open file or no file is opened, open it.
if l.file == nil || l.currentName != l.file.Name() {
l.file, err = os.OpenFile(l.currentName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
log.Fatalf("Cannot open file %q. Error: %s", l.currentName, err)
}
}
}
// Write data.
n, err = l.file.Write(data)
return
}
// Global log file definition.
var logFile *LogFile
// Log the http request.
func logRequest(w http.ResponseWriter, r *http.Request) {
log.Printf("Request %s %s", r.Method, r.URL)
// Generate log entry.
var buff bytes.Buffer
fmt.Fprintf(&buff, "%s", payloadHeader(uuid(), time.Now().UnixNano()))
fmt.Fprintf(&buff, "%s %s %s\n", r.Method, r.URL, r.Proto)
r.Header.Write(&buff)
fmt.Fprint(&buff, "\r\n")
body, _ := io.ReadAll(r.Body)
buff.Write(body)
fmt.Fprint(&buff, payloadSeparator)
// Write to log file.
logFile.write(buff.Bytes())
}
// The config for this run, set in flags.
type Config struct {
HTTPBind string
HTTPPort int
LogFile string
}
// Global config variable.
var config Config
// Generate help information.
func usage() {
fmt.Println("http log server")
flag.PrintDefaults()
os.Exit(2)
}
// The main program.
func main() {
// Parse flags.
flag.Usage = usage
flag.StringVar(&config.HTTPBind, "bind", "", "HTTP bind address")
flag.IntVar(&config.HTTPPort, "port", 8080, "HTTP port")
flag.StringVar(&config.LogFile, "log-file", "http-%Y%m%d.log", "Log file name with date")
flag.Parse()
// Setup the log file.
logFile = new(LogFile)
// Handle all http requests with the logRequest handler.
http.HandleFunc("/", logRequest)
// Start the HTTP server/
fmt.Printf("Starting server at port %d\n", config.HTTPPort)
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", config.HTTPBind, config.HTTPPort), nil); err != nil {
log.Fatal(err)
}
}