{-# LANGUAGE TemplateHaskell #-} {-| Module: IHP.Telemetry Description: Reports IHP Version + OS to digitally induced Copyright: (c) digitally induced GmbH, 2020 -} module IHP.Telemetry where import IHP.Prelude import qualified IHP.Version as Version import qualified System.Info as System import qualified Network.Wreq as Wreq import qualified Control.Exception.Safe as Exception import qualified Crypto.Hash.SHA512 as SHA512 import qualified System.Directory as Directory import qualified Data.ByteString.Base16 as Base16 import qualified Data.Text as T import qualified Data.Text.IO as TIO import qualified IHP.Log.Types as Log import qualified IHP.Log as Log import qualified IHP.EnvVar as EnvVar data TelemetryInfo = TelemetryInfo { ihpVersion :: !Text , os :: !Text , arch :: !Text , projectId :: !Text } deriving (Eq, Show) -- | Reports telemetry info to the IHP Telemetry server -- -- This can be disabled by setting the env var IHP_TELEMETRY_DISABLED=1 reportTelemetry :: (?context :: context, Log.LoggingProvider context) => IO () reportTelemetry = do isDisabled <- EnvVar.envOrDefault "IHP_TELEMETRY_DISABLED" False unless isDisabled do payload <- toPayload <$> getTelemetryInfo Log.info (tshow payload) result <- Exception.try (Wreq.post "https://ihp-telemetry.digitallyinduced.com/CreateEvent" payload) case result of Left (e :: IOException) -> Log.warn ("Telemetry failed: " <> show e) Right _ -> Log.info ("IHP Telemetry is activated. This can be disabled by setting env variable IHP_TELEMETRY_DISABLED=1" :: Text) getTelemetryInfo :: IO TelemetryInfo getTelemetryInfo = do projectId <- getProjectId iswin <- isWindows `catch` \(_ :: IOException) -> pure False let opsys | System.os == "linux" && iswin = "linux (WSL)" | otherwise = System.os pure TelemetryInfo { ihpVersion = Version.ihpVersion, os = cs opsys, arch = cs System.arch, projectId } -- this seems to be the generally accepted way of detecting running under the Windows Subsystem for Linux isWindows :: IO Bool isWindows = do p <- TIO.readFile "/proc/version" pure $ T.isInfixOf "microsoft" (T.toLower p) -- WSL1 is Microsoft, WSL2 is microsoft -- | The project id is a an anonymous identifier to keep track of distinct projects. -- -- The project id is a hash of the current working directory. We use sha512 to make sure no one -- is able to get back the original path from the hash. getProjectId :: IO Text getProjectId = do cwd <- Directory.getCurrentDirectory cwd |> cs |> SHA512.hash |> Base16.encode |> cs |> pure -- | Transforms a telemetry info into a payload to be used with the telemetry request toPayload :: TelemetryInfo -> [(ByteString, ByteString)] toPayload TelemetryInfo { .. } = [("ihpVersion", cs ihpVersion), ("os", cs os), ("arch", cs arch), ("projectId", cs projectId)]