Skip to content

Commit

Permalink
Add zap logger integration (#4)
Browse files Browse the repository at this point in the history
* refactor: Refactor test suite in gelflogger and cleanup code

The test suite in the gelflogger module is refactored to improve
readability and maintainability. This involved moving the mock server
functions to a separate 'helper' package. Also, the main logger file was
cleaned up by removing unused imports and commented code.

* refactor: Update gelflogger and introduce NewZeroLogger

Implemented a baseLogProcessor in gelflogger's NewLogger for flexible
log field processing. This change improves modularity by shifting the
task of field processing from the exclusive scope of
`formatGELFMessage`, allowing each log entry to be processed
individually within the `Log` function. A redundant test case was also
removed for clarity. Lastly, the `NewZeroLogger` function was added in
`zerologger.go` to create a zerolog logger with a GelfWriter.

* feat: Add zaplogger package and update dependencies

Introduced a new zaplogger package for gelf-logger including tests, and
updated required dependencies in the go.mod and go.sum files. The
zaplogger package houses NewZapLogger, ProcessZapLoggerFields, and
ConvertZapLogLevelToGraylog functions. Additionally, necessary
dependencies like 'go.uber.org/zap' was added for functionality.

* Update go.yml

Updated go.yml according to warning "Node.js 16 actions are deprecated."
  • Loading branch information
jame-developer committed Mar 10, 2024
1 parent 9b997e3 commit d5e3112
Show file tree
Hide file tree
Showing 10 changed files with 488 additions and 79 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4.1.1

- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5.0.0
with:
go-version: '1.22'

Expand Down
48 changes: 23 additions & 25 deletions gelflogger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/jame-developer/gelf-logger/pkg/zerologger"
"net"
"os"
"strconv"
Expand All @@ -27,12 +26,13 @@ import (
// - ensureConnection: Ensures that a connection to the Graylog server is established, reconnecting if necessary.
// - Log: Sends a log message to the Graylog server.
type Logger struct {
conn net.Conn
connLock sync.Mutex
address string
useTLS bool
tslConfig *tls.Config
host string
conn net.Conn
connLock sync.Mutex
address string
useTLS bool
tslConfig *tls.Config
host string
baseLogProcessor func(fields map[string]interface{}) (int, float64, []byte, error)
}

// NewLogger creates a new Logger.
Expand All @@ -59,9 +59,9 @@ type Logger struct {
//
// This creates a new Logger that will use TLS when connecting
// to the specified address.
func NewLogger(address string, useTSL bool, tslConfig *tls.Config) (*Logger, error) {
func NewLogger(address string, useTSL bool, tslConfig *tls.Config, baseLogProcessor func(fields map[string]interface{}) (int, float64, []byte, error)) (*Logger, error) {
host, _ := os.Hostname()
logger := &Logger{address: address, useTLS: useTSL, tslConfig: tslConfig, host: host}
logger := &Logger{address: address, useTLS: useTSL, tslConfig: tslConfig, host: host, baseLogProcessor: baseLogProcessor}
err := logger.connect()
if err != nil {
return nil, err
Expand Down Expand Up @@ -127,7 +127,19 @@ func (l *Logger) ensureConnection() error {

// Log Ensure the connection is alive before logging
func (l *Logger) Log(message string, fields map[string]interface{}) error {
gelfMessage, err := formatGELFMessage(message, fields, l.host)
graylogLevel, glTimeStamp, fullMessage, err := l.baseLogProcessor(fields)
if err != nil {
return err
}
gelfMsg := map[string]interface{}{
"version": "1.1",
"host": l.host,
"short_message": message,
"full_message": string(fullMessage),
"timestamp": glTimeStamp,
"level": graylogLevel,
}
gelfMessage, err := formatGELFMessage(gelfMsg, fields)
if err != nil {
return err
}
Expand All @@ -136,7 +148,6 @@ func (l *Logger) Log(message string, fields map[string]interface{}) error {

_, err = l.conn.Write(gelfMessage)
if err != nil {
//log.Printf("Failed to write to Graylog: %v", err)
err := l.connect()
if err != nil {
return err
Expand All @@ -157,20 +168,7 @@ func (l *Logger) Log(message string, fields map[string]interface{}) error {
// The GELF message is then marshaled into a byte slice.
// If an error occurs during marshaling, it is logged and returned.
// Finally, the GELF message byte slice is returned along with any error that occurred.
func formatGELFMessage(message string, fields map[string]interface{}, host string) ([]byte, error) {
graylogLevel, glTimeStamp, fullMessage, err2 := zerologger.ProcessZerologFields(fields)
if err2 != nil {
return nil, err2
}

gelfMsg := map[string]interface{}{
"version": "1.1",
"host": host,
"short_message": message,
"full_message": string(fullMessage),
"timestamp": glTimeStamp,
"level": graylogLevel,
}
func formatGELFMessage(gelfMsg, fields map[string]interface{}) ([]byte, error) {

for k, v := range fields {
if boolVal, ok := v.(bool); ok {
Expand Down
62 changes: 14 additions & 48 deletions gelflogger_test.go
Original file line number Diff line number Diff line change
@@ -1,48 +1,16 @@
package gelflogger
package gelflogger_test

import (
"crypto/tls"
"net"
gelflogger "github.com/jame-developer/gelf-logger"
"github.com/jame-developer/gelf-logger/pkg/helper"
"testing"
)

// startMockServer creates a mock TCP server and returns a network listener.
// The server listens on the specified TCP port on the loopback address, "127.0.0.1".
// The returned listener should be closed after use to free the associated resources.
func startMockServer(t *testing.T) net.Listener {
mockServerPort := "5555"
l, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", mockServerPort))
if err != nil {
t.Fatalf("Failed to start mock TCP server: %v", err)
}
return l
}

// startMockTLSServer creates a mock TLS TCP server and returns a network listener.
// It loads the X.509 key pair from the given PEM-encoded files, 'testcert.pem' and 'testkey.pem', for TLS configuration.
// If the key pair cannot be loaded, the function aborts with a fatal error.
// The server listens on the specified TCP port on the loopback address, "127.0.0.1".
// The returned listener should be closed after use to free the associated resources.
// To create these test certificate files, you can use OpenSSL with the following commands in your `test_data` folder under project root:
//
// `openssl req -newkey rsa:2048 -nodes -keyout testkey.pem -x509 -days 365 -out testcert.pem`
func startMockTLSServer(t *testing.T) net.Listener {
mockTLSServerPort := "5556"
cert, err := tls.LoadX509KeyPair("./test_data/testcert.pem", "./test_data/testkey.pem")
if err != nil {
t.Fatalf("Failed to load keypair for TLS: %v", err)
}
l, err := tls.Listen("tcp", net.JoinHostPort("127.0.0.1", mockTLSServerPort), &tls.Config{Certificates: []tls.Certificate{cert}})
if err != nil {
t.Fatalf("Failed to start mock TLS TCP server: %v", err)
}
return l
}

func TestNewLogger(t *testing.T) {
// Set up the mock server here
mockServer := startMockServer(t)
mockTLSServer := startMockTLSServer(t)
mockServer := helper.StartMockServer(t)
mockTLSServer := helper.StartMockTLSServer(t)

defer t.Cleanup(func() {
_ = mockServer.Close()
Expand Down Expand Up @@ -85,7 +53,9 @@ func TestNewLogger(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewLogger(tt.address, tt.useTLS, tt.tlsConfig)
_, err := gelflogger.NewLogger(tt.address, tt.useTLS, tt.tlsConfig, func(fields map[string]interface{}) (int, float64, []byte, error) {
return 0, 0, nil, nil
})
if (err != nil) != tt.wantErr {
t.Errorf("NewLogger() error = %v, wantErr %v", err, tt.wantErr)
}
Expand All @@ -95,7 +65,7 @@ func TestNewLogger(t *testing.T) {

func TestWriteWithMockServer(t *testing.T) {
// Set up the mock server here
mockServer := startMockServer(t)
mockServer := helper.StartMockServer(t)

defer t.Cleanup(func() {
_ = mockServer.Close()
Expand Down Expand Up @@ -144,29 +114,25 @@ func TestWriteWithMockServer(t *testing.T) {
wantN: 0,
wantErr: true,
},
{
name: "invalid time value",
message: []byte(`{"message":"info", "time":"2024-01-01T01:01:01"}`),
wantN: 0,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

// Initialize our logger with the mock server's address
logger, _ := NewLogger(mockServer.Addr().String(), false, nil)
logger, _ := gelflogger.NewLogger(mockServer.Addr().String(), false, nil, func(fields map[string]interface{}) (int, float64, []byte, error) {
return 0, 0, nil, nil
})

gw := &GelfWriter{
gw := &gelflogger.GelfWriter{
Logger: logger,
}
if tt.stopServerBeforeTest {
_ = mockServer.Close()
}
defer func() {
if tt.stopServerBeforeTest {
mockServer = startMockServer(t)
mockServer = helper.StartMockServer(t)
}
}()

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ go 1.22.0
require (
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/sys v0.12.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 6 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -17,12 +15,16 @@ github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
59 changes: 59 additions & 0 deletions pkg/helper/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package helper

import (
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"net"
"testing"
"time"
)

// StartMockServer creates a mock TCP server and returns a network listener.
// The server listens on the specified TCP port on the loopback address, "127.0.0.1".
// The returned listener should be closed after use to free the associated resources.
func StartMockServer(t *testing.T) net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("Failed to start mock TCP server: %v", err)
}
return l
}

// StartMockTLSServer creates a mock TLS TCP server and returns a network listener.
// It loads the X.509 key pair from the given PEM-encoded files, 'testcert.pem' and 'testkey.pem', for TLS configuration.
// If the key pair cannot be loaded, the function aborts with a fatal error.
// The server listens on the specified TCP port on the loopback address, "127.0.0.1".
// The returned listener should be closed after use to free the associated resources.
// To create these test certificate files, you can use OpenSSL with the following commands in your `test_data` folder under project root:
//
// `openssl req -newkey rsa:2048 -nodes -keyout testkey.pem -x509 -days 365 -out testcert.pem`
func StartMockTLSServer(t *testing.T) net.Listener {

cert := CreateTestCertificate()
l, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{cert}})
if err != nil {
t.Fatalf("Failed to start mock TLS TCP server: %v", err)
}
return l
}

func CreateTestCertificate() tls.Certificate {
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{Organization: []string{"Acme Co"}},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 180),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

derBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey)
return tls.Certificate{Certificate: [][]byte{derBytes}, PrivateKey: privateKey}
}
Loading

0 comments on commit d5e3112

Please sign in to comment.