Continuous Integration Documentation Coverage Status

This is Heroku's Go-specific language metrics, as a standalone package.

Heroku's support is inside an organization-internal base dumping-ground repo, which pulls in quite a few dependencies and is not a stable interface.

This package reproduces the core functionality of in a more usable API and without all the other dependencies.

This package uses semantic versioning.

We do not support the silent on-init enabling method of hmetrics: all production code which might error should log what it's doing and we are designed to integrate with production logging.

This library does not panic, by policy, even when it probably should. If the Spawn() function returns a non-nil error then that's probably panic-worthy.


import (


func main() {
    // This depends upon your logging library, etc.
    msg, cancel, err := hmetrics.Spawn(func(e error) {
        log.Printf("hmetrics error: %s", e)
    if err != nil {
        // if environment variable not found or empty, that's not an error,
        // this is something which means we expected to log but never will
        // be able to.
    if cancel != nil {
        defer cancel()
    log.Print(msg) // for warm fuzzy feelings that stuff has started correctly

    // do your work


Probably plenty, starting with a complete lack of tests as this was written in frustration during one night of coding.

Yes, the absence of any tests is a bug.

Pull requests very welcome, as well as bug reports (unless it's just to say "there are no tests!").

At the time I write this notice, I haven't even tried using this code yet. It's very shiny new. It passes go build, go vet ./... and golint ./... and that's it.



    Package hmetrics is an opinionated, simple to plumb, implementation of Heroku's Go language metrics, which plays nicely with logs. Heroku's implementation is simpler if you don't want to log or to have sane capped exponential backoff. This package is simpler to use if you do.

    The callback you pass must be present and has no return value; this is different from Heroku's implementation.

    We deliberately support neither nil logging callbacks nor callbacks being able to cancel metrics collection via their return code. We make a number of checks before spawning the go-routine which does metrics posts and return any errors resulting from those, so the only errors once spawned and exposed to the callback afterwards will be context cancellation (your action), problems collecting stats (should be transient) or HTTP errors posting to the endpoint, which presumably will recover at some point. There's no documented guidance on HTTP errors which indicate "service has had to move, abort and restart to collect the new URL", so any analysis you might do in a callback is a guessing game of little utility.

    Just call Spawn() with your error-logging callback and handle the return values from Spawn as you see fit.



    View Source
    const EnvKeyEndpoint = "HEROKU_METRICS_URL"

      EnvKeyEndpoint defines the name of the environment variable defining where metrics should be posted to. Its absence in environ inhibits hmetrics startup.

      View Source
      const PackageHTTPVersion = "1.0"

        PackageHTTPVersion is the version string reported by default in the HTTP User-Agent header of our POST requests.


        View Source
        var ErrMissingPoster = errors.New("hmetrics: given a nil poster callback")

          ErrMissingPoster indicates that you've tried to not provide a callback. We don't support that. This is the one scenario for which we considered a library panic. Provide a callback. If you want to discard logable events, that's your decision and one which should be explicit in your code.


          func GetHTTPClient

          func GetHTTPClient() *http.Client

            GetHTTPClient returns the current *http.Client used in requests to post metrics to Heroku's endpoint. If nil, an reference to a new empty http.Client will be returned instead.

            func GetHTTPUserAgent

            func GetHTTPUserAgent() string

              GetHTTPUserAgent returns the current HTTP User-Agent used in requests to post metrics to Heroku's endpoint made available to your app.

              func SetHTTPClient

              func SetHTTPClient(c *http.Client)

                SetHTTPClient is used to provide a non-standard HTTP client for use for posting the metrics to Heroku's endpoint. You'd typically only need this when testing, to override the certificate authority trust store (or if you don't normally want to trust the PKIX CA used by Heroku and need to special-case it for them). SetHTTPClient does not return anything. Use GetHTTPClient to get the current value. SetHTTPClient is safe to call at any time from any go-routine, but is only referenced by the library when starting a loop, and the loop only exits on context cancellation, so you'll need to cancel any previous poster and spawn a new one.

                func SetHTTPTimeout

                func SetHTTPTimeout(limit time.Duration) (previous time.Duration)

                  SetHTTPTimeout modifies the timeout for our HTTP requests to post metrics. Pass a non-zero time.Duration to modify. Pass 0 to SetHTTPTimeout to make no modification. SetHTTPTimeout returns the previous value. SetHTTPTimeout is safe to call at any time from any go-routine.

                  func SetHTTPUserAgent

                  func SetHTTPUserAgent(ua string)

                    SetHTTPUserAgent modifies the HTTP User-Agent header used in requests to post metrics to Heroku's endpoint made available to your app. Pass a non-empty string to set a User-Agent. Passing an empty string will panic. SetHTTPUserAgent does not return anything. Use GetHTTPUserAgent to get the current value. SetHTTPUserAgent is safe to call at any time from any go-routine.

                    func SetMaxFailureBackoff

                    func SetMaxFailureBackoff(backoff time.Duration) (previous time.Duration)

                      SetMaxFailureBackoff modifies the maximum interval to which we'll back off between attempts to post metrics to the endpoint. Pass a non-zero time.Duration to modify. Pass 0 to make no modification. SetMaxFailureBackoff returns the previous value. SetMaxFailureBackoff is safe to call at any time from any go-routine.

                      func SetMetricsPostInterval

                      func SetMetricsPostInterval(interval time.Duration) (previous time.Duration)

                        SetMetricsPostInterval modifies how often we post metrics. Pass a non-zero time.Duration to modify. Pass 0 to SetMetricsPostInterval to make no modification. SetMetricsPostInterval returns the previous value. SetMetricsPostInterval is safe to call at any time from any go-routine, but the value is referenced once very shortly after starting the spawned go-routine, so to modify this, you'll need to cancel the context of the metrics poster and re-Spawn.

                        Do not use this unless you're very sure that Heroku will be happy: their systems will be designed around an expectation of a certain interval between metrics posts, and that's what we match. You can change this but don't do so without explicit guidance from a Heroku employee.

                        func SetResetFailureBackoffAfter

                        func SetResetFailureBackoffAfter(allClear time.Duration) (previous time.Duration)

                          SetResetFailureBackoffAfter modifies the all-clear duration used to reset the exponential backoff in trying to start the go-routine which posts metrics. If the metrics-posting Go routine lives for at least this long, then we consider things healthy and reset back to the value returned by SetResetFailureBackoffTo(0). Pass a non-zero time.Duration to modify. Pass 0 to SetResetFailureBackoffAfter to make no modification. SetResetFailureBackoffAfter returns the previous value. SetResetFailureBackoffAfter is safe to call at any time from any go-routine.

                          func SetResetFailureBackoffTo

                          func SetResetFailureBackoffTo(allClear time.Duration) (previous time.Duration)

                            SetResetFailureBackoffTo modifies the minimum backoff period for our exponential backoff in trying to start the go-routine to post metrics. Pass a non-zero time.Duration to modify. Pass 0 to SetResetFailureBackoffTo to make no modification. SetResetFailureBackoffTo returns the previous value. SetResetFailureBackoffTo is safe to call at any time from any go-routine.

                            func Spawn

                            func Spawn(poster ErrorPoster) (logMessage string, cancel func(), err error)

                              Spawn potentially starts the metrics-posting Go-routine.

                              The poster parameter must not be nil, or we will error.

                              Return values:

                              logMessage is something worth logging as informative about what has happened; if the error is non-nil and you want to simplify, then ignore the logMessage, but it might still be helpful even with a non-nil error.

                              cancel serves two purposes: if nil, then we did not start the go-routine, if non-nil then we did. Further, if non-nil then it's a callable function used to cancel the context used for the go-routine posting.

                              error is an active problem which kept us from starting. If we have seen an indication that logging is wanted but we do not support the URL specified (or could not parse it) then we will return an error. This should not happen in a sane environment and is probably worthy of a Fatal exit even if bad metrics export might normally not be, because your environment is messed up.


                              type ErrorPoster

                              type ErrorPoster func(error)

                                ErrorPoster is the function signature for the callback passed to Spawn, and is expected to log a message based upon the error passed to it. At its simplest:

                                import "log"
                                hmetrics.Spawn(func(e error) { log.Printf("hmetrics error: %s", e) })

                                type InvalidURLError

                                type InvalidURLError struct {
                                	// contains filtered or unexported fields

                                  InvalidURLError is an error type, indicating that we could not handle the URL which we were asked to use to post metrics. At present, that just means that the scheme could not be handled but this might be extended to handle other scenarios we realize might cause an issue, without a semver bump.

                                  func (InvalidURLError) Error

                                  func (e InvalidURLError) Error() string

                                    Error is the type-satisfying method which lets an InvalidURLError be an "error".