packages feed

freckle-app 1.21.0.0 → 1.22.0.0

raw patch · 8 files changed

+33/−552 lines, 8 filesdep +freckle-statsdep −datadogdep −ekg-coredep −extradep ~persistentPVP ok

version bump matches the API change (PVP)

Dependencies added: freckle-stats

Dependencies removed: datadog, ekg-core, extra, time

Dependency ranges changed: persistent

API changes (from Hackage documentation)

- Freckle.App.Ecs: EcsContainerMetadata :: Text -> Text -> Text -> Text -> EcsContainerMetadata
- Freckle.App.Ecs: EcsContainerTaskMetadata :: Text -> Text -> Text -> Text -> EcsContainerTaskMetadata
- Freckle.App.Ecs: EcsMetadata :: EcsContainerMetadata -> EcsContainerTaskMetadata -> EcsMetadata
- Freckle.App.Ecs: EcsMetadataErrorInvalidJSON :: Request -> HttpDecodeError -> EcsMetadataError
- Freckle.App.Ecs: EcsMetadataErrorInvalidURI :: String -> EcsMetadataError
- Freckle.App.Ecs: EcsMetadataErrorNotEnabled :: EcsMetadataError
- Freckle.App.Ecs: EcsMetadataErrorUnexpectedStatus :: Request -> Status -> EcsMetadataError
- Freckle.App.Ecs: [ecmDockerId] :: EcsContainerMetadata -> Text
- Freckle.App.Ecs: [ecmDockerName] :: EcsContainerMetadata -> Text
- Freckle.App.Ecs: [ecmImageID] :: EcsContainerMetadata -> Text
- Freckle.App.Ecs: [ecmImage] :: EcsContainerMetadata -> Text
- Freckle.App.Ecs: [ectmCluster] :: EcsContainerTaskMetadata -> Text
- Freckle.App.Ecs: [ectmFamily] :: EcsContainerTaskMetadata -> Text
- Freckle.App.Ecs: [ectmRevision] :: EcsContainerTaskMetadata -> Text
- Freckle.App.Ecs: [ectmTaskARN] :: EcsContainerTaskMetadata -> Text
- Freckle.App.Ecs: [emContainerMetadata] :: EcsMetadata -> EcsContainerMetadata
- Freckle.App.Ecs: [emContainerTaskMetadata] :: EcsMetadata -> EcsContainerTaskMetadata
- Freckle.App.Ecs: data EcsContainerMetadata
- Freckle.App.Ecs: data EcsContainerTaskMetadata
- Freckle.App.Ecs: data EcsMetadata
- Freckle.App.Ecs: data EcsMetadataError
- Freckle.App.Ecs: getEcsMetadata :: (MonadIO m, MonadError EcsMetadataError m) => m EcsMetadata
- Freckle.App.Ecs: instance Data.Aeson.Types.FromJSON.FromJSON Freckle.App.Ecs.EcsContainerMetadata
- Freckle.App.Ecs: instance Data.Aeson.Types.FromJSON.FromJSON Freckle.App.Ecs.EcsContainerTaskMetadata
- Freckle.App.Ecs: instance GHC.Generics.Generic Freckle.App.Ecs.EcsContainerMetadata
- Freckle.App.Ecs: instance GHC.Generics.Generic Freckle.App.Ecs.EcsContainerTaskMetadata
- Freckle.App.Ecs: instance GHC.Show.Show Freckle.App.Ecs.EcsMetadataError
- Freckle.App.Stats: class HasStatsClient env
- Freckle.App.Stats: counter :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> Int -> m ()
- Freckle.App.Stats: data Gauge
- Freckle.App.Stats: data Gauges
- Freckle.App.Stats: data StatsClient
- Freckle.App.Stats: data StatsSettings
- Freckle.App.Stats: dbConnections :: Gauges -> Gauge
- Freckle.App.Stats: dbEnqueuedAndProcessing :: Gauges -> Gauge
- Freckle.App.Stats: decGauge :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m) => Gauge -> m ()
- Freckle.App.Stats: defaultStatsSettings :: StatsSettings
- Freckle.App.Stats: envParseStatsSettings :: Parser Error StatsSettings
- Freckle.App.Stats: gauge :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> Double -> m ()
- Freckle.App.Stats: histogram :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env, ToMetricValue n) => Text -> n -> m ()
- Freckle.App.Stats: histogramSince :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> UTCTime -> m ()
- Freckle.App.Stats: histogramSinceMs :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> UTCTime -> m ()
- Freckle.App.Stats: incGauge :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m) => Gauge -> m ()
- Freckle.App.Stats: increment :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> m ()
- Freckle.App.Stats: instance Freckle.App.Stats.HasStatsClient Freckle.App.Stats.StatsClient
- Freckle.App.Stats: instance Freckle.App.Stats.HasStatsClient site => Freckle.App.Stats.HasStatsClient (Yesod.Core.Types.HandlerData child site)
- Freckle.App.Stats: lookupGauge :: (MonadReader app m, HasStatsClient app) => (Gauges -> Gauge) -> m Gauge
- Freckle.App.Stats: setStatsSettingsTags :: [(Text, Text)] -> StatsSettings -> StatsSettings
- Freckle.App.Stats: statsClientL :: HasStatsClient env => Lens' env StatsClient
- Freckle.App.Stats: tagged :: (MonadReader env m, HasStatsClient env) => [(Text, Text)] -> m a -> m a
- Freckle.App.Stats: tagsL :: Lens' StatsClient [(Text, Text)]
- Freckle.App.Stats: withGauge :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m) => (Gauges -> Gauge) -> m a -> m a
- Freckle.App.Stats: withStatsClient :: (MonadMask m, MonadUnliftIO m) => StatsSettings -> (StatsClient -> m a) -> m a
- Freckle.App.Stats.Rts: forkRtsStatPolling :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => m ()
- Freckle.App.Test: createPool :: IO a -> (a -> IO ()) -> Int -> NominalDiffTime -> Int -> IO (Pool a)
- Freckle.App.Test: data () => LocalPool a
- Freckle.App.Test: data () => Pool a
- Freckle.App.Test: data () => PoolConfig a
- Freckle.App.Test: defaultPoolConfig :: IO a -> (a -> IO ()) -> Double -> Int -> PoolConfig a
- Freckle.App.Test: destroyAllResources :: Pool a -> IO ()
- Freckle.App.Test: destroyResource :: Pool a -> LocalPool a -> a -> IO ()
- Freckle.App.Test: newPool :: PoolConfig a -> IO (Pool a)
- Freckle.App.Test: putResource :: LocalPool a -> a -> IO ()
- Freckle.App.Test: setNumStripes :: Maybe Int -> PoolConfig a -> PoolConfig a
- Freckle.App.Test: takeResource :: Pool a -> IO (a, LocalPool a)
- Freckle.App.Test: tryTakeResource :: Pool a -> IO (Maybe (a, LocalPool a))
- Freckle.App.Test: tryWithResource :: Pool a -> (a -> IO r) -> IO (Maybe r)
- Freckle.App.Test: withResource :: Pool a -> (a -> IO r) -> IO r
- Freckle.App: AppT :: ReaderT app (ResourceT m) a -> AppT app m a
+ Freckle.App: AppT :: ReaderT app (ResourceT m) a -> AppT app (m :: Type -> Type) a
- Freckle.App: [unAppT] :: AppT app m a -> ReaderT app (ResourceT m) a
+ Freckle.App: [unAppT] :: AppT app (m :: Type -> Type) a -> ReaderT app (ResourceT m) a
- Freckle.App: newtype AppT app m a
+ Freckle.App: newtype AppT app (m :: Type -> Type) a
- Freckle.App: runApp :: (forall b. (app -> IO b) -> IO b) -> AppT app IO a -> IO a
+ Freckle.App: runApp :: (forall b. () => (app -> IO b) -> IO b) -> AppT app IO a -> IO a
- Freckle.App.Bugsnag: data () => BeforeNotify
+ Freckle.App.Bugsnag: data BeforeNotify
- Freckle.App.Bugsnag: data () => Settings
+ Freckle.App.Bugsnag: data Settings
- Freckle.App.Bugsnag.HttpException: data () => HttpException
+ Freckle.App.Bugsnag.HttpException: data HttpException
- Freckle.App.Bugsnag.MetaData: newtype () => MetaData
+ Freckle.App.Bugsnag.MetaData: newtype MetaData
- Freckle.App.Bugsnag.SqlError: data () => SqlError
+ Freckle.App.Bugsnag.SqlError: data SqlError
- Freckle.App.Csv: class ValidateHeader a
+ Freckle.App.Csv: class ValidateHeader (a :: k)
- Freckle.App.Csv: decodeCsv :: forall a m err. (MonadThrow m, MonadValidate (Seq (CsvException err)) m, ValidateHeader a, FromNamedRecord a) => ConduitT ByteString a m ()
+ Freckle.App.Csv: decodeCsv :: forall a (m :: Type -> Type) err. (MonadThrow m, MonadValidate (Seq (CsvException err)) m, ValidateHeader a, FromNamedRecord a) => ConduitT ByteString a m ()
- Freckle.App.Csv: defaultValidateOrderedHeader :: forall a proxy m. (DefaultOrdered a, Monad m) => proxy a -> Header -> ValidateT (NESeq String) m ()
+ Freckle.App.Csv: defaultValidateOrderedHeader :: forall a proxy (m :: Type -> Type). (DefaultOrdered a, Monad m) => proxy a -> Header -> ValidateT (NESeq String) m ()
- Freckle.App.Csv: hasHeader :: Monad m => Header -> Name -> ValidateT (NESeq String) m ()
+ Freckle.App.Csv: hasHeader :: forall (m :: Type -> Type). Monad m => Header -> Name -> ValidateT (NESeq String) m ()
- Freckle.App.Csv: validateHeader :: (ValidateHeader a, Bind m, Monad m) => proxy a -> Header -> ValidateT (NESeq String) m ()
+ Freckle.App.Csv: validateHeader :: forall (m :: Type -> Type) proxy. (ValidateHeader a, Bind m, Monad m) => proxy a -> Header -> ValidateT (NESeq String) m ()
- Freckle.App.Database: class () => HasSqlBackend a
+ Freckle.App.Database: class HasSqlBackend a
- Freckle.App.Database: data () => SqlBackend
+ Freckle.App.Database: data SqlBackend
- Freckle.App.Test: ($!) :: forall (r :: RuntimeRep) a (b :: TYPE r). (a -> b) -> a -> b
+ Freckle.App.Test: ($!) :: (a -> b) -> a -> b
- Freckle.App.Test: ($) :: forall (r :: RuntimeRep) a (b :: TYPE r). (a -> b) -> a -> b
+ Freckle.App.Test: ($) :: (a -> b) -> a -> b
- Freckle.App.Test: class () => Bounded a
+ Freckle.App.Test: class Bounded a
- Freckle.App.Test: class () => Enum a
+ Freckle.App.Test: class Enum a
- Freckle.App.Test: class () => Eq a
+ Freckle.App.Test: class Eq a
- Freckle.App.Test: class () => Foldable (t :: Type -> Type)
+ Freckle.App.Test: class Foldable (t :: Type -> Type)
- Freckle.App.Test: class () => Functor (f :: Type -> Type)
+ Freckle.App.Test: class Functor (f :: Type -> Type)
- Freckle.App.Test: class () => Generic a
+ Freckle.App.Test: class Generic a
- Freckle.App.Test: class () => Num a
+ Freckle.App.Test: class Num a
- Freckle.App.Test: class () => Read a
+ Freckle.App.Test: class Read a
- Freckle.App.Test: class () => Semigroup a
+ Freckle.App.Test: class Semigroup a
- Freckle.App.Test: class () => Show a
+ Freckle.App.Test: class Show a
- Freckle.App.Test: data () => Bool
+ Freckle.App.Test: data Bool
- Freckle.App.Test: data () => Char
+ Freckle.App.Test: data Char
- Freckle.App.Test: data () => Double
+ Freckle.App.Test: data Double
- Freckle.App.Test: data () => Either a b
+ Freckle.App.Test: data Either a b
- Freckle.App.Test: data () => ExceptionHandler (m :: k -> Type) (a :: k)
+ Freckle.App.Test: data ExceptionHandler (m :: k -> Type) (a :: k)
- Freckle.App.Test: data () => Float
+ Freckle.App.Test: data Float
- Freckle.App.Test: data () => HashMap k v
+ Freckle.App.Test: data HashMap k v
- Freckle.App.Test: data () => HashSet a
+ Freckle.App.Test: data HashSet a
- Freckle.App.Test: data () => IO a
+ Freckle.App.Test: data IO a
- Freckle.App.Test: data () => Int
+ Freckle.App.Test: data Int
- Freckle.App.Test: data () => Int64
+ Freckle.App.Test: data Int64
- Freckle.App.Test: data () => Integer
+ Freckle.App.Test: data Integer
- Freckle.App.Test: data () => Map k a
+ Freckle.App.Test: data Map k a
- Freckle.App.Test: data () => Maybe a
+ Freckle.App.Test: data Maybe a
- Freckle.App.Test: data () => NominalDiffTime
+ Freckle.App.Test: data NominalDiffTime
- Freckle.App.Test: data () => NonEmpty a
+ Freckle.App.Test: data NonEmpty a
- Freckle.App.Test: data () => Ordering
+ Freckle.App.Test: data Ordering
- Freckle.App.Test: data () => ReaderT r (m :: Type -> Type) a
+ Freckle.App.Test: data ReaderT r (m :: Type -> Type) a
- Freckle.App.Test: data () => Set a
+ Freckle.App.Test: data Set a
- Freckle.App.Test: data () => SomeException
+ Freckle.App.Test: data SomeException
- Freckle.App.Test: data () => Text
+ Freckle.App.Test: data Text
- Freckle.App.Test: data () => UTCTime
+ Freckle.App.Test: data UTCTime
- Freckle.App.Test: data () => Vector a
+ Freckle.App.Test: data Vector a
- Freckle.App.Test: data () => Word
+ Freckle.App.Test: data Word
- Freckle.App.Test: error :: forall (r :: RuntimeRep) (a :: TYPE r). HasCallStack => [Char] -> a
+ Freckle.App.Test: error :: HasCallStack => [Char] -> a
- Freckle.App.Test: errorWithoutStackTrace :: forall (r :: RuntimeRep) (a :: TYPE r). [Char] -> a
+ Freckle.App.Test: errorWithoutStackTrace :: [Char] -> a
- Freckle.App.Test: infix 1 `shouldNotReturn`
+ Freckle.App.Test: infix 1 `shouldStartWith`
- Freckle.App.Test: infixr 1 <=<
+ Freckle.App.Test: infixr 1 =<<
- Freckle.App.Test: seq :: forall {r :: RuntimeRep} a (b :: TYPE r). a -> b -> b
+ Freckle.App.Test: seq :: a -> b -> b
- Freckle.App.Test: undefined :: forall (r :: RuntimeRep) (a :: TYPE r). HasCallStack => a
+ Freckle.App.Test: undefined :: HasCallStack => a
- Freckle.App.Test.Yesod: class (MonadIO m, Yesod site) => MonadYesodExample site m | m -> site
+ Freckle.App.Test.Yesod: class (MonadIO m, Yesod site) => MonadYesodExample site (m :: Type -> Type) | m -> site
- Freckle.App.Test.Yesod: data () => SIO s a
+ Freckle.App.Test.Yesod: data SIO s a
- Freckle.App.Test.Yesod: data () => SResponse
+ Freckle.App.Test.Yesod: data SResponse
- Freckle.App.Test.Yesod: data () => YesodExampleData site
+ Freckle.App.Test.Yesod: data YesodExampleData site
- Freckle.App.TextualEnum: class EnumName a
+ Freckle.App.TextualEnum: class EnumName (a :: k)
- Freckle.App.Yesod: respondQueryCanceled :: HasStatsClient site => HandlerFor site res -> HandlerFor site res
+ Freckle.App.Yesod: respondQueryCanceled :: HasStatsClient (HandlerData site site) => HandlerFor site res -> HandlerFor site res
- Freckle.App.Yesod: respondQueryCanceledHeaders :: HasStatsClient site => ResponseHeaders -> HandlerFor site res -> HandlerFor site res
+ Freckle.App.Yesod: respondQueryCanceledHeaders :: HasStatsClient (HandlerData site site) => ResponseHeaders -> HandlerFor site res -> HandlerFor site res
- Yesod.Core.Lens: envL :: Lens' (HandlerData child site) (RunHandlerEnv child site)
+ Yesod.Core.Lens: envL :: forall child site f. Functor f => (RunHandlerEnv child site -> f (RunHandlerEnv child site)) -> HandlerData child site -> f (HandlerData child site)
- Yesod.Core.Lens: siteL :: Lens' (RunHandlerEnv child site) site
+ Yesod.Core.Lens: siteL :: forall child site f. Functor f => (site -> f site) -> RunHandlerEnv child site -> f (RunHandlerEnv child site)

