Go Common
  • Common Backend Framework
  • util
    • JWT Manager
  • framework
    • cache
    • config
    • errors
    • event
    • logger
    • middleware
    • retry
    • trace
    • validator
Powered by GitBook
On this page
  • Logger Package
  • Features
  • Usage
  • Configuration
  • Logging Messages
  • StructuredJSONFormatter
  • Custom Formatter
  • No-Op Logger
  • Example
  1. framework

logger

PreviouseventNextmiddleware

Last updated 2 months ago

Logger Package

The logger package provides a structured, context-aware logging solution for Go applications. It is built on top of the library and is designed to facilitate easy integration with your projects, offering features like:

  • JSON-formatted logs suitable for production environments.

  • Support for multiple log levels (DEBUG, INFO, WARN, ERROR, FATAL).

  • Context propagation to include tracing information (e.g., trace_id, span_id).

  • Customizable log formatters and output destinations.

  • Integration with web frameworks like Gin.

Features

  • Structured Logging: Outputs logs in JSON format, making them easy to parse and analyze.

  • Context-Aware: Supports logging with context.Context, allowing you to include tracing information automatically.

  • Customizable Formatter: Use the default StructuredJSONFormatter or provide your own formatter to customize the log output.

  • Environment and Service Name: Optionally include environment and service name in your logs for better traceability.

  • No-Op Logger: Provides a no-operation logger for testing purposes, which discards all log messages.

Usage

Creating a Logger

You can create a logger using the NewLogger function, providing a Config struct to customize its behavior:

import (
    "github.com/kittipat1413/go-common/framework/logger"
    "time"
)

logConfig := logger.Config{
    Level: logger.INFO,
    Formatter: &logger.StructuredJSONFormatter{
        TimestampFormat: time.RFC3339,
        PrettyPrint:     false,
    },
    Environment: "production",
    ServiceName: "my-service",
}

log, err := logger.NewLogger(logConfig)
if err != nil {
    panic(err)
}

Alternatively, you can use the default logger:

log := logger.NewDefaultLogger()
  • The NewDefaultLogger returns a logger instance with the default or user-defined configuration.

  • If SetDefaultLoggerConfig has been called, it uses the user-defined configuration; otherwise, it uses the package's default configuration.

Updating the Default Logger Configuration

You can update the default logger configuration using SetDefaultLoggerConfig:

err := logger.SetDefaultLoggerConfig(logConfig)
if err != nil {
    // Handle error
    panic(err)
}

Configuration

The Config struct allows you to customize the logger:

type Config struct {
	// Level determines the minimum log level that will be processed by the logger.
	// Logs with a level lower than this will be ignored.
	Level LogLevel
	// Formatter is an optional field for specifying a custom logrus formatter.
	// If not provided, the logger will use the StructuredJSONFormatter by default.
	Formatter logrus.Formatter
	// Environment is an optional field for specifying the running environment (e.g., "production", "staging").
	// This field is used for adding environment-specific fields to logs.
	Environment string
	// ServiceName is an optional field for specifying the name of the service.
	// This field is used for adding service-specific fields to logs.
	ServiceName string
	// Output is an optional field for specifying the output destination for logs (e.g., os.Stdout, file).
	// If not provided, logs will be written to stdout by default.
	Output io.Writer
}

Logging Messages

The logger provides methods for different log levels:

type Logger interface {
    WithFields(fields Fields) Logger
	Debug(ctx context.Context, msg string, fields Fields)
	Info(ctx context.Context, msg string, fields Fields)
	Warn(ctx context.Context, msg string, fields Fields)
	Error(ctx context.Context, msg string, err error, fields Fields)
	Fatal(ctx context.Context, msg string, err error, fields Fields)
}

Example:

ctx := context.Background()
fields := logger.Fields{"user_id": 12345}

log.Info(ctx, "User logged in", fields)

Including Errors

For error and fatal logs, you can include an error object:

err := errors.New("something went wrong")
log.Error(ctx, "Failed to process request", err, fields)

Adding Persistent Fields

You can add persistent fields to the logger using WithFields, which returns a new logger instance:

logWithFields := log.WithFields(logger.Fields{
    "component": "authentication",
})
logWithFields.Info(ctx, "Authentication successful", nil)

