packages feed

hs-opentelemetry-sdk-1.0.0.0: src/OpenTelemetry/Resource/FaaS/Detector.hs

{-# LANGUAGE OverloadedStrings #-}

{- |
Module      :  OpenTelemetry.Resource.FaaS.Detector
Copyright   :  (c) Ian Duncan, 2024
License     :  BSD-3
Description :  Auto-detect FaaS (serverless function) resource attributes
Maintainer  :  Ian Duncan
Stability   :  experimental
Portability :  non-portable (GHC extensions)

Detects FaaS resource attributes from environment variables set by
serverless platforms:

* __AWS Lambda__: @AWS_LAMBDA_FUNCTION_NAME@, @AWS_LAMBDA_FUNCTION_VERSION@,
  @AWS_LAMBDA_LOG_STREAM_NAME@, @AWS_LAMBDA_FUNCTION_MEMORY_SIZE@
* __GCP Cloud Functions__: @FUNCTION_TARGET@, @K_REVISION@, @FUNCTION_MEMORY_MB@
* __Azure Functions__: @FUNCTIONS_WORKER_RUNTIME@, @WEBSITE_SITE_NAME@

Returns 'Nothing' when not running in a recognized FaaS environment.

@since 0.1.0.2
-}
module OpenTelemetry.Resource.FaaS.Detector (
  detectFaaS,
) where

import OpenTelemetry.Resource.Detector.Internal (lookupEnvText)
import OpenTelemetry.Resource.FaaS (FaaS (..))
import System.Environment (lookupEnv)
import Text.Read (readMaybe)


-- | @since 0.0.1.0
detectFaaS :: IO (Maybe FaaS)
detectFaaS = do
  mLambda <- detectLambda
  case mLambda of
    Just faas -> pure (Just faas)
    Nothing -> do
      mGcf <- detectGCF
      case mGcf of
        Just faas -> pure (Just faas)
        Nothing -> detectAzureFunctions


detectLambda :: IO (Maybe FaaS)
detectLambda = do
  mName <- lookupEnvText "AWS_LAMBDA_FUNCTION_NAME"
  case mName of
    Nothing -> pure Nothing
    Just name -> do
      mVersion <- lookupEnvText "AWS_LAMBDA_FUNCTION_VERSION"
      mLogStream <- lookupEnvText "AWS_LAMBDA_LOG_STREAM_NAME"
      mMemStr <- lookupEnv "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"
      let mMem = fmap (* 1048576) (mMemStr >>= readMaybe)
      mRegion <- lookupEnvText "AWS_REGION"
      mAcctAndFn <- buildLambdaArn name mRegion
      pure $
        Just
          FaaS
            { faasName = name
            , faasCloudResourceId = mAcctAndFn
            , faasVersion = mVersion
            , faasInstance = mLogStream
            , faasMaxMemory = mMem
            }
  where
    buildLambdaArn name mRegion = do
      mAcct <- lookupEnvText "AWS_ACCOUNT_ID"
      pure $ do
        region <- mRegion
        acct <- mAcct
        Just $ "arn:aws:lambda:" <> region <> ":" <> acct <> ":function:" <> name


-- Google Cloud Functions
detectGCF :: IO (Maybe FaaS)
detectGCF = do
  mTarget <- lookupEnvText "FUNCTION_TARGET"
  case mTarget of
    Nothing -> pure Nothing
    Just target -> do
      mRevision <- lookupEnvText "K_REVISION"
      mMemStr <- lookupEnv "FUNCTION_MEMORY_MB"
      let mMem = fmap (* 1048576) (mMemStr >>= readMaybe)
      pure $
        Just
          FaaS
            { faasName = target
            , faasCloudResourceId = Nothing
            , faasVersion = mRevision
            , faasInstance = Nothing
            , faasMaxMemory = mMem
            }


detectAzureFunctions :: IO (Maybe FaaS)
detectAzureFunctions = do
  mRuntime <- lookupEnvText "FUNCTIONS_WORKER_RUNTIME"
  case mRuntime of
    Nothing -> pure Nothing
    Just _ -> do
      mSiteName <- lookupEnvText "WEBSITE_SITE_NAME"
      case mSiteName of
        Nothing -> pure Nothing
        Just siteName -> do
          -- Spec: Azure faas.name = "<WEBSITE_SITE_NAME>/<FUNCTION_NAME>"
          mFuncName <- lookupEnvText "FUNCTION_NAME"
          let name = case mFuncName of
                Just funcName -> siteName <> "/" <> funcName
                Nothing -> siteName
          pure $
            Just
              FaaS
                { faasName = name
                , faasCloudResourceId = Nothing
                , faasVersion = Nothing
                , faasInstance = Nothing
                , faasMaxMemory = Nothing
                }