packages feed

opentelemetry-lightstep 0.2.0 → 0.3.0

raw patch · 9 files changed

+140/−323 lines, 9 filesdep +clockdep +containersdep +filepathdep ~basedep ~opentelemetrynew-component:exe:eventlog-to-lightstepPVP ok

version bump matches the API change (PVP)

Dependencies added: clock, containers, filepath, ghc-events, opentelemetry-extra, splitmix

Dependency ranges changed: base, opentelemetry

API changes (from Hackage documentation)

- OpenTelemetry.LightStep: withEnvConfigLightStepOpenTelemetry :: (MonadIO m, MonadMask m) => m a -> m a
- OpenTelemetry.LightStep.Config: LightStepConfig :: HostName -> PortNumber -> Text -> Text -> [(Text, Text)] -> Word -> Word -> LightStepConfig
- OpenTelemetry.LightStep.Config: [lsGlobalTags] :: LightStepConfig -> [(Text, Text)]
- OpenTelemetry.LightStep.Config: [lsGracefulShutdownTimeoutSeconds] :: LightStepConfig -> Word
- OpenTelemetry.LightStep.Config: [lsHostName] :: LightStepConfig -> HostName
- OpenTelemetry.LightStep.Config: [lsPort] :: LightStepConfig -> PortNumber
- OpenTelemetry.LightStep.Config: [lsServiceName] :: LightStepConfig -> Text
- OpenTelemetry.LightStep.Config: [lsSpanQueueSize] :: LightStepConfig -> Word
- OpenTelemetry.LightStep.Config: [lsToken] :: LightStepConfig -> Text
- OpenTelemetry.LightStep.Config: data LightStepConfig
- OpenTelemetry.LightStep.Config: getEnvConfig :: MonadIO m => m (Maybe LightStepConfig)
- OpenTelemetry.LightStep.Config: getEnvTagsWithPrefix :: Text -> IO [(Text, Text)]
- OpenTelemetry.LightStep.Config: lookupOneOfEnvs :: [String] -> IO (Maybe String)
- OpenTelemetry.LightStep.ZipkinExporter: LightStepClient :: LightStepConfig -> Async () -> TBQueue Span -> TVar Bool -> LightStepClient
- OpenTelemetry.LightStep.ZipkinExporter: ZipkinSpan :: LightStepConfig -> Span -> ZipkinSpan
- OpenTelemetry.LightStep.ZipkinExporter: [lscConfig] :: LightStepClient -> LightStepConfig
- OpenTelemetry.LightStep.ZipkinExporter: [lscSenderQueue] :: LightStepClient -> TBQueue Span
- OpenTelemetry.LightStep.ZipkinExporter: [lscSenderThread] :: LightStepClient -> Async ()
- OpenTelemetry.LightStep.ZipkinExporter: [lscShutdownVar] :: LightStepClient -> TVar Bool
- OpenTelemetry.LightStep.ZipkinExporter: [zsConfig] :: ZipkinSpan -> LightStepConfig
- OpenTelemetry.LightStep.ZipkinExporter: [zsSpan] :: ZipkinSpan -> Span
- OpenTelemetry.LightStep.ZipkinExporter: createLightStepSpanExporter :: MonadIO m => LightStepConfig -> m (Exporter Span)
- OpenTelemetry.LightStep.ZipkinExporter: data LightStepClient
- OpenTelemetry.LightStep.ZipkinExporter: data ZipkinSpan
- OpenTelemetry.LightStep.ZipkinExporter: droppedSpanCountVar :: TVar Int
- OpenTelemetry.LightStep.ZipkinExporter: instance Data.Aeson.Types.ToJSON.ToJSON OpenTelemetry.LightStep.ZipkinExporter.ZipkinSpan
- OpenTelemetry.LightStep.ZipkinExporter: mkClient :: LightStepConfig -> IO LightStepClient
- OpenTelemetry.LightStep.ZipkinExporter: rejectedSpanCountVar :: TVar Int
- OpenTelemetry.LightStep.ZipkinExporter: reportSpans :: String -> Manager -> LightStepConfig -> [Span] -> IO ()
- OpenTelemetry.LightStep.ZipkinExporter: reportedSpanCountVar :: TVar Int
- OpenTelemetry.LightStep.ZipkinExporter: tagValue2text :: TagValue -> Text
+ OpenTelemetry.Lightstep.Config: LightstepConfig :: HostName -> PortNumber -> Text -> Text -> [(Text, Text)] -> Word -> Word -> LightstepConfig
+ OpenTelemetry.Lightstep.Config: [lsGlobalTags] :: LightstepConfig -> [(Text, Text)]
+ OpenTelemetry.Lightstep.Config: [lsGracefulShutdownTimeoutSeconds] :: LightstepConfig -> Word
+ OpenTelemetry.Lightstep.Config: [lsHostName] :: LightstepConfig -> HostName
+ OpenTelemetry.Lightstep.Config: [lsPort] :: LightstepConfig -> PortNumber
+ OpenTelemetry.Lightstep.Config: [lsServiceName] :: LightstepConfig -> Text
+ OpenTelemetry.Lightstep.Config: [lsSpanQueueSize] :: LightstepConfig -> Word
+ OpenTelemetry.Lightstep.Config: [lsToken] :: LightstepConfig -> Text
+ OpenTelemetry.Lightstep.Config: data LightstepConfig
+ OpenTelemetry.Lightstep.Config: getEnvConfig :: MonadIO m => m (Maybe LightstepConfig)
+ OpenTelemetry.Lightstep.Config: getEnvTagsWithPrefix :: Text -> IO [(Text, Text)]
+ OpenTelemetry.Lightstep.Config: lookupOneOfEnvs :: [String] -> IO (Maybe String)
+ OpenTelemetry.Lightstep.Exporter: createLightstepSpanExporter :: MonadIO m => LightstepConfig -> m (Exporter Span)