Files

CHANGELOG.md view
@@ -1,4 +1,20 @@-## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-app-v1.21.0.0...main)+## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-app-v1.22.0.0...main)++## [v1.22.0.0](https://github.com/freckle/freckle-app/compare/freckle-app-v1.21.0.0...freckle-app-v1.22.0.0)++`Freckle.App.Stats` has moved to a separate package `freckle-stats`.++When migrating to `freckle-stats`, note that the instance++```hs+instance HasStatsClient site => HasStatsClient (HandlerData child site) where+  statsClientL = envL . siteL . statsClientL+```++has been removed. If you are using Yesod, you may need to copy this definition+into your own code.++`Freckle.App.Ecs` has moved to a separate package `freckle-ecs`.  ## [v1.21.0.0](https://github.com/freckle/freckle-app/compare/freckle-app-v1.20.3.0...freckle-app-v1.21.0.0) 
freckle-app.cabal view
@@ -1,6 +1,6 @@ cabal-version:      1.22 name:               freckle-app-version:            1.21.0.0+version:            1.22.0.0 license:            MIT license-file:       LICENSE maintainer:         Freckle Education@@ -29,15 +29,12 @@         Freckle.App.Bugsnag.SqlError         Freckle.App.Csv         Freckle.App.Database-        Freckle.App.Ecs         Freckle.App.Faktory.ProducerPool         Freckle.App.Ghci         Freckle.App.GlobalCache         Freckle.App.Json.Empty         Freckle.App.Random         Freckle.App.Scientist-        Freckle.App.Stats-        Freckle.App.Stats.Rts         Freckle.App.Test         Freckle.App.Test.DocTest         Freckle.App.Test.Hspec.AnnotatedException@@ -111,17 +108,15 @@         conduit-extra >=1.3.6,         containers >=0.6.5.1,         cookie >=0.4.6,-        datadog >=0.3.0.0,         doctest >=0.20.1,-        ekg-core >=0.1.1.7,         exceptions >=0.10.4,-        extra >=1.7.13,         faktory >=1.1.2.4,         freckle-env >=0.0.1.1,         freckle-exception >=0.0.0.0,         freckle-http >=0.0.0.0,         freckle-otel >=0.0.0.2,         freckle-prelude >=0.0.1.1,+        freckle-stats >=0.0.0.0,         hs-opentelemetry-api >=0.1.0.0,         hs-opentelemetry-instrumentation-persistent >=0.1.0.0,         hs-opentelemetry-instrumentation-wai >=0.1.0.0,@@ -140,9 +135,9 @@         nonempty-containers >=0.3.4.4,         openapi3 >=3.2.3,         path-pieces >=0.2.1,-        persistent >=2.13.3.5,+        persistent >=2.14.0.1,         persistent-postgresql >=2.13.5.2,-        persistent-sql-lifted >=1.20.3.0,+        persistent-sql-lifted >=0.1.0.0,         postgresql-simple >=0.6.4,         primitive >=0.7.3.0,         resource-pool >=0.4.0.0,@@ -152,7 +147,6 @@         servant-server >=0.19.2,         template-haskell >=2.18.0.0,         text >=1.2.5.0,-        time >=1.11.1.1,         transformers >=0.5.6.2,         transformers-base >=0.4.6,         typed-process >=0.2.11.0,@@ -242,13 +236,14 @@         cassava >=0.5.3.0,         conduit >=1.3.5,         freckle-app,+        freckle-stats >=0.0.0.0,         hs-opentelemetry-api >=0.1.0.0,         hspec >=2.10.10,         http-types >=0.12.3,         monad-validate >=1.3.0.0,         nonempty-containers >=0.3.4.4,         path-pieces >=0.2.1,-        persistent >=2.13.3.5,+        persistent >=2.14.0.1,         postgresql-simple >=0.6.4,         servant-server >=0.19.2,         vector >=0.12.3.1,
library/Freckle/App/Database.hs view
@@ -48,15 +48,14 @@ import Data.Pool import Data.Text qualified as T import Database.Persist.Postgresql-  ( SqlBackend-  , SqlPersistT+  ( SqlPersistT   , createPostgresqlPoolModified   , createSqlPool   , openSimpleConn   , runSqlPool   , runSqlPoolWithExtensibleHooks   )-import Database.Persist.Sql.Lifted+import Database.Persist.Sql.Lifted.Core import Database.Persist.SqlBackend.Internal.SqlPoolHooks (SqlPoolHooks (..)) import Database.Persist.SqlBackend.SqlPoolHooks import Database.PostgreSQL.Simple
− library/Freckle/App/Ecs.hs
@@ -1,92 +0,0 @@-module Freckle.App.Ecs-  ( EcsMetadata (..)-  , EcsMetadataError (..)-  , EcsContainerMetadata (..)-  , EcsContainerTaskMetadata (..)-  , getEcsMetadata-  ) where--import Freckle.App.Prelude--import Control.Monad.Except (MonadError (..))-import Data.Aeson-import Data.List.Extra (dropPrefix)-import Freckle.App.Http-import System.Environment (lookupEnv)--data EcsMetadata = EcsMetadata-  { emContainerMetadata :: EcsContainerMetadata-  , emContainerTaskMetadata :: EcsContainerTaskMetadata-  }--data EcsMetadataError-  = EcsMetadataErrorNotEnabled-  | EcsMetadataErrorInvalidURI String-  | EcsMetadataErrorUnexpectedStatus Request Status-  | EcsMetadataErrorInvalidJSON Request HttpDecodeError-  deriving stock (Show)---- | Parsing for the @/@ response------ <https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4.html#task-metadata-endpoint-v4-examples>-data EcsContainerMetadata = EcsContainerMetadata-  { ecmDockerId :: Text-  , ecmDockerName :: Text-  , ecmImage :: Text-  , ecmImageID :: Text-  }-  deriving stock (Generic)--instance FromJSON EcsContainerMetadata where-  parseJSON = genericParseJSON $ aesonDropPrefix "ecm"---- | Parsing of the @/task@ response------ <https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-metadata-endpoint-v4.html#task-metadata-endpoint-v4-response>-data EcsContainerTaskMetadata = EcsContainerTaskMetadata-  { ectmCluster :: Text-  , ectmTaskARN :: Text-  , ectmFamily :: Text-  , ectmRevision :: Text-  }-  deriving stock (Generic)--instance FromJSON EcsContainerTaskMetadata where-  parseJSON = genericParseJSON $ aesonDropPrefix "ectm"--aesonDropPrefix :: String -> Options-aesonDropPrefix x = defaultOptions {fieldLabelModifier = dropPrefix x}--getEcsMetadata :: (MonadIO m, MonadError EcsMetadataError m) => m EcsMetadata-getEcsMetadata = do-  mURI <--    liftIO $-      (<|>)-        <$> lookupEnv "ECS_CONTAINER_METADATA_URI_V4"-        <*> lookupEnv-          "ECS_CONTAINER_METADATA_URI"--  uri <- maybe (throwError EcsMetadataErrorNotEnabled) pure mURI--  EcsMetadata-    <$> makeContainerMetadataRequest uri-    <*> makeContainerMetadataRequest (uri <> "/task")--makeContainerMetadataRequest-  :: (MonadIO m, MonadError EcsMetadataError m, FromJSON a) => String -> m a-makeContainerMetadataRequest uri = do-  req <--    mapEither (EcsMetadataErrorInvalidURI . displayException) $-      parseRequest uri-  resp <- liftIO $ httpJson req--  let status = getResponseStatus resp--  unless (statusIsSuccessful status) $-    throwError $-      EcsMetadataErrorUnexpectedStatus req status--  mapEither (EcsMetadataErrorInvalidJSON req) $ getResponseBody resp--mapEither :: MonadError e m => (x -> e) -> Either x a -> m a-mapEither f = either (throwError . f) pure
− library/Freckle/App/Stats.hs
@@ -1,390 +0,0 @@-{-# LANGUAGE TupleSections #-}---- | An intentionally-leaky StatsD interface to Datadog-module Freckle.App.Stats-  ( -- $docs-    StatsSettings-  , defaultStatsSettings-  , setStatsSettingsTags-  , envParseStatsSettings--    -- * Client-  , StatsClient-  , tagsL-  , withStatsClient-  , HasStatsClient (..)--    -- * Gauges-  , Gauges-  , Gauge-  , dbConnections-  , dbEnqueuedAndProcessing-  , withGauge-  , lookupGauge-  , incGauge-  , decGauge--    -- * Reporting-  , tagged-  , increment-  , counter-  , gauge-  , histogram-  , histogramSince-  , histogramSinceMs-  ) where--import Freckle.App.Prelude--import Blammo.Logging.ThreadContext (MonadMask, withThreadContext)-import Control.Lens (Lens', lens, to, view, (&), (.~), (<>~))-import Control.Monad.Except (runExceptT)-import Control.Monad.Reader (local)-import Data.Aeson (Value (..))-import Data.String-import Data.Time (diffUTCTime)-import Freckle.App.Ecs-import Freckle.App.Env qualified as Env-import Network.StatsD.Datadog qualified as Datadog-import System.IO (hPutStrLn, stderr)-import System.Metrics.Gauge qualified as EKG-import UnliftIO.Exception (bracket_)-import Yesod.Core.Lens-import Yesod.Core.Types (HandlerData)--data StatsSettings = StatsSettings-  { amsEnabled :: Bool-  , amsSettings :: Datadog.DogStatsSettings-  , amsTags :: [(Text, Text)]-  }--defaultStatsSettings :: StatsSettings-defaultStatsSettings =-  StatsSettings-    { amsEnabled = False-    , amsSettings = Datadog.defaultSettings-    , amsTags = []-    }--setStatsSettingsTags :: [(Text, Text)] -> StatsSettings -> StatsSettings-setStatsSettingsTags x settings = settings {amsTags = x}--envParseStatsSettings :: Env.Parser Env.Error StatsSettings-envParseStatsSettings =-  StatsSettings-    <$> Env.switch "DOGSTATSD_ENABLED" mempty-    <*> ( buildSettings-            <$> optional (Env.var Env.str "DOGSTATSD_HOST" mempty)-            <*> optional (Env.var Env.auto "DOGSTATSD_PORT" mempty)-        )-    <*> ( buildTags-            <$> optional (Env.var Env.nonempty "DD_ENV" mempty)-            <*> optional (Env.var Env.nonempty "DD_SERVICE" mempty)-            <*> optional (Env.var Env.nonempty "DD_VERSION" mempty)-            <*> Env.var Env.keyValues "DOGSTATSD_TAGS" (Env.def [])-        )- where-  buildSettings mHost mPort =-    Datadog.defaultSettings-      & maybe id (Datadog.host .~) mHost-        . maybe id (Datadog.port .~) mPort--  buildTags mEnv mService mVersion tags =-    catMaybes-      [ ("env",) <$> mEnv-      , ("environment",) <$> mEnv -- Legacy-      , ("service",) <$> mService-      , ("version",) <$> mVersion-      ]-      <> tags--data Gauges = Gauges-  { gdbConnections :: Gauge-  -- ^ Track open db connections-  , gdbEnqueuedAndProcessing :: Gauge-  -- ^ Track enqueued and processing queries-  }--data Gauge = Gauge-  { gName :: Text-  , gGauge :: EKG.Gauge-  }--dbConnections :: Gauges -> Gauge-dbConnections = gdbConnections--dbEnqueuedAndProcessing :: Gauges -> Gauge-dbEnqueuedAndProcessing = gdbEnqueuedAndProcessing--data StatsClient = StatsClient-  { scClient :: Datadog.StatsClient-  , scTags :: [(Text, Text)]-  , scGauges :: Gauges-  }--tagsL :: Lens' StatsClient [(Text, Text)]-tagsL = lens scTags $ \x y -> x {scTags = y}--gaugesL :: Lens' StatsClient Gauges-gaugesL = lens scGauges $ \x y -> x {scGauges = y}--class HasStatsClient env where-  statsClientL :: Lens' env StatsClient--instance HasStatsClient StatsClient where-  statsClientL = id--instance HasStatsClient site => HasStatsClient (HandlerData child site) where-  statsClientL = envL . siteL . statsClientL--withStatsClient-  :: (MonadMask m, MonadUnliftIO m)-  => StatsSettings-  -> (StatsClient -> m a)-  -> m a-withStatsClient StatsSettings {..} f = do-  gauges <- liftIO $ do-    gdbConnections <- Gauge "active_pool_connections" <$> EKG.new-    gdbEnqueuedAndProcessing <- Gauge "queries_enqueued_and_processing" <$> EKG.new-    pure Gauges {..}--  if amsEnabled-    then do-      tags <- (amsTags <>) <$> getEcsMetadataTags-      Datadog.withDogStatsD amsSettings $ \client ->-        -- Add the tags to the thread context so they're present in all logs-        withThreadContext (map toPair tags) $-          f-            StatsClient-              { scClient = client-              , scTags = tags-              , scGauges = gauges-              }-    else do-      f $-        StatsClient-          { scClient = Datadog.Dummy-          , scTags = amsTags-          , scGauges = gauges-          }- where-  toPair = bimap (fromString . unpack) String--withGauge-  :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m)-  => (Gauges -> Gauge)-  -> m a-  -> m a-withGauge getGauge f = do-  gauge' <- lookupGauge getGauge-  bracket_ (inc gauge') (dec gauge') f- where-  inc = incGauge-  dec = decGauge--lookupGauge-  :: (MonadReader app m, HasStatsClient app)-  => (Gauges -> Gauge)-  -> m Gauge-lookupGauge accessor = view $ statsClientL . gaugesL . to accessor--incGauge-  :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m)-  => Gauge-  -> m ()-incGauge g@Gauge {..} = do-  liftIO $ EKG.inc gGauge-  publishGauge g--decGauge-  :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m)-  => Gauge-  -> m ()-decGauge g@Gauge {..} = do-  liftIO $ EKG.dec gGauge-  publishGauge g--publishGauge-  :: (MonadReader app m, HasStatsClient app, MonadUnliftIO m)-  => Gauge-  -> m ()-publishGauge Gauge {..} = do-  n <- liftIO $ EKG.read gGauge-  gauge gName $ fromIntegral n---- | Include the given tags on all metrics emitted from a block-tagged-  :: (MonadReader env m, HasStatsClient env) => [(Text, Text)] -> m a -> m a-tagged tags = local $ statsClientL . tagsL <>~ tags---- | Synonym for @'counter' 1@-increment-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => Text -> m ()-increment name = counter name 1--counter-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env)-  => Text-  -> Int-  -> m ()-counter = sendMetric Datadog.Counter--gauge-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env)-  => Text-  -> Double-  -> m ()-gauge = sendMetric Datadog.Gauge---- | Emit an elapsed duration (which Datadog calls a /histogram/)------ The 'ToMetricValue' constraint can be satisfied by most numeric types and is--- assumed to be seconds.-histogram-  :: ( MonadUnliftIO m-     , MonadReader env m-     , HasStatsClient env-     , Datadog.ToMetricValue n-     )-  => Text-  -> n-  -> m ()-histogram = sendMetric Datadog.Histogram--histogramSince-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env)-  => Text-  -> UTCTime-  -> m ()-histogramSince = histogramSinceBy toSeconds where toSeconds = round @_ @Int--histogramSinceMs-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env)-  => Text-  -> UTCTime-  -> m ()-histogramSinceMs = histogramSinceBy toMilliseconds- where-  toMilliseconds = (* 1000) . realToFrac @_ @Double--histogramSinceBy-  :: ( MonadUnliftIO m-     , MonadReader env m-     , HasStatsClient env-     , Datadog.ToMetricValue n-     )-  => (NominalDiffTime -> n)-  -> Text-  -> UTCTime-  -> m ()-histogramSinceBy f name time = do-  now <- liftIO getCurrentTime-  let delta = f $ now `diffUTCTime` time-  sendMetric Datadog.Histogram name delta--sendMetric-  :: ( MonadUnliftIO m-     , MonadReader env m-     , HasStatsClient env-     , Datadog.ToMetricValue v-     )-  => Datadog.MetricType-  -> Text-  -> v-  -> m ()-sendMetric metricType name metricValue = do-  StatsClient {..} <- view statsClientL--  Datadog.send scClient $-    Datadog.metric (Datadog.MetricName name) metricType metricValue-      & (Datadog.tags .~ map (uncurry Datadog.tag) scTags)--getEcsMetadataTags :: MonadIO m => m [(Text, Text)]-getEcsMetadataTags = do-  eMetadata <- runExceptT getEcsMetadata-  either (([] <$) . err) (pure . toTags) eMetadata- where-  err e = liftIO $ hPutStrLn stderr $ "Error reading ECS Metadata: " <> show e--  toTags (EcsMetadata EcsContainerMetadata {..} EcsContainerTaskMetadata {..}) =-    [ ("container_id", ecmDockerId)-    , ("container_name", ecmDockerName)-    , ("docker_image", ecmImage)-    , ("image_tag", ecmImageID)-    , ("cluster_name", ectmCluster)-    , ("task_arn", ectmTaskARN)-    , ("task_family", ectmFamily)-    , ("task_version", ectmRevision)-    ]---- $docs------ == Usage------ - Use 'envParseStatsSettings' to configure things------   @---   data AppSettings = AppSettings---    { -- ...---    , appStatsSettings :: StatsSettings---    }------   loadSettings :: IO AppSettings---   loadSettings = Env.parse id $ AppSettings---     <$> -- ...---     <*> 'envParseStatsSettings'---   @------   This will read,------   - @DOGSTATSD_ENABLED=x@---   - @DOGSTATSD_HOST=127.0.0.1@---   - @DOGSTATSD_PORT=8125@---   - @DOGSTATSD_TAGS=[<key>:<value>,...]@---   - Optionally @DD_ENV@, @DD_SERVICE@, and @DD_VERSION@------ - Give your @App@ a 'HasStatsClient' instance------   @---   data App = App---     { -- ...---     , appStatsClient :: 'StatsClient'---     }------   instance 'HasStatsClient' App where---     'statsClientL' = lens appStatsClient $ \x y -> { appStatsClient = y }---   @------ - Use 'withStatsClient' to build and store a client on your @App@ when you---   run it------   @---   'withStatsClient' appStatsSettings $ \client -> do---     app <- App---       <$> ...---       <*> pure client------     'runApp' app $ ...---   @------ - Throughout your application code, emit metrics as desired------   @---   import qualified Freckle.App.Stats as Stats------   myFunction :: (MonadIO m, MonadReader env m, 'HasStatsClient' env) => m ()---   myFunction = do---     start <- liftIO getCurrentTime---     result <- myAction------     Stats.'increment' \"action.attempt\"---     Stats.'histogramSinceMs' \"action.duration\" start------     case result of---       Left err -> do---         Stats.'increment' \"action.failure\"---         -- ...---       Right x -. do---         Stats.'increment' \"action.success\"---         -- ...---   @
− library/Freckle/App/Stats/Rts.hs
@@ -1,47 +0,0 @@--- | Send RTS statistics via "Freckle.App.Stats"-module Freckle.App.Stats.Rts-  ( forkRtsStatPolling-  ) where--import Freckle.App.Prelude--import Control.Immortal qualified as Immortal-import Data.HashMap.Strict qualified as HashMap-import Freckle.App.Stats (HasStatsClient)-import Freckle.App.Stats qualified as Stats-import System.Metrics qualified as Ekg-import System.Metrics.Distribution.Internal qualified as Ekg-import UnliftIO.Concurrent (threadDelay)---- | Initialize a thread to poll RTS stats------ Stats are collected via `ekg-core` and 'System.Metrics.registerGcMetrics'-forkRtsStatPolling-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env) => m ()-forkRtsStatPolling = do-  store <- liftIO Ekg.newStore-  liftIO $ Ekg.registerGcMetrics store--  void $ Immortal.create $ \_ -> do-    sample <- liftIO $ Ekg.sampleAll store-    traverse_ (uncurry flushEkgSample) $ HashMap.toList sample--    let seconds n = n * 1000000-    threadDelay $ seconds 1--flushEkgSample-  :: (MonadUnliftIO m, MonadReader env m, HasStatsClient env)-  => Text-  -> Ekg.Value-  -> m ()-flushEkgSample name = \case-  Ekg.Counter n -> Stats.counter name $ fromIntegral n-  Ekg.Gauge n -> Stats.gauge name $ fromIntegral n-  Ekg.Distribution d -> do-    Stats.gauge (name <> "." <> "mean") $ Ekg.mean d-    Stats.gauge (name <> "." <> "variance") $ Ekg.variance d-    Stats.gauge (name <> "." <> "sum") $ Ekg.sum d-    Stats.gauge (name <> "." <> "min") $ Ekg.min d-    Stats.gauge (name <> "." <> "max") $ Ekg.max d-    Stats.counter (name <> "." <> "count") $ fromIntegral $ Ekg.count d-  Ekg.Label _ -> pure ()
library/Freckle/App/Yesod.hs view
@@ -20,18 +20,20 @@ import Network.HTTP.Types (ResponseHeaders, status503) import Network.Wai qualified as W import Yesod.Core.Handler (HandlerFor, sendWaiResponse)-import Yesod.Core.Types (HandlerContents)+import Yesod.Core.Types (HandlerContents, HandlerData)  -- | Catch 'SqlError' when queries are canceled due to timeout and respond 503 -- -- Also logs and increments a metric. respondQueryCanceled-  :: HasStatsClient site => HandlerFor site res -> HandlerFor site res+  :: HasStatsClient (HandlerData site site)+  => HandlerFor site res+  -> HandlerFor site res respondQueryCanceled = respondQueryCanceledHeaders []  -- | 'respondQueryCanceledHeaders' but adding headers to the 503 response respondQueryCanceledHeaders-  :: HasStatsClient site+  :: HasStatsClient (HandlerData site site)   => ResponseHeaders   -> HandlerFor site res   -> HandlerFor site res
package.yaml view
@@ -1,5 +1,5 @@ name: freckle-app-version: 1.21.0.0+version: 1.22.0.0  maintainer: Freckle Education category: Utils@@ -94,17 +94,15 @@     - conduit-extra     - containers     - cookie-    - datadog     - doctest-    - ekg-core     - exceptions-    - extra     - faktory     - freckle-env     - freckle-exception     - freckle-http     - freckle-otel     - freckle-prelude+    - freckle-stats     - hs-opentelemetry-api     - hs-opentelemetry-instrumentation-persistent     - hs-opentelemetry-instrumentation-wai@@ -135,7 +133,6 @@     - semigroupoids     - template-haskell     - text-    - time     - transformers     - transformers-base     - typed-process@@ -165,6 +162,7 @@       - cassava       - conduit       - freckle-app+      - freckle-stats       - hs-opentelemetry-api       - hspec       - http-types