You're better off following the examples in C# and using the Task-wrapped public APIs than going spelunking into the dire straits of Hopac and F#.
Just pull in Logary.CSharp to make this happen. You'll also have to open the Logary namespace.
To start with, if you're new to Logary, you can use logSimple and it will work like most other logging frameworks. So what are those semantics exactly?
Logary runs its targets concurrently. When you log a Message, all targets whose Rules make it relevant for your Message, receives the Message, each target tries to send that Message to its, well, target.
Because running out of memory generally is unwanted, each target has a RingBuffer that the messages are put into when you use the Logger. Unless all targets' RingBuffer accept the Message, the call to log doesn't complete. This is similar to how other logging frameworks work.
But then, what about the call to log? Behind the scenes it calls lockWithAck and tries to commit to the returned Alt [Promise [unit]] (the outer Alt, that is). If the RingBuffer is full then this Alt cannot be committed to, so there's code that drops the log message after 5000 ms.
Hence; logSimple tries its best to log your message but if you app crashes directly after calling logSimple or your Logstash or other target infrastructure is down, you cannot be sure everything is logged. The decision was made that it's more important that your app keeps running than that all targets you have configured successfully log your Messages.
The outer Alt ensures that the Message has been placed in all configured targets' RingBuffers.
The inner Promise that the Message has successfully been written from all Targets that received it. It ensures that your logging infrastructure has received the message.
It's up to each target to deal with Acks in its own way, but a 'best-practices' Ack implementation can be seen in the RabbitMQ target. It's a best-practices Ack implementation because RabbitMQ supports publisher confirms (that serve as Acks), asynchronous publish and also durable messaging.
The C# signature of the above functions is as follows:
type Message =
[<Extension>]
static member LogWithAck (logger, message, bufferCt, promiseCt) : Task<Task> =
Alt.toTasks bufferCt promiseCt (Logger.logWithAck logger message)
and can be used like so:
var message = MessageModule.Event(LogLevel.Warn, "Here be dragons!");
// force the buffers of all configured targets to be flushed
await logger.LogWithAck(message);
You need to add a rebind to the latest F# version in your executable:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<Paket>True</Paket>
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-999.999.999.999" newVersion="4.4.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Yes, it's very stable.
For tow reasons;
Hopac supports a few things that async doesn't:
We also wanted support for synchronous rendezvous between channels/job/alts/promises/etc. This still supports asynchronous operations towards the outside. Together it makes for an excellent choice for cooperating 'agents', like the Registry and Supervisor and Target Instance that we have in the library.
Besides the technical upsides, it's a good thing there's a book written about the concurrency model that Hopac implements – Concurrent Programming in ML which lets us get developers up to speed quickly.
Finally, our unit tests sped up 30x when porting from Async. The performance boost is a nice feature of a logging framework and comes primarily from less GC collection and the 'hand off' between synchronising concurrency primitives being synchronously scheduled inside Hopac rather than implemented using Thread/Semaphore/Monitor primitives on top of the ThreadPool.
Inspect the version specified in the Logary package and ensure that you have that exact version installed. Hopac is currently pre-v1 so it is often doing breaking changes between versions.
Why Logary instead of one of the classic logging frameworks?
Why Logary rather than Metrics.NET, the primary alternative?
In order to understand the differences, you first need to understand the vocabulary. Logary uses the name Message to mean either an Event , a Gauge or a Derived . This comes from analysing the different sorts of things one would like to ship from an app.
Starting with an Event ; this is the main value when you're logging (in fact, it's Logary.PointValue.Event(template:string) that you're using.) An event is like a Gauge at a particular instant on the global timeline with a value of 1 (one).
Which brings us to what a Gauge is. It's a specific value at an instant. It's what you see as a temporature on a thermometer in your apartment, e.g. 10.2 degrees celcius . In the International System of Units (SI-Units), you could say it's the same as 283.2 K. Logary aims to be the foundational layer for all your metrics, so it uses these units. A Gauge value of your temperature could be created like so Message.gaugeWithUnit Kelvin (Float 283.2) or Gauge (Float 283.2, Kelvin) .
A Derived metric , like Kelvin/s is useful if you're planning on writing a thermostat to control the temperature. A change in target temperature causes a rate of change.
Another sample metric could be represented by the name [| "MyApp"; "API" "requests" |] and PointValue of Derived (Float 144.2, Div (Scalar, Seconds)) , if the API is experiencing a request rate of 144.2 requests per second.
Armed with this knowledge, we can now do a mapping between Codahale's metrics and those of Logary:
Metrics like the above are taken from different sources:
let mhz = Div(Scaled(Hz, 1e-6)) in Gauge(Fraction (1300, 36800), Div(mhz, mhz))
as collected by Rutta's Shipper from a compute node.
The aim of Logary is to connect values from call-sites, to configurable derivations, such as percentiles(, potentially again to derivations), and finally to targets which can then store them.
LogManager.Flush
)Target.flush
)Alt<_>
, C#: Task
) returned fromlogWithAck
.Alt<Promise<unit>>
/Task<Task>
in C# (same method as above).return this
pattern) – similar to Serilog but with a more callback-oriented API.Logary.Metrics.WinPerfCounters
.Facade.[fs,cs]
-file that you version control yourself.Messages
it can send at once.Logary.CSharp
. Serilog doesn't have a F# APILogary.Services.Rutta
on the server; events and metrics.TimeScope
and the ability to instrument your code for sending timing information.