A Go middleware that stores various information about your web application (response time, status code count, etc.)

Overview

Go stats handler

Build Status

stats is a net/http handler in golang reporting various metrics about your web application.

This middleware has been developed and required for the need of picfit, an image resizing server written in Go.

Compatibility

This handler supports the following frameworks at the moment:

We don't support your favorite Go framework? Send me a PR or create a new issue and I will implement it :)

Installation

  1. Make sure you have a Go language compiler >= 1.3 (required) and git installed.
  2. Make sure you have the following go system dependencies in your $PATH: bzr, svn, hg, git
  3. Ensure your GOPATH is properly set.
  4. Download it:
go get github.com/thoas/stats

Usage

Basic net/http

To use this handler directly with net/http, you need to call the middleware with the handler itself:

package main

import (
    "net/http"
    "github.com/thoas/stats"
)

func main() {
    h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    handler := stats.New().Handler(h)
    http.ListenAndServe(":8080", handler)
}

Negroni

If you are using negroni you can implement the handler as a simple middleware in server.go:

package main

import (
    "net/http"
    "github.com/codegangsta/negroni"
    "github.com/thoas/stats"
    "encoding/json"
)

func main() {
    middleware := stats.New()

    mux := http.NewServeMux()

    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    mux.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")

        stats := middleware.Data()

        b, _ := json.Marshal(stats)

        w.Write(b)
    })

    n := negroni.Classic()
    n.Use(middleware)
    n.UseHandler(mux)
    n.Run(":3000")
}

HTTPRouter

If you are using HTTPRouter you need to call the middleware with the handler itself:

package main

import (
        "encoding/json"
        "github.com/julienschmidt/httprouter"
        "github.com/thoas/stats"
        "net/http"
)

func main() {
        router := httprouter.New()
        s := stats.New()
        router.GET("/stats", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
                w.Header().Set("Content-Type", "application/json; charset=utf-8")
                s, err := json.Marshal(s.Data())
                if err != nil {
                        http.Error(w, err.Error(), http.StatusInternalServerError)
                }
                w.Write(s)
        })
        http.ListenAndServe(":8080", s.Handler(router))
}

Martini

If you are using martini, you can implement the handler as a wrapper of a Martini.Context in server.go:

package main

import (
    "encoding/json"
    "github.com/go-martini/martini"
    "github.com/thoas/stats"
    "net/http"
)

func main() {
    middleware := stats.New()

    m := martini.Classic()
    m.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })
    m.Get("/stats", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")

        stats := middleware.Data()

        b, _ := json.Marshal(stats)

        w.Write(b)
    })

    m.Use(func(c martini.Context, w http.ResponseWriter, r *http.Request) {
        beginning, recorder := middleware.Begin(w)

        c.Next()

        middleware.End(beginning, stats.WithRecorder(recorder))
    })
    m.Run()
}

Run it in a shell:

$ go run server.go

Then in another shell run:

$ curl http://localhost:3000/stats | python -m "json.tool"

Expect the following result:

{
    "total_response_time": "1.907382ms",
    "average_response_time": "86.699\u00b5s",
    "average_response_time_sec": 8.6699e-05,
    "count": 1,
    "pid": 99894,
    "status_code_count": {
        "200": 1
    },
    "time": "2015-03-06 17:23:27.000677896 +0100 CET",
    "total_count": 22,
    "total_response_time_sec": 0.0019073820000000002,
    "total_status_code_count": {
        "200": 22
    },
    "unixtime": 1425659007,
    "uptime": "4m14.502271612s",
    "uptime_sec": 254.502271612
}

See examples to test them.

Inspiration

Antoine Imbert is the original author of this middleware.

Originally developed for go-json-rest, it had been ported as a simple Golang handler by Florent Messa to be used in various frameworks.

This middleware implements a ticker which is launched every seconds to reset requests/sec and will implement new features in a near future :)

