info

package
v1.21.0 Latest Latest
Warning

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

Go to latest
Published: Mar 27, 2026 License: MIT Imports: 6 Imported by: 0

README

Info Package

License Go Version Coverage

The info package provides a flexible, thread-safe mechanism for managing and exposing component metadata. It supports a hybrid approach of static configuration, manual updates, and dynamic on-demand generation of information.


Table of Contents


Overview

This package allows applications to expose self-describing information (such as version, status, runtime metrics) in a structured and concurrent-safe manner. It is designed to be the foundational layer for monitoring and service discovery metadata.

Design Philosophy
  1. Flexibility: Support both static data (set once) and dynamic data (computed on access).
  2. Concurrency: Ensure all operations are thread-safe without heavy locking overhead (using atomic maps).
  3. Simplicity: Provide a clean interface for registering data providers and retrieving results.
Key Features
  • Dynamic Retrieval: Register functions to generate names and info data on the fly.
  • Manual Control: Manually set, update, or delete specific data points.
  • Thread Safety: Safe for concurrent reads and writes using lock-free atomic structures.
  • Serialization: Built-in support for json.Marshaler and encoding.TextMarshaler.
  • Fallback Logic: Robust name resolution strategy (Function > Manual > Default).
Key Benefits
  • Real-time Accuracy: Dynamic functions are executed on every call, ensuring data is always fresh.
  • Low Overhead: Atomic operations minimize contention compared to standard mutexes.
  • Ease of Use: Simple API for both simple static cases and complex dynamic scenarios.

Architecture

Package Structure
info/                        # Main package
├── doc.go                   # Package documentation and design overview
├── encode.go                # JSON and Text marshaling logic
├── info.go                  # Public API implementation (Name/Info wrappers)
├── info_test.go             # Core unit tests
├── interface.go             # Interface definitions and constructor
├── manual_test.go           # Tests for manual manipulation methods
└── model.go                 # Internal struct and logic implementation
Dataflow

When Info() is called, the component aggregates data from multiple sources.

+----------------+       +-------------------------+
|  Client Call   | ----> |      Info() Method      |
+----------------+       +------------+------------+
                                      |
                                      v
                         +-------------------------+
                         | 1. Execute Registered   |
                         |    Info Function (if any)|
                         +------------+------------+
                                      |
                                      v
                         +-------------------------+
                         | 2. Merge with Manual    |
                         |    Data (SetData/AddData)|
                         +------------+------------+
                                      |
                                      v
                         +-------------------------+
                         | 3. Return Combined Map  |
                         +-------------------------+
Name Resolution Strategy

When Name() is called, the following priority is applied:

  1. Dynamic Function: If registered, execute it. If it returns a valid string, use it.
  2. Manual Override: If SetName() was called, use that value.
  3. Default Name: Fallback to the name provided during New().

Performance

The implementation uses github.com/nabbar/golib/atomic (based on sync.Map) for internal state management.

Benchmarks (Intel Core i7-4700HQ)
  • Concurrent Reads: ~19 ns/op (Zero allocations) for Name reads.
  • Dynamic Execution: ~39 ns/op for dynamic name functions.
  • Data Deletion: ~27 ns/op for removing keys.
  • Info Generation: ~500-1000 ns/op depending on complexity and allocations.
Characteristics
  • Reads: Highly efficient, especially for manually set data.
  • Dynamic Generation: The performance depends on the registered function. Since no caching is performed by the library, expensive operations in registered functions will impact every read call. Users are encouraged to implement their own caching inside the function if needed.
  • Allocations: Optimized to minimize allocations during data merging and encoding.

Use Cases

1. Service Metadata

Expose static build information alongside dynamic uptime metrics.

inf, _ := info.New("my-service")
inf.SetData(map[string]interface{}{
    "version": "1.0.0",
    "commit":  "a1b2c3d",
})
inf.RegisterInfo(func() (map[string]interface{}, error) {
    return map[string]interface{}{
        "uptime": time.Since(startTime).String(),
    }, nil
})
2. Health Check Response

Use Info() to generate a health status payload.

inf.RegisterInfo(func() (map[string]interface{}, error) {
    dbStatus := checkDB()
    redisStatus := checkRedis()
    return map[string]interface{}{
        "status": "up",
        "components": map[string]string{
            "db":    dbStatus,
            "redis": redisStatus,
        },
    }, nil
})

Quick Start

Installation
go get github.com/nabbar/golib/monitor/info
Basic Implementation
package main

import (
    "fmt"
    "github.com/nabbar/golib/monitor/info"
)

func main() {
    // Initialize
    i, err := info.New("my-app")
    if err != nil {
        panic(err)
    }

    // Set some static data
    i.AddData("environment", "production")

    // Output name
    fmt.Println(i.Name()) // Output: my-app
}
Dynamic Data
i.RegisterInfo(func() (map[string]interface{}, error) {
    return map[string]interface{}{
        "goroutines": runtime.NumGoroutine(),
    }, nil
})

