├── CONTRIBUTING.md ├── Command ├── BaseCommand.php ├── DecrementCommand.php ├── GaugeCommand.php ├── IncrementCommand.php ├── SetCommand.php └── TimingCommand.php ├── DependencyInjection ├── Compiler │ └── CollectorCompilerPass.php ├── Configuration.php └── LiuggioStatsDClientExtension.php ├── Exception.php ├── LICENSE ├── Listener └── StatsDCollectorListener.php ├── LiuggioStatsDClientBundle.php ├── Resources ├── config │ ├── collectors.yml │ └── services.yml └── doc │ ├── advanced.md │ └── installation.md ├── Service └── StatsDCollectorService.php ├── StatsCollector ├── DbalStatsCollector.php ├── ExceptionStatsCollector.php ├── MemoryStatsCollector.php ├── StatsCollector.php ├── StatsCollectorInterface.php ├── TimeStatsCollector.php ├── UserStatsCollector.php └── VisitorStatsCollector.php └── composer.json /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to StatsD Client Bundle 2 | 3 | :+1::tada: Thanks for taking the time to contribute! 4 | Your Contributions, Suggestions and Feedbacks are valuable to us. 5 | 6 | The following is a set of guidelines for contributing to StatsD Client Bundle. These are guidelines, not rules. 7 | 8 | ## What should I know before I get started? 9 | 10 | Please refer to README.md file for articles and documentations which will help you understand the project better. 11 | 12 | Active contribution and patches are very welcome. 13 | To keep things in shape we have quite a bunch of unit tests. If you're submitting pull requests please make sure that they are still passing and if you add functionality please 14 | take a look at the coverage as well it should be pretty high :) 15 | 16 | - First fork and then clone the repository 17 | 18 | ``` 19 | git clone git://github.com/liuggio/StatsDClientBundle.git 20 | cd StatsDClientBundle 21 | ``` 22 | 23 | - Create a new branch 24 | ``` 25 | git checkout -b "your-branch-name" 26 | ``` 27 | 28 | - Install vendors: 29 | 30 | ``` bash 31 | php composer.phar install --dev 32 | ``` 33 | 34 | - This will give you proper results: 35 | 36 | ``` bash 37 | phpunit --coverage-html reports 38 | ``` 39 | 40 | ## How Can I Contribute? 41 | 42 | #### Reporting Bugs by creating Issues 43 | #### Suggesting Enhancements 44 | #### Getting Started with first contribution - Check out Issues listed 45 | 46 | ## Pull Requests 47 | 48 | Create a Pull Request in order to get your code changes added to the repository. 49 | 50 | The process described here has several goals: 51 | - Make sure you create PR’s from a different branch and not from the master branch of your fork. 52 | - Maintain Code quality. 53 | - Fix problems that are important. 54 | - Engage with the community to get reviews and work towards better. 55 | 56 | Thanks :) 57 | -------------------------------------------------------------------------------- /Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | getContainer()->get('liuggio_stats_d_client.factory'); 12 | } 13 | 14 | protected function getClientService() 15 | { 16 | return $this->getContainer()->get('liuggio_stats_d_client.service'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Command/DecrementCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class DecrementCommand extends BaseCommand 15 | { 16 | protected function configure() 17 | { 18 | parent::configure(); 19 | 20 | $this 21 | ->setName('statsd:decrement') 22 | ->setDescription('Decreases a counter by 1 in StatsD') 23 | ->addArgument('key', InputArgument::REQUIRED, 'The key') 24 | ->setHelp(<<%command.name% command sends a decrement metric to StatsD: 26 | 27 | %command.full_name% 28 | 29 | EOT 30 | ); 31 | } 32 | 33 | protected function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $data = $this->getDataFactory()->decrement($input->getArgument('key')); 36 | $this->getClientService()->send($data); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Command/GaugeCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class GaugeCommand extends BaseCommand 15 | { 16 | protected function configure() 17 | { 18 | parent::configure(); 19 | 20 | $this 21 | ->setName('statsd:gauge') 22 | ->setDescription('Sends a gauge metric to StatsD') 23 | ->addArgument('key', InputArgument::REQUIRED, 'The key') 24 | ->addArgument('value', InputArgument::REQUIRED, 'The value') 25 | ->setHelp(<<%command.name% command sends a gauge metric to StatsD: 27 | 28 | %command.full_name% 29 | 30 | EOT 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $data = $this->getDataFactory()->gauge( 37 | $input->getArgument('key'), 38 | $input->getArgument('value') 39 | ); 40 | $this->getClientService()->send($data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Command/IncrementCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class IncrementCommand extends BaseCommand 15 | { 16 | protected function configure() 17 | { 18 | parent::configure(); 19 | 20 | $this 21 | ->setName('statsd:increment') 22 | ->setDescription('Increases a counter by 1 in StatsD') 23 | ->addArgument('key', InputArgument::REQUIRED, 'The key') 24 | ->setHelp(<<%command.name% command sends an increment metric to StatsD: 26 | 27 | %command.full_name% 28 | 29 | EOT 30 | ); 31 | } 32 | 33 | protected function execute(InputInterface $input, OutputInterface $output) 34 | { 35 | $data = $this->getDataFactory()->increment($input->getArgument('key')); 36 | $this->getClientService()->send($data); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Command/SetCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class SetCommand extends BaseCommand 15 | { 16 | protected function configure() 17 | { 18 | parent::configure(); 19 | 20 | $this 21 | ->setName('statsd:set') 22 | ->setDescription('Sends a set metric to StatsD') 23 | ->addArgument('key', InputArgument::REQUIRED, 'The key') 24 | ->addArgument('value', InputArgument::REQUIRED, 'The value') 25 | ->setHelp(<<%command.full_name% command sends a set metric to StatsD: 27 | 28 | ./app/console %command.full_name% 29 | 30 | EOT 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $data = $this->getDataFactory()->set( 37 | $input->getArgument('key'), 38 | $input->getArgument('value') 39 | ); 40 | $this->getClientService()->send($data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Command/TimingCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TimingCommand extends BaseCommand 15 | { 16 | protected function configure() 17 | { 18 | parent::configure(); 19 | 20 | $this 21 | ->setName('statsd:timing') 22 | ->setDescription('Sends a timing metric to StatsD') 23 | ->addArgument('key', InputArgument::REQUIRED, 'The key') 24 | ->addArgument('value', InputArgument::REQUIRED, 'The value') 25 | ->setHelp(<<%command.full_name% command sends a timing metric to StatsD: 27 | 28 | ./app/console %command.full_name% 29 | 30 | EOT 31 | ); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $data = $this->getDataFactory()->timing( 37 | $input->getArgument('key'), 38 | $input->getArgument('value') 39 | ); 40 | $this->getClientService()->send($data); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DependencyInjection/Compiler/CollectorCompilerPass.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class CollectorCompilerPass implements CompilerPassInterface 16 | { 17 | public function process(ContainerBuilder $container) 18 | { 19 | if (false === $container->hasDefinition('liuggio_stats_d_client.collector.service')) { 20 | return; 21 | } 22 | 23 | $serviceDefinition = $container->getDefinition('liuggio_stats_d_client.collector.service'); 24 | $collectorsEnabled = $container->getParameter('liuggio_stats_d_client.collectors'); 25 | $collectorIsEnabled = $container->getParameter('liuggio_stats_d_client.enable_collector'); 26 | 27 | if ($collectorIsEnabled && null !== $collectorsEnabled && \is_array($collectorsEnabled) && \count($collectorsEnabled) > 0) { 28 | foreach ($container->findTaggedServiceIds('stats_d_collector') as $id => $attributes) { 29 | // only if there's on the parameter means that is enable 30 | if (isset($collectorsEnabled[$id])) { 31 | // setterInjection 32 | $collectorReference = new Reference($id); 33 | $collectorDefinition = $container->getDefinition($id); 34 | $collectorDefinition->addMethodCall('setStatsdDataFactory', [new Reference('liuggio_stats_d_client.factory')]); 35 | $collectorDefinition->addMethodCall('setStatsDataKey', [$collectorsEnabled[$id]]); 36 | // adding to this collector the the collection 37 | $serviceDefinition->addMethodCall('add', [$collectorReference]); 38 | // if is doctrine.dbal 39 | // we need to attach also the collector to the dbal sql logger chain 40 | if ('liuggio_stats_d_client.collector.dbal' === $id) { 41 | $chainLogger = $container->getDefinition('doctrine.dbal.logger.chain'); 42 | if (null !== $chainLogger) { 43 | $chainLogger->addMethodCall('addLogger', [$collectorReference]); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | debug = $debug; 20 | } 21 | 22 | /** 23 | * {@inheritdoc} 24 | */ 25 | public function getConfigTreeBuilder() 26 | { 27 | $treeBuilder = new TreeBuilder('liuggio_stats_d_client'); 28 | $rootNode = method_exists(TreeBuilder::class, 'getRootNode') ? $treeBuilder->getRootNode() : $treeBuilder->root('liuggio_stats_d_client'); 29 | 30 | $rootNode 31 | ->children() 32 | ->booleanNode('enable_collector')->defaultFalse()->end() 33 | ->arrayNode('connection') 34 | ->children() 35 | ->scalarNode('class')->defaultValue('Liuggio\StatsdClient\Sender\SocketSender')->end() 36 | ->scalarNode('debug_class')->defaultValue('Liuggio\StatsdClient\Sender\SysLogSender')->end() 37 | ->scalarNode('debug')->defaultValue($this->debug)->end() 38 | ->scalarNode('port')->defaultValue(8125)->end() 39 | ->scalarNode('host')->defaultValue('localhost')->end() 40 | ->scalarNode('reduce_packet')->defaultValue(true)->end() 41 | ->scalarNode('protocol')->defaultValue('udp')->end() 42 | ->scalarNode('fail_silently')->defaultValue(true)->end() 43 | ->end() 44 | ->end() 45 | ->arrayNode('collectors')->canBeUnset() 46 | ->prototype('scalar')->end() 47 | ->useAttributeAsKey('name') 48 | ->end() 49 | ->arrayNode('monolog')->canBeUnset() 50 | ->children() 51 | ->scalarNode('enable')->defaultValue(false)->end() 52 | ->scalarNode('prefix')->defaultValue('log')->end() 53 | ->scalarNode('level')->defaultValue('warning')->end() 54 | ->arrayNode('formatter') 55 | ->children() 56 | ->scalarNode('class')->defaultValue('Liuggio\StatsdClient\Monolog\Formatter\StatsDFormatter')->end() 57 | ->scalarNode('format')->defaultValue(null)->end() 58 | ->booleanNode('context_logging')->defaultValue(false)->end() 59 | ->booleanNode('extra_logging')->defaultValue(false)->end() 60 | ->scalarNode('words')->defaultValue(2)->end() 61 | ->end() 62 | ->end() 63 | ->end() 64 | ->end() 65 | ->end(); 66 | 67 | return $treeBuilder; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /DependencyInjection/LiuggioStatsDClientExtension.php: -------------------------------------------------------------------------------- 1 | getParameter('kernel.debug')); 25 | $config = $this->processConfiguration($configuration, $configs); 26 | 27 | foreach ($config['connection'] as $k => $v) { 28 | $container->setParameter($this->getAlias().'.connection.'.$k, $v); 29 | } 30 | $container->setParameter($this->getAlias().'.enable_collector', $config['enable_collector']); 31 | $container->setParameter($this->getAlias().'.collectors', $config['collectors']); 32 | 33 | $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 34 | $loader->load('services.yml'); 35 | 36 | if ($config['enable_collector']) { 37 | $loader->load('collectors.yml'); 38 | 39 | if (\count($config['collectors'])) { 40 | // Define the Listener 41 | $definition = new Definition('Liuggio\StatsDClientBundle\Listener\StatsDCollectorListener', 42 | [new Reference('liuggio_stats_d_client.collector.service')] 43 | ); 44 | $definition->addTag('kernel.event_subscriber'); 45 | $container->setDefinition('liuggio_stats_d_client.collector.listener', $definition); 46 | } 47 | } 48 | // monolog 49 | if (!empty($config['monolog']) && $config['monolog']['enable']) { 50 | $this->loadMonologHandler($config, $container); 51 | } 52 | 53 | // set the debug sender 54 | if ($config['connection']['debug']) { 55 | $senderService = new Definition($config['connection']['debug_class'], []); 56 | $container->setDefinition('liuggio_stats_d_client.sender.service', $senderService); 57 | } else { 58 | $senderService = new Definition($config['connection']['class'], [ 59 | $config['connection']['host'], 60 | $config['connection']['port'], 61 | $config['connection']['protocol'], 62 | ]); 63 | $container->setDefinition('liuggio_stats_d_client.sender.service', $senderService); 64 | } 65 | } 66 | 67 | private function convertLevelToConstant($level) 68 | { 69 | return \is_int($level) ? $level : \constant('Monolog\Logger::'.\strtoupper($level)); 70 | } 71 | 72 | /** 73 | * Loads the Monolog configuration. 74 | * 75 | * @param array $config A configuration array 76 | * @param ContainerBuilder $container A ContainerBuilder instance 77 | */ 78 | protected function loadMonologHandler(array $config, ContainerBuilder $container) 79 | { 80 | $def2 = new Definition($config['monolog']['formatter']['class'], [ 81 | $config['monolog']['formatter']['format'], 82 | $config['monolog']['formatter']['context_logging'], 83 | $config['monolog']['formatter']['extra_logging'], 84 | $config['monolog']['formatter']['words'], 85 | ] 86 | ); 87 | $container->setDefinition('monolog.formatter.statsd', $def2); 88 | 89 | $def = new Definition('Liuggio\StatsdClient\Monolog\Handler\StatsDHandler', [ 90 | new Reference('liuggio_stats_d_client.service'), 91 | new Reference('liuggio_stats_d_client.factory'), 92 | $config['monolog']['prefix'], 93 | $this->convertLevelToConstant($config['monolog']['level']), 94 | ]); 95 | $def->setPublic(false); 96 | $def->addMethodCall('setFormatter', [new Reference('monolog.formatter.statsd')]); 97 | 98 | $container->setDefinition('monolog.handler.statsd', $def); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Exception.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class StatsDCollectorListener implements EventSubscriberInterface 19 | { 20 | protected $collector; 21 | protected $exception; 22 | protected $children; 23 | protected $requests; 24 | protected $collectors; 25 | 26 | /** 27 | * Constructor. 28 | * 29 | * @param StatsDCollectorService $collector A collector instance 30 | * @param bool $onlyException true if the collector only collects data when an exception occurs, false otherwise 31 | * @param bool $onlyMasterRequests true if the collector only collects data when the request is a master request, false otherwise 32 | */ 33 | public function __construct(StatsDCollectorService $collector, $onlyException = false, $onlyMasterRequests = false) 34 | { 35 | $this->collector = $collector; 36 | $this->onlyException = (bool) $onlyException; 37 | $this->onlyMasterRequests = (bool) $onlyMasterRequests; 38 | $this->collectors = []; 39 | } 40 | 41 | /** 42 | * Handles the onKernelException event. 43 | * 44 | * @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance 45 | */ 46 | public function onKernelException(GetResponseForExceptionEvent $event) 47 | { 48 | if ($this->onlyMasterRequests && HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { 49 | return; 50 | } 51 | 52 | $this->exception = $event->getException(); 53 | } 54 | 55 | public function onKernelRequest(GetResponseEvent $event) 56 | { 57 | $this->requests[] = $event->getRequest(); 58 | } 59 | 60 | /** 61 | * Handles the onKernelResponse event. 62 | * 63 | * @param FilterResponseEvent $event A FilterResponseEvent instance 64 | */ 65 | public function onKernelResponse(FilterResponseEvent $event) 66 | { 67 | $master = HttpKernelInterface::MASTER_REQUEST === $event->getRequestType(); 68 | if ($this->onlyMasterRequests && !$master) { 69 | return; 70 | } 71 | 72 | if ($this->onlyException && null === $this->exception) { 73 | return; 74 | } 75 | 76 | $request = $event->getRequest(); 77 | $exception = $this->exception; 78 | $this->exception = null; 79 | 80 | $dataToSend = $this->collector->collect($master, $request, $event->getResponse(), $exception); 81 | 82 | if (null === $dataToSend || !\is_array($dataToSend) || \count($dataToSend) < 1) { 83 | return; 84 | } 85 | $this->collector->send($dataToSend); 86 | } 87 | 88 | public static function getSubscribedEvents() 89 | { 90 | return [ 91 | // kernel.request must be registered as early as possible to not break 92 | // when an exception is thrown in any other kernel.request listener 93 | KernelEvents::REQUEST => ['onKernelRequest', 1024], 94 | KernelEvents::RESPONSE => ['onKernelResponse', -100], 95 | KernelEvents::EXCEPTION => 'onKernelException', 96 | ]; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /LiuggioStatsDClientBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new CollectorCompilerPass()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /Resources/config/collectors.yml: -------------------------------------------------------------------------------- 1 | services: 2 | liuggio_stats_d_client.collector.visitor: 3 | class: 'Liuggio\StatsDClientBundle\StatsCollector\VisitorStatsCollector' 4 | calls: 5 | - ['setOnlyOnMasterResponse', [true]] 6 | tags: 7 | - { name: stats_d_collector } 8 | 9 | liuggio_stats_d_client.collector.memory: 10 | class: 'Liuggio\StatsDClientBundle\StatsCollector\MemoryStatsCollector' 11 | calls: 12 | - ['setOnlyOnMasterResponse', [true]] 13 | tags: 14 | - { name: stats_d_collector } 15 | 16 | liuggio_stats_d_client.collector.user: 17 | class: 'Liuggio\StatsDClientBundle\StatsCollector\UserStatsCollector' 18 | calls: 19 | - ['setSecurityContext', ['@security.authorization_checker']] 20 | - ['setOnlyOnMasterResponse', [true]] 21 | tags: 22 | - { name: stats_d_collector } 23 | 24 | liuggio_stats_d_client.collector.exception: 25 | class: 'Liuggio\StatsDClientBundle\StatsCollector\ExceptionStatsCollector' 26 | calls: 27 | - ['setOnlyOnMasterResponse', [false]] 28 | tags: 29 | - { name: stats_d_collector } 30 | 31 | liuggio_stats_d_client.collector.time: 32 | class: 'Liuggio\StatsDClientBundle\StatsCollector\TimeStatsCollector' 33 | calls: 34 | - ['setOnlyOnMasterResponse', [true]] 35 | tags: 36 | - { name: stats_d_collector } 37 | 38 | #special connector dbal is also a sqllogger 39 | liuggio_stats_d_client.collector.dbal: 40 | class: 'Liuggio\StatsDClientBundle\StatsCollector\DbalStatsCollector' 41 | arguments: ['query'] 42 | calls: 43 | - ['setOnlyOnMasterResponse', [true]] 44 | tags: 45 | - { name: stats_d_collector } 46 | -------------------------------------------------------------------------------- /Resources/config/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | # statsd-php-client 3 | liuggio_stats_d_client.factory: 4 | class: 'Liuggio\StatsdClient\Factory\StatsdDataFactory' 5 | arguments: 6 | - 'Liuggio\StatsdClient\Entity\StatsdData' 7 | 8 | liuggio_stats_d_client.service: 9 | class: 'Liuggio\StatsdClient\StatsdClient' 10 | arguments: 11 | - '@liuggio_stats_d_client.sender.service' 12 | - '%liuggio_stats_d_client.connection.reduce_packet%' 13 | - '%liuggio_stats_d_client.connection.fail_silently%' 14 | 15 | # collector service 16 | liuggio_stats_d_client.collector.service: 17 | class: 'Liuggio\StatsDClientBundle\Service\StatsDCollectorService' 18 | arguments: 19 | - '@liuggio_stats_d_client.service' 20 | 21 | # simplified service 22 | liuggio_stats_d.service: 23 | class: 'Liuggio\StatsdClient\Service\StatsdService' 24 | arguments: 25 | - '@liuggio_stats_d_client.service' 26 | - '@liuggio_stats_d_client.factory' 27 | 28 | statsd: 29 | alias: liuggio_stats_d.service 30 | -------------------------------------------------------------------------------- /Resources/doc/advanced.md: -------------------------------------------------------------------------------- 1 | Advanced use 2 | ============ 3 | 4 | ## How to create your personal Collector 5 | 6 | 7 | - Create a class that extends StatsCollector then create the `collect` function 8 | 9 | ```php 10 | use Liuggio\StatsDClientBundle\StatsCollector\StatsCollector; 11 | 12 | use Symfony\Component\HttpFoundation\Request; 13 | use Symfony\Component\HttpFoundation\Response; 14 | 15 | class ExceptionStatsCollector extends StatsCollector 16 | { 17 | //... 18 | 19 | public function collect(Request $request, Response $response, \Exception $exception = null) 20 | { 21 | if ... 22 | $statData = $this->getStatsdDataFactory()->increment($this->getStatsDataKey()); 23 | $this->addStatsData($statData); 24 | 25 | return true; 26 | } 27 | } 28 | ``` 29 | 30 | 31 | - add the service 32 | 33 | ```yaml 34 | liuggio_stats_d_client.collector.exception: 35 | class: %THE CLASS% 36 | tags: 37 | - { name: stats_d_collector} #this is important, this will be selected by the CollectorService 38 | 39 | ``` 40 | 41 | - enable it in the config.yml 42 | 43 | ```yaml 44 | collectors: 45 | # serviceReference: prefix 46 | liuggio_stats_d_client.collector.exception: 'YOURNAME.exception' 47 | ``` 48 | 49 | 50 | 51 | 52 | ## The Flow 53 | 54 | The CollectorService collects all the collector classes that have a tag `data_collector` in their definition 55 | 56 | 1. A listener `StatsDCollectorListener` is subscribed to kernel events only if the parameter `enable_collector` is true 57 | 58 | 2. When an Event is thrown, `StatsDCollectorService` is called withe arguments (Response, Request, Exception) 59 | 60 | 3. `StatsDCollectorService` calls all the registered collectors that have `StatsCollectorInterface` 61 | 62 | 4. `StatsDCollectorService` sends all data to StatsdClientInterface 63 | 64 | 5. `StatsdClientInterface` provides to send to the server 65 | 66 | 6. On Graphite you'll have all data and all that happens in your Symfony2 application 67 | 68 | A similar pattern is used by the Profiler in Symfony 69 | -------------------------------------------------------------------------------- /Resources/doc/installation.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | 1. First, add the dependent bundles to the vendor/bundles directory. Add the following lines to the composer.json file 5 | 6 | ```json 7 | "require": { 8 | # .. 9 | "liuggio/statsd-client-bundle": "1.6.*", 10 | # .. 11 | } 12 | ``` 13 | 14 | 2. Then run `composer install` 15 | 16 | 17 | 3. Then add in your `app/AppKernel` 18 | 19 | ```php 20 | 21 | class AppKernel extends Kernel 22 | { 23 | public function registerBundles() 24 | { 25 | $bundles = array( 26 | // ... 27 | new Liuggio\StatsDClientBundle\LiuggioStatsDClientBundle(), 28 | // ... 29 | 30 | ``` 31 | 32 | 4. Then add to config/yaml the minimal configuration 33 | 34 | ```yaml 35 | 36 | # app/config/config.yml 37 | liuggio_stats_d_client: 38 | connection: 39 | host: localhost 40 | port: 8125 41 | 42 | ``` 43 | 44 | 5. When you are in develop env (mmm when kernel.debug is true) the packets use the SyslogSender 45 | see [full-configuration](#full-configuration--max-power) in order to modify it 46 | 47 | 48 | Working with the `Service` 49 | ------------- 50 | 51 | ### StatsdDataFactory 52 | 53 | This service creates the (StatsDataInterface) object to send 54 | 55 | Reference: `liuggio_stats_d_client.factory` 56 | 57 | ```php 58 | $data = $this->get('liuggio_stats_d_client.factory')->increment('log.error'); 59 | 60 | ``` 61 | 62 | ### StatsDClient 63 | 64 | Reference: `liuggio_stats_d_client.service` 65 | 66 | This service SENDS the data over the UDP interface 67 | 68 | from a controller call ``` $this->get('liuggio_stats_d_client.service')->send($data) ``` 69 | 70 | the `$data` is the object created by the factory 71 | 72 | the send method will optimise the data sent in order to speed up the connection. 73 | 74 | ### Simplified StatsD service 75 | 76 | Reference: `statsd` 77 | 78 | This service implements `liuggio_stats_d_client.factory` methods, as well as flush. 79 | It makes using service less verbose. 80 | ```php 81 | $this->get('statsd') 82 | ->increment('log.error') 83 | ->gauge('log.rate', 25) 84 | ->flush(); 85 | ``` 86 | 87 | Working with `Monolog` 88 | ------------- 89 | 90 | To monitor your logs, add the following lines to your application configuration 91 | file, in order to enable the StatsDHandler 92 | 93 | 94 | ``` yaml 95 | # app/config/config.yml 96 | 97 | liuggio_stats_d_client: 98 | connection: 99 | host: localhost 100 | port: 8125 101 | protocol: udp 102 | reduce_packet: true 103 | fail_silently: true 104 | enable_collector: false #default is false 105 | monolog: 106 | enable: true 107 | prefix: 'my-app' 108 | level: 'warning' 109 | formatter: 110 | context_logging: true # if you want additional packets for context, default is false. 111 | extra_logging: true # if you want additional packets for extra, default is false. 112 | words: 2 # the number of the word in the stats key, default is 2. 113 | 114 | ``` 115 | 116 | But you also have to say to Monolog to use that handler 117 | 118 | ``` yaml 119 | # app/config/config.yml 120 | 121 | monolog: 122 | handlers: 123 | main: 124 | type: fingers_crossed 125 | action_level: warning 126 | handler: streamed 127 | streamed: 128 | type: stream 129 | path: %kernel.logs_dir%/%kernel.environment%.log 130 | level: critical 131 | #---------------------------------- 132 | stats_d: 133 | type: service 134 | id: monolog.handler.statsd 135 | level: warning 136 | # channels: 137 | # type: exclusive # Include all, except those listed below 138 | # elements: [ ] 139 | 140 | ``` 141 | 142 | 143 | Working with Personal Collector 144 | ------------- 145 | 146 | If the information from Monolog is not enough for you, you can use the Collectors that collect the information and then send the data to the StatsD Server. 147 | 148 | 149 | ``` yaml 150 | 151 | # app/config/config.yml 152 | liuggio_stats_d_client: 153 | connection: 154 | host: localhost 155 | port: 8125 156 | protocol: udp 157 | reduce_packet: true 158 | fail_silently: true 159 | enable_collector: true 160 | collectors: 161 | liuggio_stats_d_client.collector.dbal: 'my-app.query' 162 | liuggio_stats_d_client.collector.visitor: 'my-app.visitor' 163 | liuggio_stats_d_client.collector.memory: 'my-app.memory' 164 | liuggio_stats_d_client.collector.user: 'my-app.user' 165 | liuggio_stats_d_client.collector.exception: 'my-app.exception' 166 | liuggio_stats_d_client.collector.time: 'my-app.time' # the time is a "fake" one, it is ~100ms smaller than the Kernel but you can still monitor the evolution of your application 167 | 168 | 169 | ``` 170 | 171 | For example the `liuggio_stats_d_client.collector.dbal` will collect a lot of the information provided by the doctrine logging. 172 | 173 | In order to enable the query collector you also have to add to your doctrine.dbal the profiling variable from your config.yml 174 | eg. 175 | ``` yaml 176 | 177 | # Doctrine Configuration 178 | doctrine: 179 | dbal: 180 | profiling: true 181 | 182 | ``` 183 | 184 | 185 | 186 | Full Configuration / Max Power 187 | ------------ 188 | 189 | 190 | then add to config/yaml 191 | 192 | ``` yaml 193 | 194 | # app/config/config.yml 195 | liuggio_stats_d_client: 196 | monolog: 197 | enable: true 198 | prefix: 'my-app.log' 199 | formatter: 200 | # class: # if you want to change the formatter class. 201 | # format: # if you want to change the format. 202 | context_logging: true # if you want additional packets for context. 203 | extra_logging: true # if you want additional packets for extra. 204 | words: 2 # the number of the word in the stats key. 205 | level: 'warning' 206 | context_logging: true 207 | connection: 208 | # class: Liuggio\StatsdClient\Sender\SocketSender 209 | # debug_class: Liuggio\StatsdClient\Sender\SysLogSender # or EchoSender 210 | # debug: %kernel.debug% # use false if you want to disable debugging and shot packet over Socket 211 | host: localhost 212 | port: 8125 213 | # protocol: udp 214 | # reduce_packet: true 215 | # fail_silently: true 216 | enable_collector: true 217 | collectors: 218 | liuggio_stats_d_client.collector.dbal: 'my-app.query' 219 | liuggio_stats_d_client.collector.visitor: 'my-app.visitor' 220 | liuggio_stats_d_client.collector.memory: 'my-app.memory' 221 | liuggio_stats_d_client.collector.user: 'my-app.user' 222 | liuggio_stats_d_client.collector.exception: 'my-app.exception' 223 | liuggio_stats_d_client.collector.time: 'my-app.time' 224 | 225 | 226 | ``` 227 | 228 | -------------------------------------------------------------------------------- /Service/StatsDCollectorService.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class StatsDCollectorService 16 | { 17 | private $collectors; 18 | private $statsDClient; 19 | 20 | /** 21 | * Constructor. 22 | */ 23 | public function __construct(StatsdClientInterface $stats_d_client) 24 | { 25 | $this->collectors = []; 26 | $this->statsDClient = $stats_d_client; 27 | } 28 | 29 | /** 30 | * Collects data for the given Response. 31 | * 32 | * @param bool $isMasterRequest 33 | * @param Request $request A Request instance 34 | * @param Response $response A Response instance 35 | * @param \Exception $exception An exception instance if the request threw one 36 | * 37 | * @return array 38 | */ 39 | public function collect($isMasterRequest, Request $request, Response $response, \Exception $exception = null) 40 | { 41 | $statSData = []; 42 | foreach ($this->collectors as $collector) { 43 | if ($collector->getOnlyOnMasterResponse() && !$isMasterRequest) { 44 | continue; 45 | } 46 | 47 | $collector->collect($request, $response, $exception); 48 | $statSData = \array_merge($statSData, $collector->getStatsData()); 49 | } 50 | 51 | return $statSData; 52 | } 53 | 54 | /** 55 | * Gets the Collectors associated with this profiler. 56 | * 57 | * @return array An array of collectors 58 | */ 59 | public function all() 60 | { 61 | return $this->collectors; 62 | } 63 | 64 | /** 65 | * Sets the Collectors associated with this profiler. 66 | * 67 | * @param array $collectors An array of collectors 68 | */ 69 | public function set(array $collectors = []) 70 | { 71 | $this->collectors = []; 72 | foreach ($collectors as $collector) { 73 | $this->add($collector); 74 | } 75 | } 76 | 77 | /** 78 | * Adds a Collector. 79 | * 80 | * @param StatsCollectorInterface $collector A StatsCollectorInterface instance 81 | */ 82 | public function add(StatsCollectorInterface $collector) 83 | { 84 | $this->collectors[$collector->getStatsDataKey()] = $collector; 85 | } 86 | 87 | /** 88 | * Returns true if a Collector for the given name exists. 89 | * 90 | * @param string $name A collector name 91 | * 92 | * @return bool 93 | */ 94 | public function has($name) 95 | { 96 | return isset($this->collectors[$name]); 97 | } 98 | 99 | /** 100 | * Gets a Collector by name. 101 | * 102 | * @param string $name A collector name 103 | * 104 | * @return StatsCollectorInterface A StatsCollectorInterface instance 105 | * 106 | * @throws \InvalidArgumentException if the collector does not exist 107 | */ 108 | public function get($name) 109 | { 110 | if (!isset($this->collectors[$name])) { 111 | throw new \InvalidArgumentException(\sprintf('Collector "%s" does not exist.', $name)); 112 | } 113 | 114 | return $this->collectors[$name]; 115 | } 116 | 117 | /** 118 | * Send to StatD all the data collected. 119 | * 120 | * @param mixed $data An array of StatData 121 | */ 122 | public function send($data) 123 | { 124 | return $this->statsDClient->send($data); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /StatsCollector/DbalStatsCollector.php: -------------------------------------------------------------------------------- 1 | extractFirstWord($sql); 15 | if (empty($verb)) { 16 | return; 17 | } 18 | $key = \sprintf('%s.%s', $this->getStatsDataKey(), $verb); 19 | if (null === $this->getStatsdDataFactory()) { 20 | return; 21 | } 22 | $statData = $this->getStatsdDataFactory()->increment($key); 23 | $this->addStatsData($statData); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function stopQuery() 30 | { 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /StatsCollector/ExceptionStatsCollector.php: -------------------------------------------------------------------------------- 1 | getStatsDataKey(), $exception->getCode()); 26 | $statData = $this->getStatsdDataFactory()->increment($key); 27 | $this->addStatsData($statData); 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /StatsCollector/MemoryStatsCollector.php: -------------------------------------------------------------------------------- 1 | 1024) { 19 | return (int) ($bit / 1024); 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | /** 26 | * Collects data for the given Response. 27 | * 28 | * @param Request $request A Request instance 29 | * @param Response $response A Response instance 30 | * @param \Exception $exception An exception instance if the request threw one 31 | * 32 | * @return bool 33 | */ 34 | public function collect(Request $request, Response $response, \Exception $exception = null) 35 | { 36 | $statData = $this->getStatsdDataFactory()->gauge($this->getStatsDataKey(), $this->getMemoryUsage()); 37 | $this->addStatsData($statData); 38 | 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StatsCollector/StatsCollector.php: -------------------------------------------------------------------------------- 1 | setStatsDataKey($stat_key); 33 | $this->statsdDataFactory = $stats_data_factory; 34 | $this->setOnlyOnMasterResponse($only_on_master_response); 35 | } 36 | 37 | /** 38 | * Collects data for the given Response. 39 | * 40 | * @param Request $request A Request instance 41 | * @param Response $response A Response instance 42 | * @param \Exception $exception An exception instance if the request threw one 43 | * 44 | * @return bool 45 | */ 46 | public function collect(Request $request, Response $response, \Exception $exception = null) 47 | { 48 | return true; 49 | } 50 | 51 | /** 52 | * @return mixed 53 | */ 54 | public function getStatsData() 55 | { 56 | if (null === $this->statsData) { 57 | return []; 58 | } 59 | 60 | return $this->statsData; 61 | } 62 | 63 | /** 64 | * @param \Liuggio\StatsdClient\Entity\StatsdDataInterface $statsData 65 | * 66 | * @return mixed 67 | */ 68 | public function addStatsData(StatsdDataInterface $statsData) 69 | { 70 | $this->statsData[] = $statsData; 71 | } 72 | 73 | /** 74 | * @param string $key 75 | */ 76 | public function setStatsDataKey($key) 77 | { 78 | $this->statsDataKey = $key; 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getStatsDataKey() 85 | { 86 | return $this->statsDataKey; 87 | } 88 | 89 | /** 90 | * @param \Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface $StatsdDataFactory 91 | */ 92 | public function setStatsdDataFactory(StatsdDataFactoryInterface $StatsdDataFactory) 93 | { 94 | $this->statsdDataFactory = $StatsdDataFactory; 95 | } 96 | 97 | /** 98 | * @return \Liuggio\StatsdClient\Factory\StatsdDataFactory 99 | */ 100 | public function getStatsdDataFactory() 101 | { 102 | return $this->statsdDataFactory; 103 | } 104 | 105 | /** 106 | * @param bool $onlyOnMasterResponse 107 | */ 108 | public function setOnlyOnMasterResponse($onlyOnMasterResponse) 109 | { 110 | $this->onlyOnMasterResponse = $onlyOnMasterResponse; 111 | } 112 | 113 | /** 114 | * @return bool 115 | */ 116 | public function getOnlyOnMasterResponse() 117 | { 118 | return $this->onlyOnMasterResponse; 119 | } 120 | 121 | /** 122 | * Extract the first word, its maximum length is limited to $maxLenght chars. 123 | * 124 | * @param string $string 125 | * 126 | * @return mixed 127 | */ 128 | protected function extractFirstWord($string, $maxLength = 25) 129 | { 130 | $string = \str_replace(['"', "'"], '', $string); 131 | $string = \trim($string); 132 | $length = (\strlen($string) > $maxLength) ? $maxLength : \strlen($string); 133 | $string = \strtolower(\strstr(\substr(\trim($string), 0, $length), ' ', true)); 134 | 135 | return $string; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /StatsCollector/StatsCollectorInterface.php: -------------------------------------------------------------------------------- 1 | server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME')); 22 | 23 | $time = \microtime(true) - $startTime; 24 | $time = \round($time * 1000); 25 | 26 | $statData = $this->getStatsdDataFactory()->timing($this->getStatsDataKey(), $time); 27 | $this->addStatsData($statData); 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /StatsCollector/UserStatsCollector.php: -------------------------------------------------------------------------------- 1 | getSecurityContext()) { 24 | return true; 25 | } 26 | 27 | $key = \sprintf('%s.anonymous', $this->getStatsDataKey()); 28 | try { 29 | if ($this->getSecurityContext()->isGranted('IS_AUTHENTICATED_FULLY')) { 30 | $key = \sprintf('%s.logged', $this->getStatsDataKey()); 31 | } 32 | } catch (AuthenticationCredentialsNotFoundException $exception) { 33 | //do nothing 34 | } 35 | $statData = $this->getStatsdDataFactory()->increment($key); 36 | $this->addStatsData($statData); 37 | 38 | return true; 39 | } 40 | 41 | public function setSecurityContext(AuthorizationCheckerInterface $security_context) 42 | { 43 | $this->security_context = $security_context; 44 | } 45 | 46 | public function getSecurityContext() 47 | { 48 | return $this->security_context; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /StatsCollector/VisitorStatsCollector.php: -------------------------------------------------------------------------------- 1 | getStatsdDataFactory()->increment($this->getStatsDataKey()); 22 | $this->addStatsData($statData); 23 | 24 | return true; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "liuggio/statsd-client-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Provides a statsd client and simple ready-to-use support for #Symfony2 Application", 5 | "keywords": ["symfony", "statsd", "graphite", "monitor"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Liuggio", 10 | "email": "liuggio@gmail.com", 11 | "homepage": "https://github.com/Liuggio" 12 | }, 13 | { 14 | "name": "Community contributions", 15 | "homepage": "https://github.com/liuggio/StatsDClientBundle/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": "^7.0 || ^8.0", 20 | "liuggio/statsd-php-client": "~1.0.14", 21 | "symfony/framework-bundle": "^4.0 || ^5.0" 22 | }, 23 | "require-dev": { 24 | "doctrine/dbal": ">=2.2.0", 25 | "monolog/monolog": ">=1.2.0", 26 | "phpunit/phpunit": "^6.5 || ^7.0 || ^9.5", 27 | "symfony/yaml": "^4.0 || ^5.0" 28 | }, 29 | "suggest": { 30 | "doctrine/dbal": "Doctrine DBAL, collect statistic to/from the database (>=2.2.0 required)", 31 | "monolog/monolog": "Monolog, in order to generate statistic from log >=1.2.0)" 32 | }, 33 | "autoload": { 34 | "psr-0": { "Liuggio\\StatsDClientBundle": "" } 35 | }, 36 | "target-dir": "Liuggio/StatsDClientBundle", 37 | "extra": { 38 | "branch-alias": { 39 | "dev-master": "1.8.x-dev" 40 | } 41 | } 42 | } 43 | --------------------------------------------------------------------------------