middlemonitor

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 39 Imported by: 0

README

Middle-Monitor SDK for Go

Drop-in SDK for error reporting with Middle-Monitor. Auto-configured via environment variables, with automatic error and panic capture.

Documentation: middlemonitor.io/docs#sdk

Installation

go get github.com/middle-monitor/sdk-go

Set the environment variables and the SDK initializes itself:

export MIDDLE_MONITOR_API_URL="https://api.middlemonitor.io"
export MIDDLE_MONITOR_TOKEN="your_token"
export MIDDLE_MONITOR_SERVICE="my-service"
package main

import (
    "github.com/labstack/echo/v4"
    "github.com/middle-monitor/sdk-go"
)

func main() {
    e := echo.New()

    // One line to enable automatic capture
    e.Use(middlemonitor.EchoMiddleware())

    e.GET("/", func(c echo.Context) error {
        return fmt.Errorf("an error") // Captured automatically
    })

    e.Logger.Fatal(e.Start(":8080"))
}

Usage with Gin

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/middle-monitor/sdk-go"
)

func main() {
    r := gin.Default()

    // One line to enable automatic capture
    r.Use(middlemonitor.GinMiddleware())

    r.GET("/", func(c *gin.Context) {
        panic("a panic") // Captured automatically
    })

    r.Run(":8080")
}

Usage with net/http (ServeMux, gorilla/mux, chi, ...)

package main

import (
    "net/http"

    "github.com/middle-monitor/sdk-go"
)

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        panic("a panic") // Captured automatically
    })

    // One line to enable automatic capture
    http.ListenAndServe(":8080", middlemonitor.HTTPMiddleware(mux))
}

Works with any router built on http.Handler, e.g. gorilla/mux: r.Use(middlemonitor.HTTPMiddleware).

Panic capture at startup

package main

import (
    "github.com/middle-monitor/sdk-go"
)

func main() {
    defer middlemonitor.CapturePanicGlobal()

    panic("a startup panic") // Captured automatically
}

Manual setup (optional)

package main

import (
    "github.com/middle-monitor/sdk-go"
)

func main() {
    middlemonitor.InitWithConfig(
        "https://api.middlemonitor.io",
        "my-service",
        "your_token",
    )

    err := doSomething()
    if err != nil {
        middlemonitor.ReportError(err)
    }
}

Logging

Send structured logs to Middle-Monitor:

// Buffered (async, batched)
middlemonitor.Log(ctx, middlemonitor.LogLevelINFO, "Message", map[string]string{
    "key": "value",
})

// Immediate (sync)
middlemonitor.LogSync(ctx, middlemonitor.LogLevelERROR, "Critical error", nil)

// Flush before shutdown
middlemonitor.FlushLogs(ctx)

Levels: LogLevelDEBUG, LogLevelINFO, LogLevelWARN, LogLevelERROR, LogLevelFATAL, LogLevelPANIC.

Global functions

  • middlemonitor.ReportError(err) — report an error
  • middlemonitor.ReportErrorWithDetails(err, file, line) — report with file/line details
  • middlemonitor.CapturePanicGlobal() — capture a panic (use with defer)
  • middlemonitor.Log(ctx, level, message, attrs) — send a buffered log
  • middlemonitor.LogSync(ctx, level, message, attrs) — send a log immediately
  • middlemonitor.FlushLogs(ctx) — flush the log buffer before shutdown

Environment variables

Variable Description Required Default
MIDDLE_MONITOR_API_URL Middle-Monitor ingestion URL Recommended https://api.middlemonitor.io
MIDDLE_MONITOR_TOKEN Authentication token Recommended
MIDDLE_MONITOR_SERVICE Service name No "unknown"
MIDDLE_MONITOR_DISABLE_HTTP_ERROR_REPORTING Stop the middlewares from reporting 5xx No false
MIDDLE_MONITOR_PPROF_URL Scrape an external pprof server instead of profiling in-process No

