ginlogctx

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 17, 2026 License: MIT Imports: 8 Imported by: 0

README

ginlogctx logo

ginlogctx

Request-scoped Logrus fields for Gin, with sensible defaults and flexible custom context.

Go Report Card MIT License GoDoc Contributions welcome

ginlogctx is a small Go package that gives Gin services an out-of-the-box request ID flow and enriches logrus entries with request-scoped fields inside handlers.

Under the hood, it builds on the idea behind github.com/gin-contrib/requestid and packages that behavior together with Logrus request-scoped enrichment, so a service can:

  • generate or reuse a request ID for every Gin request
  • attach that request_id automatically to application logs
  • extend the same request context with custom fields such as user_id

The goal is to cover the common "I need correlated logs per request" use case without forcing small and medium projects into the complexity of a full observability stack.

Out of the box it adds:

  • request_id

And it lets applications add any extra request-scoped fields they want, such as:

  • user_id
  • product_id
  • tenant_id
  • account_id

It is designed for teams that already use:

  • github.com/gin-gonic/gin
  • github.com/sirupsen/logrus

If you are comparing this package with a tracing stack, see:

Installation

go get github.com/FabioRNobrega/ginlogctx

Or add it to your go.mod:

require github.com/FabioRNobrega/ginlogctx

Run Tests With Docker

If you do not want to install Go locally, you can run the package tests with:

make docker-test

The Docker setup mounts the repository into a Go container and keeps module and build caches in named Docker volumes so repeated test runs are faster.

Features

  • Provides request ID handling out of the box and adds request_id automatically
  • Enriches plain logrus.Info/Error/Warn/... calls through a Logrus hook
  • Supports custom request-scoped fields through resolvers
  • Includes optional request completion logging
  • Keeps the built-in request completion log focused on HTTP fields instead of caller metadata
  • Preserves explicitly set log fields instead of overwriting them
  • Keeps setup small and easy to drop into existing Gin services

Why Use It

ginlogctx is a good fit when you want:

  • request-level correlation in logs
  • a simple request_id story for Gin services
  • custom fields like user_id, tenant_id, or product_id
  • log-based tracking in tools such as Datadog without introducing tracing or APM first

It is intentionally a lighter approach than full distributed tracing. For many services, especially internal APIs and smaller systems, correlated logs are enough to answer:

  • which logs belong to this request?
  • which user triggered it?
  • what happened across my services if I forward the same request ID?

If later you need full spans, distributed traces, baggage, and cross-process trace visualization, you can still adopt OpenTelemetry on top of or alongside this approach.

How It Works

ginlogctx binds request fields for the lifetime of the active Gin request and uses a Logrus hook to inject them into log entries emitted on that same request goroutine.

This means:

  • It works automatically for logs emitted during normal handler execution
  • It does not automatically follow spawned goroutines
  • Background work should propagate context explicitly if you want the same fields there

Quick Start

This is the minimal setup. It gives you request_id out of the box.

package main

