GithubHelp home page GithubHelp logo

bugsnag / bugsnag-symfony Goto Github PK

View Code? Open in Web Editor NEW
43.0 29.0 21.0 476 KB

BugSnag notifier for the Symfony PHP framework. Monitor and report errors in your Symfony apps.

Home Page: https://docs.bugsnag.com/platforms/php/symfony

License: MIT License

PHP 86.33% Makefile 0.13% Shell 0.61% Twig 4.94% Ruby 1.08% Gherkin 5.18% Dockerfile 0.86% JavaScript 0.84% CSS 0.02%
bugsnag symfony php error-monitoring error-reporting error-notification exception-reporting crash-reporting crash-reporting-tool exception-handling

bugsnag-symfony's Introduction

Bugsnag exception reporter for Symfony

Build Status StyleCI Status Documentation

The Bugsnag Notifier for Symfony gives you instant notification of errors and exceptions in your Symfony PHP applications. Learn more about error monitoring and error reporting for your Symfony PHP apps.

Features

  • Automatically report unhandled exceptions and crashes
  • Report handled exceptions
  • Attach user information and custom diagnostic data to determine how many people are affected by a crash

Getting started

  1. Create a Bugsnag account
  2. Complete the instructions in the integration guide
  3. Report handled exceptions using Bugsnag::notify()
  4. Customize your integration using the configuration options

Support

Contributing

All contributors are welcome! For information on how to build, test, and release, see our contributing guide.

License

The Bugsnag Symfony library is free software released under the MIT License. See LICENSE for details.

bugsnag-symfony's People

Contributors

cawllec avatar damienalexandre avatar fractalwrench avatar gracecheung avatar grahamcampbell avatar iambrosi avatar imjoehaines avatar kattrali avatar luke-belton avatar mclack avatar niamzor avatar nispeon avatar oleg-andreyev avatar rjharrison avatar tomlongridge avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bugsnag-symfony's Issues

Configurable timeout on Guzzle connection

As we are sometimes experiencing longer response times from our Bugsnag servers it would be great to configure a timeout on the used Guzzle Connection.

Currently this does not seem to be possible.

Symfony 4 support

composer.json should be updated to support also Symfony 4, currently requiring this project is impossible for it.

Consider not notifying on HttpExceptions

Hi all, a minor suggestion, with friendly reference to https://github.com/evolution7/Evolution7BugsnagBundle/blob/master/EventListener/Exception/HttpKernelListener.php#L45-L47

Expected behavior

4** range errors should not trigger error notifications, since these types of Exceptions are handled via Symfony and not logged as error.

Observed behavior

4** range errors trigger error notifications

Steps to reproduce

Request a non-existing URL or otherwise trigger a 403/401 error and receive an error notification

Version

1.5.0

Additional information

I understand this behavior can be suppressed by registering a custom callback, but out of the box I imagine most people, including Bugsnag operators, would like to suppress a flood of 404 reports, which other solutions are more adept at monitoring.

Thank you for your time.

Add support for Symfony 5

Expected behavior

Compatibility with Symfony 5

Observed behavior

composer.json dependencies prevent Symfony 5 to be installed

As Symfony 5 is about to be released at the end of the month (currently in beta), did you plan to release a new version of this bundle to add the compatibility soon?
If not, I may find the time to make a PR if you need help πŸ™‚

Attempted to call an undefined method named "getUsername" of class "App\Entity\User".

Describe the bug

Attempted to call an undefined method named "getUsername" of class "App\Entity\User" on bugsnag/bugsnag-symfony v1.11.0
There cannot be getUsername() method because it was changed to getUserIdentifier() method in Symfony 5.3

Environment

  • Bugsnag version: 3.26.1
  • PHP version: 8.1
  • Symfony version: 6.0

Error:

Fatal error: Uncaught Error: Call to undefined method App\Entity\User::getUsername() in .../vendor/bugsnag/bugsnag-symfony/DependencyInjection/ClientFactory.php on line 392

PHP Fatal error: Unsupported operand types

The bundle throws a fatal error when SymfonyRequest::getData() is called, the Content-Type of the request is application/json and the request body is empty. In this case, the first operand in $this->getInput() + $this->request->query->all(); is not an array.

