Cimfu.Suave.Healthcheck


Tutorial

This tutorial is not yet complete, but here is an example of using the healthcheck functionality with Suave. In each of these examples, it is presumed that your existing Suave application is bound to myApp.

1: 
2: 
3: 
4: 
5: 
let hcMap = Map.ofList ["main", Checks.serverMain]

let app =
  myApp
  |> prefixWithHealthcheck hcMap

You can also choose the endpoint at which you would like your healthchecks reported, as in the following example:

1: 
2: 
3: 
let appAtAltAddress =
  myApp
  |> prefixWithHealthcheckAt "/livecheck" hcMap

By using prefixWithHealthcheck or prefixWithHealthcheckAt, the healthcheck functions will attach as a prefix to myApp. This means that if there is any request to the healthcheck endpoint, it will be handled by the healthcheck service. Requests to this endpoint that are invalid (such as POSTs) will be handled by returning an appropriate response (such as a 405 METHOD NOT ALLOWED) instead of passing the context to myApp.

In this way, you can choose to attach multiple healthchecks for services hosted by the same instance:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let service1Hc = Map.ofList ["main", Checks.serverMain; "redis", Checks.alwaysHealthy]
let service2Hc = Map.ofList ["main", Checks.serverMain; "unreliable", Checks.alwaysUnhealthy]

let appWithTwoServices =
  myApp
  |> prefixWithHealthcheckAt "/service-1/livecheck" service1Hc
  |> prefixWithHealthcheckAt "/service-2/livecheck" service2Hc

If you'd prefer to have greater control where in your routing table the healthcheck handler appears, you can use handleHealthcheck or handleHealthcheckAt instead.

1: 
2: 
3: 
4: 
let appUsingHandler =
  choose
    [ handleHealthcheckAt "/health" hcMap
      RequestErrors.NOT_FOUND "Couldn't find what you were looking for..." ]

Healthcheck evaluation

When a healthcheck is evaluated it will return a result indicating the current health of the service:

1: 
let result = Checks.serverMain () |> Async.RunSynchronously
No value has been returned

If we then disable the main server switch and re-evaluate the healthcheck that check will now evaluate to Unhealthy.

1: 
2: 
HealthSwitch.ServerMain.Disable ()
let newResult = Checks.serverMain () |> Async.RunSynchronously
No value has been returned

The evaluation of a set of healthchecks together creates an aggregate healthcheck result:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let aggregateResult =
  Map.ofList
    [ "main", Checks.serverMain
      "redis", Checks.alwaysHealthy
      "unreliable", Checks.alwaysUnhealthy ]
  |> evaluateHealthchecks
  |> Async.RunSynchronously
No value has been returned

This result gets serialized to JSON and returned to the client with the appropriate HTTP status

No value has been returned
namespace Suave
module Http

from Suave
namespace Cimfu
namespace Cimfu.Suave
module Healthcheck

from Cimfu.Suave
val myApp : Suave.WebPart.WebPart<HttpContext>

Full name: Tutorial.myApp
val hcMap : Map<string,Healthcheck>

Full name: Tutorial.hcMap
Multiple items
module Map

from Microsoft.FSharp.Collections

--------------------
type Map<'Key,'Value (requires comparison)> =
  interface IEnumerable
  interface IComparable
  interface IEnumerable<KeyValuePair<'Key,'Value>>
  interface ICollection<KeyValuePair<'Key,'Value>>
  interface IDictionary<'Key,'Value>
  new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
  member Add : key:'Key * value:'Value -> Map<'Key,'Value>
  member ContainsKey : key:'Key -> bool
  override Equals : obj -> bool
  member Remove : key:'Key -> Map<'Key,'Value>
  ...

Full name: Microsoft.FSharp.Collections.Map<_,_>

--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
val ofList : elements:('Key * 'T) list -> Map<'Key,'T> (requires comparison)

Full name: Microsoft.FSharp.Collections.Map.ofList
module Checks

from Cimfu.Suave.Healthcheck
val serverMain : Healthcheck

Full name: Cimfu.Suave.Healthcheck.Checks.serverMain
val app : Suave.WebPart.WebPart<HttpContext>

Full name: Tutorial.app
val prefixWithHealthcheck : hcMap:Map<string,Healthcheck> -> app:Suave.WebPart.WebPart<HttpContext> -> Suave.WebPart.WebPart<HttpContext>

Full name: Cimfu.Suave.Healthcheck.prefixWithHealthcheck
val appAtAltAddress : Suave.WebPart.WebPart<HttpContext>

Full name: Tutorial.appAtAltAddress
val prefixWithHealthcheckAt : healthcheckRoot:string -> hcMap:Map<string,Healthcheck> -> app:Suave.WebPart.WebPart<HttpContext> -> Suave.WebPart.WebPart<HttpContext>

