Compare commits
10 Commits
47416f00ed
...
29c31d44f8
Author | SHA1 | Date | |
---|---|---|---|
29c31d44f8 | |||
7e0266aefc | |||
66aae3d758 | |||
4bc389781a | |||
3638c0c4df | |||
2c3d9a94fa | |||
500b4b0a96 | |||
3a94b5e22a | |||
e94fd9e1ee | |||
fdcfbb02ac |
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
@ -30,7 +31,13 @@ func (c *ConsoleHandler) Handle(ctx context.Context, record slog.Record) error {
|
|||||||
colorFunc = color.New(color.FgWhite).SprintfFunc()
|
colorFunc = color.New(color.FgWhite).SprintfFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(colorFunc("%s [%s]: %s", timestamp, record.Level, record.Message))
|
// Get the caller information
|
||||||
|
_, file, line, ok := runtime.Caller(5)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
}
|
||||||
|
fmt.Println(colorFunc("%s [%s] %s:%d: %s", timestamp, record.Level, file, line, record.Message))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
event.go
1
event.go
@ -15,6 +15,7 @@ type Event struct {
|
|||||||
Data json.RawMessage `json:"data"`
|
Data json.RawMessage `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func NewEvent(level, message string, data json.RawMessage) Event {
|
func NewEvent(level, message string, data json.RawMessage) Event {
|
||||||
return Event{
|
return Event{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
|
2
go.mod
2
go.mod
@ -1,4 +1,4 @@
|
|||||||
module git.eeqj.de/sneak/go-simplelog
|
module sneak.berlin/go/simplelog
|
||||||
|
|
||||||
go 1.22.1
|
go 1.22.1
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@ package simplelog
|
|||||||
import "log/slog"
|
import "log/slog"
|
||||||
|
|
||||||
// Handler defines the interface for different log outputs.
|
// Handler defines the interface for different log outputs.
|
||||||
type Handler interface {
|
type ExtendedHandler interface {
|
||||||
slog.Handler
|
slog.Handler
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package simplelog
|
package simplelog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONHandler struct{}
|
type JSONHandler struct{}
|
||||||
|
@ -10,8 +10,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -43,6 +43,8 @@ var (
|
|||||||
|
|
||||||
type RELPHandler struct {
|
type RELPHandler struct {
|
||||||
relpServerURL string
|
relpServerURL string
|
||||||
|
relpHost string
|
||||||
|
relpPort string
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
ch chan Event
|
ch chan Event
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
@ -50,51 +52,52 @@ type RELPHandler struct {
|
|||||||
timer *time.Timer
|
timer *time.Timer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRELPHandler(relpURL string) (*RELPHandler, error) {
|
func NewRELPHandler(relpURL string) (ExtendedHandler, error) {
|
||||||
parsedURL, err := url.Parse(relpURL)
|
parsedURL, err := url.Parse(relpURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error parsing RELP URL: %v", err)
|
return nil, fmt.Errorf("error parsing RELP URL: %v", err)
|
||||||
}
|
}
|
||||||
if parsedURL.Scheme != "tcp" {
|
if parsedURL.Scheme != "tcp" {
|
||||||
return nil, fmt.Errorf("RELP URL must have the tcp scheme, got %s", parsedURL.Scheme)
|
return nil, fmt.Errorf("the RELP URL must have the tcp scheme, got %s", parsedURL.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host, port, err := net.SplitHostPort(parsedURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error splitting host and port: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(cacheDir, "simplelog"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(cacheDir, "simplelog"), 0755); err != nil {
|
||||||
return nil, fmt.Errorf("Failed to create cache directory: %v", err)
|
return nil, fmt.Errorf("failed to create cache directory: %v", err)
|
||||||
}
|
}
|
||||||
r := &RELPHandler{
|
r := &RELPHandler{
|
||||||
relpServerURL: parsedURL.Host,
|
|
||||||
ch: make(chan Event, diskBufferLimit),
|
ch: make(chan Event, diskBufferLimit),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
failedCh: make(chan Event, diskBufferLimit),
|
failedCh: make(chan Event, diskBufferLimit),
|
||||||
timer: time.NewTimer(diskWriteInterval),
|
timer: time.NewTimer(diskWriteInterval),
|
||||||
|
relpHost: host,
|
||||||
|
relpPort: port,
|
||||||
}
|
}
|
||||||
if relpDebug {
|
if relpDebug {
|
||||||
log.Printf("Created new RELP handler for server at %s", r.relpServerURL)
|
log.Printf("Created new RELP handler for server at %s", r.relpServerURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = r.Startup()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RELPHandler) connectToRELPServer() (net.Conn, error) {
|
func (r *RELPHandler) connectToRELPServer() (net.Conn, error) {
|
||||||
parsedURL, err := url.Parse(r.relpServerURL)
|
conn, err := net.Dial("tcp", net.JoinHostPort(r.relpHost, r.relpPort))
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error parsing RELP URL: %v", err)
|
|
||||||
}
|
|
||||||
if parsedURL.Scheme != "tcp" {
|
|
||||||
return nil, fmt.Errorf("RELP URL must have the tcp scheme, got %s", parsedURL.Scheme)
|
|
||||||
}
|
|
||||||
host, port, err := net.SplitHostPort(parsedURL.Host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error splitting host and port: %v", err)
|
|
||||||
}
|
|
||||||
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if relpDebug {
|
if relpDebug {
|
||||||
log.Printf("Failed to connect to RELP server at %s: %v", net.JoinHostPort(host, port), err)
|
log.Printf("Failed to connect to RELP server at %s: %v", net.JoinHostPort(r.relpHost, r.relpPort), err)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if relpDebug {
|
if relpDebug {
|
||||||
log.Printf("Successfully connected to RELP server at %s", net.JoinHostPort(host, port))
|
log.Printf("Successfully connected to RELP server at %s", net.JoinHostPort(r.relpHost, r.relpPort))
|
||||||
}
|
}
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
@ -139,13 +142,20 @@ func (r *RELPHandler) Handle(ctx context.Context, record slog.Record) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error marshaling attributes: %v", err)
|
return fmt.Errorf("error marshaling attributes: %v", err)
|
||||||
}
|
}
|
||||||
event := NewEvent(record.Level.String(), record.Message, jsonData)
|
// Get the caller information
|
||||||
select {
|
_, file, line, ok := runtime.Caller(5)
|
||||||
case r.ch <- event:
|
if !ok {
|
||||||
return nil // Successfully sent event to channel
|
file = "???"
|
||||||
default:
|
line = 0
|
||||||
return fmt.Errorf("failed to log event: channel is full")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event := NewExtendedEvent(record.Level.String(), record.Message, jsonData, file, line)
|
||||||
|
for _, handler := range cl.handlers {
|
||||||
|
if err := handler.Handle(ctx, event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RELPHandler) receiveEventsFromChannel() {
|
func (r *RELPHandler) receiveEventsFromChannel() {
|
||||||
|
134
simplelog.go
134
simplelog.go
@ -3,12 +3,11 @@ package simplelog
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
"runtime"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
|
|
||||||
"github.com/mattn/go-isatty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -16,53 +15,21 @@ var (
|
|||||||
webhookURL = os.Getenv("LOGGER_WEBHOOK_URL")
|
webhookURL = os.Getenv("LOGGER_WEBHOOK_URL")
|
||||||
)
|
)
|
||||||
|
|
||||||
type CustomHandler struct {
|
|
||||||
handlers []slog.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *CustomHandler) Handle(ctx context.Context, record slog.Record) error {
|
|
||||||
for _, handler := range cl.handlers {
|
|
||||||
if err := handler.Handle(ctx, record); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *CustomHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
|
||||||
for _, handler := range cl.handlers {
|
|
||||||
if handler.Enabled(ctx, level) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *CustomHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
newHandlers := make([]slog.Handler, len(cl.handlers))
|
|
||||||
for i, handler := range cl.handlers {
|
|
||||||
newHandlers[i] = handler.WithAttrs(attrs)
|
|
||||||
}
|
|
||||||
return &CustomHandler{handlers: newHandlers}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cl *CustomHandler) WithGroup(name string) slog.Handler {
|
|
||||||
newHandlers := make([]slog.Handler, len(cl.handlers))
|
|
||||||
for i, handler := range cl.handlers {
|
|
||||||
newHandlers[i] = handler.WithGroup(name)
|
|
||||||
}
|
|
||||||
return &CustomHandler{handlers: newHandlers}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ourCustomLogger *slog.Logger
|
var ourCustomLogger *slog.Logger
|
||||||
|
var ourCustomHandler slog.Handler
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ourCustomLogger = NewCustomHandler()
|
ourCustomHandler = NewMultiplexHandler()
|
||||||
|
ourCustomLogger = slog.New(ourCustomHandler)
|
||||||
slog.SetDefault(ourCustomLogger)
|
slog.SetDefault(ourCustomLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCustomHandler() *CustomHandler {
|
type MultiplexHandler struct {
|
||||||
cl := &CustomHandler{}
|
handlers []ExtendedHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMultiplexHandler() slog.Handler {
|
||||||
|
cl := &MultiplexHandler{}
|
||||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||||
cl.handlers = append(cl.handlers, NewConsoleHandler())
|
cl.handlers = append(cl.handlers, NewConsoleHandler())
|
||||||
} else {
|
} else {
|
||||||
@ -84,3 +51,84 @@ func NewCustomHandler() *CustomHandler {
|
|||||||
}
|
}
|
||||||
return cl
|
return cl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cl *MultiplexHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||||
|
for _, handler := range cl.handlers {
|
||||||
|
if err := handler.Handle(ctx, record); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *MultiplexHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||||
|
// send us all events
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *MultiplexHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
newHandlers := make([]ExtendedHandler, len(cl.handlers))
|
||||||
|
for i, handler := range cl.handlers {
|
||||||
|
newHandlers[i] = handler.WithAttrs(attrs)
|
||||||
|
}
|
||||||
|
return &MultiplexHandler{handlers: newHandlers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cl *MultiplexHandler) WithGroup(name string) slog.Handler {
|
||||||
|
newHandlers := make([]ExtendedHandler, len(cl.handlers))
|
||||||
|
for i, handler := range cl.handlers {
|
||||||
|
newHandlers[i] = handler.WithGroup(name)
|
||||||
|
}
|
||||||
|
return &MultiplexHandler{handlers: newHandlers}
|
||||||
|
}
|
||||||
|
type ExtendedEvent interface {
|
||||||
|
GetID() uuid.UUID
|
||||||
|
GetTimestamp() time.Time
|
||||||
|
GetLevel() string
|
||||||
|
GetMessage() string
|
||||||
|
GetData() json.RawMessage
|
||||||
|
GetFile() string
|
||||||
|
GetLine() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type extendedEvent struct {
|
||||||
|
Event
|
||||||
|
File string `json:"file"`
|
||||||
|
Line int `json:"line"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetID() uuid.UUID {
|
||||||
|
return e.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetTimestamp() time.Time {
|
||||||
|
return e.Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetLevel() string {
|
||||||
|
return e.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetMessage() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetData() json.RawMessage {
|
||||||
|
return e.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetFile() string {
|
||||||
|
return e.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extendedEvent) GetLine() int {
|
||||||
|
return e.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExtendedEvent(baseEvent Event, file string, line int) ExtendedEvent {
|
||||||
|
return extendedEvent{
|
||||||
|
Event: baseEvent,
|
||||||
|
File: file,
|
||||||
|
Line: line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Set the environment variables for the RELP server URL and optionally for the webhook URL
|
# Set the environment variables for the RELP server URL and optionally for the webhook URL
|
||||||
export LOGGER_RELP_URL="tcp://10.201.1.18:20514"
|
export LOGGER_RELP_URL="tcp://10.201.1.18:20514"
|
||||||
export LOGGER_WEBHOOK_URL="https://example.com/webhook"
|
#export LOGGER_WEBHOOK_URL="https://example.com/webhook"
|
||||||
export RELP_DEBUG=1
|
export RELP_DEBUG=1
|
||||||
|
|
||||||
# Run the Go program
|
# Run the Go program
|
||||||
|
Loading…
Reference in New Issue
Block a user