# Alog

Alog is a stackable logging framework for the Ada programming language. It aims to be straight forward to use and is easily extendable. It provides support for various logger types, log facilities, loglevel policies and message transformations.

## Prerequisites

Alog is written in Ada so you need to have an Ada Compiler such as GNAT installed to build Alog.

## Download

### Release Version

The current release version of alog is available at https://www.codelabs.ch/download/.

### Verify a Release

To verify the integrity and authenticity of the distribution tarball type the following commands:

    $ wget -q https://www.codelabs.ch/keys/0xDBF6D7E1095FD0D9.asc -O - | gpg --import
    $ gpg --verify libalog-{version}.tar.bz2.sig

The key fingerprint of the public key (0xDBF6D7E1095FD0D9) is:

    Key fingerprint = 298F 4B32 C3C4 1D88 5949  86F3 DBF6 D7E1 095F D0D9

### Development Version

The current development version of alog is available through its git repository:

    $ git clone https://git.codelabs.ch/git/alog.git

A browsable version of the repository is also available here: https://git.codelabs.ch/?p=alog.git

## Licence

    Copyright (C) 2008-2022 Reto Buerki
    Copyright (C) 2008-2022 Adrian-Ken Rueegsegger

    Alog is free software; you can redistribute it and/or modify it under the terms
    of the GNU Lesser General Public License as published by the Free Software
    Foundation; either version 2.1 of the License, or (at your option) any later
    version.

## Installation

The Alog library has no special dependencies. To run the testcases, you need to have the Ahven Unit Test-Framework installed:

- Ahven (Test-Framework): http://ahven.stronglytyped.org/

The building and installation process of Alog is simple. Just type in the following commands. You must be root to install the library system wide.

    $ tar -xzf libalog-{version}.tar.bz2
    $ cd libalog-{version}
    $ make
    $ make PREFIX=/usr/local install

This will compile and install the Alog library. If no `PREFIX` is specified, `$(HOME)/libraries` is used as installation directory.

## Tests

After compiling and linking Alog, you can test if everything works as expected by typing the following command:

    $ make tests

You should then see `PASS` behind each of the tests.

## Usage

### Logger

Logger instances are used to manage an arbitrary number of log facilities and message transformations. Various logger types exist which are suitable for different scenarios:

Logger

  This is the basic Alog logger type. This logger is easy to use and good enough for most situations. However, it does not provide thread safety.

Tasked Logger

  The Alog tasked logger encapsulates a basic logger instance inside a server task to provide safe concurrent logging. Since calls to this logger are potentially blocking operations, it cannot be used from within a protected action.

Active Logger

  The Alog active logger provides task safe concurrent logging from any context.

### Facility

Another basic entity in the Alog framework is called a Facility. Facilities are log destinations and used to log messages to different backends, e.g. a file or a database. Currently, the framework provides the following log facilities:

File_Descriptor

  Writes log messages to file or console.

Syslog

  Writes log messages to syslog.

### Transformation

Transformations are used to modify a log message before it is passed on to a facility. The following message transformations are available:

Casing (toUpper / toLower)

  Convert a log message to upper/lower case.

### Policy

Alog supports destination and if needed source filtering by means of loglevel policies. Refer to the example section for information on how to setup an use these policy mechanism.

## Examples

The examples presented in this section will give an introduction on how to use the Alog framework in your own project.

### Logger

The following example uses a basic logger instance to log messages to standard output. Furthermore, a file based facility is attached which writes log messages to a file.

    with Alog.Logger;
    with Alog.Facilities.File_Descriptor;

    use Alog;

    --  Alog logger example.
    procedure Logger_Example1 is
       --  Initialize logger instance with default file descriptor facility
       --  (logs to stdout).
       Log : Logger.Instance (Init => True);
    begin
       --  Write a message with loglevel 'Info' to stdout.
       Log.Log_Message
         (Level => Info,
          Msg   => "This is a testmessage from Alog logger");

       Attach_FD_Facility :
       declare
          FD : constant Facilities.File_Descriptor.Handle :=
            new Facilities.File_Descriptor.Instance;
       begin
          FD.Set_Logfile (Path => "/tmp/alog.log");
          Log.Attach_Facility (Facility => Facilities.Handle (FD));

          --  Log a message to file and stdout.
          Log.Log_Message (Source => "Example",
                           Level  => Warning,
                           Msg    => "Another testmessage");
       end Attach_FD_Facility;
    end Logger_Example1;

The logger will take care about cleaning up all the attached facilities when it goes out of scope. However, you can do this explicitly by calling `Logger.Clear` as well.

### Facility