// Call Info() to execute function and get data
data := i.Info()

Best Practices

✅ DO
  • Do use SetName to update the component name if the default one changes during lifecycle.
  • Do handle nil returns from Info() if no data has been populated.
  • Do keep registered functions lightweight to ensure fast Info() responses.
❌ DON'T
  • Don't perform heavy blocking operations (like network calls) inside RegisterInfo functions without your own caching layer, as this will slow down every call to Info().
  • Don't assume data persistence if you rely solely on RegisterInfo; the function result is transiently merged with stored data.

API Reference

Interface Info

The main interface combining read and write operations.

type Info interface {
    montps.Info
    montps.InfoSet
}
Read Methods (montps.Info)
  • Name() string: Returns the component name.
  • Info() map[string]interface{}: Returns the aggregated info map.
  • MarshalJSON() ([]byte, error): Serializes to JSON.
  • MarshalText() ([]byte, error): Serializes to Text.
Write Methods (montps.InfoSet)
  • SetName(string): Sets/overrides the name.
  • SetData(map[string]interface{}): Replaces all info data.
  • AddData(string, interface{}): Adds or updates a specific key.
  • DelData(string): Removes a specific key.
  • RegisterName(func() (string, error)): Registers a dynamic name generator.
  • RegisterInfo(func() (map[string]interface{}, error)): Registers a dynamic info generator.

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Code Quality

    • Follow Go best practices.
    • Maintain code coverage > 90%.
    • Ensure all tests pass, including race detection.
  2. AI Usage Policy

    • AI must NEVER be used to generate package code or core functionality.
    • AI assistance is limited to: Testing, Debugging, and Documentation.
    • All AI-assisted work must be reviewed by humans.
  3. Testing

    • Use Ginkgo v2 / Gomega.
    • Add tests for any new features or bug fixes.

Resources

Package Documentation
  • GoDoc - Full API documentation.

AI Transparency

In compliance with EU AI Act Article 50.4: AI assistance was used for testing, documentation, and bug resolution under human supervision. All core functionality is human-designed and validated.


License

MIT License - See LICENSE file for details.

Copyright (c) 2020-2026 Nicolas JUHEL

Documentation

Overview

Package info provides a robust and thread-safe metadata management system for monitored components. It acts as a dynamic repository for identifying information (Name) and descriptive key-value pairs (Data) about a service, application, or internal component.

Core Philosophy

The 'info' package is designed to bridge the gap between static configuration and dynamic runtime state. It allows a component to have a permanent identity (Default Name) while also supporting manual overrides and real-time data injection through provider functions.

Architecture & Internal Logic

The internal state is managed by an 'inf' structure which utilizes a high-performance atomic map (libatm.Map) to ensure non-blocking, concurrent access from multiple health check routines or API endpoints.

## Naming Resolution Hierarchy

When the Name() method is invoked, the package follows a strict priority-based resolution logic:

  1. Dynamic Provider: If a function is registered via RegisterName(), it is executed. If it returns a non-empty string, this result is used.
  2. Manual Override: If no dynamic result is available, the package checks for a name set manually via SetName().
  3. Default Fallback: If neither of the above exists, the default name provided at initialization (via New()) is returned.

Naming Dataflow Diagram:

[ Call Name() ]
      |
      +--> [ Check Dynamic Provider (RegisterName) ] --(exists & non-empty)--> [ RETURN RESULT ]
      |
      +--> [ Check Manual Override (SetName) ] --------(exists)--------------> [ RETURN RESULT ]
      |
      +--> [ Default Name (New) ] -------------------------------------------> [ RETURN RESULT ]

## Metadata (Data) Resolution Logic

The Data() method aggregates information from two distinct layers:

  1. Static Store: Key-value pairs explicitly added via AddData() or SetData().
  2. Dynamic Provider: A map returned by the function registered via RegisterData().

The resolution logic merges these layers into a single map. In the event of a key collision, the data from the Dynamic Provider takes precedence, ensuring that real-time runtime values always override stale static configuration.

Metadata Dataflow Diagram:

[ Call Data() ]
      |
      +--[ Retrieve all static keys from Atomic Map ]
      |
      +--[ Execute Dynamic Provider (RegisterData) ]
      |
      +--[ Merge Logic: Dynamic values OVERWRITE static values ]
      |
      +--> [ RETURN MERGED MAP ]

Key Features

  • Thread-Safe: Atomic operations ensure consistency without the overhead of heavy mutexes.
  • Zero-Caching Policy: Provider functions are executed on every call, ensuring that data like CPU usage, memory stats, or current version is always fresh.
  • LIFO-like Cleanliness: SetData() replaces only user-defined metadata, preserving internal naming logic and registered functions.
  • Standard Interfaces: Implements encoding.TextMarshaler and json.Marshaler for seamless integration with logging frameworks and REST APIs.

Usage Examples

## Initializing and Adding Static Data

