From eae7418fbc5fd589b3bd2798b02f82f590e4b743 Mon Sep 17 00:00:00 2001 From: ycc Date: Tue, 16 Jul 2024 10:49:36 +0200 Subject: [PATCH] log stuff added --- go.mod | 8 ++++ go.sum | 15 +++++++ logger.go | 41 ++++++++++++++++++++ logs/lokiwriter.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+) create mode 100644 go.sum create mode 100644 logger.go create mode 100644 logs/lokiwriter.go diff --git a/go.mod b/go.mod index b12a132..50b23b8 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module oc-lib go 1.22.0 + +require github.com/rs/zerolog v1.33.0 + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + golang.org/x/sys v0.12.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..98afda4 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/logger.go b/logger.go new file mode 100644 index 0000000..feacb5e --- /dev/null +++ b/logger.go @@ -0,0 +1,41 @@ +package oclib + +import ( + "oc-lib/logs" + "os" + "runtime" + "time" + + "github.com/rs/zerolog" +) + +var logger zerolog.Logger + +// CreateLogger +// Create a new logger +// Parameters: +// - appname: string : the name of the application using oclib +// - url: string : the url of a loki logger, console log only if "" +// Returns: +// - zerolog.Logger : the logger that will log for the library and the app +func CreateLogger(appname string, url string) zerolog.Logger { + + if url != "" { + labels := map[string]string{ + "app": "app", + "code": "go", + "platform": runtime.GOOS, + } + + lokiWriter := logs.NewLokiWriter(url, labels) + + consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} + + multiWriter := zerolog.MultiLevelWriter(consoleWriter, lokiWriter) + + logger = zerolog.New(multiWriter).With().Timestamp().Logger() + } else { + logger = zerolog.New(os.Stdout).With().Timestamp().Logger() + } + return logger +} diff --git a/logs/lokiwriter.go b/logs/lokiwriter.go new file mode 100644 index 0000000..46867ad --- /dev/null +++ b/logs/lokiwriter.go @@ -0,0 +1,97 @@ +package logs + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" +) + +type LokiWriter struct { + url string + labels map[string]string + httpClient *http.Client +} + +type LokiPayload struct { + Streams []LokiStream `json:"streams"` +} + +type LokiStream struct { + Stream map[string]string `json:"stream"` + Values [][]string `json:"values"` +} + +func NewLokiWriter(url string, labels map[string]string) *LokiWriter { + return &LokiWriter{ + url: url, + labels: labels, + httpClient: &http.Client{}, + } +} + +func (w *LokiWriter) Write(p []byte) (n int, err error) { + // Use zerolog to parse the log level + var event map[string]interface{} + if err := json.Unmarshal(p, &event); err != nil { + return 0, fmt.Errorf("failed to unmarshal log event: %w", err) + } + + level := "" + if l, ok := event["level"].(string); ok { + level = l + } + + message := "" + if m, ok := event["message"].(string); ok { + message = m + } + + // Add log level to labels + labels := make(map[string]string) + for k, v := range w.labels { + labels[k] = v + } + labels["level"] = level + + // Format the timestamp in nanoseconds + timestamp := fmt.Sprintf("%d000000", time.Now().UnixNano()/int64(time.Millisecond)) + + stream := LokiStream{ + Stream: labels, + Values: [][]string{ + {timestamp, message}, + }, + } + + payload := LokiPayload{ + Streams: []LokiStream{stream}, + } + + payloadBytes, err := json.Marshal(payload) + if err != nil { + return 0, fmt.Errorf("failed to marshal payload: %w", err) + } + + //fmt.Printf("Sending payload to Loki: %s\n", string(payloadBytes)) + + req, err := http.NewRequest("POST", w.url, bytes.NewReader(payloadBytes)) + if err != nil { + return 0, fmt.Errorf("failed to create HTTP request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := w.httpClient.Do(req) + if err != nil { + return 0, fmt.Errorf("failed to send log to Loki: %w", err) + } + defer resp.Body.Close() + + //fmt.Printf("Loki response status: %d\n", resp.StatusCode) + if resp.StatusCode != http.StatusNoContent { + return 0, fmt.Errorf("received non-204 response from Loki: %d", resp.StatusCode) + } + + return len(p), nil +}