First commit
This commit is contained in:
commit
4d168ce8b8
29
.github/workflows/release.yaml
vendored
Normal file
29
.github/workflows/release.yaml
vendored
Normal 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
30
.goreleaser.yaml
Normal 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
19
LICENSE.txt
Normal 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
14
README.md
Normal 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
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/grmrgecko/goreplay-http-logger
|
||||||
|
|
||||||
|
go 1.20
|
153
main.go
Normal file
153
main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user