contra-tracers-1.0.0: src/Data/Generics/Tracers.hs
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at http://mozilla.org/MPL/2.0/.
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
{-# OPTIONS_HADDOCK prune #-}
-- | This module provides a way to generically define and configure multiple
-- tracers for multiple logging components.
--
-- This is useful in order to associate separate configuration (e.g. minimum
-- severity) to tracers corresponding to different parts of an application. For
-- example, in a webserver, one may imagine wanting to lower the verbosity of a
-- tracer used for all HTTP requests, but keep the database tracer at a debug
-- level.
--
-- While this is possible to do without generics, it usually requires a bit of
-- boilerplate and repetitions. This module makes it easy to define one-single
-- data object as a record, for representing both severities configurations and
-- concrete instantiated tracers. Type signatures are a bit frightening, but the
-- usage is pretty simple (see 'Control.Tracer' for examples)
--
module Data.Generics.Tracers
( -- * Definition
IsRecordOfTracers
, TracerDefinition (..)
, type TracerHKD
-- * Construction
, configureTracers
, defaultTracers
-- Internal
, SomeMsg (..)
) where
import Prelude
import GHC.Generics
import Control.Tracer
( Tracer (..), nullTracer )
import Data.Aeson
( ToJSON (..) )
import Data.Functor.Const
( Const (..) )
import Data.Functor.Contravariant
( contramap )
import Data.Kind
( Type )
import Data.Severity
( HasSeverityAnnotation (..), Severity )
-- * Definition
-- | A constraint-alias defining generic record of tracers.
type IsRecordOfTracers tracers m =
( GConfigureTracers m (Rep (tracers m MinSeverities)) (Rep (tracers m Concrete))
, Generic (tracers m MinSeverities)
, Generic (tracers m Concrete)
)
-- | A data kind to control what's defined in a generic record of tracer.
--
-- Use 'configureTracers' to turn a record of configuration into a record of
-- concrete tracers.
data TracerDefinition = Concrete | MinSeverities
-- | A high-kinded type family to make definitions of tracers more readable. For
-- example, one can define a multi-component set of tracers as such:
--
-- data Tracers m (kind :: TracerDefinition) = Tracers
-- { tracerHttp :: TracerHKD kind (Tracer m HttpLog)
-- , tracerStart :: TracerHKD kind (Tracer m StartLog)
-- }
type family TracerHKD (definition :: TracerDefinition) (tracer :: Type) :: Type where
TracerHKD MinSeverities tracer = Const (Maybe Severity) tracer
TracerHKD Concrete tracer = tracer
-- * Construction
-- | Convert a 'MinSeverities' of tracers into a 'Concrete' record of tracers,
-- using a base opaque 'Tracer'.
configureTracers
:: ( IsRecordOfTracers tracers m )
=> tracers m MinSeverities
-> Tracer m SomeMsg
-> tracers m Concrete
configureTracers f tr =
to . gConfigureTracers mempty tr . from $ f
class GConfigureTracers m (f :: Type -> Type) (g :: Type -> Type) where
gConfigureTracers
:: String
-> Tracer m SomeMsg
-> f (Const (Maybe Severity) tr)
-> g tr
instance GConfigureTracers m f g => GConfigureTracers m (D1 c f) (D1 c g) where
gConfigureTracers field tr = M1 . gConfigureTracers field tr . unM1
instance GConfigureTracers m f g => GConfigureTracers m (C1 c f) (C1 c g) where
gConfigureTracers field tr = M1 . gConfigureTracers field tr . unM1
instance (Selector s, GConfigureTracers m f g) => GConfigureTracers m (S1 s f) (S1 s g) where
gConfigureTracers _ tr = M1 . gConfigureTracers field tr . unM1
where
field = selName (undefined :: S1 s f a)
instance (GConfigureTracers m f0 g0, GConfigureTracers m f1 g1)
=> GConfigureTracers m (f0 :*: f1) (g0 :*: g1)
where
gConfigureTracers field tr (f0 :*: f1) =
gConfigureTracers field tr f0
:*:
gConfigureTracers field tr f1
instance (ToJSON msg, HasSeverityAnnotation msg, Applicative m)
=> GConfigureTracers m (K1 i (Const (Maybe Severity) (Tracer m msg))) (K1 i (Tracer m msg))
where
gConfigureTracers field tr = \case
K1 (Const (Just minSeverity)) ->
K1 (contramap (SomeMsg minSeverity field) tr)
K1 (Const Nothing) ->
K1 nullTracer
-- | Assign a default value to a record of tracers.
--
-- This is useful when used in conjunction with 'Const' and a record of tracer
-- severities:
--
-- defaultTracers Info == Tracers
-- { tracerHttp = Const (Just Info)
-- , tracerDb = Const (Just Info)
-- }
--
defaultTracers
:: forall tracers (m :: Type -> Type).
( Generic (tracers m MinSeverities)
, GDefaultRecord (Rep (tracers m MinSeverities))
, GDefaultRecordType (Rep (tracers m MinSeverities)) ~ Maybe Severity
)
=> Maybe Severity
-> tracers m MinSeverities
defaultTracers =
to . gDefaultRecord
-- | This generic class allows for defining a default value to a record of
-- functors wrapping some type 'a'.
class GDefaultRecord (f :: Type -> Type) where
type GDefaultRecordType f :: Type
gDefaultRecord :: GDefaultRecordType f -> f (Const (GDefaultRecordType f) b)
instance GDefaultRecord f => GDefaultRecord (D1 c f) where
type GDefaultRecordType (D1 c f) = GDefaultRecordType f
gDefaultRecord a = M1 (gDefaultRecord a)
instance GDefaultRecord f => GDefaultRecord (C1 c f) where
type GDefaultRecordType (C1 c f) = GDefaultRecordType f
gDefaultRecord a = M1 (gDefaultRecord a)
instance GDefaultRecord f => GDefaultRecord (S1 c f) where
type GDefaultRecordType (S1 c f) = GDefaultRecordType f
gDefaultRecord a = M1 (gDefaultRecord a)
instance (GDefaultRecord f, GDefaultRecord g, GDefaultRecordType f ~ GDefaultRecordType g) => GDefaultRecord (f :*: g) where
type GDefaultRecordType (f :*: g) = GDefaultRecordType f
gDefaultRecord a = gDefaultRecord a :*: gDefaultRecord a
instance GDefaultRecord (K1 i (Const a b)) where
type GDefaultRecordType (K1 i (Const a b)) = a
gDefaultRecord a = K1 (Const a)
-- Internal
-- A GADT capturing constraints as existential.
data SomeMsg where
SomeMsg
:: forall msg. (ToJSON msg, HasSeverityAnnotation msg)
=> Severity
-> String
-> msg
-> SomeMsg