MIDDLE_MONITOR_TOKEN also acts as the opt-in switch: with no token set, the SDK does not initialize itself and every entry point is a no-op, so an application that never configured Middle-Monitor never sends anything.

Only server errors (5xx) and panics are reported. 4xx responses (401, 404, etc.) are treated as client errors and are not sent.

Applications that report their own errors

The HTTP middlewares submit every 5xx to the Errors view, building the message from the response body. If your application already reports its errors from its own error helper, you get two entries per failure — one with the real cause, one generic. Disable the middleware's half:

cfg := middlemonitor.NewConfig(apiURL, service, token)
cfg.DisableHTTPErrorReporting = true
middlemonitor.Init(cfg)

Panics are still reported.

Profiling

CaptureCPUProfile and CaptureHeapProfile profile the running process directly — no pprof HTTP server to start and secure:

client := middlemonitor.GetGlobalClient()
client.CaptureCPUProfile(ctx, 30*time.Second)
client.CaptureHeapProfile(ctx)

Set MIDDLE_MONITOR_PPROF_URL (or Config.PprofURL) only to profile a different process through its pprof endpoint.

Troubleshooting

"failed to upload metrics" / "connection refused"

The SDK sends traces, logs, and metrics to the ingestion endpoint (OTLP: /v1/traces, /v1/logs, /v1/metrics). A connection error means the SDK can't reach it. Check that MIDDLE_MONITOR_API_URL points at your ingestion endpoint and that outbound HTTPS is allowed:

export MIDDLE_MONITOR_API_URL="https://api.middlemonitor.io"

Documentation

Index

Constants

View Source
const KeyExceptionMessage = "middlemonitor_exception_message"

KeyExceptionMessage is the context key for storing the real error message when returning a 500 without exposing details to the client. The middleware reads this value and forwards it to Middle Monitor so the Errors view shows the actual cause rather than a generic "HTTP 500".

Echo: c.Set(middlemonitor.KeyExceptionMessage, err.Error()); return c.JSON(500, map[string]any{}) Gin: c.Set(middlemonitor.KeyExceptionMessage, err.Error()); c.JSON(500, map[string]any{})

Variables

View Source
var (
	ErrNotInitialized = errors.New("client not initialized")
	ErrConfigMissing  = errors.New("config not initialized")
	ErrConfigRequired = errors.New("endpoint and token required")

	// client.go
	ErrResourceCreate = errors.New("failed to create resource")
	ErrTraceExport    = errors.New("failed to create trace exporter")
	ErrMetricExport   = errors.New("failed to create metric exporter")
	ErrConfigLoad     = errors.New("failed to load config")
	ErrClientCreate   = errors.New("failed to create client")
	ErrTracerShutdown = errors.New("failed to shutdown tracer provider")
	ErrMeterShutdown  = errors.New("failed to shutdown meter provider")
	ErrShutdown       = errors.New("shutdown failed")

	// log.go
	ErrMarshal       = errors.New("marshal failed")
	ErrRequestCreate = errors.New("failed to create request")
	ErrLogSend       = errors.New("failed to send logs")
	ErrLogExport     = errors.New("log export failed")

	// profile.go
	ErrPprofRequest      = errors.New("failed to create pprof request")
	ErrProfileInProgress = errors.New("cpu profile already in progress")
	ErrProfileFetch      = errors.New("failed to fetch profile")
	ErrProfileRead       = errors.New("failed to read profile")
	ErrProfileEmpty      = errors.New("empty profile data")
	ErrMultipartBuild    = errors.New("multipart build failed")
	ErrUploadRequest     = errors.New("failed to create upload request")
	ErrProfileUpload     = errors.New("profile upload failed")

	// config.go
	ErrSamplingConfig = errors.New("failed to parse sampling config")
	ErrSamplingValue  = errors.New("invalid sampling value")
	ErrSamplingRange  = errors.New("sampling must be between -1 and 1")
	ErrLogLevel       = errors.New("invalid log level")
	ErrMinHTTPStatus  = errors.New("invalid min http status")
	ErrBoolValue      = errors.New("invalid boolean value")
	ErrErrorSubmit    = errors.New("failed to submit error to backend")
)

