Reading time: 6 minutes


How do I use Hopac from C#?

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.

What's logVerboseWithAck, logWithAck and how does it differ from logSimple?

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.

logWithAck – so what's up with Promise?

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.

How do Promises work with C#?

The C# signature of the above functions is as follows:

type Message =
  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);

Getting MissingMethodException from FSharp.Core

You need to add a rebind to the latest F# version in your executable:

<?xml version="1.0" encoding="utf-8"?>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
        <bindingRedirect oldVersion="" newVersion="" />

Is v5.0.x a stable version?

Yes, it's very stable.

Why does Logary depend on FParsec?

For tow reasons;

  1. we use Chiron for json formatting which depend on FParsec
  2. Aether is vendored in Logary.Utils.Aether and depend on it.

Why do you depend on Hopac?

Hopac supports a few things that async doesn't:

  1. Rendezvous and selective concurrency primitives (select A or B)
  2. Negative ACKs instead of CancellationToken-s

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.

Getting MissingMethodException from Hopac.Core

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.

Comparison to NLog and log4net

Why Logary instead of one of the classic logging frameworks?

  • You get semantic logging with Logary

  • More targets to choose from

  • Larger community of target writers

  • Easier to write targets; they can crash and that's handled by Logary internally

  • Support for zero-dependency usage through Logary.Facade

  • Better/more extensive Rule-based hierarchies

  • Targets can be decoupled from the network and Ack is a first-level primitive

  • You get back an Alt (Promise (unit) ) that you can use to synchronise your calling code for when the log message is required to be durable; you can't do this with NLog or log4net

  • There's an object model you can use from the calling code

  • Logary is F#, so it's easier to keep bug-free relative to many other languages

  • Logary doesn't keep static state around; easy to refactor, easy to extend

Comparison to Codahale metrics & Metrics.NET

Why Logary rather than Metrics.NET?

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:

  • Gauges (measuring instantaneous values) -> PointValue.Gauge(.., ..) .

  • Timers (measuring durations) -> PointValue.Gauge(.., Scaled(Seconds, 10e9) (in nanoseconds)

  • Meter (measuring rates) -> PointValue.Derived(.., Div(Scalar, Seconds)) or PointValue.Derived(.., Div(Other "requests", Seconds))

  • Counters (counting events) -> PointValue.Event("User logged in")

  • Histograms (tracking value distributions) -> PointValue.Derived (with suffixes) and Reservoirs.

Metrics like the above are taken from different sources:

  • At call site (e.g. " Event happened", or "it took 50 ns to connect ")

  • At a process level, derived from Gauge and Event from different call-sites in your app (e.g. "The 99.9th percentile of '[time] ns to connect' is 145 ns").

  • At process level, taken from the operating system (Process is using 36.3% of CPU)

  • At a system level (e.g. the CPU utilisation is 0.352% – which can be represented as

  • 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.

Logary and Serilog

  • Both support structured logging
  • Both run on .Net
  • Logary is based on cooperative multithreading whilst Serilog is mostly lock-free concurrent
  • Logary was built from running high-throughput distributed systems 24/7 in production and has learnt its lessons similar to Serilog.
  • Logary can be run in multi-instance mode without using any global shared state (aka. statics), which is similar to Serilog
  • Serilog's Enrichers = Logary's middleware
  • Serilog's Sink = Logary's Target
  • Targets in Logary tend to use IO completion ports/async-as-"green threads", AFAIK Sinks are running and calling out using synchronous/blocking APIs to a larger extent
  • Logary supports flushing all targets (LogManager.Flush)
  • Logary supports flushing a single target (Target.flush)
  • Logary supports backpressure (F#: Alt<_> , C#: Task) returned fromlogWithAck.
  • Logary further supports backpressure by waiting for all targets to flush a particular message (e.g. you should always block on Fatal messages to finish logging) through Alt<Promise<unit>>/Task<Task> in C# (same method as above).
  • Logary's C# API doesn't support misconfiguring Logary, because it's been built with chaining types together (going beyond the return this pattern) – similar to Serilog but with a more callback-oriented API.
  • Logary supports Metrics – Gauges, Derived values, Histograms, Reservoirs
  • Logary supports Health checks out of the box
  • Logary has built-in support for Windows Performance Counters metrics shipping through Logary.Metrics.WinPerfCounters.
  • Logary provides a Facade for your libraries to depend on, to avoid dependency hell forcing you to upgrade all your libraries whenever Logary changes (which it does often, in order to improve!) – a single Facade.[fs,cs]-file that you version control yourself.
  • Logary supports Targets that batch, out of the box, similar to Serilog. The Target is responsible for choosing how many Messages it can send at once.
  • Logary supports Targets that fail by restarting them
  • Logary supports Targets' last will – use to handle poison Messages
  • Logary is written in F#, Serilog in C#
  • Logary has a C# API Logary.CSharp. Serilog doesn't have a F# API
  • Logary supports adding structured data to metrics
  • Logary's InfluxDb target supports fields and can batch multiple Windows Performance Counters or metrics into a single Measurement
  • Logary has a JS-counterpart, logary-js which lets you log intoLogary.Services.Rutta on the server; events and metrics.
  • Logary has paid support available if you need it.
  • Logary supports the side-kick pattern, where you outsource your shipping of logs from your main process to a co-process through Rutta's Shipper and Router.
  • Logary has TimeScope and the ability to instrument your code for sending timing information.
  • Logary has preliminary support for Zipkin.
  • Both are awesome and @nblumhardt is an awesome dude. Use whichever you feel most comfortable with!