Enrico
Enrico

Reputation: 6276

F# errors with ILogger in Azure Functions v2

I have a quite big project with Azure Function v2 written in F#. I create a library in C# where in the constructor I can pass a ILogger log. If only I add this library in the Azure Function project, every function returns an error.

Microsoft.Azure.WebJobs.Host: Cannot bind parameter 'log' to type ILogger. Make sure the parameter Type is supported by the binding

enter image description here

In F# I tried to add

[<FunctionName("organization")>]
let public organization
    ( [<HttpTrigger(AuthorizationLevel.Function, "post", Route = "organisations/{organisationId}")>]
        request : HttpRequestMessage,
        organisationId : string,
        executionContext : ExecutionContext,
        log : ILogger) =

      let PaymentGateway = PaymentGateway(Environment.GetEnvironmentVariable "Api_Secret_Key", log)

      ...

I saw some posts for example on GitHub or another post where other people have the same issue. Apparently, the solution is to update the Azure Function from v2 to v3 but I can't do that in my case right now.

Is there any solution?

Update

The .fsproj is

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <AzureFunctionsVersion>v2</AzureFunctionsVersion>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Update="FSharp.Core" Version="4.6.2" />
  </ItemGroup>
</Project>

Upvotes: 0

Views: 362

Answers (2)

Enrico
Enrico

Reputation: 6276

After a solid week of tests and changes, I found a non-ideal solution but it is working.

In the function I define logInfo like that:

let logInfo : Action<string> = new Action<string>(fun (x: string) -> log.LogInformation(x))

In my C# library, I removed any reference to Microsoft.Extensions.Logging and I replace it with:

private Action<string> _logger;
public PaymentGateway(string apiKey, Action<string> logger) {
    _logger = logger;
}

At least, I have logs in my component. I don't like this solution but it is what I can do with F#.

Upvotes: 0

suziki
suziki

Reputation: 14113

No matter what, your fs is indeed problematic. I posted here the file and code of function that can be triggered successfully:

This is the structure of my function app:

enter image description here

This is my .fsproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <AzureFunctionsVersion>v3</AzureFunctionsVersion>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="HttpTrigger.fs" />
    <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.3" />
  </ItemGroup>
  <ItemGroup>
    <Content Include="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </Content>
  </ItemGroup>
</Project>

And this is my trigger file HttpTrigger.fs:

namespace Company.Function

open System
open System.IO
open Microsoft.AspNetCore.Mvc
open Microsoft.Azure.WebJobs
open Microsoft.Azure.WebJobs.Extensions.Http
open Microsoft.AspNetCore.Http
open Newtonsoft.Json
open Microsoft.Extensions.Logging

module HttpTrigger =
    // Define a nullable container to deserialize into.
    [<AllowNullLiteral>]
    type NameContainer() =
        member val Name = "" with get, set

    // For convenience, it's better to have a central place for the literal.
    [<Literal>]
    let Name = "name"

    [<FunctionName("HttpTrigger")>]
    let run ([<HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)>]req: HttpRequest) (log: ILogger) =
        async {
            log.LogInformation("F# HTTP trigger function processed a request.")

            let nameOpt = 
                if req.Query.ContainsKey(Name) then
                    Some(req.Query.[Name].[0])
                else
                    None

            use stream = new StreamReader(req.Body)
            let! reqBody = stream.ReadToEndAsync() |> Async.AwaitTask

            let data = JsonConvert.DeserializeObject<NameContainer>(reqBody)

            let name =
                match nameOpt with
                | Some n -> n
                | None ->
                   match data with
                   | null -> ""
                   | nc -> nc.Name
            
            let responseMessage =             
                if (String.IsNullOrWhiteSpace(name)) then
                    "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                else
                    "Hello, " +  name + ". This HTTP triggered function executed successfully."

            return OkObjectResult(responseMessage) :> IActionResult
        } |> Async.StartAsTask

Works fine, successful log:

enter image description here

These is my step to create and run F# function:

1.Run these two command in cmd:

dotnet new --install Microsoft.Azure.WebJobs.ItemTemplates

dotnet new --install Microsoft.Azure.WebJobs.ProjectTemplates

2.Create function app:

dotnet new func --language F# --name FunctionsInFSharp

3.Create trigger:

dotnet new http --language F# --name HttpTrigger

4.Add trigger to compile:

<Compile Include="HttpTrigger.fs" />

Please add above code to ItemGroup in .fsproj file.

5.Change the template, change these from none to Content:

enter image description here

All I have given is a simple example of how the F# function can be triggered successfully, please try it and let me know whether you can solve it.:)

Upvotes: 1

Related Questions