Full name: Cimfu.Suave.Healthcheck.prefixWithHealthcheckAt
val service1Hc : Map<string,Healthcheck>

Full name: Tutorial.service1Hc
val alwaysHealthy : unit -> Async<HealthcheckResult>

Full name: Cimfu.Suave.Healthcheck.Checks.alwaysHealthy
val service2Hc : Map<string,Healthcheck>

Full name: Tutorial.service2Hc
val alwaysUnhealthy : unit -> Async<HealthcheckResult>

Full name: Cimfu.Suave.Healthcheck.Checks.alwaysUnhealthy
val appWithTwoServices : Suave.WebPart.WebPart<HttpContext>

Full name: Tutorial.appWithTwoServices
val appUsingHandler : obj

Full name: Tutorial.appUsingHandler
val handleHealthcheckAt : healthcheckRoot:string -> hcMap:Map<string,Healthcheck> -> WebPart

Full name: Cimfu.Suave.Healthcheck.handleHealthcheckAt
val result : HealthcheckResult

Full name: Tutorial.result
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T
Multiple items
type HealthSwitch =
  new : unit -> HealthSwitch
  new : disabledMsg:string -> HealthSwitch
  new : enabledMsg:string option * disabledMsg:string option -> HealthSwitch
  new : getTime:(unit -> Instant) * enabledMsg:string option * disabledMsg:string option -> HealthSwitch
  private new : getTime:(unit -> Instant) * enabledMsg:string option * disabledMsg:string option * dummy:unit -> HealthSwitch
  member Disable : unit -> unit
  member Disable : msg:string option -> unit
  member Enable : unit -> unit
  member Enable : msg:string option -> unit
  member Check : Healthcheck
  ...

Full name: Cimfu.Suave.Healthcheck.HealthSwitch

--------------------
new : unit -> HealthSwitch
new : disabledMsg:string -> HealthSwitch
new : enabledMsg:string option * disabledMsg:string option -> HealthSwitch
new : getTime:(unit -> NodaTime.Instant) * enabledMsg:string option * disabledMsg:string option -> HealthSwitch
property HealthSwitch.ServerMain: HealthSwitch
member HealthSwitch.Disable : unit -> unit
member HealthSwitch.Disable : msg:string option -> unit
val newResult : HealthcheckResult

Full name: Tutorial.newResult
member HealthSwitch.Enable : unit -> unit
member HealthSwitch.Enable : msg:string option -> unit
val aggregateResult : Internal.AggregateHealthcheckResult

Full name: Tutorial.aggregateResult
val evaluateHealthchecks : hcMap:Map<string,Healthcheck> -> Async<Internal.AggregateHealthcheckResult>

Full name: Cimfu.Suave.Healthcheck.evaluateHealthchecks
module Chiron
val serializedResult : string

Full name: Tutorial.serializedResult
Multiple items
module Json

from Chiron.Mapping

--------------------
module Json

from Chiron.Formatting

--------------------
module Json

from Chiron.Parsing

--------------------
module Json

from Chiron.Optics

--------------------
module Json

from Chiron.Functional

--------------------
type Json =
  | Array of Json list
  | Bool of bool
  | Null of unit
  | Number of decimal
  | Object of Map<string,Json>
  | String of string
  static member Array_ : Prism<Json,Json list>
  static member private Array__ : (Json -> Json list option) * (Json list -> Json)
  static member Bool_ : Prism<Json,bool>
  static member private Bool__ : (Json -> bool option) * (bool -> Json)
  static member Null_ : Prism<Json,unit>
  static member private Null__ : (Json -> unit option) * (unit -> Json)
  static member Number_ : Prism<Json,decimal>
  static member private Number__ : (Json -> decimal option) * (decimal -> Json)
  static member Object_ : Prism<Json,Map<string,Json>>
  static member private Object__ : (Json -> Map<string,Json> option) * (Map<string,Json> -> Json)
  static member String_ : Prism<Json,string>
  static member private String__ : (Json -> string option) * (string -> Json)

Full name: Chiron.Json

--------------------
type Json<'a> = Json -> JsonResult<'a> * Json

Full name: Chiron.Functional.Json<_>
val serialize : a:'a -> Json (requires member ToJson)

Full name: Chiron.Mapping.Json.serialize
val formatWith : options:JsonFormattingOptions -> json:Json -> string

Full name: Chiron.Formatting.Json.formatWith
type JsonFormattingOptions =
  {Spacing: StringBuilder -> StringBuilder;
   NewLine: int -> StringBuilder -> StringBuilder;}
  static member Compact : JsonFormattingOptions
  static member Pretty : JsonFormattingOptions
  static member SingleLine : JsonFormattingOptions

Full name: Chiron.Formatting.JsonFormattingOptions
property JsonFormattingOptions.Pretty: JsonFormattingOptions
F# Project
Fork me on GitHub