From 4512176ae35c039ece17a49a2c285b73e6670c64 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 2 Apr 2017 11:30:14 +0530 Subject: [PATCH 01/12] symfony/console and illuminate/console added as dependencies and some minor fixes --- composer.json | 4 +++- src/Sneaker.php | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4266c78..5d7f6ab 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,9 @@ "illuminate/config": "5.3.*|5.4.*", "illuminate/mail": "5.3.*|5.4.*", "illuminate/log": "5.3.*|5.4.*", - "symfony/debug": "~3.1|~3.2" + "illuminate/console": "5.3.*|5.4.*", + "symfony/debug": "~3.1|~3.2", + "symfony/console": "~3.1|~3.2", }, "autoload": { "psr-4": { diff --git a/src/Sneaker.php b/src/Sneaker.php index 15cc729..973ba62 100644 --- a/src/Sneaker.php +++ b/src/Sneaker.php @@ -4,8 +4,8 @@ use Exception; use Illuminate\Log\Writer; -use Illuminate\Contracts\Mail\Mailer; use Illuminate\Config\Repository; +use Illuminate\Contracts\Mail\Mailer; class Sneaker { @@ -64,6 +64,7 @@ public function __construct(Repository $config, * Checks an exception which should be tracked and captures it if applicable. * * @param \Exception $exception + * @param bool $sneaking * @return void */ public function captureException(Exception $exception, $sneaking = false) From d0d5f020b146f5c9907faf2357c008bb8d75f564 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 9 Apr 2017 09:17:02 +0530 Subject: [PATCH 02/12] Mail and slack channels added using new notifications --- composer.json | 5 +- config/sneaker.php | 37 +++- resources/views/email/body.blade.php | 6 +- resources/views/email/subject.blade.php | 1 - resources/views/raw.blade.php | 1 - src/Commands/Sneak.php | 2 +- src/ExceptionMailer.php | 50 ----- .../Handler.php} | 77 ++++--- src/Facades/Sneaker.php | 21 ++ src/Notifiable.php | 137 ++++++++++++ src/Notifications/ExceptionCaught.php | 102 +++++++++ src/Report.php | 205 ++++++++++++++++++ src/Sneaker.php | 129 ++++++----- src/SneakerServiceProvider.php | 20 +- 14 files changed, 636 insertions(+), 157 deletions(-) delete mode 100644 resources/views/email/subject.blade.php delete mode 100644 resources/views/raw.blade.php delete mode 100644 src/ExceptionMailer.php rename src/{ExceptionHandler.php => Exceptions/Handler.php} (52%) create mode 100644 src/Facades/Sneaker.php create mode 100644 src/Notifiable.php create mode 100644 src/Notifications/ExceptionCaught.php create mode 100644 src/Report.php diff --git a/composer.json b/composer.json index 5d7f6ab..42e7dd7 100644 --- a/composer.json +++ b/composer.json @@ -12,14 +12,15 @@ ], "require": { "php": ">=5.4.0", + "guzzlehttp/guzzle": "^6.2", "illuminate/support": "5.3.*|5.4.*", - "illuminate/view": "5.3.*|5.4.*", "illuminate/config": "5.3.*|5.4.*", - "illuminate/mail": "5.3.*|5.4.*", + "illuminate/notifications": "5.3.*|5.4.*", "illuminate/log": "5.3.*|5.4.*", "illuminate/console": "5.3.*|5.4.*", "symfony/debug": "~3.1|~3.2", "symfony/console": "~3.1|~3.2", + "nesbot/carbon": "~1.20" }, "autoload": { "psr-4": { diff --git a/config/sneaker.php b/config/sneaker.php index a8d3803..886ec93 100644 --- a/config/sneaker.php +++ b/config/sneaker.php @@ -10,7 +10,7 @@ | Should we email error traces? | */ - 'silent' => env('SNEAKER_SILENT', true), + 'silent' => env('SNEAKER_SILENT', false), /* |-------------------------------------------------------------------------- @@ -27,17 +27,46 @@ Symfony\Component\Debug\Exception\FatalErrorException::class, ], + /* + |-------------------------------------------------------------------------- + | Notification Delivery Channels + |-------------------------------------------------------------------------- + | + | The channels on which the notification will be delivered. + | + */ + + 'notifications' => [ + 'mail', + 'slack', + ], + /* |-------------------------------------------------------------------------- | Error email recipients |-------------------------------------------------------------------------- | - | Email stack traces to these addresses. + | The email address used to deliver the notification. + | + */ + + 'mail' => [ + 'to' => [ + // 'your@email.com', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Slack Webhook Url + |-------------------------------------------------------------------------- + | + | The webhook URL to which the notification should be delivered. | */ - 'to' => [ - // 'hello@example.com', + 'slack' => [ + 'webhook_url' => env('SNEAKER_SLACK_WEBHOOK_URL'), ], /* diff --git a/resources/views/email/body.blade.php b/resources/views/email/body.blade.php index 944cd8c..c67c8af 100644 --- a/resources/views/email/body.blade.php +++ b/resources/views/email/body.blade.php @@ -18,16 +18,16 @@ border-radius: 10px; border: 1px solid #ccc; } - {!! $css !!} + {!! $report->getHtmlStylesheet() !!} - {!! $content !!} + {!! $report->getHtmlContent() !!}
Requested Url - {{ request()->url() }}
- 🕐  {{ date('l, jS \of F Y h:i:s a') }} {{ date_default_timezone_get() }} + 🕐  {{ $report->getTime()->format('l, jS \of F Y h:i:s a') }} {{ $report->getTime()->tzName }}
diff --git a/resources/views/email/subject.blade.php b/resources/views/email/subject.blade.php deleted file mode 100644 index 686f886..0000000 --- a/resources/views/email/subject.blade.php +++ /dev/null @@ -1 +0,0 @@ -[Sneaker] | {{ get_class($exception) }} | Server - {{ request()->server('SERVER_NAME') }} | Environment - {{ config('app.env') }} diff --git a/resources/views/raw.blade.php b/resources/views/raw.blade.php deleted file mode 100644 index 0e9c7db..0000000 --- a/resources/views/raw.blade.php +++ /dev/null @@ -1 +0,0 @@ -{!! $content !!} diff --git a/src/Commands/Sneak.php b/src/Commands/Sneak.php index 195e3ed..d5183d7 100644 --- a/src/Commands/Sneak.php +++ b/src/Commands/Sneak.php @@ -54,7 +54,7 @@ public function handle() $this->overrideConfig(); try { - app('sneaker')->captureException(new DummyException, true); + $this->laravel->make('sneaker')->captureException(new DummyException, true); $this->info('Sneaker is working fine ✅'); } catch (Exception $e) { diff --git a/src/ExceptionMailer.php b/src/ExceptionMailer.php deleted file mode 100644 index cf59283..0000000 --- a/src/ExceptionMailer.php +++ /dev/null @@ -1,50 +0,0 @@ -subject = $subject; - - $this->body = $body; - } - - /** - * Build the message. - * - * @return $this - */ - public function build() - { - return $this->view('sneaker::raw') - ->with('content', $this->body); - } -} diff --git a/src/ExceptionHandler.php b/src/Exceptions/Handler.php similarity index 52% rename from src/ExceptionHandler.php rename to src/Exceptions/Handler.php index 5aaf111..e34443e 100644 --- a/src/ExceptionHandler.php +++ b/src/Exceptions/Handler.php @@ -1,55 +1,80 @@ view = $view; + $this->exception = $exception; } /** - * Create a string for the given exception. + * Get the name of given exception. + * + * @return string + */ + public function getExceptionName() + { + return get_class($this->exception); + } + + /** + * Convert the given exception to a readable message. * - * @param \Exception $exception * @return string */ - public function convertExceptionToString($exception) + public function convertExceptionToMessage() { - return $this->view->make('sneaker::email.subject', compact('exception'))->render(); + return sprintf("Exception '%s' with message '%s' in %s", + $this->getExceptionName(), + $this->exception->getMessage(), + $this->exception->getFile() + ); } /** - * Create a html for the given exception. + * Convert the given exception to a stack trace string. + * + * @return string + */ + public function convertExceptionToStacktrace() + { + return $this->exception->getTraceAsString(); + } + + /** + * Convert the given exception to a html. * - * @param \Exception $exception * @return string */ - public function convertExceptionToHtml($exception) + public function convertExceptionToHtml() { - $flat = $this->getFlattenedException($exception); + $flattened = $this->getFlattenedException($this->exception); $handler = new SymfonyExceptionHandler(); - return $this->decorate($handler->getContent($flat), $handler->getStylesheet($flat), $flat); + return [ + 'content' => $this->removeTitle($handler->getContent($flattened)), + 'stylesheet' => $handler->getStylesheet($flattened) + ]; } /** @@ -60,27 +85,13 @@ public function convertExceptionToHtml($exception) */ private function getFlattenedException($exception) { - if (!$exception instanceof FlattenException) { + if (! $exception instanceof FlattenException) { $exception = FlattenException::create($exception); } return $exception; } - /** - * Get the html response content. - * - * @param string $content - * @param string $css - * @return string - */ - private function decorate($content, $css, $exception) - { - $content = $this->removeTitle($content); - - return $this->view->make('sneaker::email.body', compact('content', 'css', 'exception'))->render(); - } - /** * Removes title from content as it is same for all exceptions and has no real value. * diff --git a/src/Facades/Sneaker.php b/src/Facades/Sneaker.php new file mode 100644 index 0000000..b062f3a --- /dev/null +++ b/src/Facades/Sneaker.php @@ -0,0 +1,21 @@ +fill($attributes); + } + + /** + * Route notifications for the mail channel. + * + * @return string|array + */ + public function routeNotificationForMail() + { + return $this->emails; + } + + /** + * Route notifications for the Slack channel. + * + * @return string + */ + public function routeNotificationForSlack() + { + return $this->slack_webhook_url; + } + + /** + * Get an attribute from the model. + * + * @param string $key + * @return mixed + */ + public function getAttribute($key) + { + if (isset($this->attributes[$key])) { + return $this->attributes[$key]; + } + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes + * @return $this + * + * @throws \Illuminate\Database\Eloquent\MassAssignmentException + */ + public function fill(array $attributes) + { + foreach ($attributes as $key => $value) { + $this->setAttribute($key, $value); + } + + return $this; + } + + /** + * Set a given attribute on the model. + * + * @param string $key + * @param mixed $value + * @return $this + */ + public function setAttribute($key, $value) + { + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Dynamically retrieve attributes on the report. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the report. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + + /** + * Determine if an attribute exists on the report. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return ! is_null($this->getAttribute($key)); + } + + /** + * Unset an attribute on the report. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key]); + } +} diff --git a/src/Notifications/ExceptionCaught.php b/src/Notifications/ExceptionCaught.php new file mode 100644 index 0000000..9949537 --- /dev/null +++ b/src/Notifications/ExceptionCaught.php @@ -0,0 +1,102 @@ +report = $report; + + $this->channels = $channels; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return $this->channels; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->subject($this->getSubject()) + ->view('sneaker::email.body', ['report' => $this->report]); + } + + /** + * Get the Slack representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\SlackMessage + */ + public function toSlack($notifiable) + { + return (new SlackMessage) + ->error() + ->content($this->getSubject()) + ->attachment(function ($attachment) { + $attachment->title($this->report->getMessage()) + ->content("```\n" . $this->report->getStacktrace() . "\n```") + ->markdown(['title', 'text']) + ->timestamp($this->report->getTime()) + ->footer('Sneaker'); + }); + } + + /** + * Get the subject of notification. + * + * @return string + */ + private function getSubject() + { + return sprintf("[Sneaker] | %s | Server - %s | Environment - %s", + $this->report->getName(), + request()->server('SERVER_NAME'), + $this->report->getEnv() + ); + } +} diff --git a/src/Report.php b/src/Report.php new file mode 100644 index 0000000..a404280 --- /dev/null +++ b/src/Report.php @@ -0,0 +1,205 @@ +time = Carbon::now(); + } + + /** + * Set the error name. + * + * @param string $name + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get the error name. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the error message. + * + * @param string $message + * @return $this + */ + public function setMessage($message) + { + $this->message = $message; + + return $this; + } + + /** + * Get the error message. + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * Get the error time. + * + * @return string + */ + public function getTime() + { + return $this->time; + } + + /** + * Set the application environment. + * + * @param string $env + * @return $this + */ + public function setEnv($env) + { + $this->env = $env; + + return $this; + } + + /** + * Get the application environment. + * + * @return string + */ + public function getEnv() + { + return $this->env; + } + + /** + * Set the error stacktrace. + * + * @param string $stacktrace + * @return $this + */ + public function setStacktrace($stacktrace) + { + $this->stacktrace = $stacktrace; + + return $this; + } + + /** + * Get the error stacktrace. + * + * @return string + */ + public function getStacktrace() + { + return $this->stacktrace; + } + + /** + * Set the error html. + * + * @param string $html + * @return $this + */ + public function setHtml($html) + { + $this->html = $html; + + return $this; + } + + /** + * Get the error html. + * + * @return string + */ + public function getHtml() + { + return $this->html; + } + + /** + * Get the error html content. + * + * @return string + */ + public function getHtmlContent() + { + return $this->html['content']; + } + + /** + * Get the error html content. + * + * @return string + */ + public function getHtmlStylesheet() + { + return $this->html['stylesheet']; + } +} diff --git a/src/Sneaker.php b/src/Sneaker.php index 973ba62..e14075c 100644 --- a/src/Sneaker.php +++ b/src/Sneaker.php @@ -3,60 +3,38 @@ namespace SquareBoat\Sneaker; use Exception; -use Illuminate\Log\Writer; -use Illuminate\Config\Repository; -use Illuminate\Contracts\Mail\Mailer; +use Illuminate\Contracts\Logging\Log; +use Illuminate\Contracts\Config\Repository; +use SquareBoat\Sneaker\Notifications\ExceptionCaught; +use SquareBoat\Sneaker\Exceptions\Handler as ExceptionHandler; class Sneaker { /** * The config implementation. * - * @var \Illuminate\Config\Repository + * @var \Illuminate\Contracts\Config\Repository */ private $config; - /** - * The exception handler implementation. - * - * @var \SquareBoat\Sneaker\ExceptionHandler - */ - private $handler; - - /** - * The mailer instance. - * - * @var \Illuminate\Contracts\Mail\Mailer - */ - private $mailer; - /** * The log writer implementation. * - * @var \Illuminate\Log\Writer + * @var \Illuminate\Contracts\Logging\Log */ private $logger; /** * Create a new sneaker instance. * - * @param \Illuminate\Config\Repository $config - * @param \SquareBoat\Sneaker\ExceptionHandler $handler - * @param \Illuminate\Contracts\Mail\Mailer $mailer - * @param \Illuminate\Log\Writer $logger + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Illuminate\Contracts\Logging\Log $logger * @return void */ - public function __construct(Repository $config, - ExceptionHandler $handler, - Mailer $mailer, - Writer $logger) + public function __construct(Repository $config, Log $logger) { $this->config = $config; - $this->handler = $handler; - - $this->mailer = $mailer; - $this->logger = $logger; } @@ -82,12 +60,7 @@ public function captureException(Exception $exception, $sneaking = false) $this->capture($exception); } } catch (Exception $e) { - $this->logger->error(sprintf( - 'Exception thrown in Sneaker when capturing an exception (%s: %s)', - get_class($e), $e->getMessage() - )); - - $this->logger->error($e); + $this->logSneakerException($e); if ($sneaking) { throw $e; @@ -95,23 +68,6 @@ public function captureException(Exception $exception, $sneaking = false) } } - /** - * Capture an exception. - * - * @param \Exception $exception - * @return void - */ - private function capture($exception) - { - $recipients = $this->config->get('sneaker.to'); - - $subject = $this->handler->convertExceptionToString($exception); - - $body = $this->handler->convertExceptionToHtml($exception); - - $this->mailer->to($recipients)->send(new ExceptionMailer($subject, $body)); - } - /** * Checks if sneaker is silent. * @@ -128,7 +84,7 @@ private function isSilent() * @param Exception $exception * @return boolean */ - private function shouldCapture(Exception $exception) + private function shouldCapture($exception) { $capture = $this->config->get('sneaker.capture'); @@ -174,4 +130,67 @@ private function isExceptionFromBot() return false; } + + /** + * Capture an exception. + * + * @param \Exception $exception + * @return void + */ + private function capture($exception) + { + $report = $this->getReport($exception); + + $notifiable = $this->getNotifiable(); + + $notifiable->notify( + new ExceptionCaught($report, $this->config->get('sneaker.notifications')) + ); + } + + /** + * Get the report of exception. + * + * @param \Exception $exception + * @return \SquareBoat\Sneaker\Report + */ + private function getReport($exception) + { + $handler = new ExceptionHandler($exception); + + return (new Report) + ->setEnv($this->config->get('app.env')) + ->setName($handler->getExceptionName()) + ->setHtml($handler->convertExceptionToHtml()) + ->setMessage($handler->convertExceptionToMessage()) + ->setStacktrace($handler->convertExceptionToStacktrace()); + } + + /** + * Get the notifiable. + * + * @return \SquareBoat\Sneaker\Notifiable + */ + private function getNotifiable() + { + return new Notifiable([ + 'emails' => $this->config->get('sneaker.mail.to'), + 'slack_webhook_url' => $this->config->get('sneaker.slack.webhook_url') + ]); + } + + /** + * Logs the exception thrown by Sneaker itself. + * + * @param \Exception $exception + */ + private function logSneakerException(Exception $exception) + { + $this->logger->error(sprintf( + 'Exception thrown in Sneaker when capturing an exception (%s: %s)', + get_class($exception), $exception->getMessage() + )); + + $this->logger->error($exception); + } } diff --git a/src/SneakerServiceProvider.php b/src/SneakerServiceProvider.php index aef60ad..af818a8 100644 --- a/src/SneakerServiceProvider.php +++ b/src/SneakerServiceProvider.php @@ -11,10 +11,10 @@ class SneakerServiceProvider extends ServiceProvider * * @var bool */ - protected $defer = false; + protected $defer = true; /** - * Bootstrap the application services. + * Bootstrap the sneaker's services. * * @return void */ @@ -22,10 +22,6 @@ public function boot() { $this->loadViewsFrom(__DIR__ . '/../resources/views', 'sneaker'); - $this->publishes([ - __DIR__ . '/../resources/views/email' => resource_path('views/vendor/sneaker/email') - ], 'views'); - $this->publishes([ __DIR__.'/../config/sneaker.php' => config_path('sneaker.php'), ], 'config'); @@ -38,7 +34,7 @@ public function boot() } /** - * Register the application services. + * Register the sneaker's services. * * @return void */ @@ -52,4 +48,14 @@ public function register() return $this->app->make(Sneaker::class); }); } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['sneaker']; + } } From 01355e327717c134792329278c2e180fc08f4afa Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Fri, 21 Apr 2017 22:30:00 +0530 Subject: [PATCH 03/12] Request info added in notofication. --- resources/views/email/body.blade.php | 72 ++++++- src/Markdown.php | 266 ++++++++++++++++++++++++++ src/Notifications/ExceptionCaught.php | 12 +- src/Report.php | 30 +++ src/Request.php | 63 ++++++ src/Sneaker.php | 13 +- src/demo.html | 114 +++++++++++ 7 files changed, 565 insertions(+), 5 deletions(-) create mode 100644 src/Markdown.php create mode 100644 src/Request.php create mode 100644 src/demo.html diff --git a/resources/views/email/body.blade.php b/resources/views/email/body.blade.php index c67c8af..b7bbbc3 100644 --- a/resources/views/email/body.blade.php +++ b/resources/views/email/body.blade.php @@ -18,14 +18,82 @@ border-radius: 10px; border: 1px solid #ccc; } + + .padding { + padding: 15px 28px; + } + + .extra-info .title { + color: #4674ca; + font-size: large; + font-family: Monaco,monospace; + border-bottom: 1px solid #ccc; + } + + .tags.no-margin { + margin-bottom: -10px; + } + .tags { + padding-left: 0; + list-style: none; + display: flex; + flex-wrap: wrap; + font-size: 13px; + } + .tags li { + white-space: nowrap; + margin: 0 10px 10px 0; + border-radius: 1px; + display: flex; + border: 1px solid #d0c9d7; + border-radius: 3px; + box-shadow: 0 1px 2px rgba(0,0,0,.04); + line-height: 1.2; + max-width: 100%; + } + .tags .key, .tags .value { + padding: 4px 8px; + min-width: 0; + white-space: nowrap; + } + .tags .value, .tags .value>a { + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; + } + .tags .value { + color: #4674ca; + background: #fbfbfc; + border-left: 1px solid #d8d2de; + border-radius: 0 3px 3px 0; + font-family: Monaco,monospace; + } + .tags .key, .tags .value { + padding: 4px 8px; + min-width: 0; + white-space: nowrap; + } + {!! $report->getHtmlStylesheet() !!} {!! $report->getHtmlContent() !!} -
- Requested Url - {{ request()->url() }} + +
+
Request
+
+
+ @foreach (array_filter($report->getRequest()) as $key => $item) +
  • + {{ $key }} + {{ $item }} +
  • + @endforeach +
    +
    +
    🕐  {{ $report->getTime()->format('l, jS \of F Y h:i:s a') }} {{ $report->getTime()->tzName }}
    diff --git a/src/Markdown.php b/src/Markdown.php new file mode 100644 index 0000000..8541a7a --- /dev/null +++ b/src/Markdown.php @@ -0,0 +1,266 @@ +writeln($text) + ->br(); + } + + /** + * @param string $header + * @return $this + */ + public function h1($header) + { + $header = $this->singleLine($header); + + return $this + ->writeln($header) + ->writeln(str_repeat("=", mb_strlen($header))) + ->br(); + } + + /** + * @param string $header + * @return $this + */ + public function h2($header) + { + $header = $this->singleLine($header); + + return $this + ->writeln($header) + ->writeln(str_repeat("-", mb_strlen($header))) + ->br(); + } + + /** + * @param string $header + * @return $this + */ + public function h3($header) + { + $header = $this->singleLine($header); + + return $this + ->writeln('### ' . $header) + ->br(); + } + + /** + * @param string $text + * @return $this + */ + public function blockqoute($text) + { + $lines = explode("\n", $text); + $newLines = array_map(function ($line) { + return trim('> ' . $line); + }, $lines); + + $content = implode("\n", $newLines); + + return $this->p($content); + } + + /** + * @param array $list + * @return $this + */ + public function bulletedList(array $list) + { + foreach ($list as $element) { + + $lines = explode("\n", $element); + + foreach ($lines as $i => $line) { + if ($i == 0) { + $this->writeln('* ' . $line); + } else { + $this->writeln(' ' . $line); + } + } + } + + $this->br(); + + return $this; + } + + /** + * @param array $list + * @return $this + */ + public function numberedList(array $list) + { + foreach (array_values($list) as $key => $element) { + + $lines = explode("\n", $element); + + foreach ($lines as $i => $line) { + if ($i == 0) { + $this->writeln(($key + 1) . '. ' . $line); + } else { + $this->writeln(' ' . $line); + } + } + } + + $this->br(); + + return $this; + } + + /** + * @return $this + */ + public function hr() + { + return $this->p('---------------------------------------'); + } + + /** + * @param string $code + * @param string $lang + * @return $this + */ + public function code($code, $lang = '') + { + return $this + ->writeln('```' . $lang) + ->writeln($code) + ->writeln('```') + ->br(); + } + + /** + * @return $this + */ + public function br() + { + return $this->write("\n"); + } + + /** + * @param string $code + * @return string + */ + public function inlineCode($code) + { + return sprintf('`%s`', $code); + } + + /** + * @param string $string + * @return string + */ + public function inlineItalic($string) + { + return sprintf('*%s*', $string); + } + + /** + * @param string $string + * @return string + */ + public function inlineBold($string) + { + return sprintf('**%s**', $string); + } + + /** + * @param string $url + * @param string $title + * @return string + */ + public function inlineLink($url, $title) + { + return sprintf('[%s](%s)', $title, $url); + } + + /** + * @param string $url + * @param string $title + * @return string + */ + public function inlineImg($url, $title) + { + return sprintf('![%s](%s)', $title, $url); + } + + /** + * @return string + */ + public function getMarkdown() + { + return trim($this->markdown); + } + + /** + * @param string $string + * @return $this + */ + protected function writeln($string) + { + return $this + ->write($string) + ->br(); + } + + /** + * @param string $string + * @return $this + */ + protected function write($string) + { + $this->markdown .= $string; + + return $this; + } + + /** + * @param $string + * @return mixed + */ + protected function singleLine($string) + { + $string = str_replace("\n", "", $string); + $string = preg_replace('/\s+/', " ", $string); + + return trim($string); + } + + /** + * @return string + */ + public function __toString() + { + return $this->getMarkdown(); + } +} diff --git a/src/Notifications/ExceptionCaught.php b/src/Notifications/ExceptionCaught.php index 9949537..350a2f4 100644 --- a/src/Notifications/ExceptionCaught.php +++ b/src/Notifications/ExceptionCaught.php @@ -79,8 +79,16 @@ public function toSlack($notifiable) ->content($this->getSubject()) ->attachment(function ($attachment) { $attachment->title($this->report->getMessage()) - ->content("```\n" . $this->report->getStacktrace() . "\n```") - ->markdown(['title', 'text']) + ->content((string) Markdown::block()->code($this->report->getStacktrace())) + ->markdown(['title', 'text']); + }) + ->attachment(function ($attachment) { + $attachment->title('Request') + ->fields(array_map(function($item) { + return (string) Markdown::block()->code($item); + }, array_filter($this->report->getRequest())) + ) + ->markdown(['fields']) ->timestamp($this->report->getTime()) ->footer('Sneaker'); }); diff --git a/src/Report.php b/src/Report.php index a404280..6935750 100644 --- a/src/Report.php +++ b/src/Report.php @@ -48,6 +48,13 @@ class Report */ protected $html; + /** + * The associated request. + * + * @var string + */ + protected $request; + /** * Create a new report instance. * @@ -202,4 +209,27 @@ public function getHtmlStylesheet() { return $this->html['stylesheet']; } + + /** + * Set the associated request. + * + * @param string $request + * @return $this + */ + public function setRequest($request) + { + $this->request = $request; + + return $this; + } + + /** + * Get the associated request. + * + * @return array + */ + public function getRequest() + { + return $this->request; + } } diff --git a/src/Request.php b/src/Request.php new file mode 100644 index 0000000..9201e5b --- /dev/null +++ b/src/Request.php @@ -0,0 +1,63 @@ +request = $request; + } + + /** + * Get the request formatted as meta data. + * + * @return array + */ + public function getMetaData() + { + $data = []; + + $data['URL'] = $this->request->fullUrl(); + + $data['Method'] = $this->request->getMethod(); + + $data['IP-Address'] = $this->request->getClientIp(); + + if ($headers = $this->request->headers->all()) { + $data['Host'] = Arr::get($headers, 'host.0'); + + $data['Connection'] = Arr::get($headers, 'connection.0'); + + $data['Upgrade-Insecure-Requests'] = Arr::get($headers, 'upgrade-insecure-requests.0'); + + $data['User-Agent'] = Arr::get($headers, 'user-agent.0'); + + $data['Accept'] = Arr::get($headers, 'accept.0'); + + $data['Referer'] = Arr::get($headers, 'referer.0'); + + $data['Accept-Encoding'] = Arr::get($headers, 'accept-encoding.0'); + + $data['Accept-Language'] = Arr::get($headers, 'accept-language.0'); + } + + return $data; + } +} \ No newline at end of file diff --git a/src/Sneaker.php b/src/Sneaker.php index e14075c..36024af 100644 --- a/src/Sneaker.php +++ b/src/Sneaker.php @@ -17,6 +17,13 @@ class Sneaker */ private $config; + /** + * The request implementation. + * + * @var \SquareBoat\Sneaker\Request + */ + private $request; + /** * The log writer implementation. * @@ -28,13 +35,16 @@ class Sneaker * Create a new sneaker instance. * * @param \Illuminate\Contracts\Config\Repository $config + * @param \SquareBoat\Sneaker\Request $request * @param \Illuminate\Contracts\Logging\Log $logger * @return void */ - public function __construct(Repository $config, Log $logger) + public function __construct(Repository $config, Request $request, Log $logger) { $this->config = $config; + $this->request = $request; + $this->logger = $logger; } @@ -160,6 +170,7 @@ private function getReport($exception) return (new Report) ->setEnv($this->config->get('app.env')) + ->setRequest($this->request->getMetaData()) ->setName($handler->getExceptionName()) ->setHtml($handler->convertExceptionToHtml()) ->setMessage($handler->convertExceptionToMessage()) diff --git a/src/demo.html b/src/demo.html new file mode 100644 index 0000000..8a7d0d3 --- /dev/null +++ b/src/demo.html @@ -0,0 +1,114 @@ + try { + if ($app['auth']->check()) { + $user = $app['auth']->user(); + $client->user_context(array( + 'id' => $user->getAuthIdentifier(), + )); + } + } catch (\Exception $e) { + error_log(sprintf('sentry.breadcrumbs error=%s', $e->getMessage())); + } + +$ua = $request->server('HTTP_USER_AGENT'); + +$ua = $request->header('User-Agent'); + + + +$headers = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; + } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; + } + } + dd($headers); + + +
    +
  • + browser + Firefox 3.6 +
  • +
  • + device + Other +
  • +
  • + level + error +
  • +
  • + logger + production +
  • +
  • + os + 7 +
  • +
  • + server_name + 154 +
  • +
  • + site + in +
  • +
  • + url + em +
  • +
  • + user + dsdsd +
  • +
    + +.pills.no-margin { + margin-bottom: -10px; +} + +.pills { + padding-left: 0; + list-style: none; + display: flex; + flex-wrap: wrap; + font-size: 13px; +} + +.pills li { + white-space: nowrap; + margin: 0 10px 10px 0; + border-radius: 1px; + display: flex; + border: 1px solid #d0c9d7; + border-radius: 3px; + box-shadow: 0 1px 2px rgba(0,0,0,.04); + line-height: 1.2; + max-width: 100%; +} + +.pills .key, .pills .value { + padding: 4px 8px; + min-width: 0; + white-space: nowrap; +} + +.pills .value, .pills .value>a { + max-width: 100%; + text-overflow: ellipsis; + white-space: nowrap; +} +.pills .value { + color: #4674ca; + background: #fbfbfc; + border-left: 1px solid #d8d2de; + border-radius: 0 3px 3px 0; + font-family: Monaco,monospace; +} +.pills .key, .pills .value { + padding: 4px 8px; + min-width: 0; + white-space: nowrap; +} From f43d9bb6b879d49629fdb9ddecd36ab085b990dc Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sat, 22 Apr 2017 12:56:31 +0530 Subject: [PATCH 04/12] User and Extra MetaData added in notification --- resources/views/email/body.blade.php | 35 ++++++++ src/Notifications/ExceptionCaught.php | 20 +++++ src/Report.php | 62 +++++++++++++- src/Sneaker.php | 32 ++++++++ src/demo.html | 114 -------------------------- 5 files changed, 148 insertions(+), 115 deletions(-) delete mode 100644 src/demo.html diff --git a/resources/views/email/body.blade.php b/resources/views/email/body.blade.php index b7bbbc3..6c8dd16 100644 --- a/resources/views/email/body.blade.php +++ b/resources/views/email/body.blade.php @@ -61,6 +61,9 @@ text-overflow: ellipsis; white-space: nowrap; } + .tags .key { + font-family:inherit; + } .tags .value { color: #4674ca; background: #fbfbfc; @@ -80,6 +83,38 @@ {!! $report->getHtmlContent() !!} + @if($report->getUser()) +
    +
    User
    +
    +
    + @foreach (array_filter($report->getUser()) as $key => $item) +
  • + {{ $key }} + {{ $item }} +
  • + @endforeach +
    +
    +
    + @endif + + @if($report->getExtra()) +
    +
    Extra Data
    +
    +
    + @foreach (array_filter($report->getExtra()) as $key => $item) +
  • + {{ $key }} + {{ $item }} +
  • + @endforeach +
    +
    +
    + @endif +
    Request
    diff --git a/src/Notifications/ExceptionCaught.php b/src/Notifications/ExceptionCaught.php index 350a2f4..6eb569d 100644 --- a/src/Notifications/ExceptionCaught.php +++ b/src/Notifications/ExceptionCaught.php @@ -82,6 +82,26 @@ public function toSlack($notifiable) ->content((string) Markdown::block()->code($this->report->getStacktrace())) ->markdown(['title', 'text']); }) + ->attachment(function ($attachment) { + if($user = $this->report->getUser()) { + $attachment->title('User') + ->fields(array_map(function($item) { + return (string) Markdown::block()->code($item); + }, array_filter($user)) + ) + ->markdown(['fields']); + } + }) + ->attachment(function ($attachment) { + if($extra = $this->report->getExtra()) { + $attachment->title('Extra Data') + ->fields(array_map(function($item) { + return (string) Markdown::block()->code($item); + }, array_filter($extra)) + ) + ->markdown(['fields']); + } + }) ->attachment(function ($attachment) { $attachment->title('Request') ->fields(array_map(function($item) { diff --git a/src/Report.php b/src/Report.php index 6935750..af25ea0 100644 --- a/src/Report.php +++ b/src/Report.php @@ -51,10 +51,24 @@ class Report /** * The associated request. * - * @var string + * @var array */ protected $request; + /** + * The associated user. + * + * @var array + */ + protected $user; + + /** + * The associated extra data. + * + * @var array + */ + protected $extra; + /** * Create a new report instance. * @@ -232,4 +246,50 @@ public function getRequest() { return $this->request; } + + /** + * Set the associated user. + * + * @param string $user + * @return $this + */ + public function setUser($user) + { + $this->user = $user; + + return $this; + } + + /** + * Get the associated user. + * + * @return array + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the associated extra data. + * + * @param string $extra + * @return $this + */ + public function setExtra($extra) + { + $this->extra = $extra; + + return $this; + } + + /** + * Get the associated extra data. + * + * @return array + */ + public function getExtra() + { + return $this->extra; + } } diff --git a/src/Sneaker.php b/src/Sneaker.php index 36024af..723a995 100644 --- a/src/Sneaker.php +++ b/src/Sneaker.php @@ -3,6 +3,7 @@ namespace SquareBoat\Sneaker; use Exception; +use Illuminate\Support\Arr; use Illuminate\Contracts\Logging\Log; use Illuminate\Contracts\Config\Repository; use SquareBoat\Sneaker\Notifications\ExceptionCaught; @@ -31,6 +32,13 @@ class Sneaker */ private $logger; + /** + * The meta data to be added in sneaker notifications. + * + * @var array + */ + private $metaData = []; + /** * Create a new sneaker instance. * @@ -171,6 +179,8 @@ private function getReport($exception) return (new Report) ->setEnv($this->config->get('app.env')) ->setRequest($this->request->getMetaData()) + ->setUser(Arr::get($this->metaData, 'user')) + ->setExtra(Arr::get($this->metaData, 'extra')) ->setName($handler->getExceptionName()) ->setHtml($handler->convertExceptionToHtml()) ->setMessage($handler->convertExceptionToMessage()) @@ -204,4 +214,26 @@ private function logSneakerException(Exception $exception) $this->logger->error($exception); } + + /** + * Execute the user context callback. + * + * @param callable $callback + * @return void + */ + public function userContext($callback) + { + $this->metaData['user'] = call_user_func($callback); + } + + /** + * Execute the extra context callback. + * + * @param callable $callback + * @return void + */ + public function extraContext($callback) + { + $this->metaData['extra'] = call_user_func($callback); + } } diff --git a/src/demo.html b/src/demo.html deleted file mode 100644 index 8a7d0d3..0000000 --- a/src/demo.html +++ /dev/null @@ -1,114 +0,0 @@ - try { - if ($app['auth']->check()) { - $user = $app['auth']->user(); - $client->user_context(array( - 'id' => $user->getAuthIdentifier(), - )); - } - } catch (\Exception $e) { - error_log(sprintf('sentry.breadcrumbs error=%s', $e->getMessage())); - } - -$ua = $request->server('HTTP_USER_AGENT'); - -$ua = $request->header('User-Agent'); - - - -$headers = array(); - foreach ($_SERVER as $key => $value) { - if (0 === strpos($key, 'HTTP_')) { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))))] = $value; - } elseif (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH')) && $value !== '') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))))] = $value; - } - } - dd($headers); - - -
    -
  • - browser - Firefox 3.6 -
  • -
  • - device - Other -
  • -
  • - level - error -
  • -
  • - logger - production -
  • -
  • - os - 7 -
  • -
  • - server_name - 154 -
  • -
  • - site - in -
  • -
  • - url - em -
  • -
  • - user - dsdsd -
  • -
    - -.pills.no-margin { - margin-bottom: -10px; -} - -.pills { - padding-left: 0; - list-style: none; - display: flex; - flex-wrap: wrap; - font-size: 13px; -} - -.pills li { - white-space: nowrap; - margin: 0 10px 10px 0; - border-radius: 1px; - display: flex; - border: 1px solid #d0c9d7; - border-radius: 3px; - box-shadow: 0 1px 2px rgba(0,0,0,.04); - line-height: 1.2; - max-width: 100%; -} - -.pills .key, .pills .value { - padding: 4px 8px; - min-width: 0; - white-space: nowrap; -} - -.pills .value, .pills .value>a { - max-width: 100%; - text-overflow: ellipsis; - white-space: nowrap; -} -.pills .value { - color: #4674ca; - background: #fbfbfc; - border-left: 1px solid #d8d2de; - border-radius: 0 3px 3px 0; - font-family: Monaco,monospace; -} -.pills .key, .pills .value { - padding: 4px 8px; - min-width: 0; - white-space: nowrap; -} From 98edc18766eb77712b05357a8dd911ee7c92dca5 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 30 Apr 2017 14:26:38 +0530 Subject: [PATCH 05/12] Sanitized the data provided by user and some code clean ups --- composer.json | 5 ++++- resources/views/email/body.blade.php | 6 +++--- src/Commands/Sneak.php | 31 +++++++++++++++++++++++---- src/Notifications/ExceptionCaught.php | 28 +++++++++++++----------- src/Request.php | 2 +- src/Sneaker.php | 29 +++++++++++++++++++++---- src/helpers.php | 27 +++++++++++++++++++++++ 7 files changed, 103 insertions(+), 25 deletions(-) create mode 100644 src/helpers.php diff --git a/composer.json b/composer.json index 42e7dd7..4dc908c 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,10 @@ "autoload": { "psr-4": { "SquareBoat\\Sneaker\\": "src/" - } + }, + "files": [ + "src/helpers.php" + ] }, "minimum-stability": "dev" } diff --git a/resources/views/email/body.blade.php b/resources/views/email/body.blade.php index 6c8dd16..898c8be 100644 --- a/resources/views/email/body.blade.php +++ b/resources/views/email/body.blade.php @@ -88,7 +88,7 @@
    User
    - @foreach (array_filter($report->getUser()) as $key => $item) + @foreach ($report->getUser() as $key => $item)
  • {{ $key }} {{ $item }} @@ -104,7 +104,7 @@
    Extra Data
    - @foreach (array_filter($report->getExtra()) as $key => $item) + @foreach ($report->getExtra() as $key => $item)
  • {{ $key }} {{ $item }} @@ -119,7 +119,7 @@
    Request
    - @foreach (array_filter($report->getRequest()) as $key => $item) + @foreach ($report->getRequest() as $key => $item)
  • {{ $key }} {{ $item }} diff --git a/src/Commands/Sneak.php b/src/Commands/Sneak.php index d5183d7..f571a60 100644 --- a/src/Commands/Sneak.php +++ b/src/Commands/Sneak.php @@ -3,6 +3,7 @@ namespace SquareBoat\Sneaker\Commands; use Exception; +use SquareBoat\Sneaker\Sneaker; use Illuminate\Console\Command; use Illuminate\Config\Repository; use SquareBoat\Sneaker\Exceptions\DummyException; @@ -31,17 +32,27 @@ class Sneak extends Command */ private $config; + /** + * The sneaker implementation. + * + * @var \SquareBoat\Sneaker\Sneaker + */ + private $sneaker; + /** * Create a sneak command instance. * * @param \Illuminate\Config\Repository $config + * @param \SquareBoat\Sneaker\Sneaker $sneaker * @return void */ - public function __construct(Repository $config) + public function __construct(Repository $config, Sneaker $sneaker) { parent::__construct(); $this->config = $config; + + $this->sneaker = $sneaker; } /** @@ -54,11 +65,23 @@ public function handle() $this->overrideConfig(); try { - $this->laravel->make('sneaker')->captureException(new DummyException, true); + $this->sneaker->userContext(function() { + return [ + 'id' => 10, + 'name' => 'John Doe' + ]; + }) + ->extraContext(function() { + return [ + 'App' => 'Project X', + 'Version' => 'v3.0.0' + ]; + }) + ->captureException(new DummyException, true); $this->info('Sneaker is working fine ✅'); - } catch (Exception $e) { - (new ConsoleApplication)->renderException($e, $this->output); + } catch (Exception $exception) { + (new ConsoleApplication)->renderException($exception, $this->output); } } diff --git a/src/Notifications/ExceptionCaught.php b/src/Notifications/ExceptionCaught.php index 6eb569d..1f204f3 100644 --- a/src/Notifications/ExceptionCaught.php +++ b/src/Notifications/ExceptionCaught.php @@ -85,29 +85,20 @@ public function toSlack($notifiable) ->attachment(function ($attachment) { if($user = $this->report->getUser()) { $attachment->title('User') - ->fields(array_map(function($item) { - return (string) Markdown::block()->code($item); - }, array_filter($user)) - ) + ->fields($this->getPayload($user)) ->markdown(['fields']); } }) ->attachment(function ($attachment) { if($extra = $this->report->getExtra()) { $attachment->title('Extra Data') - ->fields(array_map(function($item) { - return (string) Markdown::block()->code($item); - }, array_filter($extra)) - ) + ->fields($this->getPayload($extra)) ->markdown(['fields']); } }) ->attachment(function ($attachment) { $attachment->title('Request') - ->fields(array_map(function($item) { - return (string) Markdown::block()->code($item); - }, array_filter($this->report->getRequest())) - ) + ->fields($this->getPayload($this->report->getRequest())) ->markdown(['fields']) ->timestamp($this->report->getTime()) ->footer('Sneaker'); @@ -127,4 +118,17 @@ private function getSubject() $this->report->getEnv() ); } + + /** + * Add markdoen to given payload. + * + * @param array $items + * @return array + */ + private function getPayload($items) + { + return array_map(function($item) { + return (string) Markdown::block()->code($item); + }, $items); + } } diff --git a/src/Request.php b/src/Request.php index 9201e5b..27ae729 100644 --- a/src/Request.php +++ b/src/Request.php @@ -58,6 +58,6 @@ public function getMetaData() $data['Accept-Language'] = Arr::get($headers, 'accept-language.0'); } - return $data; + return array_filter($data); } } \ No newline at end of file diff --git a/src/Sneaker.php b/src/Sneaker.php index 723a995..4b74e5b 100644 --- a/src/Sneaker.php +++ b/src/Sneaker.php @@ -4,6 +4,7 @@ use Exception; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Illuminate\Contracts\Logging\Log; use Illuminate\Contracts\Config\Repository; use SquareBoat\Sneaker\Notifications\ExceptionCaught; @@ -219,21 +220,41 @@ private function logSneakerException(Exception $exception) * Execute the user context callback. * * @param callable $callback - * @return void + * @return $this */ public function userContext($callback) { - $this->metaData['user'] = call_user_func($callback); + $this->metaData['user'] = $this->sanitize(call_user_func($callback)); + + return $this; } /** * Execute the extra context callback. * * @param callable $callback - * @return void + * @return $this */ public function extraContext($callback) { - $this->metaData['extra'] = call_user_func($callback); + $this->metaData['extra'] = $this->sanitize(call_user_func($callback)); + + return $this; + } + + /** + * [sanitize description] + * @param [type] $items [description] + * @return [type] [description] + */ + private function sanitize($items) + { + $items = $items instanceof Collection ? $items->all() : $items; + + if (! is_array($items)) { + return []; + } + + return array_flat($items); } } diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..776d395 --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,27 @@ + $item) { + $item = $item instanceof Illuminate\Support\Collection ? $item->all() : $item; + + if(is_array($item)) { + $result = $result + array_flat($item, $prefix . $key . '.'); + } else { + $result[$prefix.$key] = $item; + } + } + + return $result; + } +} From d73b2ab26abc88baf3388a7744a050a024781d39 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 30 Apr 2017 21:01:06 +0530 Subject: [PATCH 06/12] config file text updated --- config/sneaker.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/sneaker.php b/config/sneaker.php index 886ec93..d46f31e 100644 --- a/config/sneaker.php +++ b/config/sneaker.php @@ -4,7 +4,7 @@ /* |-------------------------------------------------------------------------- - | Sends an email on Exception or be silent + | Sends a notification on Exception or be silent. |-------------------------------------------------------------------------- | | Should we email error traces? @@ -17,7 +17,7 @@ | A list of the exception types that should be captured. |-------------------------------------------------------------------------- | - | For which exception class emails should be sent? + | For which exception class notification should be sent? | | You can also use '*' in the array which will in turn captures every | exception. @@ -74,7 +74,7 @@ | Ignore Crawler Bots |-------------------------------------------------------------------------- | - | For which bots should we NOT send error emails? + | For which bots should we NOT send error notifications? | */ 'ignored_bots' => [ From a73104e68d5591c750927be6c4466ff0016de98a Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 30 Apr 2017 23:44:39 +0530 Subject: [PATCH 07/12] Code clean up and config file updated with verbose comments --- config/sneaker.php | 15 ++++++++++----- src/Commands/Sneak.php | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/config/sneaker.php b/config/sneaker.php index d46f31e..a5c8cf6 100644 --- a/config/sneaker.php +++ b/config/sneaker.php @@ -14,17 +14,22 @@ /* |-------------------------------------------------------------------------- - | A list of the exception types that should be captured. + | A list of exception types that should be captured. |-------------------------------------------------------------------------- | - | For which exception class notification should be sent? + | For which exception type notification should be sent? | - | You can also use '*' in the array which will in turn captures every - | exception. + | By defautl we have set the array to '*', which means that we will capture + | all the exceptions that occurs in the application. To explicitly list + | the class define them below as: + | + | 'capture' => [ + | Symfony\Component\Debug\Exception\FatalErrorException::class, + | ], | */ 'capture' => [ - Symfony\Component\Debug\Exception\FatalErrorException::class, + '*' ], /* diff --git a/src/Commands/Sneak.php b/src/Commands/Sneak.php index f571a60..90f52c9 100644 --- a/src/Commands/Sneak.php +++ b/src/Commands/Sneak.php @@ -67,8 +67,8 @@ public function handle() try { $this->sneaker->userContext(function() { return [ - 'id' => 10, - 'name' => 'John Doe' + 'ID' => 10, + 'Name' => 'John Doe' ]; }) ->extraContext(function() { From 2793f9f954097df94d6321f506cfb2eb3520c36b Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Sun, 30 Apr 2017 23:51:23 +0530 Subject: [PATCH 08/12] Readme updated for v3 --- README.md | 136 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 96 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 0978880..1037ebe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Laravel Exception Notifications -An easy way to send emails with stack trace whenever an exception occurs on the server for Laravel applications. +An easy way to send Notifications via email and slack channels along with full stack trace whenever an exception occurs on the server for Laravel applications. ![sneaker example image](sneaker.png?raw=true "Sneaker") @@ -15,96 +15,114 @@ $ composer require squareboat/sneaker ``` ### Configure Laravel - -Once installation operation is complete, simply add the service provider to your project's `config/app.php` file: +Once installation operation is complete, simply add both the service provider and facade classes to your project's `config/app.php file: #### Service Provider -``` + +```php SquareBoat\Sneaker\SneakerServiceProvider::class, ``` -### Add Sneaker's Exception Capturing +#### Facade +```php +'Sneaker' => SquareBoat\Sneaker\Facades\Sneaker::class, +``` + +### Add Sneaker's Exception Capturing Add exception capturing to `app/Exceptions/Handler.php`: ```php public function report(Exception $exception) { - app('sneaker')->captureException($exception); + if ($this->shouldReport($exception)) { + Sneaker::captureException($exception); + } parent::report($exception); } ``` -### Configuration File +### Configuration -Create the Sneaker configuration file with this command: +Create the Sneaker configuration file using this command: ```bash $ php artisan vendor:publish --provider="SquareBoat\Sneaker\SneakerServiceProvider" ``` -The config file will be published in `config/sneaker.php` +The config file will be published in `config/sneaker.php` Following are the configuration attributes used for the Sneaker. #### silent -The package comes with `'silent' => true,` configuration by default, since you probably don't want error emailing enabled on your development environment. Especially if you've set `'debug' => true,`. +The package comes with `'silent' => false,` configuration by default. It means that Sneaker is configured to send notifications when an exception occurs. ```php -'silent' => env('SNEAKER_SILENT', true), +'silent' => env('SNEAKER_SILENT', false), ``` - -For sending emails when an exception occurs set `SNEAKER_SILENT=false` in your `.env` file. - +To avoid sending notifications on your development environment set `SNEAKER_SILENT=true` in your `.env` file. #### capture -It contains the list of the exception types that should be captured. You can add your exceptions here for which you want to send error emails. +It contains the list of the exception types that should be captured. You can add your exceptions tyeps here for which you want to send notifiactions. -By default, the package has included `Symfony\Component\Debug\Exception\FatalErrorException::class`. +By defautl we have `'*'` in the array, which means that we will capture all the exceptions that occurs in the application. ```php 'capture' => [ - Symfony\Component\Debug\Exception\FatalErrorException::class, + '*' ], ``` -You can also use `'*'` in the `$capture` array which will in turn captures every exception. +To explicitly list the exception types, remove `'*'` and define them below as: ```php 'capture' => [ - '*' + Symfony\Component\Debug\Exception\FatalErrorException::class, + ... ], ``` -To use this feature you should add the following code in `app/Exceptions/Handler.php`: +#### notifications -```php -public function report(Exception $exception) -{ - if ($this->shouldReport($exception)) { - app('sneaker')->captureException($exception); - } +It lists the channels on which the notification will be delivered. Senaker by default supports `'mail'` and `'slack'` channels. - parent::report($exception); -} +```bash +'notifications' => [ + 'mail', + 'slack', +], +``` + +#### mail + +### to +The email address used to deliver the notification. + +```php +'mail' => [ + 'to' => [ + // 'your@email.com', + ], +], ``` -#### to +#### slack -This is the list of recipients of error emails. +### webhook_url +The webhook URL to which the notification should be delivered. Webhook URLs may be generated by adding an "Incoming Webhook" service to your Slack team. ```php -'to' => [ - // 'hello@example.com', +'slack' => [ + 'webhook_url' => env('SNEAKER_SLACK_WEBHOOK_URL'), ], ``` #### ignored_bots -This is the list of bots for which we should NOT send error emails. +This is the list of bots for which we should NOT send error notifications. ```php 'ignored_bots' => [ @@ -115,19 +133,55 @@ This is the list of bots for which we should NOT send error emails. ], ``` -## Customize +## Adding Context +Sometimes you may need more information for debugging when an exception occurs like which user got the excpetion, app version, etc... -If you need to customize the subject and body of email, run following command: +For that we can add some context as: -```bash -$ php artisan vendor:publish --provider="SquareBoat\Sneaker\SneakerServiceProvider" +### User Context +You can add user context to be send in each notification. + +```php +Sneaker::userContext(function() { + return [ + 'ID' => 10, + 'Name' => 'John Doe' + ]; +}); ``` -> Note - Don't run this command again if you have run it already. +### Extra Context +You can add extra context to be send in each notification. -Now the email's subject and body view are located in the `resources/views/vendor/sneaker` directory. +```php +Sneaker::extraContext(function() { + return [ + 'App' => 'Project X', + 'Version' => 'v3.0.0' + ]; +}); +``` -We have passed the thrown exception object `$exception` in the view which you can use to customize the view to fit your needs. +The best place to add them is in `report()` method of `app/Exceptions/Handler.php`: + +```php +public function report(Exception $exception) +{ + Sneaker::userContext(function() { + // return user context array + }); + + Sneaker::extraContext(function() { + // return extra context array + }); + + if ($this->shouldReport($exception)) { + Sneaker::captureException($exception); + } + + parent::report($exception); +} +``` ## Sneak ### Test your integration @@ -139,6 +193,8 @@ $ php artisan sneaker:sneak A `SquareBoat\Sneaker\Exceptions\DummyException` class will be thrown and captured by Sneaker. The captured exception will appear in your configured email immediately. +You can also add the verbosity flags `-v/-vv/-vvv`. + ## Security If you discover any security related issues, please email amit.gupta@squareboat.com instead of using the issue tracker. From bfc8abd833a9343d56448d4e9e0c9f69ab7c31ef Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Mon, 1 May 2017 10:28:36 +0530 Subject: [PATCH 09/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1037ebe..009e171 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ $ composer require squareboat/sneaker ``` ### Configure Laravel -Once installation operation is complete, simply add both the service provider and facade classes to your project's `config/app.php file: +Once installation operation is complete, simply add both the service provider and facade classes to your project's `config/app.php` file: #### Service Provider From d8606d0bf11e9324672b6758fd270be5d6c0438e Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Mon, 4 Sep 2017 15:06:55 +0530 Subject: [PATCH 10/12] ReadmE UPDATED --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1037ebe..dc20850 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ To avoid sending notifications on your development environment set `SNEAKER_SILE #### capture -It contains the list of the exception types that should be captured. You can add your exceptions tyeps here for which you want to send notifiactions. +It contains the list of the exception types that should be captured. You can add your exceptions types here for which you want to send notifiactions. By defautl we have `'*'` in the array, which means that we will capture all the exceptions that occurs in the application. @@ -76,7 +76,7 @@ By defautl we have `'*'` in the array, which means that we will capture all the ], ``` -To explicitly list the exception types, remove `'*'` and define them below as: +To explicitly list the exception types, remove `'*'` and define them as: ```php 'capture' => [ @@ -87,7 +87,7 @@ To explicitly list the exception types, remove `'*'` and define them below as: #### notifications -It lists the channels on which the notification will be delivered. Senaker by default supports `'mail'` and `'slack'` channels. +It lists the channels on which the notification will be delivered. Out of the box, Senaker supports `'mail'` and `'slack'` channels, and plan is to add more in future. ```bash 'notifications' => [ @@ -99,7 +99,7 @@ It lists the channels on which the notification will be delivered. Senaker by de #### mail ### to -The email address used to deliver the notification. +The email address used to deliver the mail notification. ```php 'mail' => [ @@ -112,7 +112,7 @@ The email address used to deliver the notification. #### slack ### webhook_url -The webhook URL to which the notification should be delivered. Webhook URLs may be generated by adding an "Incoming Webhook" service to your Slack team. +The webhook URL to which the slack notification should be delivered. Webhook URLs may be generated by adding an "Incoming Webhook" service to your Slack team. ```php 'slack' => [ @@ -185,13 +185,13 @@ public function report(Exception $exception) ## Sneak ### Test your integration -To verify that Sneaker is configured correctly and our integration is working, use `sneaker:sneak` Artisan command: +To verify that Sneaker is configured correctly and your integration is working, use `sneaker:sneak` Artisan command: ```bash $ php artisan sneaker:sneak ``` -A `SquareBoat\Sneaker\Exceptions\DummyException` class will be thrown and captured by Sneaker. The captured exception will appear in your configured email immediately. +A `SquareBoat\Sneaker\Exceptions\DummyException` class will be thrown and captured by Sneaker. The captured exception will appear in your configured notifiaction channel immediately. You can also add the verbosity flags `-v/-vv/-vvv`. From bb96bd464574f3b0c04a655808f42ed16bc5a6c3 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Mon, 4 Sep 2017 15:07:28 +0530 Subject: [PATCH 11/12] Minor Fix --- src/Commands/Sneak.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Commands/Sneak.php b/src/Commands/Sneak.php index 90f52c9..fa98525 100644 --- a/src/Commands/Sneak.php +++ b/src/Commands/Sneak.php @@ -65,7 +65,8 @@ public function handle() $this->overrideConfig(); try { - $this->sneaker->userContext(function() { + $this->sneaker + ->userContext(function() { return [ 'ID' => 10, 'Name' => 'John Doe' From 1847aa1cffa016e10b4addba112cda4c278a2e66 Mon Sep 17 00:00:00 2001 From: Amit Gupta Date: Mon, 4 Sep 2017 15:07:57 +0530 Subject: [PATCH 12/12] MInor UI changes --- resources/views/email/body.blade.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/views/email/body.blade.php b/resources/views/email/body.blade.php index 898c8be..4ae658b 100644 --- a/resources/views/email/body.blade.php +++ b/resources/views/email/body.blade.php @@ -12,11 +12,13 @@ .extra-info { background-color: #FFFFFF; padding: 15px 28px; + margin: 0 auto; margin-bottom: 20px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; border: 1px solid #ccc; + width: 970px; } .padding { @@ -129,8 +131,10 @@
  • -
    - 🕐  {{ $report->getTime()->format('l, jS \of F Y h:i:s a') }} {{ $report->getTime()->tzName }} +
    +
    + 🕐  {{ $report->getTime()->format('l, jS \of F Y h:i:s a') }} {{ $report->getTime()->tzName }} +