latest refactoring

This commit is contained in:
Jeffrey Paul 2019-11-03 10:00:01 -08:00
parent 58954151b5
commit 3b543fe5a5
6 changed files with 298 additions and 140 deletions

View File

@ -25,7 +25,7 @@ ifneq ($(UNAME_S),Darwin)
GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)" GOFLAGS = -ldflags "-linkmode external -extldflags -static $(GOLDFLAGS)"
endif endif
default: rundebug default: run
rundebug: build rundebug: build
DEBUG=1 ./$(FN) DEBUG=1 ./$(FN)

5
api.go
View File

@ -48,7 +48,9 @@ func (a *TootArchiverAPIServer) getRouter() *gin.Engine {
}) })
r.GET("/", func(c *gin.Context) { r.GET("/", func(c *gin.Context) {
ir := a.archiver.locator.instanceReport() ir := a.archiver.manager.instanceReport()
il := a.archiver.manager.instanceListForApi()
c.JSON(200, gin.H{ c.JSON(200, gin.H{
// FIXME(sneak) add more stuff here // FIXME(sneak) add more stuff here
"status": "ok", "status": "ok",
@ -59,6 +61,7 @@ func (a *TootArchiverAPIServer) getRouter() *gin.Engine {
"up": ir.up, "up": ir.up,
"identified": ir.identified, "identified": ir.identified,
}, },
"instanceList": il,
}) })
}) })

View File

