diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+0.2 [2019.08.28]
+-------------------
+* First release.
+* Endpoints builtin
+ * info
+ * healthcheck
+ * logger
+ * metrics
+ * refresh configuration
+ * swagger
+* Tracable log.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Daniel YU
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# boots-web
+
+[![Hackage](https://img.shields.io/hackage/v/boots-web.svg?logo=haskell)](https://hackage.haskell.org/package/boots-web)
+[![Build](https://img.shields.io/travis/leptonyu/boots.svg?logo=travis)](https://travis-ci.org/leptonyu/boots)
+[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/leptonyu/boots/blob/master/boots-web/LICENSE)
+![Hackage-Deps](https://img.shields.io/hackage-deps/v/boots-web)
+
+Factory for quickly building a web application.
+
+### Packages based on boots
+
+- [![Hackage](https://img.shields.io/badge/boots-app-orange)](https://hackage.haskell.org/package/boots-app) Factory for quickly building an application.
+- [![Hackage](https://img.shields.io/badge/boots-web-orange)](https://hackage.haskell.org/package/boots-web) Factory for quickly building a web application.
+- [![Hackage](https://img.shields.io/badge/boots-cloud-orange)](https://hackage.haskell.org/package/boots-cloud) Factory for quickly building a microservice.
+
diff --git a/boots-web.cabal b/boots-web.cabal
new file mode 100644
--- /dev/null
+++ b/boots-web.cabal
@@ -0,0 +1,67 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: d9adb6d374b0cb9dc52eda97c75b686f163de767b0da55ef021afd0a472624e6
+
+name:           boots-web
+version:        0.2
+synopsis:       Factory for quickly building a web application
+description:    A quick out-of-box factory using to build web application with many useful builtin components based on [boots](https://hackage.haskell.org/package/boots) and [servant](https://hackage.haskell.org/package/servant).
+category:       Library, Application, Servant, Swagger, Web
+homepage:       https://github.com/leptonyu/boots#readme
+author:         Daniel YU
+maintainer:     leptonyu@gmail.com
+copyright:      2019 Daniel YU
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+library
+  exposed-modules:
+      Boots.Web
+      Boots.Factory.Endpoint
+      Boots.Factory.Error
+      Boots.Factory.Random
+      Boots.Factory.Trace
+  other-modules:
+      Boots.Factory.Web
+      Boots.Metrics
+      Boots.Endpoint.Class
+      Boots.Endpoint.Health
+      Boots.Endpoint.Info
+      Boots.Endpoint.Logger
+      Boots.Endpoint.Metrics
+      Boots.Endpoint.Refresh
+      Boots.Endpoint.Swagger
+  hs-source-dirs:
+      src
+  default-extensions: RecordWildCards ScopedTypeVariables
+  ghc-options: -Wall -fno-warn-orphans -fno-warn-missing-signatures
+  build-depends:
+      aeson >=1.4.4 && <1.5
+    , base >=4.10 && <5
+    , boots >=0.2 && <0.3
+    , boots-app >=0.2 && <0.3
+    , bytestring >=0.10.8 && <0.11
+    , containers >=0.6.0 && <0.7
+    , ekg-core >=0.1.1 && <0.2
+    , http-types >=0.12.3 && <0.13
+    , microlens >=0.4.10 && <0.5
+    , monad-logger >=0.3.30 && <0.4
+    , salak >=0.3.5 && <0.4
+    , servant-server >=0.16.2 && <0.17
+    , servant-swagger >=1.1.7.1 && <1.2
+    , swagger2 >=2.4 && <2.5
+    , text >=1.2.3 && <1.3
+    , time >=1.8.0 && <1.10
+    , unordered-containers >=0.2.10 && <0.3
+    , vault >=0.3.1 && <0.4
+    , wai >=3.2.2 && <3.3
+    , warp >=3.2.28 && <3.4
+  default-language: Haskell2010
diff --git a/src/Boots/Endpoint/Class.hs b/src/Boots/Endpoint/Class.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Class.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+module Boots.Endpoint.Class where
+
+import           Boots
+import           Boots.Factory.Web
+import qualified Data.HashMap.Strict     as HM
+import qualified Data.Swagger            as S
+import           Data.Text               (Text)
+import           Servant
+import           Servant.Server.Internal
+
+data EndpointTag
+
+instance HasServer api ctx
+  => HasServer (EndpointTag :> api) ctx where
+  type ServerT (EndpointTag :> api) m = ServerT api m
+  route _ b = pathRouter "endpoints" . (route (Proxy @api) b)
+  hoistServerWithContext _ = hoistServerWithContext (Proxy @api)
+
+instance HasSwagger api => HasSwagger (EndpointTag :> api) where
+  toSwagger _ = toSwagger (Proxy @api) & S.applyTags [S.Tag "endpoints" (Just "Endpoints API") Nothing]
+
+-- | Register endpoint, use this function to create custom endpoints.
+registerEndpoint
+  :: forall context env api n
+  . ( HasSwagger api
+    , HasServer api context
+    , HasWeb context env
+    , MonadIO n
+    , MonadMask n)
+  => Text -- ^ Endpoint name, used for path, @/endpoints/:name@.
+  -> Proxy context -- ^ Context proxy.
+  -> Proxy api -- ^ Api proxy.
+  -> ServerT api (App env) -- ^ Api server.
+  -> Factory n (WebEnv env context) ()
+registerEndpoint name pc _ server = do
+  WebEnv{..} <- getEnv
+  let ok = enabled endpoint && HM.lookup name (endpoints endpoint) /= Just False
+  when ok $ logDebug $ "Endpoint " <> toLogStr name <> " actived."
+  tryServeWithSwagger ok pc (Proxy @(EndpointTag :> api)) server
diff --git a/src/Boots/Endpoint/Health.hs b/src/Boots/Endpoint/Health.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Health.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE DeriveAnyClass    #-}
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections     #-}
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+
+module Boots.Endpoint.Health where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Factory.Web
+import           Data.Aeson
+import           Servant
+
+
+type EndpointHealth = "health" :> Get '[JSON] Health
+
+instance ToJSON HealthStatus
+instance ToJSON Health where
+  toJSON = genericToJSON defaultOptions
+    { omitNothingFields = True }
+instance ToSchema HealthStatus
+instance ToSchema Health
+
+-- | Register health endpoint.
+endpointHealth
+  :: (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context
+  -> Factory n (WebEnv env context) ()
+endpointHealth pc = do
+  health <- asksEnv (view askHealth)
+  registerEndpoint "health" pc (Proxy @EndpointHealth) $ liftIO $ do
+    h@Health{..} <- health
+    if status == UP then return h else throwM err400 { errBody = encode h }
diff --git a/src/Boots/Endpoint/Info.hs b/src/Boots/Endpoint/Info.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Info.hs
@@ -0,0 +1,61 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE DeriveAnyClass    #-}
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+
+module Boots.Endpoint.Info where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Factory.Web
+import           Control.Concurrent
+import           Data.Aeson
+import           Data.Text            (Text)
+import           Data.Version         (Version, showVersion)
+import           GHC.Generics
+import           GHC.RTS.Flags
+import           Servant
+import           System.Info
+
+data Info = Info
+  { name       :: !Text
+  , instanceId :: !Text
+  , version    :: !Version
+  , profile    :: !Bool
+  } deriving (Show, Generic, ToSchema)
+
+type EndpointInfo = "info" :> Get '[JSON] Info
+
+-- | Register info endpoint.
+endpointInfo
+  :: (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context
+  -> Factory n (WebEnv env context) ()
+endpointInfo pc = do
+  app <- asksEnv (view askApp)
+  registerEndpoint "info" pc (Proxy @EndpointInfo) $ liftIO $ do
+    rtsf <- getRTSFlags
+    return (go rtsf app)
+  where
+    {-# INLINE go #-}
+    go RTSFlags{..} AppEnv{..}=
+      let ProfFlags{..} = profilingFlags
+      in Info{profile = find doHeapProfile , ..}
+    {-# INLINE find #-}
+    find NoHeapProfiling = False
+    find _               = True
+
+instance ToJSON Info where
+  toJSON Info{..} = object
+    [ "application"   .= name
+    , "instanceId"    .= instanceId
+    , "version"       .= version
+    , "isMultithread" .= rtsSupportsBoundThreads
+    , "isProfile"     .= profile
+    , "os"            .= os
+    , "arch"          .= arch
+    , "compiler"      .= (compilerName <> "-" <> showVersion compilerVersion)
+    ]
diff --git a/src/Boots/Endpoint/Logger.hs b/src/Boots/Endpoint/Logger.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Logger.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DataKinds                  #-}
+{-# LANGUAGE DeriveGeneric              #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE TypeApplications           #-}
+{-# LANGUAGE TypeOperators              #-}
+module Boots.Endpoint.Logger where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Factory.Web
+import           Data.Aeson
+import           GHC.Generics
+import           Salak
+import           Servant
+
+type EndpointLogger = "logger" :> (Get '[JSON] LogInfo :<|> ReqBody '[JSON] LogInfo :> Put '[JSON] NoContent)
+
+newtype LogInfo = LogInfo
+  { level :: String
+  } deriving (Show, Generic, ToJSON, FromJSON, ToSchema)
+
+-- | Register logger endpoint.
+endpointLogger
+  :: (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context
+  -> Factory n (WebEnv env context) ()
+endpointLogger pc = do
+  LogFunc{..} <- asksEnv (view askLogger)
+  registerEndpoint "logger" pc (Proxy @EndpointLogger) (getLogInfo logLvl :<|> putLogInfo logLvl)
+  where
+    getLogInfo w = liftIO $ LogInfo . show <$> getWritable w
+    putLogInfo w LogInfo{..} = liftIO $ setWritable (rightToMaybe $ levelFromStr $ fromString level) w >> return NoContent
diff --git a/src/Boots/Endpoint/Metrics.hs b/src/Boots/Endpoint/Metrics.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Metrics.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+module Boots.Endpoint.Metrics(
+    HasMetrics(..)
+  , endpointMetrics
+  ) where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Factory.Web
+import           Boots.Metrics
+import qualified Data.HashMap.Strict    as HM
+import qualified Data.Map.Strict        as M
+import           Data.Text              (Text)
+import           Network.HTTP.Types
+import           Network.Wai
+import           Servant
+import           System.Metrics
+import qualified System.Metrics.Counter as Counter
+
+
+type EndpointMetrics = "metrics" :> Get '[JSON] Metrics
+
+type Metrics = M.Map Text Text
+
+-- | Register metrics endpoint.
+endpointMetrics
+  :: (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context
+  -> Factory n (WebEnv env context) ()
+endpointMetrics pc = do
+  store       <- asksEnv (view askMetrics)
+  LogFunc{..} <- asksEnv (view askLogger)
+  liftIO $ do
+    registerGcMetrics store
+    registerCounter "log.failure" logFail store
+  let newC n = liftIO $ createCounter n store
+  requests <- newC "http.server.requests"
+  req_fail <- newC "http.server.requests.failure"
+  registerMiddleware
+      $ \app env req resH -> app env req
+      $ \res -> do
+        Counter.inc requests
+        when (statusCode (responseStatus res) >= 400) $ Counter.inc req_fail
+        resH res
+  registerEndpoint "metrics" pc (Proxy @EndpointMetrics) (liftIO $ go store)
+  where
+    {-# INLINE go #-}
+    go s = do
+      sample <- sampleAll s
+      return
+        $ M.fromList
+        $ HM.toList
+        $ HM.map g2 sample
+    {-# INLINE showT #-}
+    showT :: Show a => a -> Text
+    showT = fromString . show
+    {-# INLINE g2 #-}
+    g2 (Counter i)      = showT i
+    g2 (Gauge i)        = showT i
+    g2 (Label x)        = x
+    g2 (Distribution i) = showT i
diff --git a/src/Boots/Endpoint/Refresh.hs b/src/Boots/Endpoint/Refresh.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Refresh.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE DeriveAnyClass    #-}
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+module Boots.Endpoint.Refresh where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Factory.Web
+import           Data.Aeson
+import           GHC.Generics
+import           Salak
+import           Servant
+
+type EndpointRefresh = "refresh" :> Post '[JSON] Refresh
+
+data Refresh = Refresh
+  { hasError :: !Bool
+  , msgs     :: ![String]
+  } deriving (Eq, Show, Generic, ToJSON, ToSchema)
+
+-- | Register refresh endpoint.
+endpointRefresh
+  :: (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context
+  -> Factory n (WebEnv env context) ()
+endpointRefresh pc = do
+  reload <- askReload
+  registerEndpoint "refresh" pc (Proxy @EndpointRefresh) (liftIO $ go <$> reload)
+  where
+    {-# INLINE go #-}
+    go ReloadResult{..} = Refresh{..}
diff --git a/src/Boots/Endpoint/Swagger.hs b/src/Boots/Endpoint/Swagger.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Endpoint/Swagger.hs
@@ -0,0 +1,31 @@
+{-# LANGUAGE CPP               #-}
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+{-# LANGUAGE TypeOperators     #-}
+module Boots.Endpoint.Swagger where
+
+import qualified Data.Swagger   as S
+import           Data.Text      (Text, pack)
+import           Data.Version   (Version, showVersion)
+import           Data.Word
+import           Lens.Micro
+import           Servant
+#if __GLASGOW_HASKELL__ < 804
+import           Data.Semigroup
+#endif
+
+type EndpointSwagger = "endpoints" :> "swagger" :> Get '[JSON] S.Swagger
+
+-- | Swagger modification
+baseInfo
+  :: String  -- ^ Hostname
+  -> Text    -- ^ Server Name
+  -> Version -- ^ Server version
+  -> Word16  -- ^ Port
+  -> S.Swagger -- ^ Old swagger
+  -> S.Swagger
+baseInfo hostName n v p s = s
+  & S.info . S.title   .~ (n <> " API Documents")
+  & S.info . S.version .~ pack (showVersion v)
+  & S.host ?~ S.Host hostName (Just $ fromIntegral p)
diff --git a/src/Boots/Factory/Endpoint.hs b/src/Boots/Factory/Endpoint.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Factory/Endpoint.hs
@@ -0,0 +1,46 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module:      Boots.Factory.Endpoint
+-- Copyright:   2019 Daniel YU
+-- License:     MIT
+-- Maintainer:  leptonyu@gmail.com
+-- Stability:   experimental
+-- Portability: portable
+--
+-- This module provide supports for endpoints.
+--
+module Boots.Factory.Endpoint(
+    buildEndpoints
+  , registerEndpoint
+  , endpointInfo
+  , endpointLogger
+  , endpointRefresh
+  , endpointHealth
+  , endpointMetrics
+  ) where
+
+import           Boots
+import           Boots.Endpoint.Class
+import           Boots.Endpoint.Health
+import           Boots.Endpoint.Info
+import           Boots.Endpoint.Logger
+import           Boots.Endpoint.Metrics
+import           Boots.Endpoint.Refresh
+import           Boots.Factory.Web
+
+-- | Register all endpoints provided by this package.
+buildEndpoints
+  :: forall context env n
+  . (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context -- ^ Context proxy.
+  -> Proxy env -- ^ Environment proxy.
+  -> Factory n (WebEnv env context) ()
+buildEndpoints pc _ = do
+  WebEnv{..} <- getEnv
+  unless   (enabled endpoint) $ logInfo "Endpoint is disabled."
+  tryBuild (enabled endpoint) $ do
+    endpointInfo    pc
+    endpointLogger  pc
+    endpointRefresh pc
+    endpointHealth  pc
+    endpointMetrics pc
diff --git a/src/Boots/Factory/Error.hs b/src/Boots/Factory/Error.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Factory/Error.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE TypeApplications    #-}
+-- |
+-- Module:      Boots.Factory.Error
+-- Copyright:   2019 Daniel YU
+-- License:     MIT
+-- Maintainer:  leptonyu@gmail.com
+-- Stability:   experimental
+-- Portability: portable
+--
+-- This module provide supports for handling exception.
+--
+module Boots.Factory.Error(
+    buildError
+  , buildWebLogger
+  , toMonadLogger
+  , L.runLoggingT
+  ) where
+
+import           Boots
+import           Boots.Factory.Web
+import           Control.Exception    (catch)
+import qualified Control.Monad.Logger as L
+import           GHC.Stack
+import           Network.HTTP.Types
+import           Network.Wai
+
+-- | Catch exception, convert to Resonpose.
+{-# INLINE buildError #-}
+buildError
+  :: forall context env n
+  . (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context -> Proxy env -> Factory n (WebEnv env context) ()
+buildError _ _ = tryBuildByKey True "web.error.enabled" $
+  registerMiddleware $ \app env req resH -> app env req resH `catch`
+    \e -> do
+      runAppT env $ logException e
+      resH (whenException e)
+
+-- | Register logging requests.
+{-# INLINE buildWebLogger #-}
+buildWebLogger
+  :: forall context env n
+  . (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context -> Proxy env -> Factory n (WebEnv env context) ()
+buildWebLogger _ _ = tryBuildByKey True "web.log.enabled" $
+  registerMiddleware $ \app env req resH -> app env req
+    $ \res -> do
+      runAppT env $ toLog req (responseStatus res)
+      resH res
+
+{-# INLINE toLog #-}
+toLog :: HasLogger env => Request -> Status -> App env ()
+toLog ~req Status{..} =
+  let {-# INLINE g #-}
+      g (Just i) = " \"" <> toLogStr i <> "\""
+      g _        = " \"\""
+      lf = if statusCode < 400 then logInfo else logWarn
+  in lf $ "\""
+    <> toLogStr (requestMethod req)
+    <> " "
+    <> toLogStr (rawPathInfo req)
+    <> toLogStr (rawQueryString req)
+    <> " "
+    <> toLogStr (show $ httpVersion req)
+    <> "\""
+    <> g (requestHeaderReferer req)
+    <> g (requestHeaderHost req)
+    <> g (requestHeaderUserAgent req)
+    <> " "
+    <> toLogStr statusCode
+
+-- | Adapter to [monad-logger](https://hackage.haskell.org/package/monad-logger).
+{-# INLINE toMonadLogger #-}
+toMonadLogger :: ToLogStr msg => LogFunc -> L.Loc -> L.LogSource -> L.LogLevel -> msg -> IO ()
+toMonadLogger LogFunc{..} L.Loc{..} _ ll = logfunc g1 (g2 ll) . toLogStr
+  where
+    {-# INLINE g1 #-}
+    g1 = uncurry (uncurry (SrcLoc loc_package loc_module loc_filename) loc_start) loc_end
+    {-# INLINE g2 #-}
+    g2 L.LevelDebug     = LevelDebug
+    g2 L.LevelInfo      = LevelInfo
+    g2 L.LevelWarn      = LevelWarn
+    g2 L.LevelError     = LevelError
+    g2 (L.LevelOther _) = LevelTrace
diff --git a/src/Boots/Factory/Random.hs b/src/Boots/Factory/Random.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Factory/Random.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE TypeApplications    #-}
+-- |
+-- Module:      Boots.Factory.Random
+-- Copyright:   2019 Daniel YU
+-- License:     MIT
+-- Maintainer:  leptonyu@gmail.com
+-- Stability:   experimental
+-- Portability: portable
+--
+-- This module provide supports for creating a thread unsafe random seed.
+--
+module Boots.Factory.Random(
+    buildRandom
+  ) where
+
+import           Boots
+import           Boots.Factory.Web
+
+-- | Create a thread unsafe random seed per request.
+buildRandom
+  :: forall context env n
+  . (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context -> Proxy env -> Factory n (WebEnv env context) ()
+buildRandom _ _ = tryBuildByKey True "web.random.enabled" $
+  registerMiddleware $ \app env req resH -> do
+    seed <- unRD (view askRandom env) splitSMGen
+    makeRD0 seed $ \rd -> app (over askRandom (const rd) env) req resH
diff --git a/src/Boots/Factory/Trace.hs b/src/Boots/Factory/Trace.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Factory/Trace.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections     #-}
+{-# LANGUAGE TypeApplications  #-}
+-- |
+-- Module:      Boots.Factory.Trace
+-- Copyright:   2019 Daniel YU
+-- License:     MIT
+-- Maintainer:  leptonyu@gmail.com
+-- Stability:   experimental
+-- Portability: portable
+--
+-- This module provide supports for generating trace info.
+--
+module Boots.Factory.Trace(
+    buildTrace
+  ) where
+
+import           Boots
+import           Boots.Factory.Web
+import           Data.ByteString    (ByteString)
+import           Network.HTTP.Types
+import           Network.Wai
+
+{-# INLINE hTraceId #-}
+hTraceId :: HeaderName
+hTraceId = "X-B3-TraceId"
+
+{-# INLINE hSpanId #-}
+hSpanId :: HeaderName
+hSpanId = "X-B3-SpanId"
+
+-- | Generate trace info for each request.
+buildTrace
+  :: forall env context n
+  . (HasWeb context env, MonadMask n, MonadIO n)
+  => Proxy context -> Proxy env -> Factory n (WebEnv env context) ()
+buildTrace _ _ = tryBuildByKey True "web.trace.enabled" $
+  registerMiddleware $ \app env req resH -> do
+    let x64 = runAppT env $ hex64 <$> nextW64 :: IO ByteString
+    ids <- case lookup hTraceId (requestHeaders req) of
+      Just tid -> (lookup hSpanId (requestHeaders req),tid,) <$> x64
+      _        -> (Nothing,,) <$> x64 <*> x64
+    app (over askLogger (addTrace $ go ids) env) req resH
+  where
+    {-# INLINE go #-}
+    go :: (Maybe ByteString, ByteString, ByteString) -> LogStr
+    go (Just pid, tid, sid) = toLogStr tid <> "," <> toLogStr sid <> "," <> toLogStr pid
+    go (_, tid, sid) = toLogStr tid <> "," <> toLogStr sid <> ","
diff --git a/src/Boots/Factory/Web.hs b/src/Boots/Factory/Web.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Factory/Web.hs
@@ -0,0 +1,312 @@
+{-# LANGUAGE AllowAmbiguousTypes   #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings     #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE TupleSections         #-}
+{-# LANGUAGE TypeApplications      #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+{-# LANGUAGE UndecidableInstances  #-}
+
+module Boots.Factory.Web(
+    buildWeb
+  , HasWeb
+  -- ** Configuration
+  , HasWebConfig(..)
+  , WebConfig(..)
+  , EndpointConfig(..)
+  -- ** Environment
+  , WebEnv(..)
+  , newWebEnv
+  , askEnv
+  -- ** Modified Middleware
+  , EnvMiddleware
+  , registerMiddleware
+  -- ** Api serve
+  , tryServe
+  , trySwagger
+  , tryServeWithSwagger
+  -- ** Utilities
+  , HasSwagger(..)
+  , HasServer(..)
+  , HasContextEntry(..)
+  , SetContextEntry(..)
+  , Context(..)
+  , askContext
+  , logException
+  , whenException
+  , ToSchema
+  , Vault
+  ) where
+
+import           Boots
+import           Boots.Endpoint.Swagger
+import           Boots.Metrics
+import           Control.Exception
+    ( SomeException
+    , fromException
+    )
+import qualified Data.HashMap.Strict                 as HM
+import           Data.Maybe
+import           Data.Swagger                        (Swagger)
+import           Data.Swagger.Schema                 (ToSchema)
+import           Data.Text                           (Text)
+import           Data.Text.Lazy                      (toStrict)
+import           Data.Text.Lazy.Encoding
+import           Data.Word
+import           Network.Wai
+import           Network.Wai.Handler.Warp
+import           Salak
+import           Servant
+import           Servant.Server.Internal.ServerError (responseServerError)
+import           Servant.Swagger
+
+-- | Application Configuration.
+data WebConfig = WebConfig
+  { hostname :: !String -- ^ Applicatoin hostname, used in swagger.
+  , port     :: !Word16 -- ^ Application http port.
+  } deriving (Eq, Show)
+
+instance Default WebConfig where
+  {-# INLINE def #-}
+  def = WebConfig "localhost" 8888
+
+instance FromProp m WebConfig where
+  {-# INLINE fromProp #-}
+  fromProp = WebConfig
+    <$> "host" .?: hostname
+    <*> "port" .?: port
+
+-- | Environment values with `WebConfig`.
+class HasWebConfig env where
+  askWebConfig :: Lens' env WebConfig
+
+instance HasWebConfig WebConfig where
+  askWebConfig = id
+
+-- | Endpoint configuration.
+data EndpointConfig = EndpointConfig
+  { enabled   :: Bool
+  , endpoints :: HM.HashMap Text Bool
+  }
+
+instance FromProp m EndpointConfig where
+  fromProp = EndpointConfig
+    <$> "enabled" .?= True
+    <*> "enabled" .?= HM.empty
+
+-- | Web environment, which defined all components to build a web application.
+data WebEnv env context = WebEnv
+  { serveW     :: forall api. HasServer api context
+               => Proxy api -> Context context -> Server api -> Application
+               -- ^ A wrapper of `serveWithContext`.
+  , serveA     :: forall api. HasSwagger api
+               => Proxy api -> Swagger
+              -- ^ A wrapper of `toSwagger`.
+  , middleware :: EnvMiddleware env -- ^ Modified middleware.
+  , envs       :: env -- ^ Application environment.
+  , context    :: env -> Context context -- ^ Function used to generate @context@ from @env.
+  , config     :: WebConfig -- ^ Web configuration.
+  , endpoint   :: EndpointConfig -- ^ Endpoint configuration.
+  , store      :: Store -- ^ Metrics store.
+  }
+
+{-# INLINE askEnv' #-}
+askEnv' :: Lens' (WebEnv env context) env
+askEnv' = lens envs (\x y -> x { envs = y})
+
+-- | Environment values with `WebEnv`.
+instance HasWebConfig (WebEnv env context) where
+  askWebConfig = lens config (\x y -> x { config = y})
+
+instance HasMetrics (WebEnv env context) where
+  {-# INLINE askMetrics #-}
+  askMetrics = lens store (\x y -> x { store = y})
+instance HasSalak env => HasSalak (WebEnv env context) where
+  {-# INLINE askSalak #-}
+  askSalak = askEnv' . askSalak
+instance HasLogger env => HasLogger (WebEnv env context) where
+  {-# INLINE askLogger #-}
+  askLogger = askEnv' . askLogger
+instance HasRandom env => HasRandom (WebEnv env context) where
+  {-# INLINE askRandom #-}
+  askRandom = askEnv' . askRandom
+instance HasApp env => HasApp (WebEnv env context) where
+  {-# INLINE askApp #-}
+  askApp = askEnv' . askApp
+instance HasHealth env => HasHealth (WebEnv env context) where
+  {-# INLINE askHealth #-}
+  askHealth = askEnv' . askHealth
+
+-- | Unified constraints for web environment.
+type HasWeb context env =
+  ( HasApp env
+  , HasLogger env
+  , HasHealth env
+  , HasRandom env
+  , HasSalak env
+  , HasContextEntry context env)
+
+-- | Class type used to modify @context@ entries.
+class HasContextEntry context env => SetContextEntry context env where
+  setContextEntry :: env -> Context context -> Context context
+
+instance {-# OVERLAPPABLE #-} SetContextEntry as env => SetContextEntry (a : as) env where
+  {-# INLINE setContextEntry #-}
+  setContextEntry env (a :. as) = a :. setContextEntry env as
+
+instance SetContextEntry (env : as) env where
+  {-# INLINE setContextEntry #-}
+  setContextEntry env (_ :. as) = env :. as
+
+-- | Lens for modify @env@ in @context@.
+{-# INLINE askContext #-}
+askContext :: SetContextEntry context env => Lens' (Context context) env
+askContext = lens getContextEntry (flip setContextEntry)
+
+instance (SetContextEntry context env, HasSalak env) => HasSalak (Context context) where
+  {-# INLINE askSalak #-}
+  askSalak = askContext @context @env . askSalak
+instance (SetContextEntry context env, HasLogger env) => HasLogger (Context context) where
+  {-# INLINE askLogger #-}
+  askLogger = askContext @context @env . askLogger
+instance (SetContextEntry context env, HasRandom env) => HasRandom (Context context) where
+  {-# INLINE askRandom #-}
+  askRandom = askContext @context @env . askRandom
+
+-- | Create a web environment.
+{-# INLINE newWebEnv #-}
+newWebEnv
+  :: (HasContextEntry context env, HasLogger env)
+  => env -- ^ Application environment.
+  -> (env -> Context context) -- ^ Function used to generate @context@ from @env@.
+  -> WebConfig -- ^ Web configuration.
+  -> EndpointConfig -- ^ Endpoint configuration.
+  -> Store -- ^ Metrics store.
+  -> WebEnv env context
+newWebEnv = WebEnv serveWithContext toSwagger id
+
+-- | Get application environment @env@.
+{-# INLINE askEnv #-}
+askEnv :: MonadMask n => Factory n (WebEnv env context) env
+askEnv = envs <$> getEnv
+
+-- | Modified wai `Middleware`, which support modify @env@.
+type EnvMiddleware env = (env -> Application) -> env -> Application
+
+-- | Register a modified middleware.
+{-# INLINE registerMiddleware #-}
+registerMiddleware
+  :: MonadMask n
+  => EnvMiddleware env
+  -> Factory n (WebEnv env context) ()
+registerMiddleware md = modifyEnv $ \web -> web { middleware = md . middleware web }
+
+-- | Build a web application from `WebEnv`.
+buildWeb
+  :: forall context env n
+  . ( MonadIO n
+    , MonadMask n
+    , HasWeb context env
+    )
+  => Proxy context -- ^ @context@ proxy.
+  -> Proxy env -- ^ @env@ proxy.
+  -> Factory n (WebEnv env context) (IO ()) -- ^ Factory which create an application from `WebEnv`.
+buildWeb _ _ = do
+  (WebEnv{..} :: WebEnv env context) <- getEnv
+  within envs $ do
+    AppEnv{..}        <- asksEnv (view askApp)
+    let serveWarp WebConfig{..} = runSettings
+          $ defaultSettings
+          & setPort (fromIntegral port)
+          & setOnExceptionResponse whenException
+          & setOnException (\_ -> runAppT envs . logException)
+    let ok = enabled endpoint && HM.lookup "swagger" (endpoints endpoint) /= Just False
+    when ok
+      $ logInfo
+      $ "Swagger enabled: http://"
+      <> toLogStr (hostname config)
+      <> ":"
+      <> toLogStr (port config)
+      <> "/endpoints/swagger"
+    logInfo $ "Service started on port(s): " <> toLogStr (port config)
+    delay $ logInfo "Service ended"
+    return
+      $ serveWarp config
+      $ flip middleware envs
+      $ \env1 -> if ok
+        then serveW (Proxy @EndpointSwagger) (context env1)
+              (return $ baseInfo (hostname config) name version (port config) $ serveA $ Proxy @EmptyAPI)
+        else serveW (Proxy @EmptyAPI) (context env1) emptyServer
+
+-- | Log exception.
+{-# INLINE logException #-}
+logException :: HasLogger env => SomeException -> App env ()
+logException = logError . toLogStr . formatException
+
+-- | Convert an exception into `Network.Wai.Response`.
+{-# INLINE whenException #-}
+whenException :: SomeException -> Network.Wai.Response
+whenException e = responseServerError
+  $ fromMaybe err400 { errBody = fromString $ show e} (fromException e :: Maybe ServerError)
+
+-- | Format exception.
+{-# INLINE formatException #-}
+formatException :: SomeException -> Text
+formatException e = case fromException e of
+  Just ServerError{..} -> fromString errReasonPhrase <> " " <> toStrict (decodeUtf8 errBody)
+  _                    -> fromString $ show e
+
+-- | Serve web server with swagger.
+tryServeWithSwagger
+  :: forall env context api n
+  . ( HasContextEntry context env
+    , HasServer api context
+    , HasSwagger api
+    , MonadMask n)
+  => Bool -- ^ If do this action.
+  -> Proxy context -- ^ Context proxy.
+  -> Proxy api -- ^ Api proxy.
+  -> ServerT api (App env) -- ^ Api server.
+  -> Factory n (WebEnv env context) ()
+tryServeWithSwagger b pc proxy server = do
+  trySwagger b    proxy
+  tryServe   b pc proxy server
+
+-- | Try serve a swagger definition.
+trySwagger
+  :: (MonadMask n, HasSwagger api)
+  => Bool -- ^ If do this action.
+  -> Proxy api -- ^ Api proxy.
+  -> Factory n (WebEnv env context) ()
+trySwagger b api = tryBuild b $ modifyEnv $ \web -> web { serveA = serveA web . gop api }
+
+-- | Try serve a web server.
+tryServe
+  :: forall env context api n
+  . ( HasContextEntry context env
+    , HasServer api context
+    , MonadMask n)
+  => Bool -- ^ If do this action.
+  -> Proxy context -- ^ Context proxy.
+  -> Proxy api -- ^ Api proxy.
+  -> ServerT api (App env) -- ^ Api server.
+  -> Factory n (WebEnv env context) ()
+tryServe b pc proxy server = tryBuild b $
+  modifyEnv
+    $ \web -> web { serveW = \p c s -> serveW web (gop p proxy) c
+    $ s :<|> hoistServerWithContext proxy pc (go . runAppT (getContextEntry c :: env)) server }
+  where
+    {-# INLINE go #-}
+    go :: IO a -> Servant.Handler a
+    go = liftIO
+
+{-# INLINE gop #-}
+gop :: forall a b. Proxy a -> Proxy b -> Proxy (a :<|> b)
+gop _ _ = Proxy
+
+
diff --git a/src/Boots/Metrics.hs b/src/Boots/Metrics.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Metrics.hs
@@ -0,0 +1,12 @@
+module Boots.Metrics(
+    HasMetrics(..)
+  , Store
+  , newStore
+  ) where
+
+import           Boots
+import           System.Metrics
+
+-- | Environment values with `Store`.
+class HasMetrics env where
+  askMetrics :: Lens' env Store
diff --git a/src/Boots/Web.hs b/src/Boots/Web.hs
new file mode 100644
--- /dev/null
+++ b/src/Boots/Web.hs
@@ -0,0 +1,87 @@
+{-# LANGUAGE DataKinds         #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications  #-}
+-- |
+-- Module:      Boots.Web
+-- Copyright:   2019 Daniel YU
+-- License:     MIT
+-- Maintainer:  leptonyu@gmail.com
+-- Stability:   experimental
+-- Portability: portable
+--
+-- A quick out-of-box factory using to build web application with many useful builtin web components,
+-- based on [boots-app](https://hackage.haskell.org/package/boots-app) and [servant](https://hackage.haskell.org/package/servant).
+--
+-- 1. Builtin metrics, use [ekg-core](https://hackage.haskell.org/package/ekg-core) as backend.
+-- 2. Builtin tracing log, support B3-Tags.
+-- 3. Builtin endpoints, info, healthcheck, logger, metrics, refresh configuration, swagger api.
+-- 4. Builtin error management.
+--
+-- Hackage [boots-consul](https://hackage.haskell.org/package/boots-consul) provides consul support for building microservices.
+--
+module Boots.Web(
+  -- * Boot web
+    bootWeb
+  , module Boots.Factory.Web
+  -- * Metrics
+  , module Boots.Metrics
+  -- * Middleware
+  -- ** Endpoint
+  , module Boots.Factory.Endpoint
+  -- ** Tracing
+  , module Boots.Factory.Trace
+  -- ** Other
+  , module Boots.Factory.Error
+  , module Boots.Factory.Random
+  ) where
+
+import           Boots.Factory.Web
+import           Boots.Metrics
+
+import           Boots.Factory.Endpoint
+import           Boots.Factory.Error
+import           Boots.Factory.Random
+import           Boots.Factory.Trace
+
+import           Boots
+import           Data.Version           (Version)
+
+-- | A out-of-box web application booter with many predefined components.
+bootWeb
+  :: forall api env context
+  . ( HasServer api context
+    , HasSwagger api
+    , HasWeb context env)
+  => String -- ^ Application name.
+  -> Version -- ^ Application version.
+  -> (AppEnv -> env) -- ^ Function which generates @env@ using `AppEnv`.
+  -> (env -> Context context) -- ^ Function which generates @context@ using @env@.
+  -> (Proxy context -> Proxy env -> Factory IO (WebEnv env context) ()) -- ^ Customized `Factory`.
+  -> Proxy api -- ^ Api proxy.
+  -> ServerT api (App env) -- ^ Servant api server.
+  -> IO ()
+bootWeb appName ver fenv fcxt buildCustom api server = boot $ do
+  app  <- buildApp appName ver
+  within app $ do
+    conf  <- require "application"
+    ec    <- require "endpoints"
+    store <- liftIO newStore
+    logInfo $ "Start Service [" <> toLogStr (name app) <> "] ..."
+    let
+      c = newWebEnv
+         (fenv app)
+         fcxt
+         conf
+         ec
+         store :: WebEnv env context
+      pe = Proxy @env
+      pc = Proxy @context
+    within c $ do
+      tryServeWithSwagger True  pc api server
+      buildError     pc pe
+      buildCustom    pc pe
+      buildWebLogger pc pe
+      buildTrace     pc pe
+      buildRandom    pc pe
+      buildEndpoints pc pe
+      buildWeb       pc pe
