-
-
Notifications
You must be signed in to change notification settings - Fork 37
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 all 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
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) | ||
|
||
|
||
-- | 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 comment
The 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
when (useStableHttpSemantics options) useStableAttributes
it would be easy to forget to check bothuseStable
anduseOld
because the compiler does not enforce it. This way, the compiler will say "missing cases in pattern match".