blob: ad32db9d882dfe76ad582850bec6484092a44f08 [file] [log] [blame]
package main
import (
"fmt"
"math/rand"
"time"
"github.com/go-logr/logr"
)
// This application demonstrates the usage of logger.
// It's a simple reconciliation loop that pretends to
// receive notifications about updates from a some API
// server, make some changes, and then submit updates of
// its own.
// This uses object-based logging. It's also possible
// (but a bit trickier) to use file-level "base" loggers.
var objectMap = map[string]Object{
"obj1": Object{
Name: "obj1",
Kind: "one",
Details: 33,
},
"obj2": Object{
Name: "obj2",
Kind: "two",
Details: "hi",
},
"obj3": Object{
Name: "obj3",
Kind: "one",
Details: 1,
},
}
type Object struct {
Name string
Kind string
Details interface{}
}
type Client struct {
objects map[string]Object
log logr.Logger
}
func (c *Client) Get(key string) (Object, error) {
c.log.V(1).Info("fetching object", "key", key)
obj, ok := c.objects[key]
if !ok {
return Object{}, fmt.Errorf("no object %s exists", key)
}
c.log.V(1).Info("pretending to deserialize object", "key", key, "json", "[insert real json here]")
return obj, nil
}
func (c *Client) Save(obj Object) error {
c.log.V(1).Info("saving object", "key", obj.Name, "object", obj)
if rand.Intn(2) == 0 {
return fmt.Errorf("couldn't save to %s", obj.Name)
}
c.log.V(1).Info("pretending to post object", "key", obj.Name, "url", "https://fake.test")
return nil
}
func (c *Client) WatchNext() string {
time.Sleep(2*time.Second)
keyInd := rand.Intn(len(c.objects))
currInd := 0
for key := range c.objects {
if currInd == keyInd {
return key
}
currInd++
}
c.log.Info("watch ended")
return ""
}
type Controller struct {
log logr.Logger
expectedKind string
client *Client
}
func (c *Controller) Run() {
c.log.Info("starting reconciliation")
for key := c.client.WatchNext(); key != ""; key = c.client.WatchNext() {
// we can make more specific loggers if we always want to attach a particular named value
log := c.log.WithValues("key", key)
// fetch our object
obj, err := c.client.Get(key)
if err != nil {
log.Error(err, "unable to reconcile object")
continue
}
// make sure it's as expected
if obj.Kind != c.expectedKind {
log.Error(nil, "got object that wasn't expected kind", "actual-kind", obj.Kind, "object", obj)
continue
}
// always log the object with log messages
log = log.WithValues("object", obj)
log.V(1).Info("reconciling object for key")
// Do some complicated updates updates
obj.Details = obj.Details.(int) * 2
// actually save the updates
log.V(1).Info("updating object", "details", obj.Details)
if err := c.client.Save(obj); err != nil {
log.Error(err, "unable to reconcile object")
}
}
c.log.Info("stopping reconciliation")
}
func NewController(log logr.Logger, objectKind string) *Controller {
ctrlLogger := log.WithName("controller").WithName(objectKind)
client := &Client{
log: ctrlLogger.WithName("client"),
objects: objectMap,
}
return &Controller{
log: ctrlLogger,
expectedKind: objectKind,
client: client,
}
}
func main() {
// use a fake implementation just for demonstration purposes
log := NewTabLogger()
// update objects with the "one" kind
ctrl := NewController(log, "one")
ctrl.Run()
}