Files

+ exe/eventlog-to-lightstep/Main.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings #-}++import qualified Data.Text as T+import OpenTelemetry.EventlogStreaming_Internal+import OpenTelemetry.Exporter+import OpenTelemetry.Lightstep.Config+import OpenTelemetry.Lightstep.Exporter+import System.Clock+import System.Environment (getArgs)+import System.FilePath+import System.IO+import Text.Printf++main :: IO ()+main = do+  args <- getArgs+  case args of+    ["read", path] -> do+      printf "Sending %s to Lightstep...\n" path+      Just lsConfig <- getEnvConfig+      let service_name = T.pack $ takeBaseName path+      exporter <- createLightstepSpanExporter lsConfig {lsServiceName = service_name}+      origin_timestamp <- fromIntegral . toNanoSecs <$> getTime Realtime+      withFile path ReadMode (work origin_timestamp exporter)+      shutdown exporter+      putStrLn "\nAll done.\n"+    _ -> do+      putStrLn "Usage:"+      putStrLn ""+      putStrLn "  eventlog-to-lightstep read <program.eventlog>"
− just-some-usage-code-that-must-compile/Main.hs
@@ -1,2 +0,0 @@-main :: IO ()-main = putStrLn "All done"
− just-some-usage-code-that-must-compile/SomeUsageOfImplicitApi.hs
@@ -1,56 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}--module SomeUsageOfImplicitApi where--import Control.Concurrent-import Control.Concurrent.Async-import Control.Monad-import OpenTelemetry.Common-import OpenTelemetry.FileExporter-import OpenTelemetry.Implicit--main :: IO ()-main = do-  exporter <- createFileSpanExporter "helloworld.trace"-  let otConfig =-        OpenTelemetryConfig-          { otcSpanExporter = exporter-          }-  withOpenTelemetry otConfig $ do-    result <- pieceOfSeriousBusinessLogic 42-    print result--pieceOfSeriousBusinessLogic :: Int -> IO Int-pieceOfSeriousBusinessLogic input = withSpan "serious business" $ do-  let result = 2 * input-  -- setTag is value-polymorphic--  -- setTag "input" input -- Int (inferred)-  -- setTag @Int "result" result -- Int (explicit)-  -- setTag @String "seriousness" "serious" -- literals are polymorphic under OverloadedStrings so we need to annotate-  -- setTag @Double "profit" 99 -- numeric literals are also polymorphic-  -- setTag "error" False -- Bool literals are not polymorphic-  -- setTag @Int "largest integer below 100" 99 -- Int (inferred)--  -- TODO: JSON values--  addEvent "log" "rpc roundtrip begin"-  withSpan "leveraging synergies" $ do-    threadDelay 10000-    addEvent "log" "enough synergies leveraged"-  addEvent "log" "All your base are belong to us"-  addEvent "log" "rpc roundtrip end"-  withSpan "project" $ do-    -- Connecting spans across threads requires some manual plumbing-    Just sp <- getCurrentActiveSpan-    asyncWork <- async $ withChildSpanOf sp "data science" $ do-      threadDelay 1000000-      pure (42 :: Int)-    -- Doing a withSpan inside a loop is fine-    forM_ [input .. input + 10] $ \i -> withSpan "sprint" $ do-      -- setTag "week" i-      threadDelay (10000 + i)-    _ <- wait asyncWork-    pure ()-  pure result
opentelemetry-lightstep.cabal view
@@ -1,8 +1,8 @@ cabal-version:       2.4 name:                opentelemetry-lightstep-description:         The OpenTelemetry Haskell Client (LightStep exporter) https://opentelemetry.io+description:         The OpenTelemetry Haskell Client (Lightstep exporter) https://opentelemetry.io category:            OpenTelemetry-version:             0.2.0+version: 0.3.0 license-file:        LICENSE license:             Apache-2.0 author:              Dmitry Ivanov@@ -37,7 +37,6 @@     -Wincomplete-record-updates     -Wincomplete-uni-patterns     -Wpartial-fields-    -Wredundant-constraints     -fhide-source-paths     -ferror-spans     -freverse-errors@@ -54,26 +53,40 @@     http-client-tls,     http-types,     network,-    opentelemetry,+    opentelemetry >= 0.3.0,+    opentelemetry-extra,     scientific,     stm,     text,     unordered-containers   hs-source-dirs: src   exposed-modules:-    OpenTelemetry.LightStep-    OpenTelemetry.LightStep.Config-    OpenTelemetry.LightStep.ZipkinExporter+    OpenTelemetry.Lightstep.Config+    OpenTelemetry.Lightstep.Exporter -test-suite just-some-usage-code-that-must-compile+executable eventlog-to-lightstep   import: options-  type:       exitcode-stdio-1.0-  main-is:    Main.hs-  other-modules:-    SomeUsageOfImplicitApi-  hs-source-dirs: just-some-usage-code-that-must-compile+  ghc-options:+    -threaded+    -rtsopts+    -eventlog   build-depends:-    base,+    base >= 4.12 && < 5,     async,-    opentelemetry,-    opentelemetry-lightstep+    bytestring,+    clock,+    containers,+    exceptions,+    filepath,+    ghc-events >= 0.13.0,+    http-client,+    http-types,+    opentelemetry >= 0.3.0,+    opentelemetry-extra,+    opentelemetry-lightstep,+    splitmix,+    text,+    unordered-containers+  main-is: Main.hs+  hs-source-dirs: exe/eventlog-to-lightstep+
− src/OpenTelemetry/LightStep.hs
@@ -1,21 +0,0 @@-module OpenTelemetry.LightStep where--import Control.Monad.Catch-import Control.Monad.IO.Class-import OpenTelemetry.Common-import OpenTelemetry.Implicit-import OpenTelemetry.LightStep.Config-import OpenTelemetry.LightStep.ZipkinExporter-import System.IO--withEnvConfigLightStepOpenTelemetry :: (MonadIO m, MonadMask m) => m a -> m a-withEnvConfigLightStepOpenTelemetry action = do-  mcfg <- getEnvConfig-  case mcfg of-    Just cfg -> do-      exporter <- createLightStepSpanExporter cfg-      let otelConfig = OpenTelemetryConfig {otcSpanExporter = exporter}-      withOpenTelemetry otelConfig action-    Nothing -> do-      liftIO $ hPutStrLn stderr "Warning: LightStep exporter is not configured"-      action
− src/OpenTelemetry/LightStep/Config.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module OpenTelemetry.LightStep.Config where--import Control.Monad.IO.Class-import Data.Foldable-import Data.Maybe-import qualified Data.Text as T-import Network.Socket-import System.Environment-import System.IO--data LightStepConfig-  = LightStepConfig-      { lsHostName :: HostName,-        lsPort :: PortNumber,-        lsToken :: T.Text,-        lsServiceName :: T.Text,-        lsGlobalTags :: [(T.Text, T.Text)],-        lsGracefulShutdownTimeoutSeconds :: Word,-        lsSpanQueueSize :: Word-      }--lookupOneOfEnvs :: [String] -> IO (Maybe String)-lookupOneOfEnvs names = asum <$> traverse lookupEnv names--getEnvTagsWithPrefix :: T.Text -> IO [(T.Text, T.Text)]-getEnvTagsWithPrefix prefix =-  mapMaybe unprefix <$> getEnvironment-  where-    unprefix ((T.stripPrefix prefix . T.pack) -> Just k, v) = Just (k, T.pack v)-    unprefix _ = Nothing--getEnvConfig :: MonadIO m => m (Maybe LightStepConfig)-getEnvConfig = liftIO $ do-  prog_name <- getProgName-  maybe_token_from_env <- lookupOneOfEnvs ["LIGHTSTEP_TOKEN", "LIGHTSTEP_ACCESS_TOKEN", "OPENTRACING_LIGHTSTEP_ACCESS_TOKEN"]-  global_tags <- getEnvTagsWithPrefix "OPENTRACING_TAG_"-  case maybe_token_from_env of-    Just t -> do-      host <- fromMaybe "ingest.lightstep.com" <$> lookupOneOfEnvs ["LIGHTSTEP_HOST", "OPENTRACING_LIGHTSTEP_COLLECTOR_HOST"]-      port <- maybe 443 read <$> lookupOneOfEnvs ["LIGHTSTEP_PORT", "OPENTRACING_LIGHTSTEP_COLLECTOR_PORT"]-      service <- fromMaybe prog_name <$> lookupOneOfEnvs ["LIGHTSTEP_SERVICE", "OPENTRACING_LIGHTSTEP_COMPONENT_NAME"]-      pure $ Just $ LightStepConfig host port (T.pack t) (T.pack service) global_tags 5 4096-    Nothing -> do-      hPutStrLn stderr "LIGHTSTEP_ACCESS_TOKEN environment variable not defined"-      pure Nothing
− src/OpenTelemetry/LightStep/ZipkinExporter.hs
@@ -1,181 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module OpenTelemetry.LightStep.ZipkinExporter where---- Zipkin V2 protocol spec: https://github.com/openzipkin/zipkin-api/blob/master/zipkin2-api.yaml--import Control.Concurrent.Async-import Control.Concurrent.STM-import Control.Monad.IO.Class-import Data.Aeson-import qualified Data.HashMap.Strict as HM-import Data.Scientific-import qualified Data.Text as T-import Network.HTTP.Client-import Network.HTTP.Client.TLS-import Network.HTTP.Types-import OpenTelemetry.Common-import OpenTelemetry.Debug-import OpenTelemetry.LightStep.Config-import System.IO.Unsafe-import Text.Printf--data ZipkinSpan-  = ZipkinSpan-      { zsConfig :: LightStepConfig,-        zsSpan :: Span-      }--tagValue2text :: TagValue -> T.Text-tagValue2text tv = case tv of-  (StringTagValue s) -> s-  (BoolTagValue b) -> if b then "true" else "false"-  (IntTagValue i) -> T.pack $ show i-  (DoubleTagValue d) -> T.pack $ show (fromFloatDigits d)--instance ToJSON ZipkinSpan where--  -- FIXME(divanov): deduplicate-  toJSON (ZipkinSpan LightStepConfig {..} s@(Span {..})) =-    let TId tid = spanTraceId s-        SId sid = spanId s-        ts = spanStartedAt `div` 1000-        duration = (spanFinishedAt - spanStartedAt) `div` 1000-     in object $-          [ "name" .= spanOperation,-            "traceId" .= T.pack (printf "%016x" tid),-            "id" .= T.pack (printf "%016x" sid),-            "timestamp" .= ts,-            "duration" .= duration,-            "tags"-              .= object-                ( [ "lightstep.access_token" .= lsToken,-                    "lightstep.component_name" .= lsServiceName-                  ]-                    <> [k .= v | (k, v) <- lsGlobalTags]-                    <> [k .= tagValue2text v | (k, v) <- HM.toList spanTags]-                ),-            "annotations"-              .= [ object ["timestamp" .= (t `div` 1000), "value" .= v]-                   | SpanEvent t _ v <- spanEvents-                 ]-          ]-            <> (maybe [] (\(SId psid) -> ["parentId" .= psid]) spanParentId)--  toEncoding (ZipkinSpan LightStepConfig {..} s@(Span {..})) =-    let TId tid = spanTraceId s-        SId sid = spanId s-        ts = spanStartedAt `div` 1000-        duration = (spanFinishedAt - spanStartedAt) `div` 1000-     in pairs-          ( "name" .= spanOperation-              <> "traceId" .= T.pack (printf "%016x" tid)-              <> "id" .= T.pack (printf "%016x" sid)-              <> "timestamp" .= ts-              <> "duration" .= duration-              <> "tags"-                .= object-                  ( [ "lightstep.access_token" .= lsToken,-                      "lightstep.component_name" .= lsServiceName-                    ]-                      <> [k .= v | (k, v) <- lsGlobalTags]-                      <> [k .= tagValue2text v | (k, v) <- HM.toList spanTags]-                  )-              <> ( maybe-                     mempty-                     (\(SId psid) -> "parentId" .= T.pack (printf "%016x" psid))-                     spanParentId-                 )-              <> "annotations"-                .= [ object ["timestamp" .= (t `div` 1000), "value" .= v]-                     | SpanEvent t _ v <- spanEvents-                   ]-          )--data LightStepClient-  = LightStepClient-      { lscConfig :: LightStepConfig,-        lscSenderThread :: Async (),-        lscSenderQueue :: TBQueue Span,-        lscShutdownVar :: TVar Bool-      }--createLightStepSpanExporter :: MonadIO m => LightStepConfig -> m (Exporter Span)-createLightStepSpanExporter cfg = liftIO do-  client <- mkClient cfg-  pure-    $! Exporter-      ( \sps -> do-          let q = lscSenderQueue client-          atomically $ do-            q_population <- fromIntegral <$> lengthTBQueue q-            let q_vacancy = fromIntegral (lsSpanQueueSize (lscConfig client) - q_population)-            modifyTVar droppedSpanCountVar (\x -> x + length sps - q_vacancy)-            mapM_-              (writeTBQueue q)-              (take q_vacancy sps)-          pure ExportSuccess-      )-      ( do-          atomically $-            writeTVar (lscShutdownVar client) True-          wait (lscSenderThread client)-      )--mkClient :: LightStepConfig -> IO LightStepClient-mkClient cfg@(LightStepConfig {..}) = do-  let endpoint = printf "https://%s:%d/api/v2/spans" lsHostName (fromIntegral lsPort :: Int)-  manager <- newManager tlsManagerSettings-  q <- newTBQueueIO (fromIntegral lsSpanQueueSize)-  shutdown_var <- newTVarIO False-  sender <- async $ do-    let loop = do-          (must_shutdown, sps) <- atomically $ do-            must_shutdown <- readTVar shutdown_var-            sps <- flushTBQueue q-            case (must_shutdown, sps) of-              (False, []) -> retry-              _ -> pure (must_shutdown, sps)-          case sps of-            [] -> pure ()-            _ -> reportSpans endpoint manager cfg sps-          dd_ "must_shutdown" must_shutdown-          case must_shutdown of-            True -> pure ()-            False -> loop-    loop-  pure $! LightStepClient cfg sender q shutdown_var--reportSpans :: String -> Manager -> LightStepConfig -> [Span] -> IO ()-reportSpans endpoint httpManager cfg sps = do-  dd_ "reportSpans" sps-  let body = encode (map (ZipkinSpan cfg) sps)-      request =-        (parseRequest_ endpoint)-          { method = "POST",-            requestBody = RequestBodyLBS body,-            requestHeaders = [("Content-Type", "application/json")]-          }-  resp <- httpLbs request httpManager-  case statusCode (responseStatus resp) of-    200 -> do-      inc 1 reportedSpanCountVar-      pure ()-    _ -> do-      -- TODO(divanov): handle failures-      inc 1 rejectedSpanCountVar-      dd_ "body" body-      dd_ "resp status" $ responseStatus resp-      dd_ "resp" $ responseBody resp--droppedSpanCountVar :: TVar Int-droppedSpanCountVar = unsafePerformIO $ newTVarIO 0-{-# NOINLINE droppedSpanCountVar #-}--reportedSpanCountVar :: TVar Int-reportedSpanCountVar = unsafePerformIO $ newTVarIO 0-{-# NOINLINE reportedSpanCountVar #-}--rejectedSpanCountVar :: TVar Int-rejectedSpanCountVar = unsafePerformIO $ newTVarIO 0-{-# NOINLINE rejectedSpanCountVar #-}
+ src/OpenTelemetry/Lightstep/Config.hs view
@@ -0,0 +1,46 @@+{-# LANGUAGE OverloadedStrings #-}++module OpenTelemetry.Lightstep.Config where++import Control.Monad.IO.Class+import Data.Foldable+import Data.Maybe+import qualified Data.Text as T+import Network.Socket+import System.Environment+import System.IO++data LightstepConfig = LightstepConfig+  { lsHostName :: HostName,+    lsPort :: PortNumber,+    lsToken :: T.Text,+    lsServiceName :: T.Text,+    lsGlobalTags :: [(T.Text, T.Text)],+    lsGracefulShutdownTimeoutSeconds :: Word,+    lsSpanQueueSize :: Word+  }++lookupOneOfEnvs :: [String] -> IO (Maybe String)+lookupOneOfEnvs names = asum <$> traverse lookupEnv names++getEnvTagsWithPrefix :: T.Text -> IO [(T.Text, T.Text)]+getEnvTagsWithPrefix prefix =+  mapMaybe unprefix <$> getEnvironment+  where+    unprefix ((T.stripPrefix prefix . T.pack) -> Just k, v) = Just (k, T.pack v)+    unprefix _ = Nothing++getEnvConfig :: MonadIO m => m (Maybe LightstepConfig)+getEnvConfig = liftIO $ do+  prog_name <- getProgName+  maybe_token_from_env <- lookupOneOfEnvs ["LIGHTSTEP_TOKEN", "LIGHTSTEP_ACCESS_TOKEN", "OPENTRACING_LIGHTSTEP_ACCESS_TOKEN"]+  global_tags <- getEnvTagsWithPrefix "OPENTRACING_TAG_"+  case maybe_token_from_env of+    Just t -> do+      host <- fromMaybe "ingest.lightstep.com" <$> lookupOneOfEnvs ["LIGHTSTEP_HOST", "OPENTRACING_LIGHTSTEP_COLLECTOR_HOST"]+      port <- maybe 443 read <$> lookupOneOfEnvs ["LIGHTSTEP_PORT", "OPENTRACING_LIGHTSTEP_COLLECTOR_PORT"]+      service <- fromMaybe prog_name <$> lookupOneOfEnvs ["LIGHTSTEP_SERVICE", "OPENTRACING_LIGHTSTEP_COMPONENT_NAME"]+      pure $ Just $ LightstepConfig host port (T.pack t) (T.pack service) global_tags 5 4096+    Nothing -> do+      hPutStrLn stderr "LIGHTSTEP_ACCESS_TOKEN environment variable not defined"+      pure Nothing
+ src/OpenTelemetry/Lightstep/Exporter.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}++module OpenTelemetry.Lightstep.Exporter where++import Control.Concurrent.Async+import Control.Concurrent.STM+import Control.Monad.IO.Class+import Data.Aeson+import qualified Data.HashMap.Strict as HM+import Data.Scientific+import qualified Data.Text as T+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Network.HTTP.Types+import OpenTelemetry.Common+import OpenTelemetry.Exporter+import OpenTelemetry.Lightstep.Config+import OpenTelemetry.SpanContext+import OpenTelemetry.ZipkinExporter+import Text.Printf++createLightstepSpanExporter :: MonadIO m => LightstepConfig -> m (Exporter Span)+createLightstepSpanExporter cfg@(LightstepConfig {..}) = liftIO do+  let zcfg =+        ZipkinConfig+          { zEndpoint = printf "https://%s:%d/api/v2/spans" lsHostName (fromIntegral lsPort :: Int),+            zServiceName = lsServiceName,+            zGlobalTags =+              [ ("lightstep.component", lsServiceName),+                ("lightstep.access_token", lsToken)+              ],+            zSpanQueueSize = lsSpanQueueSize,+            zGracefulShutdownTimeoutSeconds = lsGracefulShutdownTimeoutSeconds+          }+  createZipkinSpanExporter zcfg