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
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:
This will update your
composer.json
andcomposer.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, thecomposer install
command will install thegraylog2/gelf-php
package along with your other dependencies.Then, modify your Symfony
monolog.yaml
configuration file to include the Gelf handler:In your
.env
or Kubernetes ConfigMap, you can specify theGRAYLOG_HOSTNAME
andGRAYLOG_PORT
values: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:
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.
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.
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-resorterror_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. .