That is the case that first affected me, but I realized the same will occur if the request body is a scalar value that json_decode will actually decode (true,1, "string", etc.). I know people still debate whether these are actually valid JSON β€” but the bigger issue here is that the bundle is throwing an uncatchable error.

Further, our app is a RESTful API that accepts JSON. If it is invalid, we throw an exception with a message indicating the JSON is invalid. In the event that we actually wanted to capture that exception in Bugsnag, the subsequent json_decode would return false and trigger the fatal error before the report could be sent.

Locally, I have modified the SymfonyRequest file to cast the output of json_decode to an array to avoid the fatal error. I did not make a pull request, because I was also curious about why JSON content is submitted to Bugsnag as if it were posted as form data β€” in the latter example, how would we see the invalid JSON string that was sent as the body? I honestly don’t know if Bugsnag is set up to accept the raw body at all, but I thought I’d raise the question.

Thanks!

Cannot set custom callback for bugsnag

Hello,
I installed Bugsnag successfully on my Symfony project. Bugsnag is sending me all the errors.

Now that the configuration is OK, i wanted to add a custom Callback to choose wether i should send the bug to bugsnag or not, but here is my problem :

when i add these lines to my boot() method (in AppKernel)
$bugsnag = $this->getBundle('bugsnag');
$bugsnag->registerCallback(function ($report) { ...});

I get the following error : Bundle "bugsnag" does not exist or it is not enabled, altought i registered the bundle (new Bugsnag\BugsnagBundle\BugsnagBundle(),)..
When i try $this->getBundle('BugsnagBundle') i get an error telling me the "registerCallback" method does not exists for this bundle.

Any idea on how to solve this problem ?

Thanks

Update to 1.14.0 produce error

Describe the bug

With the update to 1.14.0 from 1.13.0 we got the following error log, if we build the app for the PROD app ENV.

Class Bugsnag\BugsnagBundle\DependencyInjection\Configuration located in ./vendor/bugsnag/bugsnag-symfony/DependencyInjection/configuration-without-return-type.php does not comply with psr-4 autoloading standard. Skipping.
Class Bugsnag\BugsnagBundle\DependencyInjection\Configuration located in ./vendor/bugsnag/bugsnag-symfony/DependencyInjection/configuration-with-return-type.php does not comply with psr-4 autoloading standard. Skipping.
composer/package-versions-deprecated: Generating version class...
composer/package-versions-deprecated: ...done generating version class
92 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

Run composer recipes at any time to see the status of your Symfony recipes.

Executing script cache:clear [KO]
 [KO]
Script cache:clear returned with error code 255
!!  
!!  Fatal error: Uncaught Error: Class "Bugsnag\BugsnagBundle\DependencyInjection\Configuration" not found in /app/vendor/bugsnag/bugsnag-symfony/DependencyInjection/BugsnagExtension.php:22
!!  Stack trace:
!!  #0 /app/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php(76): Bugsnag\BugsnagBundle\DependencyInjection\BugsnagExtension->load(Array, Object(Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationContainerBuilder))
!!  #1 /app/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php(39): Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass->process(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #2 /app/vendor/symfony/dependency-injection/Compiler/Compiler.php(94): Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass->process(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #3 /app/vendor/symfony/dependency-injection/ContainerBuilder.php(762): Symfony\Component\DependencyInjection\Compiler\Compiler->compile(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #4 /app/vendor/symfony/http-kernel/Kernel.php(594): Symfony\Component\DependencyInjection\ContainerBuilder->compile()
!!  #5 /app/vendor/symfony/http-kernel/Kernel.php(136): Symfony\Component\HttpKernel\Kernel->initializeContainer()
!!  #6 /app/src/Kernel.php(36): Symfony\Component\HttpKernel\Kernel->boot()
!!  #7 /app/vendor/symfony/framework-bundle/Console/Application.php(169): App\Kernel->boot()
!!  #8 /app/vendor/symfony/framework-bundle/Console/Application.php(75): Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands()
!!  #9 /app/vendor/symfony/console/Application.php(149): Symfony\Bundle\FrameworkBundle\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
!!  #10 /app/bin/console(42): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput))
!!  #11 {main}
!!    thrown in /app/vendor/bugsnag/bugsnag-symfony/DependencyInjection/BugsnagExtension.php on line 22
!!  PHP Fatal error:  Uncaught Error: Class "Bugsnag\BugsnagBundle\DependencyInjection\Configuration" not found in /app/vendor/bugsnag/bugsnag-symfony/DependencyInjection/BugsnagExtension.php:22
!!  Stack trace:
!!  #0 /app/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php(76): Bugsnag\BugsnagBundle\DependencyInjection\BugsnagExtension->load(Array, Object(Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationContainerBuilder))
!!  #1 /app/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php(39): Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass->process(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #2 /app/vendor/symfony/dependency-injection/Compiler/Compiler.php(94): Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass->process(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #3 /app/vendor/symfony/dependency-injection/ContainerBuilder.php(762): Symfony\Component\DependencyInjection\Compiler\Compiler->compile(Object(Symfony\Component\DependencyInjection\ContainerBuilder))
!!  #4 /app/vendor/symfony/http-kernel/Kernel.php(594): Symfony\Component\DependencyInjection\ContainerBuilder->compile()
!!  #5 /app/vendor/symfony/http-kernel/Kernel.php(136): Symfony\Component\HttpKernel\Kernel->initializeContainer()
!!  #6 /app/src/Kernel.php(36): Symfony\Component\HttpKernel\Kernel->boot()
!!  #7 /app/vendor/symfony/framework-bundle/Console/Application.php(169): App\Kernel->boot()
!!  #8 /app/vendor/symfony/framework-bundle/Console/Application.php(75): Symfony\Bundle\FrameworkBundle\Console\Application->registerCommands()
!!  #9 /app/vendor/symfony/console/Application.php(149): Symfony\Bundle\FrameworkBundle\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
!!  #10 /app/bin/console(42): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput))
!!  #11 {main}
!!    thrown in /app/vendor/bugsnag/bugsnag-symfony/DependencyInjection/BugsnagExtension.php on line 22