Functions

func CapturePanicGlobal

func CapturePanicGlobal()

CapturePanicGlobal captures a panic using the global client. Usage: defer middlemonitor.CapturePanicGlobal()

func EchoMiddleware

func EchoMiddleware() echo.MiddlewareFunc

EchoMiddleware returns an Echo middleware that automatically creates traces and logs with OpenTelemetry Usage: e.Use(middlemonitor.EchoMiddleware())

func FlushLogs

func FlushLogs(ctx context.Context)

FlushLogs sends any buffered logs immediately. Call before shutdown to avoid losing logs.

func GinMiddleware

func GinMiddleware() gin.HandlerFunc

GinMiddleware returns a Gin middleware that automatically creates traces and logs with OpenTelemetry Usage: r.Use(middlemonitor.GinMiddleware())

func HTTPMiddleware added in v0.1.3

func HTTPMiddleware(next http.Handler) http.Handler

HTTPMiddleware instruments any net/http handler: traces with OpenTelemetry, captures panics, and reports 5xx responses to the Errors view. It plugs into http.ServeMux, gorilla/mux (r.Use(middlemonitor.HTTPMiddleware)), chi, or a raw http.Handler chain.

net/http exposes no route template, so the raw URL path is used as the route for span names and sampling rules.

Usage: http.ListenAndServe(addr, middlemonitor.HTTPMiddleware(mux))

func Init

func Init(cfg *Config) error

Init initializes the global Middle-Monitor client with OpenTelemetry. It is safe to call more than once: the first successful client wins. A FAILED attempt is not remembered, so a caller can retry once the configuration is fixed — with sync.Once, one early failure used to disable the SDK for the whole process lifetime while returning nil to every later caller.

func InitSimple

func InitSimple() error

InitSimple initializes from environment variables

func InitWithConfig

func InitWithConfig(apiURL, service, token string) error

InitWithConfig initializes with explicit configuration (backward compatibility)

func Log

func Log(ctx context.Context, level LogLevel, message string, attrs map[string]string)

Log sends a log record to Middle-Monitor. It buffers logs and flushes periodically. level: DEBUG, INFO, WARN, ERROR, FATAL, PANIC

func LogSync

func LogSync(ctx context.Context, level LogLevel, message string, attrs map[string]string) error

LogSync immediately sends a log record to Middle-Monitor (no buffering).

func NewSampler

func NewSampler(cfg *Config) sdktrace.Sampler

NewSampler creates a new sampler with the given configuration

func ReportError

func ReportError(err error)

ReportError reports an error using the global client.

func ReportErrorWithDetails

func ReportErrorWithDetails(err error, file string, line int)

ReportErrorWithDetails reports an error with file and line using the global client.

func ReportException

func ReportException(message string)

ReportException sends the given message to Middle Monitor (e.g. from a background job). File/line are taken from the call stack. No HTTP code or URL — use ReportExceptionWithContext when in a request.

func ReportExceptionFromEcho

func ReportExceptionFromEcho(c echo.Context, message string)

ReportExceptionFromEcho reports an exception with only the message. Method, URL and file/line are read from the Echo context and the call stack; the HTTP code is assumed 500 (server error).

Example:

if err != nil {
    middlemonitor.ReportExceptionFromEcho(c, err.Error())
    return c.JSON(http.StatusInternalServerError, map[string]interface{}{})
}

func ReportExceptionFromGin

func ReportExceptionFromGin(c *gin.Context, message string)

ReportExceptionFromGin reports an exception with only the message. Method, URL and file/line are read from the Gin context and the call stack; the HTTP code is assumed 500 (server error).

Example:

if err != nil {
    middlemonitor.ReportExceptionFromGin(c, err.Error())
    c.JSON(http.StatusInternalServerError, map[string]interface{}{})
    return
}

func ReportExceptionWithContext

func ReportExceptionWithContext(ctx context.Context, err error)

