prometheus-wai (empty) → 0.0.0.0
raw patch · 2 files changed
+145/−0 lines, 2 filesdep +basedep +bytestringdep +containers
Dependencies added: base, bytestring, containers, http-types, prometheus, text, wai
Files
+ prometheus-wai.cabal view
@@ -0,0 +1,39 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.36.1.+--+-- see: https://github.com/sol/hpack++name: prometheus-wai+version: 0.0.0.0+description: Prometheus metrics for WAI applications+homepage: https://github.com/NorfairKing/prometheus-wai#readme+bug-reports: https://github.com/NorfairKing/prometheus-wai/issues+author: Tom Sydney Kerckhove+maintainer: syd@cs-syd.eu+copyright: Copyright (c) 2025 Tom Sydney Kerckhove+license: MIT+build-type: Simple++source-repository head+ type: git+ location: https://github.com/NorfairKing/prometheus-wai++library+ exposed-modules:+ System.Metrics.Prometheus.Wai.Middleware+ other-modules:+ Paths_prometheus_wai+ hs-source-dirs:+ src+ build-tool-depends:+ autoexporter:autoexporter+ build-depends:+ base >=4.7 && <5+ , bytestring+ , containers+ , http-types+ , prometheus+ , text+ , wai+ default-language: Haskell2010
+ src/System/Metrics/Prometheus/Wai/Middleware.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module System.Metrics.Prometheus.Wai.Middleware+ ( registerWaiMetrics,+ instrumentWaiMiddleware,+ metricsEndpointMiddleware,+ metricsEndpointAtMiddleware,+ )+where++import Control.Monad (forM)+import Data.ByteString (ByteString)+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as M+import qualified Data.Text as T+import GHC.Clock (getMonotonicTimeNSec)+import qualified Network.HTTP.Types as HTTP+import Network.Wai as Wai (Middleware)+import qualified Network.Wai as Request+import qualified Network.Wai as Wai+import System.Metrics.Prometheus.Concurrent.Registry (Registry)+import qualified System.Metrics.Prometheus.Concurrent.Registry as Prometheus+import qualified System.Metrics.Prometheus.Concurrent.Registry as Registry+import qualified System.Metrics.Prometheus.Encode.Text as Prometheus+import qualified System.Metrics.Prometheus.Metric.Counter as Counter (inc)+import qualified System.Metrics.Prometheus.Metric.Counter as Prometheus (Counter)+import qualified System.Metrics.Prometheus.Metric.Histogram as Histogram (observe)+import qualified System.Metrics.Prometheus.Metric.Histogram as Prometheus (Histogram)+import qualified System.Metrics.Prometheus.MetricId as Labels+import qualified System.Metrics.Prometheus.MetricId as Prometheus (Labels (..))++-- TODO ignore websocket requests when gathering info++data WaiMetrics = WaiMetrics+ { waiMetricsStatusCode :: !(Map Int Prometheus.Counter),+ waiMetricsDuration :: !Prometheus.Histogram+ }++-- | Register the Wai metrics with the given labels at the given registry.+registerWaiMetrics :: Prometheus.Labels -> Registry -> IO WaiMetrics+registerWaiMetrics labels registry = do+ -- Status code counters+ -- Based on the codes defined at+ -- https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status+ let codes =+ [100 .. 103]+ <> [200 .. 208]+ <> [226]+ <> [300 .. 304]+ <> [307, 308]+ <> [400 .. 418]+ <> [421 .. 426]+ <> [428, 429, 431, 451]+ <> [500 .. 508]+ <> [510, 511]+ let labelsForCode code = Labels.addLabel "http_response_code" (T.pack (show code)) labels+ waiMetricsStatusCode <- fmap M.fromList $ forM codes $ \code -> do+ counterForCode <- Prometheus.registerCounter "http_requests_total" (labelsForCode code) registry+ pure (code, counterForCode)++ -- Duration histogram+ let durationBounds =+ concat+ [ [1, 2, 3, 5],+ [10, 20, 30, 40, 50],+ [100, 200, 300, 400, 500],+ [1_000, 2_000, 3_000, 4_000, 5_000],+ [10_000]+ ]+ waiMetricsDuration <- Prometheus.registerHistogram "http_request_duration_milliseconds" labels durationBounds registry+ pure WaiMetrics {..}++-- | Record the given Wai metrics in a middleware.+instrumentWaiMiddleware :: WaiMetrics -> Wai.Middleware+instrumentWaiMiddleware WaiMetrics {..} application request sendResponse = do+ begin <- getMonotonicTimeNSec+ application request $ \response -> do+ end <- getMonotonicTimeNSec++ -- Count the status code+ mapM_ Counter.inc (M.lookup (HTTP.statusCode (Wai.responseStatus response)) waiMetricsStatusCode)++ -- Count the application response duration+ let nanos = end - begin+ millis = fromIntegral nanos / 1_000_000+ Histogram.observe millis waiMetricsDuration++ sendResponse response++-- | Add a metrics endpoint using the given industry.+metricsEndpointMiddleware :: Registry -> Wai.Middleware+metricsEndpointMiddleware = metricsEndpointAtMiddleware "/metrics"++metricsEndpointAtMiddleware :: ByteString -> Registry -> Wai.Middleware+metricsEndpointAtMiddleware path registry application request sendResponse =+ if Request.rawPathInfo request == path+ then do+ s <- Registry.sample registry+ sendResponse+ $ Wai.responseBuilder+ HTTP.ok200+ [(HTTP.hContentType, "text/plain; version=0.0.4; charset=utf-8")]+ $ Prometheus.encodeMetrics s+ else application request sendResponse