-
-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update HTTP Semantic Conventions to Stable Versions #118
Changes from 42 commits
5330fa4
5e1bab4
178e3b8
b4bff14
b18b623
e5af40c
9309250
64c1331
39ff844
f9e987c
3e853aa
a9520aa
77538ca
17d2e22
32c68be
b28866f
7d92f10
5200465
1a1aba1
29bf4d7
c2ea53f
6b1baaa
524faea
6da0bef
8a11e96
8b6f9f3
e76b118
3ca8531
4669ae3
25d1cf5
d8771ed
76f062e
5542954
234e43e
266b974
73c5dca
591ac8c
77b4c5f
e6b7fc4
e0179a4
ac5546f
9523dad
36fd483
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto-generated changes. Maybe |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module OpenTelemetry.SemanticsConfig ( | ||
SemanticsOptions (httpOption), | ||
HttpOption (..), | ||
getSemanticsOptions, | ||
getSemanticsOptions', | ||
) where | ||
|
||
import Control.Exception.Safe (throwIO, tryAny) | ||
import Data.IORef (newIORef, readIORef, writeIORef) | ||
import qualified Data.Text as T | ||
import System.Environment (lookupEnv) | ||
import System.IO.Unsafe (unsafePerformIO) | ||
|
||
|
||
{- | This is a record that contains options for whether the new stable semantics conventions should be emitted. | ||
Semantics conventions that have been declared stable: | ||
- [http](https://opentelemetry.io/blog/2023/http-conventions-declared-stable/#migration-plan) | ||
-} | ||
data SemanticsOptions = SemanticsOptions {httpOption :: HttpOption} | ||
|
||
|
||
-- | This option determines whether stable, old, or both kinds of http attributes are emitted. | ||
data HttpOption | ||
= Stable | ||
| StableAndOld | ||
| Old | ||
deriving (Show, Eq) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used this instead of set membership because I did not like that if the pattern was to use |
||
|
||
-- | These are the default values emitted if OTEL_SEM_CONV_STABILITY_OPT_IN is unset or does not contain values for a specific category of option. | ||
defaultOptions :: SemanticsOptions | ||
defaultOptions = SemanticsOptions {httpOption = Old} | ||
|
||
|
||
-- | Detects the presence of "http/dup" or "http" in OTEL_SEMCONV_STABILITY_OPT_IN or uses the default option if they are not there. | ||
parseHttpOption :: (Foldable t) => t T.Text -> HttpOption | ||
parseHttpOption envs | ||
| "http/dup" `elem` envs = StableAndOld | ||
| "http" `elem` envs = Stable | ||
| otherwise = httpOption defaultOptions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||
|
||
|
||
-- | Detects the presence of semantics options in OTEL_SEMCONV_STABILITY_OPT_IN or uses the defaultOptions if they are not present. | ||
parseSemanticsOptions :: Maybe String -> SemanticsOptions | ||
parseSemanticsOptions Nothing = defaultOptions | ||
parseSemanticsOptions (Just env) = SemanticsOptions {..} | ||
where | ||
envs = fmap T.strip $ T.splitOn "," $ T.pack env | ||
httpOption = parseHttpOption envs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The compiler will give a warning if one of the fields is unset. |
||
|
||
|
||
{- | Version of getSemanticsOptions that is not memoized. It is recommended to use getSemanticsOptions for efficiency purposes | ||
unless it is necessary to retrieve the value of OTEL_SEMCONV_STABILITY_OPT_IN every time getSemanticsOptions' is called. | ||
-} | ||
getSemanticsOptions' :: IO SemanticsOptions | ||
getSemanticsOptions' = parseSemanticsOptions <$> lookupEnv "OTEL_SEMCONV_STABILITY_OPT_IN" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used |
||
|
||
|
||
{- | Create a new memoized IO action using an 'IORef' under the surface. Note that | ||
the action may be run in multiple threads simultaneously, so this may not be | ||
thread safe (depending on the underlying action). For the sake of reading an environment | ||
variable and parsing some stuff, we don't have to be concerned about thread-safety. | ||
-} | ||
memoize :: IO a -> IO (IO a) | ||
memoize action = do | ||
ref <- newIORef Nothing | ||
pure $ do | ||
mres <- readIORef ref | ||
res <- case mres of | ||
Just res -> pure res | ||
Nothing -> do | ||
res <- tryAny action | ||
writeIORef ref $ Just res | ||
pure res | ||
either throwIO pure res | ||
|
||
|
||
{- | Retrieves OTEL_SEMCONV_STABILITY_OPT_IN and parses it into SemanticsOptions. | ||
|
||
This uses the [global IORef trick](https://www.parsonsmatt.org/2021/04/21/global_ioref_in_template_haskell.html) | ||
to memoize the settings for efficiency. Note that getSemanticsOptions stores and returns the | ||
value of the first time it was called and will not change when OTEL_SEMCONV_STABILITY_OPT_IN | ||
is updated. Use getSemanticsOptions' to read OTEL_SEMCONV_STABILITY_OPT_IN every time the | ||
function is called. | ||
-} | ||
getSemanticsOptions :: IO SemanticsOptions | ||
getSemanticsOptions = unsafePerformIO $ memoize getSemanticsOptions' | ||
{-# NOINLINE getSemanticsOptions #-} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
module OpenTelemetry.SemanticsConfigSpec where | ||
|
||
import OpenTelemetry.SemanticsConfig | ||
import System.Environment | ||
import Test.Hspec | ||
|
||
|
||
envVarName :: String | ||
envVarName = "OTEL_SEMCONV_STABILITY_OPT_IN" | ||
|
||
|
||
spec :: Spec | ||
spec = do | ||
describe "SemanticsConfig" $ do | ||
describe "HttpOption" $ do | ||
it "defaults to 'Old' when env var has no value" $ do | ||
unsetEnv envVarName | ||
semanticsOptions <- getSemanticsOptions' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uses |
||
httpOption semanticsOptions `shouldBe` Old | ||
mapM_ | ||
( \(envVarVal, expectedVal) -> | ||
it ("returns " ++ show expectedVal ++ " when env var is " ++ show envVarVal) $ do | ||
setEnv envVarName envVarVal | ||
semanticsOptions <- getSemanticsOptions' | ||
httpOption semanticsOptions `shouldBe` expectedVal | ||
) | ||
[ ("http", Stable) | ||
, ("http/du", Old) -- intentionally similar to both "http/dup" and "http" | ||
, ("http/dup", StableAndOld) | ||
, ("http/dup,http", StableAndOld) | ||
, ("http,http/dup", StableAndOld) | ||
, ("http,something-random,http/dup", StableAndOld) | ||
] | ||
context "memoization" $ do | ||
it "works" $ do | ||
setEnv envVarName "http" | ||
semanticsOptions <- getSemanticsOptions | ||
httpOption semanticsOptions `shouldBe` Stable | ||
it ("does not change when " ++ envVarName ++ " changes") $ do | ||
setEnv envVarName "http" | ||
semanticsOptions <- getSemanticsOptions | ||
setEnv envVarName "http/dup" | ||
semanticsOptions <- getSemanticsOptions | ||
httpOption semanticsOptions `shouldBe` Stable -- and not StableAndOld because of memoization |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto-generated using the command There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file was generated using the command |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are all auto-generated changes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Auto-generated change |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,11 @@ | |
- Use internals to instrument a particular callsite using modifyRequest, modifyResponse (Next best) | ||
- Provide a middleware to pull from the thread-local state (okay) | ||
- Modify the global manager to pull from the thread-local state (least good, can't be helped sometimes) | ||
|
||
[New HTTP semantic conventions have been declared stable.](https://opentelemetry.io/blog/2023/http-conventions-declared-stable/#migration-plan) Opt-in by setting the environment variable OTEL_SEMCONV_STABILITY_OPT_IN to | ||
- "http" - to use the stable conventions | ||
- "http/dup" - to emit both the old and the stable conventions | ||
Otherwise, the old conventions will be used. The stable conventions will replace the old conventions in the next major release of this library. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This haddock blurb documents the change so that other programmers using the library can opt-in. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent |
||
-} | ||
module OpenTelemetry.Instrumentation.HttpClient ( | ||
withResponse, | ||
|
@@ -68,8 +73,8 @@ withResponse | |
-> (Client.Response Client.BodyReader -> m a) | ||
-> m a | ||
withResponse httpConf req man f = do | ||
t <- httpTracerProvider | ||
inSpan'' t "withResponse" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_wrSpan -> do | ||
tracer <- httpTracerProvider | ||
inSpan'' tracer "withResponse" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_wrSpan -> do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This variable was renamed for clarity. Interestingly, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the variable rename is reasonable. Haskellers can lean on the side of being terse and I think that partly stems from mathematics backgrounds, where single letter variable naming is common. |
||
ctxt <- getContext | ||
-- TODO would like to capture the req/resp time specifically | ||
-- inSpan "http.request" (defaultSpanArguments { startingKind = Client }) $ \httpReqSpan -> do | ||
|
@@ -88,8 +93,8 @@ withResponse httpConf req man f = do | |
-} | ||
httpLbs :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response L.ByteString) | ||
httpLbs httpConf req man = do | ||
t <- httpTracerProvider | ||
inSpan'' t "httpLbs" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
tracer <- httpTracerProvider | ||
inSpan'' tracer "httpLbs" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
ctxt <- getContext | ||
req' <- instrumentRequest httpConf ctxt req | ||
resp <- liftIO $ Client.httpLbs req' man | ||
|
@@ -102,8 +107,8 @@ httpLbs httpConf req man = do | |
-} | ||
httpNoBody :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response ()) | ||
httpNoBody httpConf req man = do | ||
t <- httpTracerProvider | ||
inSpan'' t "httpNoBody" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
tracer <- httpTracerProvider | ||
inSpan'' tracer "httpNoBody" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
ctxt <- getContext | ||
req' <- instrumentRequest httpConf ctxt req | ||
resp <- liftIO $ Client.httpNoBody req' man | ||
|
@@ -141,8 +146,8 @@ httpNoBody httpConf req man = do | |
-} | ||
responseOpen :: (MonadUnliftIO m, HasCallStack) => HttpClientInstrumentationConfig -> Client.Request -> Client.Manager -> m (Client.Response Client.BodyReader) | ||
responseOpen httpConf req man = do | ||
t <- httpTracerProvider | ||
inSpan'' t "responseOpen" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
tracer <- httpTracerProvider | ||
inSpan'' tracer "responseOpen" (addAttributesToSpanArguments callerAttributes spanArgs) $ \_ -> do | ||
ctxt <- getContext | ||
req' <- instrumentRequest httpConf ctxt req | ||
resp <- liftIO $ Client.responseOpen req' man | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are all auto-generated changes