ReportExceptionWithContext reports an error to Middle Monitor. Pass the request context (e.g. c.Request().Context()) when inside an HTTP handler: method, URL and status 500 are then recovered automatically from the context set by the middleware. Outside a request, pass nil: only message and file/line from the stack are sent.

Example (Echo):

if err != nil {
    middlemonitor.ReportExceptionWithContext(c.Request().Context(), err)
    return c.JSON(http.StatusInternalServerError, map[string]interface{}{})
}

Example (Gin): middlemonitor.ReportExceptionWithContext(c.Request.Context(), err)

func SubmitApplicationError

func SubmitApplicationError(name, message, file string, line, statusCode int, httpMethod, httpURL, requestBody string)

SubmitApplicationError reports an error to the Errors view using the global client.

Types

type Client

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

Client represents a Middle-Monitor OpenTelemetry client

func GetGlobalClient

func GetGlobalClient() *Client

GetGlobalClient returns the global client, or nil when the SDK is not configured. Callers must handle nil: it is the normal state of an application that has not opted into Middle-Monitor.

func NewClient

func NewClient(apiURL, service string) *Client

NewClient creates a client. Prefer Init + GetGlobalClient for most cases.

func NewClientWithConfig

func NewClientWithConfig(cfg *Config) (*Client, error)

NewClientWithConfig creates a new OpenTelemetry-based client from Config

func (*Client) CaptureCPUProfile

func (c *Client) CaptureCPUProfile(ctx context.Context, duration time.Duration) error

CaptureCPUProfile collects a CPU profile for the given duration and uploads it to the Middle-Monitor API. It profiles this process directly; set Config.PprofURL to scrape an external pprof server instead.

func (*Client) CaptureHeapProfile

func (c *Client) CaptureHeapProfile(ctx context.Context) error

CaptureHeapProfile collects a heap profile and uploads it to the Middle-Monitor API. It profiles this process directly; set Config.PprofURL to scrape an external pprof server instead.

func (*Client) CapturePanic

func (c *Client) CapturePanic()

CapturePanic captures a panic and reports it to Middle-Monitor Usage: defer client.CapturePanic()

func (*Client) GetMeter

func (c *Client) GetMeter() metric.Meter

GetMeter returns the meter from the client

func (*Client) GetTracer

func (c *Client) GetTracer() trace.Tracer

GetTracer returns the tracer from the client

func (*Client) ReportCustomError

func (c *Client) ReportCustomError(name, message, file string, line int) error

ReportCustomError reports a named error with details.

func (*Client) ReportCustomErrorWithHTTP

func (c *Client) ReportCustomErrorWithHTTP(name, message, file string, line int, httpMethod, httpURL, httpHeaders, httpBody string) error

ReportCustomErrorWithHTTP reports a named error with HTTP context.

func (*Client) ReportError

func (c *Client) ReportError(err error) error

ReportError reports an error to Middle-Monitor using OpenTelemetry.

func (*Client) ReportErrorWithDetails

func (c *Client) ReportErrorWithDetails(err error, file string, line int) error

ReportErrorWithDetails reports an error with custom file and line using OpenTelemetry.

func (*Client) SetToken

func (c *Client) SetToken(token string)

SetToken sets the auth token.

func (*Client) Shutdown

func (c *Client) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the client

func (*Client) SubmitApplicationError

func (c *Client) SubmitApplicationError(name, message, file string, line, statusCode int, httpMethod, httpURL, requestBody string)

SubmitApplicationError reports an error to the Errors view (POST /api/v1/errors).

type Config

type Config struct {
	// OTLP endpoint (OTEL Collector or backend)
	Endpoint string

	// Insecure disables TLS (use HTTP instead of HTTPS). Set true for http:// or localhost.
	Insecure bool

	// Service information
	Service string
	Token   string

	// Export protocol (grpc or http, default: http)
	Protocol string

	// PprofURL points CaptureCPUProfile/CaptureHeapProfile at an external pprof
	// HTTP server (e.g. http://localhost:6060) instead of profiling this
	// process. Empty (the default) profiles in-process, with no socket to expose.
	PprofURL string

	// DisableHTTPErrorReporting stops the HTTP middlewares from submitting 5xx
	// responses to the Errors view. Set it when the application already reports
	// its own errors: otherwise every failure is recorded twice, once by the
	// application with the real cause and once by the middleware with whatever
	// the response body happens to carry. Panics are still always reported.
	DisableHTTPErrorReporting bool

	// Sampling configuration
	Sampling SamplingConfig
}