The following code sets up a file descriptor based facility to log messages to a file. If the file already exists, it will be overwritten.

    with Alog.Log_Request;
    with Alog.Facilities.File_Descriptor;

    use Alog;

    --  Alog file descriptor facility example.
    procedure Facility_Example1 is
       Facility : Facilities.File_Descriptor.Instance;
    begin
       --  Enable writing of loglevels.
       Facility.Toggle_Write_Loglevel (State => True);

       --  Use '/tmp/alog.log' as logfile, overwrite existing file.
       Facility.Set_Logfile (Path   => "/tmp/alog.log",
                             Append => False);

       --  Let the facility process a log request with loglevel 'Warning'.
       Facility.Process
         (Request => Log_Request.Create
            (Level   => Warning,
             Message => "This is a testmessage from Alog FD facility"));

       --  Teardown the facility.
       Facility.Teardown;
    end Facility_Example1;

### Policy

The first policy example illustrates how destination filtering works. Only log messages with loglevel `Error` or higher are written to the application error logfile. It shows how logging policies can be used to filter log messages depending on the destination (facility name). Furthermore it illustrates how the default level can be used to filter out messages with lower loglevels.

    with Alog.Dst_Filter;
    with Alog.Logger;
    with Alog.Facilities.File_Descriptor;

    use Alog;

    --  Alog destination loglevel policy example.
    procedure Policy_Example1 is
       Log : Logger.Instance (Init => True);

       Facility_Name : constant String := "Application_Errors";
       Errors        : constant Facilities.File_Descriptor.Handle
         := new Facilities.File_Descriptor.Instance;
    begin
       --  Write all error messages to '/tmp/errors.log'.
       Errors.Set_Logfile (Path => "/tmp/errors.log");
       Errors.Set_Name (Name => Facility_Name);
       Errors.Toggle_Write_Loglevel (State => True);

       --  Set loglevel policy to 'Error' for destination 'Application_Errors'.
       Dst_Filter.Set_Loglevel (Name  => Facility_Name,
                                Level => Error);

       Log.Attach_Facility (Facility => Facilities.Handle (Errors));

       --  This message will appear on stdout, but not in the error logfile.
       Log.Log_Message (Level  => Info,
                        Msg    => "This is not an error");
       --  This message will also be written to the error logfile.
       Log.Log_Message (Level  => Error,
                        Msg    => "This is an error");

       --  Set global loglevel to only log messages with level higher than Warning
       --  if no facility-specific level is set.
       Dst_Filter.Set_Default_Level (Level => Warning);
       --  This message will be filtered out.
       Log.Log_Message (Level  => Info,
                        Msg    => "This message will be filtered");
       --  This message will appear on stdout, but not in the error logfile.
       Log.Log_Message (Level  => Warning,
                        Msg    => "This is a warning message");
    end Policy_Example1;

The second policy example demonstrates how the protected policy database type of Alog can be used to implement source filtering in an application. It shows how logging policies can be constructed by specifying source specific loglevels and how this leads to log messages being filtered depending on their source.

    with Alog.Policy_DB;
    with Alog.Logger;

    use Alog;

    --  Alog source loglevel policy example.
    procedure Policy_Example2 is
       Src_Filter : Policy_DB.Protected_Policy_DB;
       Log        : Logger.Instance (Init => True);
    begin
       --  Set default loglevel to 'Info'.
       Src_Filter.Set_Default_Loglevel (Level => Info);
       --  Set source specific loglevel for all 'Example' sources to 'Debug'.
       Src_Filter.Set_Loglevel (Identifier => "Example.*",
                                Level      => Debug);

       --  This message will be logged because it matches a source specific
       --  loglevel (Example.*).
       if Src_Filter.Accept_ID (Identifier => "Example.Source1",
                                Level      => Debug)
       then
          Log.Log_Message (Source => "Example.Source1",
                           Level  => Debug,
                           Msg    => "This is a test message");
       end if;

       --  This message will not be logged because of the configured default 'Info'
       --  loglevel. There's no configured source loglevel for 'Source2'.
       if Src_Filter.Accept_ID (Identifier => "Source2",
                                Level      => Debug)
       then
          Log.Log_Message (Source => "Source2",
                           Level  => Debug,
                           Msg    => "This will not be logged");
       end if;

       --  This message will be logged because of the configured default 'Info'
       --  loglevel. There's no configured source loglevel for 'Source2'.
       if Src_Filter.Accept_ID (Identifier => "Source2",
                                Level      => Info)
       then
          Log.Log_Message (Source => "Source2",
                           Level  => Info,
                           Msg    => "This is another test message");
       end if;
    end Policy_Example2;