{-
  Copyright (c) Meta Platforms, Inc. and affiliates.
  All rights reserved.

  This source code is licensed under the BSD-style license found in the
  LICENSE file in the root directory of this source tree.
-}

{-# LANGUAGE ImplicitParams #-}

-- |
-- This module allows you to log things to stderr. Make sure to initialize your
-- application using 'Facebook.Init.initFacebook' or
-- 'Facebook.Init.withFacebook' beforehand. Otherwise you're going to get a
-- cryptic "Logging before InitGoogleLogging() is written to STDERR" error.
module Util.Log.String
  ( vlog
  , vlogIsOn
  , logInfo
  , logWarning
  , logError
  , logFatal
  ) where

import Control.Monad
import Control.Monad.IO.Class (MonadIO, liftIO)
import Foreign.C
import GHC.Stack

import Util.Log.Internal

-- | Equivalent to @VLOG(level)@, except that in Haskell we only evaluate
-- the message if verbosity at the desired level is enabled. Note that
-- this lazy evaluation of the message only applies to 'vlog', not to
-- any of the other logging functions.
vlog :: (MonadIO m, HasCallStack) => Int -> String -> m ()
vlog level msg = liftIO $ do
  b <- vlogIsOn level
  when b $ logCommon c_glog_verbose (getCaller $ getCallStack callStack) msg

-- | The calling point is actually right next to the head as the head is the
-- current function (in this case it would be logInfo/Warning/Error/Fatal).
getCaller :: [(String, SrcLoc)] -> (String, CInt)
getCaller cs =
  case cs of
    -- Take the first element of the stack if it exists
    ((_,sl):_) -> (srcLocFile sl, fromIntegral $ srcLocStartLine sl)
    _       -> ("Unknown stack trace", 0)

-- | Log message at severity level @INFO@.
logInfo :: (MonadIO m, HasCallStack) => String -> m ()
logInfo msg = liftIO $
  logCommon c_glog_info (getCaller $ getCallStack callStack) msg

-- | Log message at severity level @WARNING@.
logWarning :: (MonadIO m, HasCallStack) => String -> m ()
logWarning msg = liftIO $ logCommon c_glog_warning
  (getCaller $ getCallStack callStack) msg

-- | Log message at severity level @ERROR@.
logError :: (MonadIO m, HasCallStack) => String -> m ()
logError msg = liftIO $
  logCommon c_glog_error (getCaller $ getCallStack callStack) msg

-- | Log message at severity level @FATAL@. This will terminate the program
-- after the message is logged.
logFatal :: (MonadIO m, HasCallStack) => String -> m ()
logFatal msg =
  liftIO $ logCommon c_glog_fatal (getCaller $ getCallStack callStack) msg

logCommon :: (CString -> CInt -> CString -> IO ()) ->
             (String, CInt) -> String -> IO ()
logCommon fn (file, lineNumber) msg =
  withCString file $ \file_cstring ->
    withCString msg $ \msg_cstring ->
      fn file_cstring lineNumber msg_cstring