skip to Main Content

A couple of docker-containers, running inside a Kubernetes Cluster:

PHP
Nginx
MySQL
Redis

There is a Graylog-instance (version 3), that listens to the stdout and stderr from those containers.

In the PHP-container, there is a Symfony-application, that uses Monolog to log errors, info, warnings and notices and what-not. Monolog usually logs to a file, defined in /config/packages/prod/monolog.yaml like this:

monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_http_codes: [404, 405]
        nested:
            type: rotating_file
            path: "%kernel.logs_dir%/%kernel.environment%.log"   # <-- Here
            level: debug

My operations-department said:

Graylog cannot see any files inside the containers. That is just how it works

My question

How do I set it up, so all my errors, warnings, notices, info, etc., is to be found in Graylog?


Solution attempt 1: Get Monolog-logger to print to the console

I’ve tried doing all kinds of backflips in /config/packages/prod/monolog.yaml, to get code like this:

// src/Controller/YourController.php
namespace AppController;

use PsrLogLoggerInterface;
...

class YourController extends AbstractController
{
    public function yourAction(LoggerInterface $logger): Response
    {
        $logger->info('This is an informational log');
        return new Response('Check the logs!');
    }
}

to write logs to stdout or stderr. But nothing is printed in the stdout.

My (best) attempts:

        console:
            type:   console
            process_psr_3_messages: false
            channels: ['!event', '!doctrine', '!console']

        syslog_handler:
            type: syslog
            level: debug


Solution attempt 2: Just use error_log()

The error_log('my message')-function does output to the console. Yay!
It feels dumb though, that I have this huge logging-library (Monolog), when I in the end just use error_log() anyway.

Plus, I would like for all errors and warnings to be caught and sent to Graylog – and not just my own inserted error_log-statements.

Another part of this is that this is an example of my output, when I use error_log():

projectname-php-1         | NOTICE: PHP message: ZZZ ErrorLogStatement: Error.log().
projectname-nginx-1       | 2023/09/22 12:56:17 [error] 10#10: *20 FastCGI sent in stderr: "PHP message: ZZZ ErrorLogStatement: Error.log()" while reading response header from upstream, client: 172.20.0.1, server: symfony.local, request: "GET /my-custom-endpoint?test_case=all_logs HTTP/1.1", upstream: "fastcgi://172.20.0.5:9000", host: "127.0.0.1:8200"
projectname-nginx-1       | 172.20.0.1 - - [22/Sep/2023:12:56:17 +0000] "GET /my-custom-endpoint?test_case=all_logs HTTP/1.1" 200 35 "-" "PostmanRuntime/7.32.3" "-"
projectname-php-1         | 172.20.0.6 -  22/Sep/2023:12:56:02 +0000 "GET /index.php" 200

Which means that both my Nginx-container and my PHP-container outputs the error, which adds a lot of redundant lines in my Graylog. I can filter these, but still! It’s messy.


Solution attempt 3: "Link" my error-log to stdout

I found this suggestion here: Make a Docker application write to stdout.

Which would be to "link" the rotating error-log file, to stdout (and then maybe remove the rotating of that file).

So I tried to run this:

ln -sf /dev/stdout /var/www/docroot/var/log/my-log-file.log

But I ran into this error:

The stream or file: "/var/www/docroot/var/log/prod-2023-09-22.log" could not be opened in append mode: failed to open stream: No such file or directory.

And even if I got that to work… It seems hacky and not like the right approach. So I stopped trying to get this to work.


Solution attempt 4: Send errors from Monolog to Graylog via it’s API

This would be the ideal solution. I was just hoping for a simpler solution.
But this is what I’m fiddling with now.

2

Answers


  1. In order to send log messages from a Symfony application running in a Docker container inside a Kubernetes cluster to a Graylog instance, you might test the support for Graylog through the GELF (Graylog Extended Log Format) message format of the Monolog library.
    By using the GELF handler in Monolog, you should be able to directly send log messages to a Graylog instance.

    Add the required package in your Symfony application’s codebase:

    composer require graylog2/gelf-php
    

    This will update your composer.json and composer.lock files, adding the new dependency.
    I suppose your Dockerfile includes a line like RUN composer install --no-progress --no-interaction --optimize-autoloader: When the Docker image is rebuilt, the composer install command will install the graylog2/gelf-php package along with your other dependencies.

    Then, modify your Symfony monolog.yaml configuration file to include the Gelf handler:

    monolog:
        handlers:
            main:
                type: fingers_crossed
                action_level: error
                handler: gelf
                excluded_http_codes: [404, 405]
            gelf:
                type: gelf
                publisher:
                    hostname: "%env(GRAYLOG_HOSTNAME)%"
                    port: "%env(GRAYLOG_PORT)%"
                level: debug
    

    In your .env or Kubernetes ConfigMap, you can specify the GRAYLOG_HOSTNAME and GRAYLOG_PORT values:

    GRAYLOG_HOSTNAME=graylog.example.com
    GRAYLOG_PORT=12201
    

    The components and data flow, with a Symfony application uses Monolog with a GELF handler to send logs directly to a Graylog instance, would look like this:

    +----------------+             +-----------------+              +--------------+
    |  Kubernetes    |             |   Docker        |              |  Graylog     |
    |  Cluster       |             |  Container      |              |  Instance    |
    |                |             |  (PHP/Symfony)  |              |              |
    |  +----------+  |  HTTP Req.  |  +-----------+  |  Logs (GELF) |  +-------+   |
    |  |          |------------------>|           |------------------->|        |  |
    |  |  User    |  |             |  |  Symfony  |  |              |  |        |  |
    |  |          |<------------------|           |<-------------------|        |  |
    |  +----------+  |  HTTP Resp. |  +-----------+  |              |  +--------+  |
    |                |             |                 |              |              |
    +----------------+             +-----------------+              +--------------+
    
    • A user interacts with the Symfony application hosted within a Docker container in a Kubernetes cluster.
    • Upon receiving an HTTP request, the Symfony application processes it. When an event that needs to be logged occurs, Monolog sends the log directly to Graylog.
    • The log message is in the GELF format and sent to the specified Graylog instance using the GELF handler.
    • Graylog ingests and processes the log messages, making them available for search and analysis.

    In this setup, Monolog directly communicates with Graylog, circumventing the need to write logs to a file or console within the Docker container. This should address the limitation mentioned by your operations department, as Graylog does not need to access files inside the container.

    Login or Signup to reply.
  2. According to your question, the PHP SAPI container (PHP-FPM) is already configured with your Graylog based listener as you have demonstrated with error_log() which by default is going to the SAPI error log channel and that is where you want to have it.

    To make Monolog use that configuration, use the ErrorLogHandler one of the standard handlers.

    monolog:
       handlers:
           main:
               type:  error_log
               level: debug
    

    That is, you don’t plaster your code with error_log() calls, but more PSR-3 dedicated equivalents, that ship with the log level in the method name and more dedicated configuration options within your Symfony-App while retaining a last-resort error_log() escape hatch still integrated and working (relatively) out of the box (that is, in Graylog here).

    And I can’t imagine why you want to have log rotation within a container, however as this is Monolog, you have all the options including using multiple targets etc. .

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search