Public/Write-PSLog.ps1

function Write-PSLog {
  param(
    [ValidateSet('Info', 'Warn', 'Error', 'Start', 'End', IgnoreCase = $false)]
    [string]$Severity = 'Info',
    [Parameter(Mandatory)]
    [string]$Message,
    [ValidateScript({
          if (-not ($_ | Test-Path -PathType Container -IsValid)) {
            throw 'Path is not a valid directory.'
          }
          return $true
    })]
    [string]$LogDirectory = 'C:\PSLogDir',
    [ValidateScript({
          if (-not ($_ | Test-Path -PathType Leaf -IsValid)) {
            throw 'LogFileName is not a valid.'
          }
          return $true
    })]
    [string]$LogFileName = 'PSLogFile.json',
    [System.Management.Automation.ErrorRecord]$LastException = $_
  )
  #Identify User run as account by Home directory
  $User = $HOME | Split-Path -Leaf

  $Metadata = [PSCustomObject]@{
    Invoking_User = $User
  }

  $CallStackDepth = 0
  $FullCallStack = Get-PSCallStack
  $CallingFunction = $FullCallStack[1].FunctionName

  $LogObject = [PSCustomObject]@{
    Timestamp       = (Get-Date).ToString()
    Severity        = $Severity
    CallingFunction = $CallingFunction
    Message         = $Message
    Metadata        = $Metadata
  }

  if ($Severity -eq 'Error') {

    if ($LastException.ErrorRecord) {
      #PSCore Error
      $LastError = $LastException.ErrorRecord
    }
    else {
      #PS 5.1 Error
      $LastError = $LastException
    }

    if ($LastException.InvocationInfo.MyCommand.Version) {
      $Version = $LastError.InvocationInfo.MyCommand.Version.ToString()
    }
    $LastErrorObject = @{
      'ExceptionMessage'    = $LastError.Exception.Message
      'ExceptionSource'     = $LastError.Exception.Source
      'ExceptionStackTrace' = $LastError.Exception.StackTrace
      'PositionMessage'     = $LastError.InvocationInfo.PositionMessage
      'InvocationName'      = $LastError.InvocationInfo.InvocationName
      'MyCommandVersion'    = $Version
      'ScriptName'          = $LastError.InvocationInfo.ScriptName
    }

    # $LogObject | Add-Member -MemberType NoteProperty -Name LastError -Value $LastErrorObject
    $LogObject.psobject.Members.Add([psnoteproperty]::new('LastError',$LastErrorObject))

    $FullCallStackWithoutLogFunction = $FullCallStack | ForEach-Object {
      #loop through all the objects in the callstack result.
      #excluding the 0 position of the call stack which would represent this Write-PSLog function.
      if ($CallStackDepth -gt 0) {
        [PSCustomObject]@{
          CallStackDepth   = $CallStackDepth
          ScriptLineNumber = $_.ScriptLineNumber
          FunctionName     = $_.FunctionName
          ScriptName       = $_.ScriptName
          Location         = $_.Location
          Command          = $_.Command
          Arguments        = $_.Arguments
        }
      }
      $CallStackDepth++
    }

    # $LogObject | Add-Member -MemberType NoteProperty -Name FullCallStackDump -Value $FullCallStackWithoutLogFunction
    $LogObject.psobject.Members.Add([psnoteproperty]::new('FullCallStackDump',$FullCallStackWithoutLogFunction))

    $WriteHostColor = @{ForegroundColor = 'Red'}
  }
  if (-not (Test-Path -Path $LogDirectory -PathType Container)) {$null = New-Item -Path $LogDirectory -ItemType Directory -Force}
  $LogFilePath = Join-Path -Path $LogDirectory -ChildPath $LogFileName
  $LogObject | ConvertTo-Json  -Depth 2 | Out-File -FilePath $LogFilePath -Append -Encoding utf8

  Write-Host "$($LogObject.Timestamp) Sev=$($LogObject.Severity) CallingFunction=$($LogObject.CallingFunction) `n $($LogObject.Message)" @WriteHostColor
  if ($Severity -eq 'Error') {throw $LastException}
}