Config represents the Middle-Monitor SDK configuration

func ConfigFromEnv

func ConfigFromEnv() (*Config, error)

ConfigFromEnv creates configuration from environment variables

func GetGlobalConfig

func GetGlobalConfig() *Config

GetGlobalConfig returns the global configuration, or nil when the SDK is not configured.

func NewConfig

func NewConfig(endpoint, service, token string) *Config

NewConfig creates a new configuration with defaults

func (*Config) ShouldSampleLog

func (c *Config) ShouldSampleLog(route string, level LogLevel, httpStatus int, traceHasError bool) bool

ShouldSampleLog determines if a log should be sampled

func (*Config) ShouldSampleTrace

func (c *Config) ShouldSampleTrace(route string, hasError bool) bool

ShouldSampleTrace determines if a trace should be sampled

type HTTPStatusError added in v0.1.2

type HTTPStatusError struct {
	StatusCode int
	Body       string
}

HTTPStatusError carries a non-OK status code from an HTTP response.

func (*HTTPStatusError) Error added in v0.1.2

func (e *HTTPStatusError) Error() string

type LogLevel

type LogLevel string

LogLevel represents a log level

const (
	LogLevelDEBUG LogLevel = "DEBUG"
	LogLevelINFO  LogLevel = "INFO"
	LogLevelWARN  LogLevel = "WARN"
	LogLevelERROR LogLevel = "ERROR"
	LogLevelFATAL LogLevel = "FATAL"
	LogLevelPANIC LogLevel = "PANIC"
)

type LogsSamplingConfig

type LogsSamplingConfig struct {
	// Log levels to capture (default: [ERROR, FATAL, PANIC])
	Levels []LogLevel

	// Capture logs for HTTP status >= this code (default: 500)
	// 4xx are customer-facing; only 5xx (and panics) are reported by default.
	// 0 = disable HTTP status filtering
	MinHTTPStatus int

	// Capture all logs linked to a trace with error (default: true)
	CaptureOnTraceError bool

	// Routes to always capture logs (default: empty = all)
	AlwaysCaptureRoutes []string

	// Routes to never capture logs (default: ["/health", "/metrics", "/ready"])
	NeverCaptureRoutes []string
}

LogsSamplingConfig configures log sampling

type Sampler

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

Sampler implements OpenTelemetry sampling with custom rules

func (*Sampler) Description

func (s *Sampler) Description() string

Description implements sdktrace.Sampler

func (*Sampler) ShouldSample

func (s *Sampler) ShouldSample(params sdktrace.SamplingParameters) sdktrace.SamplingResult

ShouldSample implements sdktrace.Sampler

type SamplingConfig

type SamplingConfig struct {
	// Sampling for traces
	Traces TracesSamplingConfig

	// Sampling for logs
	Logs LogsSamplingConfig
}

SamplingConfig configures sampling for traces and logs

func DefaultSamplingConfig

func DefaultSamplingConfig() SamplingConfig

DefaultSamplingConfig returns the default sampling configuration.

type TracesSamplingConfig

type TracesSamplingConfig struct {
	// Percentage of traces to sample (0.0 - 1.0)
	// -1 means auto (uses the default percentage)
	Percentage float64

	// Always sample traces with errors (default: true)
	AlwaysSampleErrors bool

	// Routes to always sample (default: empty = all)
	AlwaysSampleRoutes []string

	// Routes to never sample (default: ["/health", "/metrics", "/ready"])
	NeverSampleRoutes []string
}

TracesSamplingConfig configures trace sampling

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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