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