I have set up Monolog for our Symfony2 project to email out critical errors to us when they occur.
However I would also like to log non-critical errors also, and to email these errors out to different recipients. I am struggling to see this in the documentation however it looks like it should be possible. I have set up the config as follows:
parameters:
error_mail_sender: error#mysite.com
error_mail_recipients: [siteerrors#mysite.com]
critical_error_mail_recipients: [siteerrors#mysite.com, developer#developers.com]
monolog:
handlers:
main_critical:
type: fingers_crossed
action_level: critical
handler: grouped_critical
bubble: false
main_error:
type: fingers_crossed
action_level: error
handler: grouped_error
grouped_critical:
type: group
members: [streamed, buffered_critical]
grouped_error:
type: group
members: [streamed, buffered_error]
streamed:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
buffered_critical:
type: buffer
handler: swift_critical
buffered_error:
type: buffer
handler: swift_error
swift_critical:
type: swift_mailer
from_email: %error_mail_sender%
to_email: %error_mail_recipients%
subject: Critical error occurred!
level: debug
swift_error:
type: swift_mailer
from_email: %error_mail_sender%
to_email: %critical_error_mail_recipients%
subject: Non-critical error occurred
level: debug
With this set up we receive the critical errors but don't get the non-critical errors.
This set up was loosely based on the (unaccepted) answer to this question: How to include the severity of a log in the e-mail subject?. (I would have up-voted the answer had it worked for me!!)
Can anyone spot what is wrong with this?
Thanks!
The problem seems to be bubble: false in the first handler, that means it'll stop propagating messages to other handlers.
Another note, I would remove the group and the streamed handler from grouped_critical, because it will already receive errors from grouped_error, resulting in duplicate entries in the log file.
So this should work:
parameters:
error_mail_sender: error#mysite.com
error_mail_recipients: [siteerrors#mysite.com]
critical_error_mail_recipients: [developer#developers.com]
monolog:
handlers:
main_critical:
type: fingers_crossed
action_level: critical
handler: buffered_critical
buffered_critical:
type: buffer
handler: swift_critical
swift_critical:
type: swift_mailer
from_email: %error_mail_sender%
to_email: %error_mail_recipients%
subject: Critical error occurred!
level: debug
main_error:
type: fingers_crossed
action_level: error
handler: grouped_error
grouped_error:
type: group
members: [streamed, buffered_error]
streamed:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
buffered_error:
type: buffer
handler: swift_error
swift_error:
type: swift_mailer
from_email: %error_mail_sender%
to_email: %critical_error_mail_recipients%
subject: Non-critical error occurred
level: debug
I reordered them to make it easier to see the two handler chains as well.
Related
Alright so I am trying to send messages to Microsoft teams using Symfony Monolog, but I cannot seem to find any sort of guides pointing how to do it or such.
I have tried doing it the same way as you would do it using Slack
I am using Symfony 3.4
monolog:
handlers:
main:
type: fingers_crossed
action_level: critical
handler: mongo
# nested:
# type: stream
# path: %kernel.logs_dir%/%kernel.environment%.log
# level: critical
teams:
type: teams
level: critical
token: '%teams_token%'
channel: '#errors'
include_extra: true
You have to create your own Handler like the SlackHandler.
<?php
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
class MicrosoftTeamsHandler extends AbstractProcessingHandler
{
...
Check this response Custom monolog handler for default monolog in Symfony 2 to register your custom handler and use it
I have a working ELK stack connected to Redis.
I also have a working stateless Symfony 4 application and I want to send all the production logs to my Redis.
I know Monolog has a Redis handler, but I don't know how I'm supposed to tweak the config/prod/monolog.yaml file to accomplish this of if there’s another approach.
This is how it looks right now:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404]
nested:
type: stream
path: "php://stderr"
level: debug
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
path: "php://stderr"
deprecation_filter:
type: filter
handler: deprecation
max_level: info
channels: ["php"]
The approach I took was, first installing the predis client:
composer require predis/predis
Then create a custom service class that extends the RedisHandler class that comes with the Monolog package:
namespace App\Service\Monolog\Handler;
use Monolog\Handler\RedisHandler;
use Monolog\Logger;
use Predis\Client as PredisClient;
class Redis extends RedisHandler
{
public function __construct( $host, $port = 6379, $level = Logger::DEBUG, $bubble = true, $capSize = false)
{
$predis = new PredisClient( "tcp://$host:$port" );
$key = 'logstash';
parent::__construct($predis, $key, $level, $bubble, $capSize);
}
}
Next, activate the service we just created on the services.yml config file:
services:
monolog.handler.redis:
class: App\Service\Monolog\Handler\Redis
arguments: [ '%redis.host%' ]
Be sure the parameter redis.host is set and points to your Redis server. In my case, my parameter value is the IP of my Redis server.
I added other parameters to the class like port and log level. You can set it at the moment of instantiating your service like with the host parameter.
Finally, configure your custom log handler service in your monolog.yaml config file. In my case, I need it only the production logs with the config as follow:
handlers:
custom:
type: service
id: monolog.handler.redis
level: debug
channels: ['!event']
I have the following monolog handler definitions:
# config_prod.yml
app_generic:
type: rotating_file
max_files: 15
path: "%param.app_logging_config.log_generic_file%"
level: info
channels: [app]
app_api:
max_files: 15
path: "%param.app_logging_config.log_api_file%"
level: info
channels: [app]
level: info
app_response:
max_files: 15
path: "%param.app_logging_config.log_response_file%"
channels: [app]
level: info
And in service.yml, my intention is to inject monolog (#logger) with an array of the above defined handlers.
#service.yml
app.app_logger:
class: AppBundle\Classes\AppLogger
arguments: ['#logger': ['#app_generic', '#app_api', '#app_response']]
calls:
- [init, ['%app_logging_config%']
tags:
- { name: monolog.logger, channel: app }
How does one pass arguments to an injected argument?
Update:
Re-reading the description, I was going for this approach, by just tagging on the service definition:
app.logger:
arguments: ['#logger']
tags:
- { name: monolog.logger, channel: app }
channels: ['app']
Or even ( if I understood correctly), adding a channels: ['app'] key and just having this in service argument:
app.logger:
arguments: ['#monolog.logger.app']
I have not been able to use ( or see via dump ) the handlers defined in config_prod.yml. I have placed these at top because of other "fingers_crossed" handlers I thought may interfere.
I woud like to know, why neither of above (documented) approaches seem to work?
Handlers in
monolog:
handlers:
handler1: ...
handler2: ...
are automatically injected in #logger service.
It looks like you need new custom logger. Please read about The DependencyInjection Component
Create dependencies
services:
app_generic:
....
app_api:
....
app_response:
....
Create custom_logger service
custom_logger:
class: Monolog\Logger
arguments: ["my logger", ["#app_generic", "#app_api", "#app_response"]
Inject you custom logger in you service
app.app_logger:
class: AppBundle\Classes\AppLogger
arguments: ['#custom_logger']
calls:
- [init, ['%app_logging_config%']
I've defined a service to handle logging of records with specific text (e.g. "matched route" and such). I'm successfully writing those records out to a separate file.
However, what I want to avoid is writing them a second time to the primary log file. I've tried tagging my service with a channel and telling the main stream log to ignore that channel, but it occurs to me that it won't work since, in essence, the main stream logging handler matches everything.
Any advice on how to proceed?
EDIT: Here's what I have at the moment:
monolog:
channels: ['noise']
handlers:
eliminate_noise_logger:
type: service
id: common.eliminate_noise_logger
channels: ['noise']
streamed:
type: stream
level: info
path: "%kernel.logs_dir%/%kernel.environment%.log"
formatter: monolog.formatter.session_request
channels: ['!noise']
console:
type: console
verbosity_levels:
VERBOSITY_NORMAL: INFO
And my services definition:
common.eliminate_noise_logger:
class: path\to\class
arguments: ["%kernel.logs_dir%/%kernel.environment%.noise.log", ["str1", "str2", "str3"]]
tags:
- { name: monolog.logger, channel: noise }
This is how I do
#config.yml
monolog:
use_microseconds: false
channels: ['secondary']
handlers:
main:
path: %kernel.logs_dir%/%kernel.language%/%kernel.environment%.log
type : fingers_crossed
action_level : error
handler : nested
channels : ['!secondary']
secondary:
type: rotating_file
max_files: 10
path: %kernel.logs_dir%/%kernel.language%/%kernel.environment%.secondary.log
level: info
channels: [secondary]
I'm facing some problems with configuring Monolog to handle "nested loggers".
What I want to do:
Log from services to dedicated files (one per service) AND from all services to one file. Each logger should be also handled by monolog.handlers.console.
Why I want to do
Each service has logic, but can use other services from DI. I want to know what exactly one service logs, so I want dedicated logger (with custom channel and custom log file) for each service. But when services relies on other services, I want to read logs in chronological order in one file.
What I have
app/config.yml:
monolog:
handlers:
my_handler:
type: stream
path: %kernel.logs_dir%/%kernel.environment%.my.log
level: info
handler: my_bundle_handler
src/My/Bundle/Resources/config/config.yml
services:
# LOGGERS
my_logger:
class: Symfony\Bridge\Monolog\Logger
arguments: [my_logger]
calls:
- [pushHandler, [#monolog.handler.console]]
- [pushHandler, [#my_bundle_handler]]
tags:
- { name: monolog.logger, channel: my_channel}
# HANDLERS
my_bundle_handler:
abstract: true # Without it it will throw exception
type: group
members: [my_service_handler]
channels: ["my_channel"]
tags:
- { name: log_handler }
my_service_handler:
class: Monolog\Handler\StreamHandler
arguments: [%kernel.logs_dir%/%kernel.environment%.my_service.log, 100]
channels: ["my_channel"]
tags:
- { name: log_handler }
It does not work as expected. It logs to my_service.log, but not to my.log.
Is there possibility to achieve what I want?
Channels in monolog works exactly like you want. Sample monolog configuration
app/config.yml
monolog:
channels: ['deletion']
handlers:
main:
type: fingers_crossed
action_level: error
handler: grouped_main
formatter: "monolog.formatter.request"
buffer_size: 30
# if you will set stop_buffering: true - you will get ALL events after first error. It could produce huge logs for console
stop_buffering: false
# this is for getsentry.com error catching
sentry:
type: raven
dsn: '%sentry_url%'
level: notice
# Groups
grouped_main:
type: group
members: [sentry, streamed_main, streamed_main_brief]
# Streams
streamed_main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
streamed_main_brief:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%_brief.log"
formatter: monolog.brief_formatter
console:
type: console
formatter: monolog.console_formatter
deletion:
# log deletion related messages
level: debug
type: stream
path: '%kernel.logs_dir%/deletion.log'
channels: ['deletion']
formatter: monolog.brief_formatter
services:
my_service:
class: Monolog\Processor\IntrospectionProcessor
tags:
- { name: monolog.processor }
monolog.console_formatter:
class: Symfony\Bridge\Monolog\Formatter\ConsoleFormatter
arguments:
- "<fg=black;bg=green>[%%datetime%%]</fg=black;bg=green> %%start_tag%%%%message%%%%end_tag%%\n"
monolog.brief_formatter:
class: Monolog\Formatter\LineFormatter
arguments:
- "[%%datetime%%] %%message%%\n"
# default format is
# - "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
If you will add code like
$this->getContainer()->get("logger")->info("Sample info");
$this->getContainer()->get("monolog.logger.deletion")->info("Deletion channel info");
$this->getContainer()->get("monolog.logger.deletion")->error("Deletion channel error");
$this->getContainer()->get("monolog.logger.deletion")->info("Deletion channel info #2");
You will get 3 log files with such a content
Channel log file
deletion.log
[2016-11-11 12:43:18] Deletion channel info
[2016-11-11 12:43:18] Deletion channel error
[2016-11-11 12:43:19] Deletion channel info #2
Default env log file
dev.log
[2016-11-11 12:43:18] event.DEBUG: Notified event "console.command" to listener "Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure". [] {"file":"...."}
[2016-11-11 12:43:18] event.DEBUG: Notified event "console.command" to listener "Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand". [] {"file":"...."}
[2016-11-11 12:43:18] app.INFO: Sample info [] {"file":"..."}
[2016-11-11 12:43:18] deletion.INFO: Deletion channel info [] {"file":"...."}
[2016-11-11 12:43:18] deletion.ERROR: Deletion channel error [] {"...."}
Brief env.log
[2016-11-11 12:43:18] Notified event "console.command" to listener "Symfony\Component\HttpKernel\EventListener\DebugHandlersListener::configure".
[2016-11-11 12:43:18] Notified event "console.command" to listener "Symfony\Bridge\Monolog\Handler\ConsoleHandler::onCommand".
[2016-11-11 12:43:18] Sample info
[2016-11-11 12:43:18] Deletion channel info
[2016-11-11 12:43:18] Deletion channel error
Also notice, that because of stop_buffering: false notice after error will not appear at dev.log, dev_brief.log, but will appear at deletion.log
And you should try sentry - its great product and his owners are cool guys :)