Steps to reproduce

  1. update bugsnag/bugsnag-symfony to 1.14.0

Environment

  • Bugsnag version: 1.14.0
  • PHP version: 8.0.30
  • Symfony version: 4.4.51

Broken ClientFactory

Hi there,

after composer installation I got the following error after clearing the cache:

Compile Error: Cannot use Bugsnag\Configuration as Configuration because the name is already in use

So far I can see, the problem is following use statement
use Bugsnag\Configuration;
in Bugsnag\BugsnagBundle\DependencyInjection\ClientFactory

If there will be an alias, you need also adjust in "make" method, renaming Configuration with alias.
$client = new Client(new Configuration($this->key), $this->resolver, $guzzle);

Problem is that Bugsnag\BugsnagBundle\DependencyInjection namespace already contains Configuration class.

After fixing that, a new issue will appear

Type error: Argument 2 passed to Bugsnag\Client::notifyException() must be callable, array given, called in /home/vagrant/zolar-of
fer-calculator/vendor/bugsnag/bugsnag-symfony/EventListener/BugsnagListener.php on line 107

I did not take a look at that further yet, but maybe you'll fix that so that I could use this bugsnag repo for my projects in future. :)

Best
Markus

[Bug] auto_notify=false does not work

Hello,
It would be useful to desactivate Bugsnag listeners/calls using a config flag.
The "auto_notify" config flag does not work as expected if set to "false" (should avoid all Bugsnag api calls)

Push errors to Bugsnag asynchronously

Description

While taking a look at my Datadog's longest traces, I noticed that, for a 404, 80% of the time is spent in curl sending data to Bugsnag:

image

Describe the solution you'd like
My proposal is to add the possibility to push data to Bugsnag asynchronously, relying on Symfony Messenger.

unit tests fail for bigger project

After installing the package on symfony 2.8.49 and running phpunit test v6.5.8 , bugsnag/bugsnag-symfony: v1.4.0 every instance of phpunit test creates a new instance of bugsnag client which causes memory leaks and tests failing.
test fail message:

Trace:
Warning: include(/Users/project/vendor/symfony/symfony/src/Symfony/Component/Debug/Exception/ContextErrorException.php): failed to open stream: Too many open files in /Users/project/vendor/composer/ClassLoader.php on line 444