inf, _ := info.New("my-database-service")
inf.AddData("environment", "production")
inf.AddData("cluster", "eu-west-1")

## Registering Dynamic Metadata

This is useful for exposing real-time metrics alongside the component's identity.

inf.RegisterData(func() (map[string]interface{}, error) {
    return map[string]interface{}{
        "goroutines": runtime.NumGoroutine(),
        "uptime":     time.Since(startTime).String(),
    }, nil
})

## Manual Name Override

inf.SetName("temporary-maintenance-mode")
// Name() now returns "temporary-maintenance-mode" instead of "my-database-service".

Thread Safety & Performance

The internal implementation uses a lock-free approach where possible, relying on atomic stores and loads. This ensures that even under heavy load (e.g., thousands of status requests per second), the impact on the monitored component's performance is negligible.

Integration with Monitor

This package is a core dependency of the 'monitor' package. In a typical monitoring setup, the 'Info' instance is passed to the monitor, which then uses it to enrich health check reports and Prometheus metrics with the component's identity and metadata.

Example (Caching)

Example_caching demonstrates the caching behavior.

package main

import (
	"fmt"

	"github.com/nabbar/golib/monitor/info"
)

func main() {
	i, _ := info.New("service")

	callCount := 0
	i.RegisterName(func() (string, error) {
		callCount++
		return fmt.Sprintf("name-%d", callCount), nil
	})

	// First call executes the function
	name1 := i.Name()
	fmt.Printf("First call: %s (callCount: %d)\n", name1, callCount)

	// Second call uses cached value
	name2 := i.Name()
	fmt.Printf("Second call: %s (callCount: %d)\n", name2, callCount)

}
Output:
First call: name-1 (callCount: 1)
Second call: name-2 (callCount: 2)
Example (ErrorHandling)

Example_errorHandling demonstrates error handling.

package main

import (
	"fmt"

	"github.com/nabbar/golib/monitor/info"
)

func main() {
	i, _ := info.New("default-service")

	i.RegisterName(func() (string, error) {
		return "", fmt.Errorf("simulated error")
	})

	// Returns default name on error
	name := i.Name()
	fmt.Println(name)
}
Output:
default-service
Example (MultipleInfo)

Example_multipleInfo demonstrates handling multiple info registrations.

package main

import (
	"fmt"

	"github.com/nabbar/golib/monitor/info"
)

func main() {
	i, _ := info.New("service")

	// First registration
	i.RegisterData(func() (map[string]interface{}, error) {
		return map[string]interface{}{
			"version": "1.0.0",
		}, nil
	})

	fmt.Println("Version:", i.Data()["version"])

	// Re-registration clears cache
	i.RegisterData(func() (map[string]interface{}, error) {
		return map[string]interface{}{
			"version": "2.0.0",
		}, nil
	})

	fmt.Println("Version:", i.Data()["version"])
}
Output:
Version: 1.0.0
Version: 2.0.0

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Encode

type Encode interface {
	// String returns a single-line, human-readable string representation of the component's identification data.
	// The standard format is: "ComponentName (key1: value1, key2: value2)".
	// If no metadata is present, it returns only the name.
	String() string

	// Bytes returns the byte slice representation of the string generated by String().
	// This is a convenience method for operations requiring a []byte instead of a string.
	Bytes() []byte
}

Encode is an interface defining the contract for representing monitor information in a serializable format. It provides methods to generate both string and byte slice representations, typically used for logging, display, or as a component of larger status reports.

type Info

type Info interface {
	montps.InfoSet
}

Info is an interface that describes the behavior of a metadata container for monitored components. It extends the montps.InfoSet interface, which already incorporates read-only access (montps.Info) and write/update methods (montps.InfoSet). This structure is typically used to store identifying information (like a name) and dynamic status data (key-value pairs) about a monitored service. Implementations of this interface MUST ensure thread-safe operations as they are often accessed from concurrent health check routines and status reporting endpoints.

func New

func New(defaultName string) (Info, error)

New initializes and returns a new instance of the Info interface, configured with a mandatory default name. This default name serves as a permanent fallback identifier for the monitored component.

Arguments:

  • defaultName: A string used as the initial name and fallback for the component. It cannot be empty.

Returns:

  • Info: A pointer to a thread-safe implementation of the Info interface.
  • error: Returns an error if the defaultName provided is empty, indicating an invalid initialization attempt.

Example usage:

// Create a new Info instance for a database service.
inf, err := info.New("database-primary")
if err != nil {
    log.Fatalf("Failed to initialize monitor info: %v", err)
}

// The default name is immediately available.
fmt.Println(inf.Name()) // Output: database-primary
Example

ExampleNew demonstrates creating a new Info instance.

package main

import (
	"fmt"
	"log"

	"github.com/nabbar/golib/monitor/info"
)

func main() {
	i, err := info.New("my-service")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(i.Name())
}
Output:
my-service

Jump to

Keyboard shortcuts

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