1
0
mirror of https://github.com/erlang/docker-erlang-example.git synced 2025-04-19 01:24:03 +03:00

Using Logstash to collect log events

This example runs a Logstash instance which takes gelf messages as input on UDP port 44622.

Build the image with

docker build -t logstash logstash/

Run it with

docker run --rm -p 44622:44622/udp logstash

Configuration

The Logstash pipeline configuration is specified in logstash/pipeline/logstash.conf.

input {
    gelf {
        use_udp => true
        port => 44622
    }
}
filter {
    # If a log message can be parsed as json, do so, and populate the
    # log event with fields found.
    json {
        skip_on_invalid_json => "true"
        source => "message"
    }
    # Convert the level field to an integer
    mutate {
        convert => {
            "level" => "integer"
        }
    }
}
output {
    stdout {
       }
    file {
        path => "/usr/share/logstash/logs/output.log"
    }
}

Run the example

Build the docker-erlang-example image:

docker build -t dockerwatch .

To forward log events (single line printouts to STDOUT) from the docker-erlang-example image into the Logstash container, use the gelf log driver and specify the UDP port number, for example:

docker run -p 8443:8443 --init --volume="$PWD/ssl:/etc/ssl/certs" --log-driver=gelf --log-opt gelf-address=udp://0.0.0.0:44622 dockerwatch

In general, a single line printout from Erlang's Logger can for instance be:

2018-11-12T15:32:20.284863+00:00 notice: Hello world

When forwarded to Logstash as is, this will result in the following log event:

{
          "image_id" => "sha256:63bb61c199d0d649d178cfdbedfc88e6253f24e534f09fd15c3ef79302931ed0",
           "command" => "/dockerwatch/bin/dockerwatch console",
           "version" => "1.1",
       "source_host" => "172.17.0.1",
      "container_id" => "a83600195dd0867c5996ea625d7f7c5fc1b87a97043c649a7cb0ed52058d75f1",
               "tag" => "a83600195dd0",
             "level" => 6,
        "image_name" => "erlang-dockerwatch",
        "@timestamp" => 2018-11-12T15:32:20.463Z,
    "container_name" => "epic_chebyshev",
           "message" => "2018-11-12T15:32:20.284863+00:00 notice: Hello world\r",
              "host" => "elxa19vlx02",
           "created" => "2018-11-12T15:12:55.198308117Z",
          "@version" => "1"
}

Notice that the message itself includes a timestamp, but it is not exactly the same as the @timestamp field of the log event. Also, the level field of the log event is 6 (info), while the level of the actual message was notice (5). To overcome this, and popluate the Logstash log event with the real values from a logger event, we can format the log event as json and let Logstash parse it (enabled by the json filter specified in logstash.conf). The following sys.config can be used:

[{kernel, [{logger,
            [%% Set formatter template to print jason
             {handler,default,logger_std_h,
              #{formatter=>
                    {logger_formatter,
                     #{template=>
                           ["{ \"@timestamp\": \"",time,
                            "\", \"level\": \"",{level_int,[level_int],[level]},
                            "\", \"message\": \"",msg,"\" }\n"]}}}},

             %% Add a primary filter to insert 'level_int' field in metadata
             {filters,log,
              [{level_int,{fun dockerwatch_filter:add_level_int/2,[]}}]},

             %% Set log level 'debug' for module dockerwatch - to get
             %% some log printouts when sending requests
             {module_level,debug,[dockerwatch]}
            ]}
          ]}
].

The log printout from Logger will now be:

{ "@timestamp": "2018-11-12T15:41:05.531172+00:00", "level": "5", "message": "Hello world" }

And the Logstash log event will be:

{
          "image_id" => "sha256:63bb61c199d0d649d178cfdbedfc88e6253f24e534f09fd15c3ef79302931ed0",
           "command" => "/dockerwatch/bin/dockerwatch console",
           "version" => "1.1",
       "source_host" => "172.17.0.1",
      "container_id" => "a83600195dd0867c5996ea625d7f7c5fc1b87a97043c649a7cb0ed52058d75f1",
               "tag" => "a83600195dd0",
             "level" => 5,
        "image_name" => "erlang-dockerwatch",
        "@timestamp" => 2018-11-12T15:41:05.531Z,
    "container_name" => "epic_chebyshev",
           "message" => "Hello world",
              "host" => "elxa19vlx02",
           "created" => "2018-11-12T15:12:55.198308117Z",
          "@version" => "1"
}

So, instead of including all information in the message field, the log event is populated with the actual timestamp, level and message as specified by the logging client.

NOTE: A currently necessary trick to get the level correct is to include a level_int field in the logger metadata, allowing the level to be represented as an integer. This is done by the primary logger filter specified in sys.config.

The docker-erlang-example is modified to issue a log event for each REST request it receives. Using the curl example lines from the simple example will therefore trigger a few events which can be seen on stdout in the logstash container.

Logstash also prints all events to a file (see output plugin file in logstash.conf). This is done mainly for the Travis build - which checks the number of lines in this file.