Comments
  • Data method is not thread safe.

    Data method is not thread safe.

    Though this method is protected by lock and create a new structure on every call, this structure still use the same instance of ResponseCounts. So it is possible that somebody would read this map in the same moment as EndWithStatus would write into it.
    In go 1.6 it will cause a panic.

    bug 
    opened by e-max 10
  • Ignore hijacked connections

    Ignore hijacked connections

    Hijacked connections can remain open for a long time (Websocket for example) and generate a wrong response time in the statistics.

    Prior to this PR, hijacked connections were considered status code 200. With this PR, we ignore the hijacked connections.

    In addition, we can remove the code specific to Websocket because Websocket is a hijacked connection.

    And finally, it fixes a bug because, prior to this PR, Websocket upgrade failures were ignored.

    opened by juliens 3
  • Project abandoned?

    Project abandoned?

    Seems like @thoas never commented or did anything in this project since april.

    Is it abandoned?

    If yes, I'll continue it in my fork (https://github.com/caarlos0/stats)...

    opened by caarlos0 3
  • Bogus HTTP response status code stats

    Bogus HTTP response status code stats

    Hi

    I'm observing what looks to be a bogus behavior of your middleware regarding the counting of HTTP responses status code. Given the following implementation with Negroni:

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"net/http"
    	"time"
    
    	"github.com/gorilla/mux"
    	"github.com/thoas/stats"
    	"github.com/urfave/negroni"
    )
    
    func main() {
    	router := mux.NewRouter()
    	httpStats := stats.New()
    
    	router.HandleFunc("/a", handleA).
    		Methods("GET", "POST", "PUT")
    
    	router.HandleFunc("/b", handleB).
    		Methods("GET")
    
    	router.HandleFunc("/c", handleC).
    		Methods("GET")
    
    	router.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
    		w.Header().Set("Content-Type", "application/json")
    		stats := httpStats.Data()
    		b, _ := json.Marshal(stats)
    		w.Write(b)
    	})
    
    	n := negroni.New()
    
    	n.UseHandler(router)
    	n.Use(httpStats)
    
    	http.ListenAndServe(":8000", n)
    }
    
    func handleA(rw http.ResponseWriter, r *http.Request) {
    	switch r.Method {
    	case "POST", "PUT":
    		time.Sleep(100 * time.Millisecond)
    		rw.WriteHeader(http.StatusCreated)
    
    	case "GET":
    		time.Sleep(10 * time.Millisecond)
    	}
    
    	fmt.Fprintf(rw, "A\n")
    }
    
    func handleB(rw http.ResponseWriter, r *http.Request) {
    	time.Sleep(50 * time.Millisecond)
    	fmt.Fprintf(rw, "B\n")
    }
    
    func handleC(rw http.ResponseWriter, r *http.Request) {
    	time.Sleep(10 * time.Millisecond)
    	fmt.Fprintf(rw, "C\n")
    }
    

    Sending the following HTTP requests...

    $ curl -i -X POST localhost:8000/a
    HTTP/1.1 201 Created
    Date: Mon, 18 Dec 2017 15:16:25 GMT
    Content-Length: 2
    Content-Type: text/plain; charset=utf-8
    
    A
    
    $ curl -i -X POST localhost:8000/b
    HTTP/1.1 405 Method Not Allowed
    Date: Mon, 18 Dec 2017 15:16:32 GMT
    Content-Length: 0
    Content-Type: text/plain; charset=utf-8
    
    $ curl -i localhost:8000/c
    HTTP/1.1 200 OK
    Date: Mon, 18 Dec 2017 15:16:40 GMT
    Content-Length: 2
    Content-Type: text/plain; charset=utf-8
    
    C
    
    $ curl -i localhost:8000/z
    HTTP/1.1 404 Not Found
    Content-Type: text/plain; charset=utf-8
    X-Content-Type-Options: nosniff
    Date: Mon, 18 Dec 2017 15:16:42 GMT
    Content-Length: 19
    
    404 page not found
    

    ...I expected the /stats response total_status_code_count value to be {"200":1,"201":1,"404":1,"405":1}, but it's not:

    $ curl localhost:8000/stats | jq .
    {
      "pid": 7201,
      "uptime": "46.962268141s",
      "uptime_sec": 46.962268141,
      "time": "2017-12-18 16:16:55.098574 +0100 CET m=+46.963702150",
      "unixtime": 1513610215,
      "status_code_count": {},
      "total_status_code_count": {
        "200": 4
      },
      "count": 0,
      "total_count": 4,
      "total_response_time": "13.083µs",
      "total_response_time_sec": 1.3083e-05,
      "average_response_time": "3.27µs",
      "average_response_time_sec": 3.27e-06
    }
    

    Am I doing something wrong?

    opened by falzm 2
  • *Options has no field or method saveResult

    *Options has no field or method saveResult

    // Options are stats options.
    type Options struct {
    	statusCode *int
    	size       int
    	recorder   ResponseWriter
    }
    

    https://github.com/thoas/stats/blob/ec540afe6231778704c45afd1fd907089d650de7/options.go#L55

    ../../thoas/stats/options.go:55:4: o.saveResult undefined (type *Options has no field or method saveResult)

    opened by artpar 1
  • Latest check in breaks my build

    Latest check in breaks my build

    I'm using your fine package in a small app thats a go module, when I did a go get -u I get the error: options.go:55:4: o.saveResult undefined (type *Options has no field or method saveResult)

    While this is not a problem, it would be advantageous if you could tag a release or - even better - use Git flow to ensure we can keep using your package as you develop.

    opened by RobHumphris 1
  • Stop recording websocket connection

    Stop recording websocket connection

    Websocket response with long pull is logged as a reallly slow request, therefore making the response time inaccurate. This PR stops logging websocket connection.

    opened by siyu6974 1
  •  Add explicity termination

    Add explicity termination

    hello, thoas and dev. Thanks for your awesome work. it is very very helpful for me.

    it seems garbage collection can not collect stats.Stats. So I'd like to add explicit gorutine termination for the purpose of garbage collection can mark as dead.

    opened by keizo042 1
  • Example for Gin doesn't work (anymore?)

    Example for Gin doesn't work (anymore?)

    image

    main.go:35: cannot use c.Writer (type gin.ResponseWriter) as type stats.ResponseWriter in argument to Stats.End:
            gin.ResponseWriter does not implement stats.ResponseWriter (missing Before method)
    FATAL: command "build" failed: exit status 2
    
    opened by blockloop 1
  • Fix function comments based on best practices from Effective Go

    Fix function comments based on best practices from Effective Go

    Every exported function in a program should have a doc comment. The first sentence should be a summary that starts with the name being declared. From effective go.

    I generated this with CodeLingo and I'm keen to get some feedback, but this is automated so feel free to close it and just say "opt out" to opt out of future CodeLingo outreach PRs.

    opened by Daanikus 0
  • Possible false response code

    Possible false response code

    if we look at https://github.com/thoas/stats/blob/master/recorder.go#L39 and take following scenario:

    func (r *recorderResponseWriter) WriteHeader(code int) {
    	r.written = true
    	r.ResponseWriter.WriteHeader(code)
    	r.status = code
    }
    
    1. We write header 200. :: r.status=200, r.ResponseWriter.status=200
    2. We write header 200. :: r.status=300, r.ResponseWriter.status=200 in step 2 internal response writer ignores new status code.

    ref:

    if I understood the issue correctly and if it really is a genuine issue, happy to make a PR

    opened by sanketplus 0
  • Why reset Response Counts?

    Why reset Response Counts?

    https://github.com/thoas/stats/blob/master/stats.go#L33

    Why reset the ResponseCounts every second? My team and I were using this value, banking on getting a total of status codes from this, but, after seeing this have switched to using the TotalResponseCounts and diffing.

    opened by steamrolla 0
  • use martini status_code_count and total_status_code_count code question,

    use martini status_code_count and total_status_code_count code question,

    { "pid": 27613, "uptime": "2m7.358545583s", "uptime_sec": 127.358545583, "time": "2016-11-10 14:45:35.526270515 +0800 CST", "unixtime": 1478760335, "status_code_count": { "200": 1 }, "total_status_code_count": { "200": 17 }, "count": 1, "total_count": 17, "total_response_time": "78.531534ms", "total_response_time_sec": 0.078531534, "average_response_time": "4.619502ms", "average_response_time_sec": 0.004619502 }

    status_code_count and total_status_code_count code always 200, reivew the code , it is hard-coded. so I think the statecode should be depend on w. func (mw *Stats) Begin(w http.ResponseWriter) (time.Time, ResponseWriter) { start := time.Now()

    writer := NewRecorderResponseWriter(w, 200)
    
    return start, writer
    

    }

    opened by DavidYangNO1 1
  • wrong status code of middleware

    wrong status code of middleware

    the example of gin should be:

    // StatMiddleware response time, status code count, etc.
    func StatMiddleware() gin.HandlerFunc {
        return func(c *gin.Context) {
            beginning, _ := Stats.Begin(c.Writer)
            c.Next()
            Stats.EndWithStatus(beginning, c.Writer.Status())
        }
    }
    

    the example of martini has same issue

    opened by cgyy 0
