Compare commits
No commits in common. "1b15eeefe2d6b64ba17b87d035017506cc983bd6" and "ef76e0113e2e4a7a70bec522440927d66aeac36a" have entirely different histories.
1b15eeefe2
...
ef76e0113e
|
@ -5,7 +5,6 @@ artifacts
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
coverage
|
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
24
Makefile
24
Makefile
|
@ -1,7 +1,6 @@
|
||||||
# Inspired from https://dustinspecker.com/posts/go-combined-unit-integration-code-coverage/ and https://netdevops.me/2023/test-coverage-for-go-integration-tests/
|
# Inspired from https://dustinspecker.com/posts/go-combined-unit-integration-code-coverage/ and https://netdevops.me/2023/test-coverage-for-go-integration-tests/
|
||||||
BIN_DIR = $(CURDIR)/artifacts
|
BIN_DIR = $(CURDIR)/artifacts
|
||||||
BINARY = $(BIN_DIR)/gocustomurls
|
BINARY = $(BIN_DIR)/gocustomurls
|
||||||
COVERAGE_DIR = $(CURDIR)/coverage
|
|
||||||
CURRENT_DIR = $(shell pwd)
|
CURRENT_DIR = $(shell pwd)
|
||||||
CUR_TAG = $(shell git tag | sort -g | tail -1 | cut -c 2-)
|
CUR_TAG = $(shell git tag | sort -g | tail -1 | cut -c 2-)
|
||||||
VERSION_NUMBER ?= 0.0.0
|
VERSION_NUMBER ?= 0.0.0
|
||||||
|
@ -30,25 +29,4 @@ lint:
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build:
|
build:
|
||||||
go build -o $(BINARY)
|
go build -o $(BINARY)
|
||||||
|
|
||||||
.PHONY: build-debug
|
|
||||||
build-debug:
|
|
||||||
mkdir -p $(BIN_DIR)
|
|
||||||
go build -cover -o $(BINARY) .
|
|
||||||
|
|
||||||
.PHONY: test
|
|
||||||
test: build-debug
|
|
||||||
rm -rf $(COVERAGE_DIR)
|
|
||||||
mkdir -p $(COVERAGE_DIR)
|
|
||||||
go test -cover ./... -args -test.gocoverdir="$(COVERAGE_DIR)"
|
|
||||||
|
|
||||||
.PHONY: coverage-full
|
|
||||||
coverage-full: test
|
|
||||||
go tool covdata textfmt -i=$(COVERAGE_DIR) -o $(COVERAGE_DIR)/coverage.out
|
|
||||||
go tool cover -func=$(COVERAGE_DIR)/coverage.out
|
|
||||||
|
|
||||||
.PHONY: coverage-html
|
|
||||||
coverage-html: coverage-full
|
|
||||||
go tool cover -html=./coverage/coverage.out -o ./coverage/coverage.html
|
|
||||||
open ./coverage/coverage.html
|
|
|
@ -74,5 +74,4 @@ do this instead
|
||||||
|
|
||||||
* [x] Fix permission errors around opening the app.log and rules.json.
|
* [x] Fix permission errors around opening the app.log and rules.json.
|
||||||
* [x] Make the flags (config, rules) required instead of optional.
|
* [x] Make the flags (config, rules) required instead of optional.
|
||||||
~~* [ ] Figure how to use logrotate (a linux utility)~~
|
* [ ] Figure how to use logrotate (a linux utility)
|
||||||
* [ ] Figure how to do log rotation as part of this app's function
|
|
6
app.go
6
app.go
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
|
@ -25,8 +24,7 @@ func (app *Application) routes() {
|
||||||
func (app *Application) Setup(port string) *http.Server {
|
func (app *Application) Setup(port string) *http.Server {
|
||||||
app.routes()
|
app.routes()
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: fmt.Sprintf(":%s", port),
|
Addr: fmt.Sprintf(":%s", port),
|
||||||
Handler: app.Mux,
|
Handler: app.Mux,
|
||||||
ReadTimeout: 2500 * time.Millisecond,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
conf.go
112
conf.go
|
@ -4,18 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type parsedConf struct {
|
|
||||||
RulesFp string `json:"rulesPath"`
|
|
||||||
LogFp string `json:"logPath"`
|
|
||||||
Compression bool `json:"compress"`
|
|
||||||
SizeToRotate string `json:"sizeToRotate"`
|
|
||||||
Port string `json:"port"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
MappingFilePath string
|
MappingFilePath string
|
||||||
MappingRules ImportRulesMappings
|
MappingRules ImportRulesMappings
|
||||||
|
@ -72,105 +62,3 @@ func (c *Config) LoadMappingFile(fp string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaults() (map[string]string, error) {
|
|
||||||
m := make(map[string]string)
|
|
||||||
confDir, err := os.UserConfigDir()
|
|
||||||
if err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
m["rulesFp"] = filepath.Join(confDir, "gocustomcurls", "rules.json")
|
|
||||||
m["confFp"] = filepath.Join(confDir, "gocustomcurls", "config.json")
|
|
||||||
m["logFp"] = filepath.Join(homeDir, ".gocustomurls", "logs", "app.log")
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateDefaultConfigFile(defaultObj map[string]string) (parsedConf, error) {
|
|
||||||
var p parsedConf
|
|
||||||
var err error
|
|
||||||
var defaults map[string]string
|
|
||||||
if len(defaultObj) == 0 {
|
|
||||||
defaults, err = getDefaults()
|
|
||||||
} else {
|
|
||||||
defaults = defaultObj
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
parentDir := filepath.Dir(defaults["confFp"])
|
|
||||||
err = os.MkdirAll(parentDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(defaults["confFp"], os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
p.RulesFp = defaults["rulesFp"]
|
|
||||||
p.LogFp = defaults["logFp"]
|
|
||||||
p.Port = "7070"
|
|
||||||
p.Compression = true
|
|
||||||
p.SizeToRotate = "5MiB"
|
|
||||||
jsonString, _ := json.Marshal(p)
|
|
||||||
err = os.WriteFile(defaults["confFp"], jsonString, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkIfSizeIsConfigured(fsize string) (bool, error) {
|
|
||||||
suffixes := []string{"KB", "MB", "GB"}
|
|
||||||
var found string
|
|
||||||
for _, suffix := range suffixes {
|
|
||||||
if strings.HasSuffix(fsize, suffix) {
|
|
||||||
found = suffix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(found) == 0 {
|
|
||||||
return false, fmt.Errorf("%s has the incorrect suffix, Please use one of this suffixes {\"KB\", \"MB\", \"GB\"}", fsize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the main config file
|
|
||||||
func (c *Config) LoadMainConfigFile(fp string) (parsedConf, error) {
|
|
||||||
var conf parsedConf
|
|
||||||
var err error
|
|
||||||
ok := isFile(fp)
|
|
||||||
if !ok {
|
|
||||||
// generate config file
|
|
||||||
errorLog.Println("Warning, generating default config file")
|
|
||||||
conf, err = generateDefaultConfigFile(map[string]string{})
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
c.MappingFilePath = conf.RulesFp
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
f, err := os.Open(fp)
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
err = json.NewDecoder(f).Decode(&conf)
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = checkIfSizeIsConfigured(conf.SizeToRotate)
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MappingFilePath = conf.RulesFp
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
126
conf_test.go
126
conf_test.go
|
@ -1,126 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadMappingFile(t *testing.T) {
|
|
||||||
|
|
||||||
t.Run("load the mapping file correctly - initial load", func(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
|
|
||||||
mkDirForTest(t, fmt.Sprintf("%s/tmp", tmpDir))
|
|
||||||
rulesJsonFp := "testData/rules.json"
|
|
||||||
|
|
||||||
expected := fmt.Sprintf("%s/tmp/rules.json", tmpDir)
|
|
||||||
|
|
||||||
cpFileForTest(t, rulesJsonFp, expected)
|
|
||||||
|
|
||||||
cfg := &Config{}
|
|
||||||
|
|
||||||
assert.Equal(t, cfg.MappingFilePath, "")
|
|
||||||
assert.Empty(t, cfg.MappingRules)
|
|
||||||
err := cfg.LoadMappingFile(expected)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
assert.Equal(t, cfg.MappingFilePath, expected)
|
|
||||||
assert.NotEmpty(t, cfg.MappingRules)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("load the mapping file correctly - reload", func(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
|
|
||||||
mkDirForTest(t, fmt.Sprintf("%s/tmp", tmpDir))
|
|
||||||
rulesJsonFp := "testData/rules.json"
|
|
||||||
|
|
||||||
expected := fmt.Sprintf("%s/tmp/rules.json", tmpDir)
|
|
||||||
|
|
||||||
cpFileForTest(t, rulesJsonFp, expected)
|
|
||||||
|
|
||||||
cfg := &Config{}
|
|
||||||
|
|
||||||
assert.Equal(t, cfg.MappingFilePath, "")
|
|
||||||
assert.Empty(t, cfg.MappingRules)
|
|
||||||
err := cfg.LoadMappingFile(expected)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
assert.Equal(t, cfg.MappingFilePath, expected)
|
|
||||||
assert.NotEmpty(t, cfg.MappingRules)
|
|
||||||
oldRules := cfg.MappingRules
|
|
||||||
|
|
||||||
rulesJsonFp = "testData/rules2.json"
|
|
||||||
|
|
||||||
cpFileForTest(t, rulesJsonFp, expected)
|
|
||||||
|
|
||||||
err = cfg.LoadMappingFile(expected)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
assert.Equal(t, cfg.MappingFilePath, expected)
|
|
||||||
assert.NotEmpty(t, cfg.MappingRules)
|
|
||||||
|
|
||||||
newRules := cfg.MappingRules
|
|
||||||
|
|
||||||
assert.NotEqualValues(t, oldRules, newRules)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDefaults(t *testing.T) {
|
|
||||||
actual, err := getDefaults()
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
assert.Contains(t, actual, "rulesFp")
|
|
||||||
assert.Contains(t, actual, "logFp")
|
|
||||||
assert.Contains(t, actual, "confFp")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSizeIsConfigured(t *testing.T) {
|
|
||||||
tests := map[string]struct {
|
|
||||||
input string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
"wrong input - K": {input: "5677.45K", want: false},
|
|
||||||
"wrong input - KiB": {input: "5677.45KiB", want: false},
|
|
||||||
"wrong input - M": {input: "9.45M", want: false},
|
|
||||||
"wrong input - G": {input: "9.45G", want: false},
|
|
||||||
"correct input - KB": {input: "5.45KB", want: true},
|
|
||||||
"correct input - MB": {input: "5677.45MB", want: true},
|
|
||||||
"correct input - GB": {input: "9.45GB", want: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range tests {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
got, _ := checkIfSizeIsConfigured(tc.input)
|
|
||||||
assert.Equal(t, got, tc.want)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateConfigFile(t *testing.T) {
|
|
||||||
tmpDir := t.TempDir()
|
|
||||||
|
|
||||||
tempLogsDir := fmt.Sprintf("%s/tmp/logs", tmpDir)
|
|
||||||
tempConfigDir := fmt.Sprintf("%s/tmp/config", tmpDir)
|
|
||||||
|
|
||||||
mkDirForTest(t, tempLogsDir)
|
|
||||||
mkDirForTest(t, tempConfigDir)
|
|
||||||
|
|
||||||
defaultConfig := map[string]string{
|
|
||||||
"rulesFp": fmt.Sprintf("%s/rules.json", tempConfigDir),
|
|
||||||
"confFp": fmt.Sprintf("%s/config.json", tempConfigDir),
|
|
||||||
"logFp": fmt.Sprintf("%s/app.log", tempLogsDir),
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := IsDirEmpty(t, tempConfigDir)
|
|
||||||
assert.Equal(t, ok, true)
|
|
||||||
ok = IsDirEmpty(t, tempLogsDir)
|
|
||||||
assert.Equal(t, ok, true)
|
|
||||||
|
|
||||||
expected, err := generateDefaultConfigFile(defaultConfig)
|
|
||||||
assert.Equal(t, err, nil)
|
|
||||||
assert.NotEmpty(t, expected)
|
|
||||||
|
|
||||||
ok = IsDirEmpty(t, tempConfigDir)
|
|
||||||
assert.Equal(t, ok, false)
|
|
||||||
ok = IsDirEmpty(t, tempLogsDir)
|
|
||||||
assert.Equal(t, ok, true)
|
|
||||||
|
|
||||||
}
|
|
8
go.mod
8
go.mod
|
@ -1,11 +1,3 @@
|
||||||
module gocustomurls
|
module gocustomurls
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.9.0
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,10 +0,0 @@
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
183
logger.go
183
logger.go
|
@ -1,18 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"compress/gzip"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"math"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,10 +40,9 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogFile struct {
|
type LogFile struct {
|
||||||
handle *os.File
|
handle *os.File
|
||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
path string
|
path string
|
||||||
fileLock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogFileRec struct {
|
type LogFileRec struct {
|
||||||
|
@ -58,176 +51,28 @@ type LogFileRec struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lf *LogFile) makeCopyTo(dst string) error {
|
func newFileLogger(path string) (*LogFile, error) {
|
||||||
var err error
|
|
||||||
r, err := os.Open(lf.path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer r.Close()
|
|
||||||
|
|
||||||
w, err := os.OpenFile(dst, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if c := w.Close(); err == nil {
|
|
||||||
err = c
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = io.Copy(w, r)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *LogFile) truncate() error {
|
|
||||||
fd, err := os.OpenFile(lf.path, os.O_TRUNC, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not open file %q for truncation: %v", lf.path, err)
|
|
||||||
}
|
|
||||||
err = fd.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not close file handler for %q after truncation: %v", lf.path, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prettyByteSize(b int64) string {
|
|
||||||
bf := float64(b)
|
|
||||||
for _, unit := range []string{"", "K", "M", "G", "T", "P", "E", "Z"} {
|
|
||||||
if math.Abs(bf) < 1024.0 {
|
|
||||||
return fmt.Sprintf("%3.1f%sB", bf, unit)
|
|
||||||
}
|
|
||||||
bf /= 1024.0
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%.1fYB", bf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compressOldFile(fname string) error {
|
|
||||||
reader, err := os.Open(fname)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("compressOldFile: failed to open existing file %s: %w", fname, err)
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
buffer := bufio.NewReader(reader)
|
|
||||||
fnameGz := fname + ".gz"
|
|
||||||
fw, err := os.OpenFile(fnameGz, os.O_WRONLY|os.O_CREATE, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("compressOldFile: failed to open new file %s: %w", fnameGz, err)
|
|
||||||
}
|
|
||||||
defer fw.Close()
|
|
||||||
|
|
||||||
zw, err := gzip.NewWriterLevel(fw, gzip.BestCompression)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("compressOldFile: failed to create gzip writer: %w", err)
|
|
||||||
}
|
|
||||||
defer zw.Close()
|
|
||||||
|
|
||||||
_, err = buffer.WriteTo(zw)
|
|
||||||
if err != nil {
|
|
||||||
_ = zw.Close()
|
|
||||||
_ = fw.Close()
|
|
||||||
return fmt.Errorf("compressOldFile: failed to write to gz file: %w", err)
|
|
||||||
}
|
|
||||||
_ = reader.Close()
|
|
||||||
|
|
||||||
err = os.Remove(fname)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("compressOldFile: failed to delete old file: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *LogFile) rotate() error {
|
|
||||||
lf.fileLock.Lock()
|
|
||||||
defer lf.fileLock.Unlock()
|
|
||||||
|
|
||||||
prefix := fmt.Sprintf("%s.%s", lf.handle.Name(), time.Now().Format("2006-01-02"))
|
|
||||||
|
|
||||||
// close file to allow for read-only access
|
|
||||||
err := lf.handle.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a copy of the old log file
|
|
||||||
err = lf.makeCopyTo(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// compress the new log file
|
|
||||||
err = compressOldFile(prefix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// truncate the old log file
|
|
||||||
err = lf.truncate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.OpenFile(lf.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
lf.handle = f
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lf *LogFile) open(maxSize string) error {
|
|
||||||
f, err := os.OpenFile(lf.path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
finfo, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
curSize := prettyByteSize(finfo.Size())
|
|
||||||
if curSize > maxSize {
|
|
||||||
err = lf.rotate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lf.handle = f
|
|
||||||
lf.logger = log.New(f, "", 0)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFileLogger(path string, maxSize string) (*LogFile, error) {
|
|
||||||
requestedFile := filepath.Clean(filepath.Join("/", path))
|
requestedFile := filepath.Clean(filepath.Join("/", path))
|
||||||
parentDir := filepath.Dir(requestedFile)
|
parentDir := filepath.Dir(requestedFile)
|
||||||
err := os.MkdirAll(parentDir, 0755)
|
err := os.MkdirAll(parentDir, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
lf := &LogFile{
|
f, err := os.OpenFile(requestedFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
path: path,
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
err = lf.open(maxSize)
|
return &LogFile{
|
||||||
return lf, err
|
handle: f,
|
||||||
// f, err := os.OpenFile(requestedFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
logger: log.New(f, "", 0),
|
||||||
// if err != nil {
|
path: path,
|
||||||
// return nil, err
|
}, nil
|
||||||
// }
|
|
||||||
// return &LogFile{
|
|
||||||
// handle: f,
|
|
||||||
// logger: log.New(f, "", 0),
|
|
||||||
// path: path,
|
|
||||||
// }, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *LogFile) Close() error {
|
func (f *LogFile) Close() error {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
f.fileLock.Lock()
|
|
||||||
defer f.fileLock.Unlock()
|
|
||||||
err := f.handle.Close()
|
err := f.handle.Close()
|
||||||
f.handle = nil
|
f.handle = nil
|
||||||
return err
|
return err
|
||||||
|
@ -295,8 +140,6 @@ func (f *LogFile) WriteLog(r *http.Request) error {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
f.fileLock.Lock()
|
|
||||||
defer f.fileLock.Unlock()
|
|
||||||
var rec = make(map[string]string)
|
var rec = make(map[string]string)
|
||||||
rec["method"] = r.Method
|
rec["method"] = r.Method
|
||||||
rec["requestUri"] = r.RequestURI
|
rec["requestUri"] = r.RequestURI
|
||||||
|
|
131
main.go
131
main.go
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
@ -32,27 +33,27 @@ func flagsSet(flags *flag.FlagSet) map[string]bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateDefaults - generate the default values for the rules.json and log files
|
// generateDefaults - generate the default values for the rules.json and log files
|
||||||
// func generateDefaults(rulesfp string, logfp string) (string, string, error) {
|
func generateDefaults(rulesfp string, logfp string) (string, string, error) {
|
||||||
// var newlogfp, newrulesfp string
|
var newlogfp, newrulesfp string
|
||||||
// var err error
|
var err error
|
||||||
// newlogfp = logfp
|
newlogfp = logfp
|
||||||
// newrulesfp = rulesfp
|
newrulesfp = rulesfp
|
||||||
// if len(newrulesfp) == 0 {
|
if len(newrulesfp) == 0 {
|
||||||
// dir, err := os.UserConfigDir()
|
dir, err := os.UserConfigDir()
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return newrulesfp, newlogfp, err
|
return newrulesfp, newlogfp, err
|
||||||
// }
|
}
|
||||||
// newrulesfp = filepath.Join(dir, "gocustomcurls", "rules.json")
|
newrulesfp = filepath.Join(dir, "gocustomcurls", "rules.json")
|
||||||
// }
|
}
|
||||||
// if len(newlogfp) == 0 {
|
if len(newlogfp) == 0 {
|
||||||
// dir, err := os.UserHomeDir()
|
dir, err := os.UserHomeDir()
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return newrulesfp, newlogfp, err
|
return newrulesfp, newlogfp, err
|
||||||
// }
|
}
|
||||||
// newlogfp = filepath.Join(dir, ".gocustomurls", "logs", "app.log")
|
newlogfp = filepath.Join(dir, ".gocustomurls", "logs", "app.log")
|
||||||
// }
|
}
|
||||||
// return newrulesfp, newlogfp, err
|
return newrulesfp, newlogfp, err
|
||||||
// }
|
}
|
||||||
|
|
||||||
// isValidPort returns true if the port is valid
|
// isValidPort returns true if the port is valid
|
||||||
// following the RFC https://datatracker.ietf.org/doc/html/rfc6056#section-2.1
|
// following the RFC https://datatracker.ietf.org/doc/html/rfc6056#section-2.1
|
||||||
|
@ -73,11 +74,9 @@ func main() {
|
||||||
flags.PrintDefaults()
|
flags.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
confFlag := flags.String("conf", "", "Required. Contains all the configurations options")
|
portFlag := flags.String("port", "7070", "Optional. Default port is 7070. Port to listen to")
|
||||||
|
rulesFileFlag := flags.String("config", "", "Optional. Contains go-import mapping")
|
||||||
// portFlag := flags.String("port", "7070", "Optional. Default port is 7070. Port to listen to")
|
logFileFlag := flags.String("logfile", "", "Optional. Default log file")
|
||||||
// rulesFileFlag := flags.String("rules", "", "Optional. Contains go-import mapping")
|
|
||||||
// logFileFlag := flags.String("logfile", "", "Optional. Default log file")
|
|
||||||
flags.Parse(os.Args[1:])
|
flags.Parse(os.Args[1:])
|
||||||
|
|
||||||
if len(flags.Args()) > 1 {
|
if len(flags.Args()) > 1 {
|
||||||
|
@ -88,23 +87,14 @@ func main() {
|
||||||
|
|
||||||
allSetFlags := flagsSet(flags)
|
allSetFlags := flagsSet(flags)
|
||||||
|
|
||||||
if !allSetFlags["conf"] {
|
var port string
|
||||||
errorLog.Println("Error: conf arguments must be set")
|
if allSetFlags["port"] {
|
||||||
flags.Usage()
|
port = *portFlag
|
||||||
os.Exit(1)
|
} else {
|
||||||
|
port = "7070"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use only one flag conf with a conf file that
|
p, err := strconv.Atoi(port)
|
||||||
// contains the following configuration, port, logfile, rulesfile, sizeofRotation
|
|
||||||
conf := *confFlag
|
|
||||||
c := &Config{}
|
|
||||||
pConf, err := c.LoadMainConfigFile(conf)
|
|
||||||
if err != nil {
|
|
||||||
errorLog.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := strconv.Atoi(pConf.Port)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Println(err)
|
errorLog.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -115,52 +105,41 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.LoadMappingFile(pConf.RulesFp)
|
var rulesFile string
|
||||||
if err != nil {
|
|
||||||
errorLog.Println(err)
|
if allSetFlags["config"] {
|
||||||
os.Exit(1)
|
rulesFile = *rulesFileFlag
|
||||||
}
|
}
|
||||||
l, err := newFileLogger(pConf.LogFp, pConf.SizeToRotate)
|
|
||||||
|
var logFile string
|
||||||
|
if allSetFlags["logFile"] {
|
||||||
|
logFile = *logFileFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
rFile, lFile, err := generateDefaults(logFile, rulesFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Println(err)
|
errorLog.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// var rulesFile string
|
// load rules mapping
|
||||||
|
c := &Config{}
|
||||||
// if allSetFlags["config"] {
|
err = c.LoadMappingFile(rFile)
|
||||||
// rulesFile = *rulesFileFlag
|
if err != nil {
|
||||||
// }
|
errorLog.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
// var logFile string
|
}
|
||||||
// if allSetFlags["logFile"] {
|
l, err := newFileLogger(lFile)
|
||||||
// logFile = *logFileFlag
|
if err != nil {
|
||||||
// }
|
errorLog.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
// rFile, lFile, err := generateDefaults(logFile, rulesFile)
|
}
|
||||||
// if err != nil {
|
|
||||||
// errorLog.Println(err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // load rules mapping
|
|
||||||
// c := &Config{}
|
|
||||||
// err = c.LoadMappingFile(rFile)
|
|
||||||
// if err != nil {
|
|
||||||
// errorLog.Println(err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
// l, err := newFileLogger(lFile)
|
|
||||||
// if err != nil {
|
|
||||||
// errorLog.Println(err)
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
|
|
||||||
app := &Application{
|
app := &Application{
|
||||||
Config: c,
|
Config: c,
|
||||||
Log: l,
|
Log: l,
|
||||||
}
|
}
|
||||||
srv := app.Setup(pConf.Port)
|
srv := app.Setup(port)
|
||||||
|
|
||||||
// For graceful shutdowns
|
// For graceful shutdowns
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"mappings": [
|
|
||||||
{
|
|
||||||
"vanity_url":"scale.dev/x/migrate",
|
|
||||||
"protocol":"git",
|
|
||||||
"real_url":"https://github.com/scale/migrate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"vanity_url":"localhost:7070/x/touche",
|
|
||||||
"protocol":"git",
|
|
||||||
"real_url":"https://github.com/mine/touche"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"mappings": [
|
|
||||||
{
|
|
||||||
"vanity_url":"scale.dev/x/migrate",
|
|
||||||
"protocol":"git",
|
|
||||||
"real_url":"https://github.com/scale/migrate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"vanity_url":"codeberg.org/woodpecker-plugins/plugin-gitea-release",
|
|
||||||
"protocol":"git",
|
|
||||||
"real_url":"https://codeberg.org/woodpecker-plugins/gitea-release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"vanity_url":"localhost:7070/x/touche",
|
|
||||||
"protocol":"git",
|
|
||||||
"real_url":"https://github.com/mine/touche"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mkDirForTest(t *testing.T, fp string) {
|
|
||||||
err := os.MkdirAll(fp, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cpFileForTest(t *testing.T, src string, dst string) {
|
|
||||||
var srcfd *os.File
|
|
||||||
var dstfd *os.File
|
|
||||||
var err error
|
|
||||||
var srcinfo os.FileInfo
|
|
||||||
srcfd, err = os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer srcfd.Close()
|
|
||||||
dstfd, err = os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer dstfd.Close()
|
|
||||||
_, err = io.Copy(dstfd, srcfd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
srcinfo, err = os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = os.Chmod(dst, srcinfo.Mode())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeForTest(t *testing.T, fp string, data []byte) {
|
|
||||||
err := os.WriteFile(fp, data, 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsDirEmpty(t *testing.T, name string) bool {
|
|
||||||
f, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// read in ONLY one file
|
|
||||||
_, err = f.Readdir(1)
|
|
||||||
|
|
||||||
// and if the file is EOF... well, the dir is empty.
|
|
||||||
return err == io.EOF
|
|
||||||
}
|
|
Loading…
Reference in New Issue