@ -7,6 +7,7 @@ import "net/http"
import "strings" import "strings"
import "sync" import "sync"
import "time" import "time"
import "errors"
import "github.com/rs/zerolog/log" import "github.com/rs/zerolog/log"
@ -32,90 +33,123 @@ const (
InstanceStatusNone InstanceStatus = iota InstanceStatusNone InstanceStatus = iota
InstanceStatusUnknown InstanceStatusUnknown
InstanceStatusAlive InstanceStatusAlive
InstanceStatusIdentified
InstanceStatusFailure InstanceStatusFailure
) )
type Instance struct { type Instance struct {
sync.Mutex sync.RWMutex
errorCount uint errorCount uint
successCount uint successCount uint
highestId int highestId int
hostName string hostname string
identified bool identified bool
fetching bool
impl InstanceImplementation impl InstanceImplementation
backend *InstanceBackend backend *InstanceBackend
status InstanceStatus status InstanceStatus
nextCheck *time.Time nextFetch time.Time
nodeInfoUrl string nodeInfoUrl string
serverVersion string serverVersion string
} }
func NewInstance(hostname string) *Instance { func NewInstance(hostname InstanceHostname) *Instance {
i := new(Instance) i := new(Instance)
i.hostName = hostname i.hostname = string(hostname)
i.status = InstanceStatusUnknown i.status = InstanceStatusUnknown
t := time.Now().Add(-1 * time.Second) i.nextFetch = time.Now().Add(-1 * time.Second)
i.nextCheck = &t
// FIXME make checks detect the node type instead of in the constructor // FIXME make checks detect the node type instead of in the constructor
return i return i
} }
func (i *Instance) setNextCheck(d time.Duration) { func (i *Instance) setNextFetchAfter(d time.Duration) {
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
then := time.Now().Add(d) i.nextFetch = time.Now().Add(d)
i.nextCheck = &then
} }
func (i *Instance) dueForCheck() bool { func (self *Instance) setFetching(f bool) {
i.Lock() self.Lock()
defer i.Unlock() defer self.Unlock()
return i.nextCheck.Before(time.Now()) self.fetching = f
} }
func (i *Instance) detectNodeType() { func (self *Instance) Fetch() {
i.Lock() self.setFetching(true)
if i.impl > Unknown { defer self.setFetching(false)
i.Unlock()
err := self.detectNodeTypeIfNecessary()
if err != nil {
self.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
log.Debug().
Str("hostname", self.hostname).
Err(err).
Msg("unable to fetch instance metadata")
return return
} }
i.Unlock()
i.fetchNodeInfo() //self.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
log.Info().Msgf("i (%s) should check for toots", self.hostname)
}
func (self *Instance) dueForFetch() bool {
self.RLock()
defer self.RUnlock()
if self.fetching {
return false
}
return self.nextFetch.Before(time.Now())
}
func (i *Instance) detectNodeTypeIfNecessary() error {
i.RLock()
if i.impl > Unknown {
i.RUnlock()
return nil
}
i.RUnlock()
return i.fetchNodeInfo()
} }
func (i *Instance) registerError() { func (i *Instance) registerError() {
i.setNextCheck(INSTANCE_ERROR_INTERVAL) i.setNextFetchAfter(INSTANCE_ERROR_INTERVAL)
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
i.errorCount++ i.errorCount++
} }
func (i *Instance) registerSuccess() { func (i *Instance) registerSuccess() {
i.setNextCheck(INSTANCE_SPIDER_INTERVAL) i.setNextFetchAfter(INSTANCE_SPIDER_INTERVAL)
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
i.successCount++ i.successCount++
} }
func (i *Instance) fetchNodeInfoURL() { func (i *Instance) Up() bool {
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostName) i.Lock()
defer i.Unlock()
return i.successCount > 0
}
func (i *Instance) fetchNodeInfoURL() error {
url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostname)
var c = &http.Client{ var c = &http.Client{
Timeout: INSTANCE_HTTP_TIMEOUT, Timeout: INSTANCE_HTTP_TIMEOUT,
} }
log.Debug(). log.Debug().
Str("url", url). Str("url", url).
Str("hostname", i.hostName). Str("hostname", i.hostname).
Msg("fetching nodeinfo reference URL") Msg("fetching nodeinfo reference URL")
resp, err := c.Get(url) resp, err := c.Get(url)
if err != nil { if err != nil {
log.Debug(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msg("unable to fetch nodeinfo, node is down?") Msg("unable to fetch nodeinfo, node is down?")
i.registerError() i.registerError()
return return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -123,28 +157,28 @@ func (i *Instance) fetchNodeInfoURL() {
if err != nil { if err != nil {
log.Debug(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msg("unable to read nodeinfo") Msg("unable to read nodeinfo")
i.registerError() i.registerError()
return return err
} }
nir := new(NodeInfoWellKnownResponse) nir := new(NodeInfoWellKnownResponse)
err = json.Unmarshal(body, &nir) err = json.Unmarshal(body, &nir)
if err != nil { if err != nil {
log.Error(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msg("unable to parse nodeinfo, node is weird") Msg("unable to parse nodeinfo, node is weird")
i.registerError() i.registerError()
return return err
} }
for _, item := range nir.Links { for _, item := range nir.Links {
if item.Rel == NodeInfoSchemaVersionTwoName { if item.Rel == NodeInfoSchemaVersionTwoName {
log.Info(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Str("nodeinfourl", item.Href). Str("nodeinfourl", item.Href).
Msg("success fetching url for nodeinfo") Msg("success fetching url for nodeinfo")
@ -152,32 +186,27 @@ func (i *Instance) fetchNodeInfoURL() {
i.nodeInfoUrl = item.Href i.nodeInfoUrl = item.Href
i.Unlock() i.Unlock()
i.registerSuccess() i.registerSuccess()
return return nil
} }
log.Debug().
Str("hostname", i.hostname).
Str("item-rel", item.Rel).
Str("item-href", item.Href).
Msg("found key in nodeinfo")
} }
log.Error(). log.Error().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Msg("incomplete nodeinfo") Msg("incomplete nodeinfo")
i.registerError() i.registerError()
return return errors.New("incomplete nodeinfo")
} }
func (i *Instance) fetchNodeInfo() { func (i *Instance) fetchNodeInfo() error {
i.fetchNodeInfoURL() err := i.fetchNodeInfoURL()
i.Lock() if err != nil {
failure := false return err
if i.nodeInfoUrl == "" {
log.Error().
Str("hostname", i.hostName).
Msg("unable to fetch nodeinfo as nodeinfo URL cannot be determined")
failure = true
}
i.Unlock()
if failure == true {
return
} }
var c = &http.Client{ var c = &http.Client{
@ -186,19 +215,19 @@ func (i *Instance) fetchNodeInfo() {
//FIXME make sure the nodeinfourl is on the same domain as the instance //FIXME make sure the nodeinfourl is on the same domain as the instance
//hostname //hostname
i.Lock() i.RLock()
url := i.nodeInfoUrl url := i.nodeInfoUrl
i.Unlock() i.RUnlock()
resp, err := c.Get(url) resp, err := c.Get(url)
if err != nil { if err != nil {
log.Error(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msgf("unable to fetch nodeinfo data") Msgf("unable to fetch nodeinfo data")
i.registerError() i.registerError()
return return err
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -206,28 +235,28 @@ func (i *Instance) fetchNodeInfo() {
if err != nil { if err != nil {
log.Error(). log.Error().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msgf("unable to read nodeinfo data") Msgf("unable to read nodeinfo data")
i.registerError() i.registerError()
return return err
} }
ni := new(NodeInfoVersionTwoSchema) ni := new(NodeInfoVersionTwoSchema)
err = json.Unmarshal(body, &ni) err = json.Unmarshal(body, &ni)
if err != nil { if err != nil {
log.Error(). log.Error().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Err(err). Err(err).
Msgf("unable to parse nodeinfo") Msgf("unable to parse nodeinfo")
i.registerError() i.registerError()
return return err
} }
log.Info(). log.Debug().
Str("serverVersion", ni.Software.Version). Str("serverVersion", ni.Software.Version).
Str("software", ni.Software.Name). Str("software", ni.Software.Name).
Str("hostName", i.hostName). Str("hostname", i.hostname).
Str("nodeInfoUrl", i.nodeInfoUrl). Str("nodeInfoUrl", i.nodeInfoUrl).
Msg("received nodeinfo from instance") Msg("received nodeinfo from instance")
@ -238,31 +267,29 @@ func (i *Instance) fetchNodeInfo() {
ni.Software.Name = strings.ToLower(ni.Software.Name) ni.Software.Name = strings.ToLower(ni.Software.Name)
if ni.Software.Name == "pleroma" { if ni.Software.Name == "pleroma" {
log.Info(). log.Debug().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Str("software", ni.Software.Name). Str("software", ni.Software.Name).
Msg("detected server software") Msg("detected server software")
i.registerSuccess() i.registerSuccess()
i.identified = true i.identified = true
i.impl = Pleroma i.impl = Pleroma
i.status = InstanceStatusAlive i.status = InstanceStatusIdentified
return nil
} else if ni.Software.Name == "mastodon" { } else if ni.Software.Name == "mastodon" {
log.Info().
Str("hostname", i.hostName).
Str("software", ni.Software.Name).
Msg("detected server software")
i.registerSuccess() i.registerSuccess()
i.identified = true i.identified = true
i.impl = Mastodon i.impl = Mastodon
i.status = InstanceStatusAlive i.status = InstanceStatusIdentified
return nil
} else { } else {
log.Error(). log.Error().
Str("hostname", i.hostName). Str("hostname", i.hostname).
Str("software", ni.Software.Name). Str("software", ni.Software.Name).
Msg("unknown implementation on server") Msg("FIXME unknown server implementation")
i.registerError() i.registerError()
return errors.New("FIXME unknown server implementation")
} }
return
} }
/* /*
@ -283,12 +310,16 @@ func (i *Instance) fetchRecentToots() ([]byte, error) {
/* /*
func (self *PleromaBackend) fetchRecentToots() ([]byte, error) { func (self *PleromaBackend) fetchRecentToots() ([]byte, error) {
//url := fmt.Sprintf("https://%s/api/statuses/public_and_external_timeline.json?count=100", i.hostName) //url :=
//fmt.Sprintf("https://%s/api/statuses/public_and_external_timeline.json?count=100",
//i.hostname)
return nil, nil return nil, nil
} }
func (self *MastodonBackend) fetchRecentTootsJsonFromMastodon() ([]byte, error) { func (self *MastodonBackend) fetchRecentTootsJsonFromMastodon() ([]byte, error) {
//url := fmt.Sprintf("https://%s/api/v1/timelines/public?limit=40&local=true", i.hostName) //url :=
//fmt.Sprintf("https://%s/api/v1/timelines/public?limit=40&local=true",
//i.hostname)
return nil, nil return nil, nil
} }
*/ */

View File

@ -1,7 +1,6 @@
package main package main
import "encoding/json" import "encoding/json"
import "fmt"
import "io/ioutil" import "io/ioutil"
import "net/http" import "net/http"
import "time" import "time"
@ -11,6 +10,8 @@ import "github.com/rs/zerolog/log"
const INDEX_API_TIMEOUT = time.Second * 60 const INDEX_API_TIMEOUT = time.Second * 60
var USER_AGENT = "https://github.com/sneak/feta indexer bot; sneak@sneak.berlin for feedback"
// check with indices only hourly // check with indices only hourly
var INDEX_CHECK_INTERVAL = time.Second * 60 * 60 var INDEX_CHECK_INTERVAL = time.Second * 60 * 60
@ -26,13 +27,11 @@ type InstanceLocator struct {
pleromaIndexNextRefresh *time.Time pleromaIndexNextRefresh *time.Time
mastodonIndexNextRefresh *time.Time mastodonIndexNextRefresh *time.Time
reportInstanceVia chan InstanceHostname reportInstanceVia chan InstanceHostname
instances map[string]*Instance
sync.Mutex sync.Mutex
} }
func NewInstanceLocator() *InstanceLocator { func NewInstanceLocator() *InstanceLocator {
i := new(InstanceLocator) i := new(InstanceLocator)
i.instances = make(map[string]*Instance)
n := time.Now() n := time.Now()
i.pleromaIndexNextRefresh = &n i.pleromaIndexNextRefresh = &n
i.mastodonIndexNextRefresh = &n i.mastodonIndexNextRefresh = &n
@ -45,20 +44,17 @@ func (self *InstanceLocator) AddInstanceNotificationChannel(via chan InstanceHos
self.reportInstanceVia = via self.reportInstanceVia = via
} }
func (self *InstanceLocator) addInstance(hostname string) { func (self *InstanceLocator) addInstance(hostname InstanceHostname) {
self.Lock() // receiver (InstanceManager) is responsible for de-duping against its
defer self.Unlock() // map
// only add it if we haven't seen the hostname before self.reportInstanceVia <- hostname
if self.instances[hostname] == nil {
log.Info().Str("hostname", hostname).Msgf("adding discovered instance")
self.instances[hostname] = NewInstance(hostname)
}
} }
func (self *InstanceLocator) Locate() { func (self *InstanceLocator) Locate() {
log.Info().Msg("InstanceLocator starting")
x := time.Now() x := time.Now()
for { for {
log.Info().Msg("InstanceLocator tick")
if self.pleromaIndexNextRefresh.Before(time.Now()) { if self.pleromaIndexNextRefresh.Before(time.Now()) {
self.locatePleroma() self.locatePleroma()
} }
@ -74,61 +70,24 @@ func (self *InstanceLocator) Locate() {
log.Debug(). log.Debug().
Str("nextPleromaIndexFetch", self.pleromaIndexNextRefresh.Format(time.RFC3339)). Str("nextPleromaIndexFetch", self.pleromaIndexNextRefresh.Format(time.RFC3339)).
Send() Send()
self.logInstanceReport()
} }
} }
} }
func (self *InstanceLocator) logInstanceReport() {
r := self.instanceReport()
log.Info().
Uint("up", r.up).
Uint("total", r.total).
Uint("identified", r.identified).
Msg("instance report")
}
type InstanceLocatorReport struct {
up uint
identified uint
total uint
}
func (r *InstanceLocatorReport) String() string {
return fmt.Sprintf("up=%d identified=%d total=%d", r.up, r.identified, r.total)
}
func (self *InstanceLocator) NumInstances() uint {
return self.instanceReport().total
}
func (self *InstanceLocator) instanceReport() *InstanceLocatorReport {
self.Lock()
defer self.Unlock()
r := new(InstanceLocatorReport)
r.total = uint(len(self.instances))
for _, elem := range self.instances {
if elem.identified == true {
r.identified = r.identified + 1
}
if elem.status == InstanceStatusAlive {
r.up = r.up + 1
}
}
return r
}
func (self *InstanceLocator) locateMastodon() { func (self *InstanceLocator) locateMastodon() {
var c = &http.Client{ var c = &http.Client{
Timeout: INDEX_API_TIMEOUT, Timeout: INDEX_API_TIMEOUT,
} }
resp, err := c.Get(mastodonIndexUrl) req, err := http.NewRequest("GET", mastodonIndexUrl, nil)
if err != nil {
panic(err)
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := c.Do(req)
if err != nil { if err != nil {
log.Error().Msgf("unable to fetch mastodon instance list: %s", err) log.Error().Msgf("unable to fetch mastodon instance list: %s", err)
t := time.Now().Add(INDEX_ERROR_INTERVAL) t := time.Now().Add(INDEX_ERROR_INTERVAL)
@ -162,7 +121,7 @@ func (self *InstanceLocator) locateMastodon() {
} }
for _, instance := range mi.Instances { for _, instance := range mi.Instances {
self.addInstance(instance.Name) self.addInstance(InstanceHostname(instance.Name))
} }
t := time.Now().Add(INDEX_CHECK_INTERVAL) t := time.Now().Add(INDEX_CHECK_INTERVAL)
@ -175,7 +134,14 @@ func (self *InstanceLocator) locatePleroma() {
var c = &http.Client{ var c = &http.Client{
Timeout: INDEX_API_TIMEOUT, Timeout: INDEX_API_TIMEOUT,
} }
resp, err := c.Get(pleromaIndexUrl)
req, err := http.NewRequest("GET", pleromaIndexUrl, nil)
if err != nil {
panic(err)
}
req.Header.Set("User-Agent", USER_AGENT)
resp, err := c.Do(req)
if err != nil { if err != nil {
log.Error().Msgf("unable to fetch pleroma instance list: %s", err) log.Error().Msgf("unable to fetch pleroma instance list: %s", err)
@ -211,7 +177,7 @@ func (self *InstanceLocator) locatePleroma() {
} }
for _, instance := range *pi { for _, instance := range *pi {
self.addInstance(instance.Domain) self.addInstance(InstanceHostname(instance.Domain))
} }
t := time.Now().Add(INDEX_CHECK_INTERVAL) t := time.Now().Add(INDEX_CHECK_INTERVAL)
self.Lock() self.Lock()

View File

@ -2,24 +2,63 @@ package main
import "sync" import "sync"
import "time" import "time"
import "fmt"
import "runtime"
//import "github.com/rs/zerolog/log" import "github.com/rs/zerolog/log"
type InstanceBackend interface { type InstanceBackend interface {
//FIXME //FIXME
} }
type InstanceManager struct { type InstanceManager struct {
sync.Mutex mu sync.Mutex
instances map[InstanceHostname]*Instance instances map[InstanceHostname]*Instance
newInstanceNotifications chan InstanceHostname newInstanceNotifications chan InstanceHostname
startup time.Time
} }
func NewInstanceManager() *InstanceManager { func NewInstanceManager() *InstanceManager {
i := new(InstanceManager) i := new(InstanceManager)
i.instances = make(map[InstanceHostname]*Instance)
return i return i
} }
func (self *InstanceManager) logCaller(msg string) {
fpcs := make([]uintptr, 1)
// Skip 2 levels to get the caller
n := runtime.Callers(3, fpcs)
if n == 0 {
log.Debug().Msg("MSG: NO CALLER")
}
caller := runtime.FuncForPC(fpcs[0] - 1)
if caller == nil {
log.Debug().Msg("MSG CALLER WAS NIL")
}
// Print the file name and line number
filename, line := caller.FileLine(fpcs[0] - 1)
function := caller.Name()
log.Debug().
Str("filename", filename).
Int("linenum", line).
Str("function", function).
Msg(msg)
}
func (self *InstanceManager) Lock() {
self.logCaller("instancemanager attempting to lock")
self.mu.Lock()
self.logCaller("instancemanager locked")
}
func (self *InstanceManager) Unlock() {
self.mu.Unlock()
self.logCaller("instancemanager unlocked")
}
func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHostname) { func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHostname) {
self.Lock() self.Lock()
defer self.Unlock() defer self.Unlock()
@ -27,8 +66,121 @@ func (self *InstanceManager) AddInstanceNotificationChannel(via chan InstanceHos
} }
func (self *InstanceManager) Manage() { func (self *InstanceManager) Manage() {
self.managerLoop()
}
func (self *InstanceManager) managerLoop() {
log.Info().Msg("InstanceManager starting")
go self.receiveNewInstanceHostnames()
self.startup = time.Now()
for { for {
// FIXME(sneak) log.Info().Msg("InstanceManager tick")
self.Lock()
for _, v := range self.instances {
go func() {
if v.dueForFetch() {
v.Fetch()
}
}()
}
self.Unlock()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }
func (self *InstanceManager) hostnameExists(newhn InstanceHostname) bool {
self.Lock()
for k, _ := range self.instances {
if newhn == k {
self.Unlock()
return true
}
}
self.Unlock()
return false
}
func (self *InstanceManager) addInstanceByHostname(newhn InstanceHostname) {
// only add it if we haven't seen the hostname before
if !self.hostnameExists(newhn) {
i := NewInstance(newhn)
self.Lock()
self.instances[newhn] = i
self.Unlock()
}
}
func (self *InstanceManager) receiveNewInstanceHostnames() {
var newhn InstanceHostname
for {
newhn = <-self.newInstanceNotifications
self.addInstanceByHostname(newhn)
}
}
func (self *InstanceManager) logInstanceReport() {
r := self.instanceReport()
log.Info().
Uint("up", r.up).
Uint("total", r.total).
Uint("identified", r.identified).
Msg("instance report")
}
type InstanceReport struct {
up uint
identified uint
total uint
}
func (r *InstanceReport) String() string {
return fmt.Sprintf("up=%d identified=%d total=%d", r.up, r.identified, r.total)
}
func (self *InstanceManager) NumInstances() uint {
return self.instanceReport().total
}
type InstanceListReport []*InstanceDetail
type InstanceDetail struct {
hostname string
up bool
nextFetch string
}
func (self *InstanceManager) instanceListForApi() InstanceListReport {
var output InstanceListReport
self.Lock()
defer self.Unlock()
for _, v := range self.instances {
id := &InstanceDetail{
hostname: v.hostname,
}
id.up = v.Up()
id.nextFetch = string(time.Now().Sub(v.nextFetch))
output = append(output, id)
fmt.Printf("%s", output)
}
return output
}
func (self *InstanceManager) instanceReport() *InstanceReport {
self.Lock()
defer self.Unlock()
r := new(InstanceReport)
r.total = uint(len(self.instances))
for _, elem := range self.instances {
if elem.identified == true {
r.identified = r.identified + 1
}
if elem.status == InstanceStatusAlive {
r.up = r.up + 1
}
}
return r
}

View File

@ -2,6 +2,7 @@ package main
import "os" import "os"
import "sync" import "sync"
import "time"
import "github.com/rs/zerolog" import "github.com/rs/zerolog"
import "github.com/rs/zerolog/log" import "github.com/rs/zerolog/log"
@ -19,6 +20,11 @@ func app() int {
identify() identify()
// always log in UTC
zerolog.TimestampFunc = func() time.Time {
return time.Now().UTC()
}
zerolog.SetGlobalLevel(zerolog.InfoLevel) zerolog.SetGlobalLevel(zerolog.InfoLevel)
if os.Getenv("DEBUG") != "" { if os.Getenv("DEBUG") != "" {
zerolog.SetGlobalLevel(zerolog.DebugLevel) zerolog.SetGlobalLevel(zerolog.DebugLevel)