Owner
Florent Messa
CTO @ulule
Florent Messa
A collection of useful middleware for Go HTTP services & web applications 🛃

gorilla/handlers Package handlers is a collection of handlers (aka "HTTP middleware") for use with Go's net/http package (or any framework supporting

Gorilla Web Toolkit 1.5k Dec 31, 2022
Chi ip banner is a chi middleware that bans some ips from your Chi http server.

Chi Ip Banner Chi ip banner is a chi middleware that bans some ips from your Chi http server. It reads a .txt file in your project's root, called bani

null 1 Jan 4, 2022
Simple, lightweight and faster response (JSON, JSONP, XML, YAML, HTML, File) rendering package for Go

Package renderer Simple, lightweight and faster response (JSON, JSONP, XML, YAML, HTML, File) rendering package for Go Installation Install the packag

Saddam H 250 Dec 13, 2022
Painless middleware chaining for Go

Alice Alice provides a convenient way to chain your HTTP middleware functions and the app handler. In short, it transforms Middleware1(Middleware2(Mid

Justinas Stankevičius 2.7k Dec 26, 2022
Go http.Hander based middleware stack with context sharing

wrap Package wrap creates a fast and flexible middleware stack for http.Handlers. Features small; core is only 13 LOC based on http.Handler interface;

go-on - web toolkit in Go 59 Apr 5, 2022
Go package that handles HTML, JSON, XML and etc. responses

gores http response utility library for Go this package is very small and lightweight, useful for RESTful APIs. installation go get github.com/alioygu

Ali OYGUR 101 Oct 31, 2022
Minimalist net/http middleware for golang

interpose Interpose is a minimalist net/http middleware framework for golang. It uses http.Handler as its core unit of functionality, minimizing compl

James Pirruccello 296 Sep 27, 2022
Lightweight Middleware for net/http

MuxChain MuxChain is a small package designed to complement net/http for specifying chains of handlers. With it, you can succinctly compose layers of

Stephen Searles 209 Dec 10, 2022
Idiomatic HTTP Middleware for Golang

Negroni Notice: This is the library formerly known as github.com/codegangsta/negroni -- Github will automatically redirect requests to this repository

null 7.3k Jan 2, 2023
A tiny http middleware for Golang with added handlers for common needs.

rye A simple library to support http services. Currently, rye provides a middleware handler which can be used to chain http handlers together while pr

InVision 99 Jan 4, 2023
Simple middleware to rate-limit HTTP requests.

Tollbooth This is a generic middleware to rate-limit HTTP requests. NOTE 1: This library is considered finished. NOTE 2: Major version changes are bac

Didip Kerabat 2.3k Dec 28, 2022
OpenID Connect (OIDC) http middleware for Go

Go OpenID Connect (OIDC) HTTP Middleware Introduction This is a middleware for http to make it easy to use OpenID Connect. Currently Supported framewo

Xenit AB 71 Jan 1, 2023
Go HTTP middleware to filter clients by IP

Go HTTP middleware to filter clients by IP

cristaltech 4 Oct 30, 2022
URL Rewrite middleware for gin

Url Rewrite middleware for gin Example In this exable these urls use the same route http://localhost:1234/test-me http://localhost:1234/index.php/test

Lucian I. Last 3 Sep 15, 2022
A customized middleware of DAPR.

A customized middleware of DAPR.

Gatty 1 Dec 24, 2021
Gin middleware for session.

wsession Gin middleware for session management with multi-backend support: cookie-based Redis memstore Usage Start using it Download and install it: g

null 0 Jan 9, 2022
Fiber middleware for server-timing

Server Timing This is a Fiber middleware for the [W3C Server-Timing API] based on mitchellh/go-server-timing

Vlad Fratila 0 Feb 6, 2022
Prometheus Common Data Exporter can parse JSON, XML, yaml or other format data from various sources (such as HTTP response message, local file, TCP response message and UDP response message) into Prometheus metric data.

Prometheus Common Data Exporter Prometheus Common Data Exporter 用于将多种来源(如http响应报文、本地文件、TCP响应报文、UDP响应报文)的Json、xml、yaml或其它格式的数据,解析为Prometheus metric数据。

null 7 May 18, 2022
Goget will send a http request, and show the request time, status, response, and save response to a file

Goget will send a http request, and show the request time, status, response, and save response to a file

LAZPbanahaker 2 Feb 9, 2022
Provide an upload endpoint that stores files on pinata and returns a json response with the uploaded file pinata url

Purpose Build a template repository to get to coding as quickly as possible, by starting from a common template which follows the guidelines here Feat

Dathan Vance Pattishall 0 Dec 30, 2021