Warning: include(): Failed opening '/Users/project/vendor/composer/../symfony/symfony/src/Symfony/Component/Debug/Exception/ContextErrorException.php' for inclusion (include_path='.:/usr/local/Cellar/[email protected]/7.1.26/share/[email protected]/pear') in /Users/project/vendor/composer/ClassLoader.php on line 444
PHP Warning: include(/Users/project/vendor/symfony/symfony/src/Symfony/Component/Debug/Exception/ContextErrorException.php): failed to open stream: Too many open files in /Users/project/vendor/composer/ClassLoader.php on line 444
PHP Warning: include(): Failed opening '/Users/project/vendor/composer/../symfony/symfony/src/Symfony/Component/Debug/Exception/ContextErrorException.php' for inclusion (include_path='.:/usr/local/Cellar/[email protected]/7.1.26/share/[email protected]/pear') in /Users/project/vendor/composer/ClassLoader.php on line 444

Fatal error: Method PHPUnit\Framework\ExceptionWrapper::__toString() must not throw an exception, caught ErrorException: Warning: include(/Users/project/vendor/phpunit/phpunit/src/Util/Filter.php): failed to open stream: Too many open files in /Users/project/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 0
PHP Fatal error: Method PHPUnit\Framework\ExceptionWrapper::__toString() must not throw an exception, caught ErrorException: Warning: include(/Users/project/vendor/phpunit/phpunit/src/Util/Filter.php): failed to open stream: Too many open files in /Users/project/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php on line 0

Fatal error: Uncaught Error: Class 'Symfony\Component\Debug\Exception\FatalErrorException' not found in /Users/project/vendor/symfony/symfony/src/Symfony/Component/Debug/ErrorHandler.php:705
Stack trace:
#0 [internal function]: Symfony\Component\Debug\ErrorHandler::handleFatalError()
#1 {main}
thrown in /Users/project/vendor/symfony/symfony/src/Symfony/Component/Debug/ErrorHandler.php on line 705
PHP Fatal error: Uncaught Error: Class 'Symfony\Component\Debug\Exception\FatalErrorException' not found in /Users/project/vendor/symfony/symfony/src/Symfony/Component/Debug/ErrorHandler.php:705

Is it possible to report PHP errors?

In documentation there is a section explaining how to configure the error report level. Therefore, when i've created a simple controller with echo $randomVar (what would show a undefined variable notice), i haven't received any error in Bugsnag.

I'm using error_reporting at the top of my app.php file, just for testing.

My environment is Symfony v3.1.6 and PHP 7.0.12 inside a Linux 14.04 instance.

GetResponseForExceptionEvent deprecation notice still occurs in 1.6

Expected behavior

With the changes in #93, no deprecation notices should be thrown.

Observed behavior

I am still seeing this notice:

User Deprecated: The "Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent::getException()" method is deprecated since Symfony 4.4, use "getThrowable()" instead.

Steps to reproduce

The easiest thing to do is to hit a route behind a firewall to trigger an AccessDeniedException in dev and check the deprecation logs in the profiler.

Version

1.6 (Symfony 4.4)

Additional information

The conditional checks in BugsnagListener::onKernelException() need to be reversed so that ExceptionEvent is checked first β€” because it simply extends GetResponseForExceptionEvent, the first condition is also true and hence the deprecation notice is still thrown.

Support for `auto_capture_sessions`

Description

The Symfony bundle does not seem to provide a way to configure the auto_capture_sessions option. The Laravel integration does provide this.

Describe the solution you'd like

It would be nice if this could be configured through the bundle configuration.

Describe alternatives you've considered

Currently I set this manually after loading the bundle and configuration in our Symfony kernel.

Listen to WorkerMessageFailedEvent

Expected behavior

Exceptions thrown in MessageHandlerInterface are catched by the BugsnagListener.

Observed behavior

This isn't the case.

Steps to reproduce

Have a MessageHandlerInterface throw an exception.

Version

  • bugsnag/bugsnag v3.18.0
  • bugsnag/bugsnag-symfony v1.5.1

I suspect all versions are impacted though.

Additional information

Every exception thrown inside a message handler is catched by the worker so it can continue working so there is no kernel.exception. Instead a WorkerMessageFailedEvent is dispatched so the BugsnagListener should also listen to it.

Symfony Console support

Description

I maintain Acquia CLI, a Symfony Console application. I can't figure out how to report exceptions to Bugsnag via this library.

Many libraries similar to Bugsnag, such as Amplitude, support a singleton pattern of interaction, i.e. Bugsnag::getInstance()->notifyException(new Exception()) as opposed to $this->get('bugsnag')->notifyException(new Exception()).

