maping

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 16 Imported by: 0

README

mAPI-ng Client

github.com/arhuman/maping/client — hosted, zero-config API observability for Go services.

The client collects per-endpoint request metrics (rate, latency, error rate, bandwidth) using client-side DDSketch aggregation, and ships compact Summaries to the mAPI-ng collector over gRPC. License: MIT (see LICENSE).


60-second quickstart

1. Install

go get github.com/arhuman/maping/client

The Gin adapter lives in its own module (github.com/arhuman/maping/client/gin) so the core client stays free of web-framework dependencies. If you use Gin, install it as well:

go get github.com/arhuman/maping/client/gin

2. Wire the middleware

Register the mAPI-ng middleware above gin.Recovery() so panics are recorded as 5xx before Recovery converts them, without altering host behavior.

import (
    maping    "github.com/arhuman/maping/client"
    mapinggin "github.com/arhuman/maping/client/gin"
)

func main() {
    // One recorder for the process lifetime.
    rec := maping.NewRecorder(maping.WithService("my-api"))

    r := gin.New()
    r.Use(mapinggin.MiddlewareWithRecorder(rec)) // above Recovery
    r.Use(gin.Recovery())

    // ... register routes ...

    srv := &http.Server{Addr: ":9090", Handler: r}
    // start srv in a goroutine ...

    // On shutdown: stop HTTP first, then flush the recorder.
    srv.Shutdown(ctx)
    rec.Shutdown(ctx)
}

See example/main.go for the complete runnable version including signal handling and correct shutdown ordering.

3. Activate

export MAPING_KEY=your-ingest-key

That is the only required configuration. Everything else is inferred.


No-op / zero-config guarantee

When MAPING_KEY is absent (or the resolved key is empty), NewRecorder returns a no-op recorder. Every method is safe and does nothing; no goroutine starts. The middleware is always safe to add to a codebase — activation is decoupled from the code change.

The client also fails open: transport errors, bad config values, and internal panics are logged once at Warn via the host's logger and never block or crash the host process.


Core API

// NewRecorder resolves config and returns a Recorder. Returns a no-op recorder
// when no ingest key is resolved.
func NewRecorder(opts ...Option) *Recorder

// Observe records one completed request. Safe on a no-op recorder.
// Safe for concurrent use.
func (*Recorder) Observe(rec Record)

// Shutdown flushes the last window and drains pending uploads.
// Call AFTER http.Server.Shutdown so no request is still writing a Record.
// Idempotent. Bounded by ctx.
func (*Recorder) Shutdown(ctx context.Context) error

SdkVersion = "0.1.0".


Gin adapter

Module: github.com/arhuman/maping/client/gin

// Middleware creates its own Recorder from opts. Use when you do not need
// to call Shutdown (fire-and-forget).
func Middleware(opts ...maping.Option) gin.HandlerFunc

// MiddlewareWithRecorder binds to a caller-owned Recorder so you control
// its lifecycle and can call rec.Shutdown on process exit.
func MiddlewareWithRecorder(rec *maping.Recorder) gin.HandlerFunc

Unmatched routes (where c.FullPath() returns "") are silently skipped to avoid emitting raw paths and exploding series cardinality.

Registration order is required: the mAPI-ng middleware must be registered before gin.Recovery(). See the quickstart above.


TLS and endpoint

The default endpoint is https://ingest.mapi-ng.dev (the hosted collector). An http:// endpoint is permitted for local or dev use and switches the transport to H2C (cleartext HTTP/2), which allows gRPC without TLS. Any other scheme is invalid config (falls back to no-op recorder with a one-time Warn log).


Configuration

Precedence for every setting: code option > env var > default.

Setting Code option Env var Default
Ingest key WithKey MAPING_KEY (none — absent means no-op)
Endpoint WithEndpoint MAPING_ENDPOINT https://ingest.mapi-ng.dev
Service name WithService MAPING_SERVICE binary name (via OTEL_SERVICE_NAME or os.Args[0])
Instance id WithInstance MAPING_INSTANCE $HOSTNAME env, else OS hostname (os.Hostname())
Flush window WithFlushWindow MAPING_FLUSH_SECONDS 10s

For full validation rules, precedence details, and invalid-config behavior see CONFIG.md.

Documentation

Overview

Package maping is the framework-agnostic Core of the mAPI-ng client: hosted, zero-config API observability for Go services (see docs/context.md). It owns config/env parsing, the in-process Summary aggregation (counters + a latency DDSketch per series), and the background Connect uploader.

The guiding contract is zero-config (CONFIG.md): the only required input is the ingest key. With no key resolved, NewRecorder returns a no-op recorder so adding mAPI-ng to a codebase is always safe — activation is a matter of flipping an env var, decoupled from the code change. The client fails open: setup and upload problems are logged (rate-limited) and surfaced in the dashboard, but never panic or block the host.