StructuredJSONFormatter

The StructuredJSONFormatter is a custom logrus.Formatter designed to include contextual information in logs. It outputs logs in JSON format with a standardized structure, making it suitable for log aggregation and analysis tools.

Features

  • Timestamp: Includes a timestamp formatted according to TimestampFormat.

  • Severity: The log level (debug, info, warning, error, fatal).

  • Message: The log message.

  • Error Handling: Automatically includes error messages if an error is provided.

  • Tracing Information: Extracts trace_id and span_id from the context if available (e.g., when using OpenTelemetry).

  • Caller Information: Adds information about the function, file, and line number where the log was generated.

  • Stack Trace: Includes a stack trace for logs at the error level or higher.

  • Custom Fields: Supports additional fields provided via logger.Fields.

  • Field Key Customization: Allows custom formatting of field keys via FieldKeyFormatter.

Configuration

You can customize the StructuredJSONFormatter when initializing the logger:

import (
    "github.com/kittipat1413/go-common/framework/logger"
    "time"
)

formatter := &logger.StructuredJSONFormatter{
    TimestampFormat: time.RFC3339, // Customize timestamp format
    PrettyPrint:     true,         // Indent JSON output
    FieldKeyFormatter: func(key string) string {
        // Customize field keys
        switch key {
        case logger.DefaultEnvironmentKey:
            return "env"
        case logger.DefaultServiceNameKey:
            return "service"
        case logger.DefaultSJsonFmtSeverityKey:
            return "level"
        case logger.DefaultSJsonFmtMessageKey:
            return "msg"
        default:
            return key
        }
    },
}

logConfig := logger.Config{
    Level:     logger.INFO,
    Formatter: formatter,
}

Example Log Entry (default FieldKeyFormatter)

{
  "caller": {
    "file": "/go-common/framework/logger/example/gin_with_logger/main.go:99",
    "function": "main.handlerWithLogger"
  },
  "environment": "development",
  "message": "Handled HTTP request",
  "request": {
    "method": "GET",
    "url": "/log"
  },
  "request_id": "afa241ba-cb59-4053-a1f5-d82e6193790c",
  "response_time": 0.000026542,
  "service_name": "logger-example",
  "severity": "info",
  "span_id": "94e92f0e1b8532e6",
  "status": 200,
  "timestamp": "2024-10-20T02:01:57+07:00",
  "trace_id": "f891fd44c417fc7efa297e6a18ddf0cf"
}
{
  "caller": {
    "file": "/go-common/framework/logger/example/gin_with_logger/main.go:78",
    "function": "main.logMessages"
  },
  "environment": "development",
  "error": "example error",
  "example_field": "error_value",
  "message": "This is an error message",
  "service_name": "logger-example",
  "severity": "error",
  "stack_trace": "goroutine 1 [running]:\ngithub.com/kittipat1413/go-common/framework/logger.getStackTrace()\n\t/Users/kittipat/go-github-repo/go-common/framework/logger/structured_json_formatter.go:181 .....\n",
  "timestamp": "2024-10-20T02:01:55+07:00"
}

Tracing Integration

The StructuredJSONFormatter can extract tracing information (trace_id and span_id) from the context.Context if you are using a tracing system like OpenTelemetry. Ensure that spans are started and the context is propagated correctly.

Caller and Stack Trace

  • Caller Information: The formatter includes the function name, file, and line number where the log was generated, aiding in debugging.

  • Stack Trace: For logs at the error level or higher, a stack trace is included. This can be useful for diagnosing issues in production.


Custom Formatter

If you need a different format or additional customization, you can implement your own formatter by satisfying the logrus.Formatter interface and providing it to the logger configuration.

type MyCustomFormatter struct {
    // Custom fields...
}

func (f *MyCustomFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    // Custom formatting logic...
}

Usage:

logConfig := logger.Config{
    Formatter: &MyCustomFormatter{},
}

No-Op Logger

For testing purposes, you can use the no-operation logger, which implements the Logger interface but discards all log messages:

log := logger.NewNoopLogger()

This can be useful to avoid cluttering test output with logs or when you need a logger that does nothing.

Example

You can find a complete working example in the repository under .

You can find a complete working example in the repository under .

framework/logger/example
framework/logger/example
logrus