The singleton pattern is a lot easier (possibly, the only way) to integrate with Symfony Console applications, which are really stripped down from a full Symfony app. For instance, I don't even know what $this or get() referenced above are supposed to refer to. In a console app, assuming you are throwing an exception from a Command (usually the case), $this would be an instance of \Symfony\Component\Console\Command\Command and it would have no such method get(). I assume this is something to do with the service container or framework bundle, which generally shouldn't / can't be accessed directly in CLI apps.

We also throw a custom AcquiaCliException and we'd really love to report to bugsnag from within that exception. Since it extends \Exception, it has no access to Symfony resources. +1 to using the singleton pattern here.

Finally, I'm not sure if Console apps support bundles, I suspect not, which means that autoreporting uncaught exceptions won't work either.

Describe the solution you'd like

Guidance on how to integrate with Console apps, via a singleton pattern or some other method.

Ignore all 404

Hi,
I would like to ignore all NotFoundHttpException. Anybody have a solution ?
Thanks :)

register_shutdown_function() causes major memory leak during testing

Expected behavior

Symfony tests should run under phpunit without major memory leaks.

Alternatively, provide a bootstrapping process which can be modified for testing. Currently, the hard-coding of the Bugsnag\Client class makes that very difficult.

Observed behavior

Each request/sub-request during testing adds 2MB-4MB of permanent memory consumption.

Steps to reproduce

class TestCase extends \Symfony\Bundle\FrameworkBundle\Test\WebTestCase 
{
    public function testMemory()
    {
        echo memory_get_usage() . PHP_EOL;
        $client->request('GET', '/');
        echo memory_get_usage() . PHP_EOL;
        $client->request('GET', '/');
        $client->request('GET', '/');
        $client->request('GET', '/');
        $client->request('GET', '/');
        $client->request('GET', '/');
        echo memory_get_usage() . PHP_EOL;
    }
}

Version

1.4.0/1.5.0

Additional information

I have confirmed that removing the register_shutdown_function([$this, 'flush']); from the Client constructor resolves the issue. Unfortunately, that encloses the state of $this until the process shuts down, which on large test suites is usually when it crashes from memory problems.

Symfony 4.x not supported.

Expected behavior

Unhandled console exception to be registered by bugsnag.

Observed behavior

Unhandled exceptions in console are not catched by bugsnag and developer end confused.

Steps to reproduce

Install package and throw \Exception as first line in any console command

Version

the one which claim to have symfony 4.x support

Additional information

frustrated that i trusted that package marked to support symfony 4 will support it.
Especially when that package is "official for symfony by bugsnag" and it is about catching unhandled errors. If error is handled then who cares.
It was cool to debug whole app to find out that error is not registed in bugsnag not because my mistake but because package is not supporting symfony 4 at all.

Deprecation is noted in https://github.com/symfony/symfony/blob/4.2/UPGRADE-4.0.md
"The console.exception event and the related ConsoleExceptionEvent class have been removed in favor of the console.error event and the ConsoleErrorEvent class."

Defaulting to ENV vars during container build is problematic

First off, this bundle has come a long way and I really like it now, great job, and thank you!

One snag I ran into though:

Expected behavior

According to the documentation I can configure this bundle via ENV vars, specifically, setting BUGSNAG_API_KEY – the idea of ENV vars being that I can change their values during deploy time to produce different runtime behavior.

Observed behavior

However, by default ENV vars only end up as a default-values to the container build step, and are consequently frozen into the container as resolved value. A change to ENV vars will not affect a change to the runtime configuration, without rebuilding the container. See: https://github.com/bugsnag/bugsnag-symfony/blob/master/DependencyInjection/Configuration.php#L26

Steps to reproduce

  1. Warm the cache (build container) while providing BUGSNAG_API_KEY via ENV var.
  2. Start a webserver hosting the bundle with a different ENV setting.
  3. Trigger error report & observe it going to the build-time account. Alternatively, look into the cached container and find frozen resolved value.

Version

1.5.0

Additional information

By explicitly specifying the api_key via ENV var, the bundle remains configurable at runtime:

bugsnag:
    api_key: "%env(BUGSNAG_API_KEY)%"

My recommendation would be to avoid setting default values via ENV inside the Configuration class and instead require them to be specified inside the bugsnag.yml config file.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.