A framework adapter (e.g. client/gin) extracts the route template and final status after each request and calls Observe with a neutral Record.

Index

Constants

View Source
const SdkVersion = "0.1.0"

SdkVersion is the client SDK version reported in every Envelope/Handshake for server-side compatibility handling.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	Key         string
	Endpoint    string
	Service     string
	Instance    string
	FlushWindow time.Duration
}

Config is the resolved recorder configuration.

type Option

type Option func(*Config)

Option configures a Recorder. Options follow the functional-options pattern; a code option always beats the matching env var, which beats the default (CONFIG.md precedence).

func WithEndpoint

func WithEndpoint(endpoint string) Option

WithEndpoint overrides the collector URL (beats MAPING_ENDPOINT).

func WithFlushWindow

func WithFlushWindow(d time.Duration) Option

WithFlushWindow overrides the flush window (beats MAPING_FLUSH_SECONDS).

func WithInstance

func WithInstance(instance string) Option

WithInstance overrides the instance id (beats MAPING_INSTANCE).

func WithKey

func WithKey(key string) Option

WithKey sets the ingest key (beats MAPING_KEY). The key encodes the tenant.

func WithService

func WithService(service string) Option

WithService overrides the logical service name (beats MAPING_SERVICE).

type Record

type Record struct {
	Method        string
	RouteTemplate string
	Status        int
	Duration      time.Duration
	ReqBytes      int64
	RespBytes     int64
}

Record is the neutral, framework-agnostic input to Observe. An adapter builds one from a completed request.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder aggregates request Records in process and ships Summaries on a flush cycle. A recorder with no resolved key is a no-op: every method is safe and does nothing, and no goroutine runs.

func NewRecorder

func NewRecorder(opts ...Option) *Recorder

NewRecorder resolves config and returns a Recorder. If the resolved key is empty (zero-config safety), it returns a no-op recorder with no goroutine. On a transport construction failure it logs once at Warn and also returns the no-op recorder — the host is never affected (CONFIG.md fail-open).

func NewRecorderForTest

func NewRecorderForTest(tx Uploader) *Recorder

NewRecorderForTest returns a running Recorder wired to an injected Uploader, with a minimal config. It is the seam adapter tests use to substitute a fake transport and assert what was observed, without a live collector.

func (*Recorder) Observe

func (r *Recorder) Observe(rec Record)

Observe records one completed request. It is safe on a no-op recorder and safe for concurrent use.

The whole body is wrapped in a panic recovery: a bug in aggregation must be invisible to the host request (the core "mAPI-ng failing is invisible to the host" guarantee, docs/context.md). A recovered panic is logged once at Warn (rate-limited) and the observation is dropped.

Steady state (the series already exists) is allocation-free: shard select + lock + in-place counter increments + sketch.Add (itself alloc-free). Only the first sighting of a new series allocates (map insert + sketch.New).

func (*Recorder) Shutdown

func (r *Recorder) Shutdown(ctx context.Context) error

Shutdown stops the uploader goroutine, does a final flush (pushing the last window onto the ring), then synchronously drains the ring — attempting to send every pending request, bounded by ctx. A graceful shutdown therefore does not lose buffered summaries: it best-effort ships them before returning (this is what the E2E test relies on). Once ctx expires it stops retrying and returns; any still-pending requests are abandoned. It is synchronous and idempotent. Hosts must call it AFTER their http.Server.Shutdown so no request is still writing a Record.

type Uploader

type Uploader interface {
	Upload(ctx context.Context, req *mapingv1.UploadRequest) error
	Register(ctx context.Context, hs *mapingv1.Handshake) error
}

Uploader is the transport dependency the Recorder needs. The concrete implementation lives in internal/transport; adapters and tests may inject a fake to observe uploads without a live collector (lang-go DI).

Directories

Path Synopsis
gin module
internal
buffer
Package buffer implements a bounded, drop-oldest ring of pending UploadRequests for the mAPI-ng client's fail-open uploader (docs/context.md).
Package buffer implements a bounded, drop-oldest ring of pending UploadRequests for the mAPI-ng client's fail-open uploader (docs/context.md).
transport
Package transport wraps the generated Connect IngestService client with the wire policy mAPI-ng requires: the gRPC protocol (ADR-0002) over a dedicated HTTP client with an explicit timeout, zstd send-compression, and cleartext HTTP/2 (H2C) for local/dev http:// endpoints.
Package transport wraps the generated Connect IngestService client with the wire policy mAPI-ng requires: the gRPC protocol (ADR-0002) over a dedicated HTTP client with an explicit timeout, zstd send-compression, and cleartext HTTP/2 (H2C) for local/dev http:// endpoints.
Package sketch implements a hand-rolled DDSketch specialised for request latency aggregation, as decided in ADR-0001.
Package sketch implements a hand-rolled DDSketch specialised for request latency aggregation, as decided in ADR-0001.

Jump to

Keyboard shortcuts

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