Expected reading time: 3 minutes
Clone it like above. Ensure you can build it. Open Logary.sln . Make a change, send a PR towards master. To balance the app.config files, try mono tools/paket.exe install --redirects --clean-redirects --createnewbindingfiles

File guidelines – module vs static method

Declare your interfaces in a MyIf.fs and its module in MyIfModule.fs with a ([CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)])).

Place these files as high as possible. Place the module as close to and below the non-module file as possible.

If it's plausible that one would like to use point-free programming with your functions, place them in a module. (E.g. Message versus MessageModule)

Factory methods go on the types for value types. For stateful objects they go on the module, named create which may or may not return a Job[] of some public state type with a private/internal constructor. (E.g. PointName versus Engine)

File guidelines – plural vs singular

All implementations go in a plural namespace. E.g. Logary.Metrics.Ticked has:

  • Logary – core namespace
  • Metrics – namespace for all metrics implementations
  • Ticked – a module and/or type that implements Logary-based ticking/ scheduling.

Another example: Logary.Target , which is a module that implements logic about the life-cycle of target instances. It's used to start/stop/pause and shutdown targets. It's singular.

Namespace guidelines – Logary.Internals or not

Things that end-users of the library are not likely to configure should go in Logary.Internals . Examples include Logary.Internals.Globals and Logary.Internals.RuntimeInfo (which is configured with the config API instead).

RuntimeInfo and internal logging

The RuntimeInfo and internal Logger should be propagated to the modules and objects that you build your solution out of. This guideline is mostly about the registry's implementation and its interaction with config and services.

When to write a new function?

Generally, keep your functions to a single responsibility and compose functions instead of extending existing functions. Composition happens through currying and partial application of 'state' or 'always-this-value' values. For state it can both go through the Services abstraction (start/pause/stop/etc) or by closing over the state with a function.

If you find that your functions are getting larger than 3 lines of code, you should probably extract part of the function. By 'default' it's better to be 1% less performant but 5% more readable, after this list of priorities:

  1. Correct
  2. CReadable/SRPorrect
  3. Fast/efficient/performant

How to open namespaces?

Prefer to open a namespace over fully qualifying a type.

Prefer to open fully qualified over partial.

open Logary.Internals open Logary.Internals.Supervisor

Instead of

open Logary.Internals open Supervisor

To start the job or not?

A module function like MyModule.create : Conf -> Job[T] should not start the server loop. Instead, just return a cold job (that can be started multiple time) and let the composition "root", such as the Registry , perform the composition and lifetime handling.

Writing a new target

Are you thinking of creating a new Target for Logary? It's a good idea if you can't find the right Target for your use case. It can also be useful if you have an internal metrics or log message engine in your company you wish to ship to.

  1. Create a new .net 4.5.1 class library in F#, under target and add that to Logary.sln.
  2. Copy the code from Logary's Targets/Noop.fs, which contains the basic structure. There are more docs in this file, to a file named MyTarget.fs in your new project.
  3. Add a nuget reference (or project reference if you're intending to send a PR) to Logary
  4. Write your Target and your Target's tests to ensure that it works
  • Remember to test when the call to your server throws exceptions or fails
  • You should use Http.fs as the HTTP client if it's a HTTP target
Target guidelines

When writing the Target, it's useful to keep these guidelines in mind.

  • It should be able to handle shutdown messages from the shutdown channel
  • It should not handle 'unexpected' exceptions, like network loss or a full disk by itself, but instead crash with an exception – the Logary supervisor will restart it after a short duration.
  • Things that are part of the target API, like different response status codes of a REST API should be handled inside the Target.
  • Don't do blocking calls;
  • Choose whether to create a target that can re-send crashing messages by choosing between TargetUtils.[willAwareNamedTarget, stdNamedTarget]
  • You can choose between consuming Messages one-by-one throughRingBuffer.take or in batches with RingBuffer.takeBatch
  • If you take a batch and the network call to send it off fails, consider sending the batch to the willChannel and throw an exception. Your target will be re-instantiated with the batch and you can now send the messages one-by-one to your target, throwing away poison messages (things that always crash).
  • If your target throws an exception, the batch of Messages or the Message you've taken from the RingBuffer will be gone, unless you send it to thewill channel.
  • Exiting the loop will cause your Target to shut down. So don't catchall exceptions without recursing afterwards. The supervisor does notrestart targets that exit on their own.
  • If your target can understand a service name, then you should always add the service name from RuntimeInfo.serviceName as passed to your loop function.
  • The RuntimeInfo contains a simple internal logger that you can assume always will accept your Messages. It allows you to debug and log exceptions from your target. By default it will write output to the STDOUT stream.
  • If you don't implement the last-will functionality, a caller that awaits the Promise in Alt<Promise<unit>> as returned from logWithAck, will block forever if your target ever crashes.
  • If you need to do JSON serialisation, consider using Logary.Utils.Chironand Logary.Utils.Aether, which are vendored copies ofChiron and Aether. Have a look at theLogstash Target for an example.
Publishing your target

When your Target is finished, either ping @haf on github, @henrikfeldt on twitter, or send a PR to this README with your implementation documented. I can assist in proof-reading your code, to ensure that it follows the empirical lessons learnt operating huge systems with Logary.