Logging

Logging is easy to integrate with Fable.Remoting, in this section, Serilog will be the recommended logging framework of choice. Dependending on which web framework you are using, you can pick one of:

Since both serilog extensions have the same API, I will only go through an example for Suave, the exact same code can be used with Giraffe if you just change Remoting.buildWebPart to Remoting.buildHttpHandler and WebPart type signatures to HttpHandler:

Basic use case

1 - Install from Nuget:

dotnet add package Suave.SerilogExtensions
# or if you are using paket
.paket/paket.exe add Suave.SerilogExtensions

2 - Wrap your WebPart given by Remoting with SerilogAdaper.Enable function:

open System
// ...
open Suave.SerilogExtensions
open Suave  

// Log unhandled exceptions 
let errorHandler (ex: Exception)  
                 (routeInfo: RouteInfo<HttpContext>) =
    // get a contextual logger with RequestId attached to it
    // .Logger() is an extension from Suave.SerilogExtensions
    let contextLogger = routeInfo.httpContext.Logger()
    // log the exception with relevant data
    let errorMsgTemplate = "Error occured while invoking {MethodName} at {RoutePath}"
    contextLogger.Error(ex, errorMsgTemplate, routeInfo.methodName, routeInfo.path)
    // No need to propagate custom errors back to client
    Ignore 

// create the WebPart
let webApp : WebPart = 
    Remoting.createApi()
    |> Remoting.fromValue musicStore
    |> Remoting.withRouteBuilder (sprintfn "/api/%s/%s") 
    |> Remoting.withErrorHandler errorHandler 
    |> Remoting.buildWebPart

// Enable logging
let webAppWithLogging : WebPart = SerilogAdapter.Enable(webApp)

3 - Create the logger and configure the sinks:

// configure Serilog
Log.Logger <- 
    LoggerConfiguration() 
      // Suave.SerilogExtensions has native destructuring mechanism
      // this helps Serilog deserialize the fsharp types like unions/records
      .Destructure.FSharpTypes()
      // use package Serilog.Sinks.Console  
      // https://github.com/serilog/serilog-sinks-console
      .WriteTo.Console() 
      // add more sinks etc.
      .CreateLogger()

4 - Start the web server

startWebServer defaultConfig webAppWithLogging

Suave.SerilogExtensions contains many configuration options that you might want to check out.

Using logger from remoting functions

Ofcourse, we don't want to just log the http requests and responses but we also want to log application specific and custom logs. To use Serilog from the remote functions, you need to Access to HttpContext and call httpContext.Logger() to get a contexual logger you can log events from.

Logger() is an extension method provided from Suave.SerilogExtensions

Suppose your protocol had the type:

type IMusicStore = {
    albums : Async<list<Album>>
}

To gain access to the HttpContext, use it as a parameter to end up with HttpContext -> IMusicStore:

// The musicStore value is dependent on the ILogger
let musicStore (logger: ILogger) : IMusicStore = {
    albums = async {
        logger.Information("Retrieving albums from database")
        let! albums = Database.getAllAlbums()
        logger.Information("Read {AlbumCount} albums from database", List.length albums)
        return albums
    }
} 

// Get the logger from the context
let musicStoreApi (context: HttpContext) : IMusicStore = 
    let logger : ILogger = context.Logger()
    musicStore logger 

// create the WebPart using 'fromContext' instead of 'fromValue' to gain access to the context
let webApp : WebPart = 
    Remoting.createApi()
    |> Remoting.fromContext musicStoreApi
    |> Remoting.withRouteBuilder (sprintfn "/api/%s/%s") 
    |> Remoting.withErrorHandler errorHandler 
    |> Remoting.buildWebPart

// Wrap with Serilog adapter
let webAppWithLogging = SerilogAdapter.Enable(webApp)

startWebServer defaultConfig webAppWithLogging

results matching ""

    No results matching ""