pkg/generator/gotext/gotext.go (194 lines of code) (raw):
// Package gotext implements the generator for generic logs.
//
// Configuration file supports including timestamps in log messages
//
// generator:
// type: gotext
// include_timestamp: true
package gotext
import (
"bytes"
"fmt"
"log"
"math/rand"
"strconv"
"strings"
"text/template"
"time"
"github.com/elastic/go-ucfg"
"github.com/elastic/spigot/pkg/generator"
"github.com/elastic/spigot/pkg/random"
)
// Name is the name of the generator in the configuration file and registry
const Name = "gotext"
func init() {
generator.Register(Name, New)
}
var (
FunctionMap = template.FuncMap{
"ToLower": strings.ToLower,
"ToUpper": strings.ToUpper,
"TimestampFormatter": TimestampFormatter,
"RandomIPv4": RandomIPv4,
"RandomPort": RandomPort,
"RandomInt": RandomInt,
"Percent": Percent,
"PlusInt": PlusInt,
"TimesInt": TimesInt,
}
)
func TimestampFormatter(format, whence string) string {
now := time.Now()
dur,err := time.ParseDuration(whence)
if err != nil {
fmt.Println("failed to parse [", whence, "] with error::", err)
} else {
// now choose a random duration within this value
trunc := int(dur.Round(time.Second).Seconds())
seconds := 0
if trunc > 0 {
seconds = rand.Intn(trunc)
}
newdur, err := time.ParseDuration(fmt.Sprintf("-%ds", seconds))
if err != nil {
// ignore
} else {
dur = newdur
}
now = now.Add(dur)
}
// this format is seconds.[1-9]
if strings.HasPrefix(format, "seconds") {
secs := now.Unix()
returnVal := fmt.Sprintf("%d", secs)
result := strings.Split(format, ".")
if len(result) > 1 {
// generate a random number of nanos
nanos := rand.Intn(1_000_000_000)
partialsString := fmt.Sprintf("%09d", nanos)
// figure out what precision to output
precision := 6
if prec, err := strconv.Atoi(result[1]); err == nil {
switch prec {
case 0:
return fmt.Sprintf("%d", secs)
case 1, 2, 3, 4, 5, 6, 7, 8, 9:
precision = prec
default:
precision = 9
}
}
returnVal = fmt.Sprintf("%d.%s", secs, partialsString[0:precision])
}
// return early
return returnVal
}
return now.Format(format)
}
func ToInt(input any) int {
if v, ok := input.(int); ok {
return v
}
switch v := input.(type) {
case string:
result, err := strconv.Atoi(v)
if err != nil {
log.Fatal("Could not convert %v (%T) to int: %v\n", input, input, err)
return 1
}
return result
default:
}
return 1
}
func PlusInt(a, b any) string {
return fmt.Sprintf("%v", ToInt(a) + ToInt(b))
}
func TimesInt(a, b any) string {
return fmt.Sprintf("%v", ToInt(a) * ToInt(b))
}
func Percent(numerator, denominator any) string {
fnum := float64(ToInt(numerator))
dnum := float64(ToInt(denominator))
result := fnum / dnum * 100.0
return fmt.Sprintf("%8.6f", result)
}
func RandomDuration() string {
// return the string interpretation of that value
return fmt.Sprintf("%01d:%02d:%02d", rand.Intn(4), rand.Intn(60), rand.Intn(60))
}
func RandomInt(maximum int) string {
randval := 0
if maximum > 0 {
// get a random value
randval = rand.Intn(maximum)
} else if maximum < 0 {
// get a random value
randval = -1 * rand.Intn(-1 * maximum)
}
// return the string interpretation of that value
return strconv.Itoa(randval)
}
func RandomIPv4() string {
return random.IPv4().String()
}
func RandomPort() string {
return strconv.Itoa(random.Port())
}
type Template struct {
Format string
Tpl *template.Template
}
type Field struct {
Name string `config:"name"`
Type string `config:"type"`
Choices []string `config:"choices"`
template *Template
}
type GoText struct {
Name string
Fields []Field
templates []Template
}
func (g *GoText) Next() ([]byte, error) {
var buf bytes.Buffer
object := make(map[string]any)
object["Timestamp"] = time.Now()
// loop over each field
for _, f := range g.Fields {
object[f.Name] = f.randomize(object)
}
// are there formats?
if len(g.templates) < 1 {
fmt.Printf("i am %v; %v\n", g.Name, g.templates)
return nil, fmt.Errorf("This has no templates to process; bailing")
}
index := rand.Intn(len(g.templates))
// attempt to generate each one
err := g.templates[index].Tpl.Execute(&buf, object)
if err != nil {
log.Fatal("Failed to execute template", g.templates[index].Format, "with error", err)
return nil, err
}
return buf.Bytes(), err
}
// New is Factory for the gotext generator
func New(cfg *ucfg.Config) (generator.Generator, error) {
c := defaultConfig()
if err := cfg.Unpack(&c); err != nil {
return nil, err
}
gotextConfig := c.Config
// check variables
// return
g := &GoText{
Name: gotextConfig.Name,
Fields: nil,
templates: nil,
}
for i, v := range gotextConfig.Fields {
f := Field{
Name: v.Name,
Type: v.Type,
Choices: v.Choices,
}
// if there is a Template field
if v.Template != nil {
t, err := template.New(strconv.Itoa(i)).Funcs(FunctionMap).Parse(*v.Template)
if err != nil {
return nil, err
}
f.template = &Template{
Format: *v.Template,
Tpl: t,
}
}
g.Fields = append(g.Fields, f)
}
for i, v := range gotextConfig.Formats {
t, err := template.New(strconv.Itoa(i)).Funcs(FunctionMap).Parse(*v)
if err != nil {
return nil, err
}
g.templates = append(g.templates, Template{
Format: *v,
Tpl: t,
})
}
return g, nil
}