{-# LANGUAGE OverloadedStrings #-} module Main where import Control.Concurrent (threadDelay) import Control.Monad (when) import Instana.SDK.SDK (InstanaContext) import qualified Instana.SDK.SDK as InstanaSDK import System.Environment (lookupEnv) import System.IO (Handle, stdout) import System.Log.Formatter import System.Log.Handler (setFormatter) import System.Log.Handler.Simple (GenericHandler, fileHandler, streamHandler) import System.Log.Logger (Priority (..), rootLoggerName, setHandlers, setLevel, updateGlobalLogger) import System.Log.Logger (infoM) import Text.Read (readMaybe) appLogger :: String appLogger = "ExampleApp" main :: IO () main = do initLogging -- Example snippet for using the Instana SDK and relying only on environment -- variables/default values for the configuration. InstanaSDK.withInstana runApp -- Example snippet for using the Instana SDK and providing a configuration -- (agent host, port, ...) directly in code (falling back to environment -- variables and then to default values). You only need to specify the -- configuration values you are interested in and can omit everything else -- (see https://www.yesodweb.com/book/settings-types). -- -- let -- config = -- InstanaSDK.defaultConfig -- { InstanaSDK.agentHost = Just "127.0.0.1" -- , InstanaSDK.agentPort = Just 42699 -- , InstanaSDK.forceTransmissionAfter = Just 1000 -- , InstanaSDK.forceTransmissionStartingAt = Just 500 -- , InstanaSDK.maxBufferedSpans = Just 1000 -- } -- InstanaSDK.withConfiguredInstana config runApp runApp :: InstanaContext -> IO () runApp instana = do startKeepAliveLoop instana data KeepAlive = No | Indefinitely | Seconds Int {-| Keeps the process alive by scheduling an endless loop. The intention is that the agent has a chance to find the announcing process in the OS' process list. -} startKeepAliveLoop :: InstanaContext -> IO () startKeepAliveLoop instana = do keepAliveSetting <- lookupEnv "KEEP_ALIVE" let keepAliveMax = if keepAliveSetting == Just "false" then No else case keepAliveSetting of Nothing -> Indefinitely Just envVal -> case readMaybe envVal of Nothing -> Indefinitely Just parsedInt -> Seconds parsedInt case keepAliveMax of No -> do -- keep alive disabled, return no op to terminate immediately infoM appLogger "KEEP_ALIVE expicitly disabled, terminating." return () Seconds s -> do infoM appLogger $ "Keeping process alive for " ++ show s ++ " seconds." keepAliveLoop instana (s * 10) 1 Indefinitely -> do infoM appLogger $ "Keeping process alive indefinitely " ++ "(KEEP_ALIVE not explicitly specified or not parseable)." keepAliveLoop instana (-1) 1 keepAliveLoop :: InstanaContext -> Int -> Int -> IO () keepAliveLoop instana maxIterations counter = do when (counter `mod` 20 == 0) $ do createSpansUsingLowLevelApi instana when (counter + 10 `mod` 20 == 0) $ do createSpansUsingBracketApi instana when (counter `mod` 100 == 0) $ do let maxStr = if maxIterations < 0 then "" else ("/" ++ (show (maxIterations `div` 10))) infoM appLogger $ "waiting [" ++ show (counter `div` 10) ++ maxStr ++ "]" -- delay by 100 ms threadDelay $ 100 * 1000 if (maxIterations >= 0 && counter >= maxIterations) then do infoM appLogger $ "waited for " ++ (show (maxIterations `div` 10)) ++ " seconds, now terminating" return () else do keepAliveLoop instana maxIterations (counter + 1) createSpansUsingBracketApi :: InstanaContext -> IO () createSpansUsingBracketApi instana = do InstanaSDK.withRootEntry instana "haskell.dummy.root.entry" (simulateExitCall instana) return () simulateExitCall :: InstanaContext -> IO () simulateExitCall instana = InstanaSDK.withExit instana "haskell.dummy.exit" (putStrLn "Here be dragons!") createSpansUsingLowLevelApi :: InstanaContext -> IO () createSpansUsingLowLevelApi instana = do -- Start an entry span (you would need to check if a trace ID and parent -- span ID are already available, for example from HTTP headers of an -- incoming request, and, if so, pass them on using InstanaSDK.startEntry -- instead of InstanaSDK.startRootEntry InstanaSDK.startRootEntry instana "haskell.dummy.root.entry" InstanaSDK.startExit instana "haskell.dummy.exit" -- Now a real app would execute some exit call, say a DB call or an -- HTTP call. -- mark the exit call as completed InstanaSDK.completeExit instana -- mark the entry as completed and return InstanaSDK.completeEntry instana return () initLogging :: IO () initLogging = do updateGlobalLogger appLogger $ setLevel INFO appFileHandler <- fileHandler "example-app.log" INFO appStreamHandler <- streamHandler stdout INFO let formattedAppFileHandler = withFormatter appFileHandler formattedAppStreamHandler = withFormatter appStreamHandler updateGlobalLogger appLogger $ setHandlers [ formattedAppFileHandler ] updateGlobalLogger rootLoggerName $ setHandlers [ formattedAppStreamHandler ] withFormatter :: GenericHandler Handle -> GenericHandler Handle withFormatter handler = setFormatter handler formatter where formatter = simpleLogFormatter "{$time $loggername $prio} $msg"