import (
	"github.com/FabioRNobrega/ginlogctx"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

func main() {
	ginlogctx.Install(logrus.StandardLogger(), ginlogctx.DefaultConfig())

	r := gin.New()
	r.Use(ginlogctx.Middleware(ginlogctx.DefaultConfig()))

	r.GET("/ping", func(c *gin.Context) {
		logrus.Info("inside handler")
		c.JSON(200, gin.H{"message": "pong"})
	})

	_ = r.Run(":8080")
}

Example log output:

{
  "level": "info",
  "msg": "inside handler",
  "request_id": "9f5275d0-3fbc-47b4-9aa9-9f29767e4f6e",
  "time": "2026-04-17T12:00:00Z"
}

Custom Fields

The recommended extension point is Config.Fields.

Each field defines:

  • the log key
  • how to resolve its value from the current *gin.Context

Add user_id

cfg := ginlogctx.DefaultConfig()
cfg.Fields = []ginlogctx.Field{
	{
		Key: "user_id",
		Resolve: func(c *gin.Context) (any, bool) {
			userID := c.GetHeader("X-User-ID")
			return userID, userID != ""
		},
	},
}

Add product_id

cfg := ginlogctx.DefaultConfig()
cfg.Fields = []ginlogctx.Field{
	{
		Key: "product_id",
		Resolve: func(c *gin.Context) (any, bool) {
			productID := c.Param("product_id")
			return productID, productID != ""
		},
	},
}

Add multiple fields

cfg := ginlogctx.DefaultConfig()
cfg.Fields = []ginlogctx.Field{
	{
		Key: "user_id",
		Resolve: func(c *gin.Context) (any, bool) {
			userID := c.GetHeader("X-User-ID")
			return userID, userID != ""
		},
	},
	{
		Key: "product_id",
		Resolve: func(c *gin.Context) (any, bool) {
			productID := c.Param("product_id")
			return productID, productID != ""
		},
	},
	{
		Key: "tenant_id",
		Resolve: func(c *gin.Context) (any, bool) {
			tenantID := c.GetHeader("X-Tenant-ID")
			return tenantID, tenantID != ""
		},
	},
}

Full Example

package main

import (
	"github.com/FabioRNobrega/ginlogctx"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

func main() {
	cfg := ginlogctx.DefaultConfig()
	cfg.Fields = []ginlogctx.Field{
		{
			Key: "user_id",
			Resolve: func(c *gin.Context) (any, bool) {
				userID := c.GetHeader("X-User-ID")
				return userID, userID != ""
			},
		},
		{
			Key: "product_id",
			Resolve: func(c *gin.Context) (any, bool) {
				productID := c.Param("product_id")
				return productID, productID != ""
			},
		},
	}

	ginlogctx.Install(logrus.StandardLogger(), cfg)

	r := gin.New()
	r.Use(ginlogctx.Middleware(cfg))

	r.GET("/products/:product_id", func(c *gin.Context) {
		logrus.WithField("operation", "fetch_product").Info("handling request")
		c.JSON(200, gin.H{"ok": true})
	})

	_ = r.Run(":8080")
}

Possible output:

{
  "level": "info",
  "msg": "handling request",
  "operation": "fetch_product",
  "product_id": "p-123",
  "request_id": "4c784c9d-ec4b-4556-a650-0eaf8111ef0d",
  "user_id": "u-456",
  "time": "2026-04-17T12:00:00Z"
}

Request Completion Logs

By default, ginlogctx also emits a request completion log with the message request completed and these fields:

  • method
  • path
  • status
  • durationMs

You can customize both the message and the level:

cfg := ginlogctx.DefaultConfig()
cfg.RequestLogMessage = "http request finished"
cfg.RequestLogLevel = logrus.DebugLevel

Or disable it:

cfg := ginlogctx.DefaultConfig()
cfg.IncludeRequestLog = false

This only disables the built-in request completion log emitted by ginlogctx. Request-scoped fields such as request_id and your custom fields still continue to be attached to the other logrus entries emitted during the request.

API

Main types and functions:

  • ginlogctx.DefaultConfig() Config
  • ginlogctx.Install(logger *logrus.Logger, cfg Config)
  • ginlogctx.NewHook(cfg Config) logrus.Hook
  • ginlogctx.Middleware(cfg Config) gin.HandlerFunc
  • type ginlogctx.Field
  • type ginlogctx.Config

Useful Config fields:

  • IncludeRequestLog enables or disables the built-in request completion log
  • RequestLogMessage customizes the request completion message
  • RequestLogLevel customizes the level used for the request completion log
  • Fields registers custom request-scoped fields such as user_id

Notes

  • request_id is the only built-in field
  • Custom fields are application-defined
  • Explicit fields already present on a log entry are not overwritten by the hook
  • Empty custom field values are ignored
  • The package is intended for logrus standard/global logging patterns

Contributing

Contributions are very welcome, whether they are:

  • bug fixes
  • better tests
  • docs improvements
  • examples
  • API refinements

If you are using ginlogctx in a real Gin service and want to improve its ergonomics, that is especially useful feedback.

For the repository Git workflow, branch naming, commit prefixes, and tag conventions, see:

License

MIT © FabioRNobrega

Documentation

Overview

Package ginlogctx enriches Logrus logs with request-scoped fields for Gin handlers.

Automatic enrichment applies only to log entries emitted on the active request goroutine while the middleware is executing. Background or detached goroutines must propagate request information explicitly if they need the same fields.

Middleware provides request ID handling out of the box and enriches logs with request_id by default. Additional request-scoped fields can be registered through Config.Fields or Config.AdditionalFields.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Install

func Install(logger *logrus.Logger, cfg Config)

Install adds the ginlogctx hook to the provided logger.

If logger is nil, the standard Logrus logger is used.

Example
package main

import (
	"github.com/FabioRNobrega/ginlogctx"
	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

func main() {
	logger := logrus.StandardLogger()
	ginlogctx.Install(logger, ginlogctx.DefaultConfig())

	router := gin.New()
	router.Use(ginlogctx.Middleware(ginlogctx.DefaultConfig()))
}

func Middleware

func Middleware(cfg Config) gin.HandlerFunc

Middleware binds request-scoped fields for the lifetime of the current Gin request.

The middleware captures request_id plus any configured custom fields, makes them available to the hook for logs emitted on the current request goroutine, and optionally emits a request completion log after the handler chain finishes.

Example (CustomFields)
package main

import (
	"github.com/FabioRNobrega/ginlogctx"
	"github.com/gin-gonic/gin"
)

func main() {
	cfg := ginlogctx.DefaultConfig()
	cfg.RequestIDField = "trace_id"
	cfg.RequestLogMessage = "http request finished"
	cfg.Fields = []ginlogctx.Field{
		{
			Key: "product_id",
			Resolve: func(c *gin.Context) (any, bool) {
				productID := c.GetHeader("X-Product-ID")
				return productID, productID != ""
			},
		},
	}

	router := gin.New()
	router.Use(ginlogctx.Middleware(cfg))
}

func NewHook

func NewHook(_ Config) logrus.Hook

NewHook creates a Logrus hook that injects the currently bound request-scoped fields into log entries emitted on the active request goroutine.

Types

type Config

type Config struct {
	RequestIDField    string
	RequestIDHeader   string
	Fields            []Field
	IncludeRequestLog bool
	RequestLogLevel   logrus.Level
	RequestLogMessage string
	AdditionalFields  func(*gin.Context) logrus.Fields
}

Config controls how request-scoped fields are collected and logged.

By default ginlogctx adds only request_id. Additional request fields such as user_id or product_id can be registered through Fields or AdditionalFields.

IncludeRequestLog controls whether Middleware emits the built-in request completion log. RequestLogMessage and RequestLogLevel customize that log when it is enabled.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the default ginlogctx configuration.

The default setup adds request_id, emits a request completion log at info level with the message "request completed", and uses the X-Request-ID header as the request ID fallback.

type Field

type Field struct {
	Key     string
	Resolve func(*gin.Context) (any, bool)
}

Field describes a custom request-scoped log field.

Resolve is called for each request handled by Middleware. If it returns ok=true and a non-empty value, the field is attached to request-scoped logs under Key.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL