Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update logger to facilitate the use of global logger #150

Merged
merged 8 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/checks.golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-go@v3
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ check-modtidy:
go mod tidy
git diff --exit-code -- go.mod go.sum

fmt:
gofmt -l -s -w .

vet:
go vet ./...

lint:
golangci-lint --version
golangci-lint run
Expand Down
1 change: 1 addition & 0 deletions cmd/demo-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func main() {
EnvPrefix: "golem",
GitVersion: version.GitVersion,
GitRevision: version.GitRevision,
LogLevel: "debug",
}

// Define application level features
Expand Down
7 changes: 3 additions & 4 deletions pkg/cli/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cli

import (
"context"
"fmt"
"github.com/zondax/golem/pkg/logger"
"strings"
Expand All @@ -15,7 +14,7 @@ func SetupConfiguration(c *cobra.Command) {
c.PersistentFlags().StringVarP(&configFileFlag, "config", "c", "", "The path to the config file to use.")
err := viper.BindPFlag("config", c.PersistentFlags().Lookup("config"))
if err != nil {
logger.GetLoggerFromContext(context.Background()).Fatalf("unable to bind config flag: %+v", err)
logger.Fatalf("unable to bind config flag: %+v", err)
}

viper.SetConfigName("config") // config file name without extension
Expand Down Expand Up @@ -50,12 +49,12 @@ func LoadConfig[T Config]() (*T, error) {
configFileOverride := viper.GetString("config")
if configFileOverride != "" {
viper.SetConfigFile(configFileOverride)
logger.GetLoggerFromContext(context.Background()).Infof("Using config file: %s", viper.ConfigFileUsed())
logger.Infof("Using config file: %s", viper.ConfigFileUsed())
}

err = viper.ReadInConfig()
if err != nil {
logger.GetLoggerFromContext(context.Background()).Fatalf("%+v", err)
logger.Fatalf("%+v", err)
}

// adds all default+configFile values in viper to struct
Expand Down
6 changes: 3 additions & 3 deletions pkg/cli/handlers.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cli

import (
"context"
"github.com/zondax/golem/pkg/logger"
"os"
"os/signal"
"syscall"

"github.com/zondax/golem/pkg/logger"
)

var defaultConfigHandler DefaultConfigHandler
Expand All @@ -15,7 +15,7 @@ func setupCloseHandler(handler CleanUpHandler) {
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
logger.GetLoggerFromContext(context.Background()).Warn("\r- Ctrl+C pressed in Terminal")
logger.Warn("\r- Ctrl+C pressed in Terminal")

if handler != nil {
handler()
Expand Down
23 changes: 12 additions & 11 deletions pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package cli

import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zondax/golem/pkg/constants"
"github.com/zondax/golem/pkg/logger"
"os"
)

type AppSettings struct {
Expand All @@ -16,6 +16,7 @@ type AppSettings struct {
EnvPrefix string // environment variable MYAPP_.....
GitVersion string
GitRevision string
LogLevel string // Global log level for the app
emmanuelm41 marked this conversation as resolved.
Show resolved Hide resolved
}

type CLI struct {
Expand Down Expand Up @@ -50,9 +51,9 @@ func (c *CLI) init() {
Run: func(cmd *cobra.Command, args []string) {
err := c.checkConfig()
if err != nil {
fmt.Printf("%s\n", c.checkConfig().Error())
logger.Errorf("%s\n", c.checkConfig().Error())
} else {
fmt.Printf("Configuration OK\n")
logger.Infof("Configuration OK\n")
}
},
}
Expand All @@ -61,15 +62,18 @@ func (c *CLI) init() {
Use: "version",
Short: "Print version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("%s\n", c.GetVersionString())
logger.Infof("%s\n", c.GetVersionString())
},
}

c.GetRoot().AddCommand(checkCmd)
c.GetRoot().AddCommand(versionCmd)

// TODO: We can make this optional? and more configurable if we see the need
logger.InitLogger(logger.Config{Level: constants.DebugLevel})
// If app log level is defined it is configued, logger.defaultConfig by default
if len(c.app.LogLevel) > 0 {
logger.InitLogger(logger.Config{Level: c.app.LogLevel})
}

setupCloseHandler(nil)
// Set Configuration Defaults
setupDefaultConfiguration(func() {
Expand All @@ -91,10 +95,7 @@ func (c *CLI) GetVersionString() string {

func (c *CLI) Run() {
if err := c.rootCmd.Execute(); err != nil {
_, err := fmt.Fprintln(os.Stderr, err)
if err != nil {
return
}
logger.Error(err.Error())
_ = logger.Sync()
os.Exit(1)
}
Expand Down
162 changes: 162 additions & 0 deletions pkg/logger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Logger Package

A structured logging package built on top of [uber-go/zap](https://github.com/uber-go/zap), providing both global and context-aware logging capabilities with sensible defaults.

## Features

- Built on top of the high-performance zap logger
- Supports both structured and printf-style logging
- Context-aware logging
- Global and instance-based logging
- Configurable log levels and encoding formats
- Request ID tracking support
- Easy integration with existing applications

## Installation

```go
go get -u github.com/zondax/golem/pkg/logger
```

## Quick Start

```go
// Initialize with default configuration
logger.InitLogger(logger.Config{
Level: "info",
Encoding: "json",
})

// Basic logging
logger.Info("Server started")
logger.Error("Connection failed")

// Structured logging with fields
log := logger.NewLogger(
logger.Field{Key: "service", Value: "api"},
logger.Field{Key: "version", Value: "1.0.0"},
)
log.Info("Service initialized")
```

## Configuration

### Logger Config

```go
type Config struct {
Level string `json:"level"` // Logging level
Encoding string `json:"encoding"` // Output format
}
```

### Log Levels

Available log levels (in order of increasing severity):
- `debug`: Detailed information for debugging
- `info`: General operational information
- `warn`: Warning messages for potentially harmful situations
- `error`: Error conditions that should be addressed
- `dpanic`: Critical errors in development that cause panic
- `panic`: Critical errors that cause panic in production
- `fatal`: Fatal errors that terminate the program

### Encoding Formats

1. **JSON Format** (Default)
- Recommended for production
- Machine-readable structured output
```json
{"level":"INFO","ts":"2024-03-20T10:00:00.000Z","msg":"Server started","service":"api"}
```

2. **Console Format**
- Recommended for development
- Human-readable output
```
2024-03-20T10:00:00.000Z INFO Server started service=api
```

## Advanced Usage

### Context-Aware Logging

```go
// Create a context with logger
ctx := context.Background()
log := logger.NewLogger(logger.Field{
Key: logger.RequestIDKey,
Value: "req-123",
})
ctx = logger.ContextWithLogger(ctx, log)

// Get logger from context
contextLogger := logger.GetLoggerFromContext(ctx)
contextLogger.Info("Processing request")
```

### Structured Logging with Fields

```go
log := logger.NewLogger()
log.WithFields(
zap.String("user_id", "12345"),
zap.String("action", "login"),
zap.Int("attempt", 1),
).Info("User login attempt")
```

### Printf-Style Logging

```go
logger.Infof("Processing item %d of %d", current, total)
logger.Errorf("Failed to connect to %s: %v", host, err)
```

## Best Practices

1. **Use Structured Logging**
```go
// Good
log.WithFields(
zap.String("user_id", "12345"),
zap.String("action", "purchase"),
zap.Float64("amount", 99.99),
).Info("Purchase completed")

// Avoid
log.Infof("User %s completed purchase of $%.2f", userID, amount)
```

2. **Include Request IDs**
```go
log.WithFields(
zap.String(logger.RequestIDKey, requestID),
).Info("Handling request")
```

3. **Proper Error Logging**
```go
if err != nil {
log.WithFields(
zap.Error(err),
zap.String("operation", "database_query"),
).Error("Query failed")
}
```

4. **Resource Cleanup**
```go
defer logger.Sync()
```

## Performance Considerations

- The logger is designed to be zero-allocation in most cases
- JSON encoding is more CPU-intensive but provides structured data
- Log level checks are performed atomically
- Field allocation is optimized for minimal overhead

## Thread Safety

The logger is completely thread-safe and can be used concurrently from multiple goroutines.
Loading
Loading