handling logging in laravel on balanced servers - laravel

I have 2 balanced servers for my Laravel app, but each of them is logging in it's own target folder (we use a daily file logging): is there any way to have a centralized location to save logs to?
I already arranged the logging configurations to include server name in the output, but i can't figure out how to handle this situation.
Maybe an object storage? or what?
Non third party solutions will be better, we would like to keep everything inside laravel and our servers, but if it's no choice we'll arrange an external solution.

Now there probably isn't a "best" way to do this and Laravel offers a lot of flexibility on how to do this if you check the docs. In fact since NewRelic is supported this can be a decent way to solve this issue if you are using NewRelic already.
We use the database to log. We can do this because the database is hosted outside the servers but in the same VPC of AWS which means the latency is very low. We have a table with id, message, detail, created_at, severity here's a simple way to do it:
In your EventServiceProvider (any service provider will work really)
Event::listen(MessageLogged::class, function (MessageLogged $messageLogged) {
[ $level, $message, $context ] = [ $messageLogged->level, $messageLogged->message, $messageLogged->context ];
if (!\Log::getLogger()->isHandling(Logger::toMonologLevel($level))) {
return; // Don't log if the logger isn't actually handling this level
}
if (($message instanceof \Exception || $message instanceof \Throwable)) {
$context = [$message];
$message = $message->getMessage();
}
DB::table('log')->insert([
'message' => $message
'detail' => implode(PHP_EOL, $context ?: []),
'created_at' => Carbon::now(),
'severity' => $level
]);
});
If performance is an issue you can also create a task that will queue the processing of the log event, but that means you have to queue it with the time the event happened rather than the time it was handled.

Related

How can I validate GET controller params in CakePHP 2?

Given this on the model:
public $validate = [
'amount' => array(
'rule' => array('comparison', '>=', 0),
'message' => 'You must buy over 0 of this item!'
)
];
How can I validate param #2 of the below?
public function buy(int $item, int $amount) {
Validation seems to be built only for POST, which I'd like to opt out of here.
First things first, modifying the database with GET requests is an anti-pattern for many different reasons. Even if you assume a friendly user agent (which you never should!), browsers can behave quirky and do unexpected stuff like for example sending GET request multiple times (that is perfectly valid as GET is not ment to modify data), which they usually won't for POST/PUT/DELETE.
I would strongly suggest to change your endpoint to handle POST requests instead.
That being said, you can generally validate whatever you want, the validation mechanisms first and foremost just validate data, they don't know or care where it stems from. You can hand over whatever data you want to your model, and let it validate it:
$data = array(
'item' => $item,
'amount' => $amount,
);
$this->ModelName->set($data);
if ($this->ModelName->validates()) {
// data is valid
} else {
// data is invalid
$errors = $this->ModelName->validationErrors;
}
Moreover you can use CakePHP's validation methods completely manually too:
App::uses('Utility', 'Validation');
$isValid = Validation::comparison($amount, '>' 0);
This example of course doesn't make too much sense, given that $isValid = $amount > 0 would do the same, however it should just show that you can validate anything everywhere without models being involved.
See also
Cookbook > Models > Data Validation > Validating Data from the Controller
Cookbook > Models > Data Validation > Core Validation Rules

Refactoring a Laravel aplication layers

My Laravel project starts to grow up and I'm starting to dealing with fat Controllers and Models. Probably I'm for away from the SOLID principles path and my code is not DRY.
I decided to refactor my layers logic to follow more SOLID and try to be more DRY. After some research over the internet I ended up with the following conclusion:
Let me explain this diagram:
First, we start with a view, where the user performs some action and the request is sent to the Controller.
Controller responsibility is to handle the request and give the response to the user. It will be able to call 3 different layers (blue numbers):
Service: to handle business logic like calculations, special actions, etc.
Repository: where all query logic will be placed. For example, if on index method we want to return the users list with users that have more than 100 posts and are ordered by name (Example 1).
Laravel Resource (Transformers): with the responsibility to transform a model into JSON. When we change our table we don't have to change all the views and controllers or models affected by that change. It will be all done in one place.
Example 1:
# UserController.php
public function index()
{
$users = new UserCollection($this->UserRep->usersWithPost());
return view('user-list', compact('users'));
}
# UserRepository.php
public function usersWithPost($min = 100)
{
return $this->model->where('post_count', '>=', $min)->orderBy('name');
}
# UserResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'post_count' => $this->post_count,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Service calls (green numbers):
It may call Repository if it needs any data from my model to perform some action.
It also may call Laravel Resource "Transformers" if there were repository calls.
The repository will use the eloquent model to persist query on my data storage (MySQL).
This is how I plan to refactor my code, however, I do not have experience with Laravel and I would like to ask more experienced developers if they can indicate me if I'm on the right path.
Do you think it makes sense and it is a good practice?
I would like to highlight that I will not switch between ORMs. I will use eloquent with MySQL as my data storage, that's why I'm planning to put all my queries to repositories to have a different layer for queries logic.

Check if model belongs to other model

In my application, I have users that are connected to businesses. These businesses have job offers. When a user wants to edit a business' job offer, the url will be like /business/foo/job/bar. When I change the business variable to the name of a different one, like this: /business/other-business/job/bar I still get the job called bar even though it does not belong to the business. I tried to use gates to check if the job offers belong to the business but it didn't work. Below is the code for the edit function which shows the edit page.
public function edit(Business $business, Job $job)
{
return view('dashboard.business.jobs.edit', [
'business' => $business,
'job' => $job,
]);
}
I can add the following code to all the functions but it is not very pretty
if($business->id !== $job->business->id) { return abort(404); }
I was wondering if there is a better solution to this problem.
Well you can choose between
if($business->jobs->contains($job->id)) return redirect()->to("somewhere");
or
if($job->business->is($business)) return redirect()->to("somewhere");
The second one is more efficient because you have to retrive from the database just one record to check if they are correlated, the business record, where the first one instead you have to retrive all the jobs of that business.
Those solution in my opinion are actually very clear, you can literally read them and understand what you are doing.
Also if you want just one line of code, you can do this:
public function edit(Business $business, Job $job)
{
return
$job->business->is($business)
?
view('dashboard.business.jobs.edit', [
'business' => $business,
'job' => $job,
])
:
return redirect()->to("somewhere");
}

Noticeable time increase when checking permissions after updating from laravel 5.7 to laravel 5.8 using silber/bouncer-rc5

I am using bouncer for my ACL needs and ever since upgrading my project from laravel 5.7 to 5.8 I've noticed a significant increase in the time it takes for my requests to process.
I'm dealing with two models (let's call them Parent and Child), as well as the permissions the authenticated user has over them.
// Takes about 110ms. Eager loads various nested relationships and counters with specific constraints
$parents = Parent::myScope(...)->get();
// Bottleneck. Takes 5 minutes (!). Used to take about 40 seconds on laravel 5.7
$parents->each(function ($parent) {
$parent->permissions = [
'edit' => auth()->user()->can('edit', $parent),
'delete' => auth()->user()->can('delete', $parent),
'restore' => auth()->user()->can('restore', $parent)
];
$parent->children()->each(function ($child) {
$child->permissions = [
'edit' => auth()->user()->can('edit', $child),
'delete' => auth()->user()->can('delete', $child),
'restore' => auth()->user()->can('restore', $child)
];
}
}
I'm appending the permissions like this because the $parents variable will be sent as json to the front-end. I'm pretty sure this implementation is wrong, and must have a better alternative but the real issue is this inexplicable five-fold increase in loading time.
The times were obtained using Debugbar measures.
Using the monitor command in redis-cli (I'm using Redis to cache the permissions), I've noticed the GET requests come more slowly than before. In fact, even after I stop a page from loading (ESC), the GET requests to Redis don't stop immediately. I'm not sure if this is normal behavior or not.
I tried to check the issues at the bouncer repo but I haven't found anything.
You're calling auth()->user() hundreds of times. Can you try calling it only once?
$user = auth()->user();
$parents->each(function ($parent) use ($user) {
$parent->permissions = [
'edit' => $user->can('edit', $parent),
'delete' => $user->can('delete', $parent),
'restore' => $user->can('restore', $parent)
];
$parent->children()->each(function ($child) {
$child->permissions = [
'edit' => $user->can('edit', $child),
'delete' => $user->can('delete', $child),
'restore' => $user->can('restore', $child)
];
}
}
Also, since you're eager-loading the children, you shouldn't fetch them all again within each loop iteration:
$parent->children()->each(function ($child) {
// ^^ remove these parentheses
$child->permissions = [
'edit' => $user->can('edit', $child),
'delete' => $user->can('delete', $child),
'restore' => $user->can('restore', $child)
];
}
After some testing a solution was found. It turns out, there was no problem with the code at all.
Something is wrong with the server. We do not know exactly what but trying to run the project on freshly installed machines got rid of those awful processing times. (Now times are at 15s on first request)
The server's problem got worse coincidentally after migrating from laravel 5.7 to 5.8 which led me to this wild goose chase.
ADDENDUM
The culprit was Xdebug. We used it to get code-coverage analysis but the performance was so bad we ended up switching to phpdbg.

How to set dynamic SMTP data in Laravel 5.4 for queued emails?

In my application each user can use his own SMTP server. Therefor the config must be provided. I'm using Laravel Notifications to send the emails. If I'm using no queue (that means sync), there is no problem.
I made a CustomNotifiable Trait:
config([
'mail.host' => $setting->smtp_host,
'mail.port' => $setting->smtp_port,
'mail.username' => $setting->smtp_username,
'mail.password' => $setting->smtp_password,
'mail.encryption' => $setting->smtp_encryption,
'mail.from.address' => $setting->smtp_from_address,
'mail.from.name' => $setting->smtp_from_name,
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
After that, I restore the original config:
config([
'mail' => $originalMailConfig
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
No problem until now.
But if it's queued, just the first config after starting the queue worker will be taken for all further emails, even if any other SMTP config is provided. The default config from config/mail.php will be overridden. But this only works the first time.
I've made in the AppServiceProvider::boot method (the SMTP config is stored at the notification):
Queue::before(function (JobProcessing $event) {
// Handle queued notifications before they get executed
if (isset($event->job->payload()['data']['command']))
{
$payload = $event->job->payload();
$command = unserialize($payload['data']['command']);
// setting dynamic SMTP data if required
if (isset($command->notification->setting))
{
config([
'mail.host' => $command->notification->setting->smtp_host,
'mail.port' => $command->notification->setting->smtp_port,
'mail.username' => $command->notification->setting->smtp_username,
'mail.password' => $command->notification->setting->smtp_password,
'mail.encryption' => $command->notification->setting->smtp_encryption,
'mail.from.address' => $command->notification->setting->smtp_from_address,
'mail.from.name' => $command->notification->setting->smtp_from_name,
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
}
}
});
Of course, the original config get restored:
Queue::after(function (JobProcessed $event) use ($originalMailConfig) {
$payload = $event->job->payload();
$command = unserialize($payload['data']['command']);
// restore global mail settings
if (isset($command->notification->setting))
{
config([
'mail' => $originalMailConfig
]);
(new \Illuminate\Mail\MailServiceProvider(app()))->register();
}
});
It seems, as the Swift Mailer has a cache or something like that. I registered a new MailServiceProvider, which should simply replace the old one. So if I set the config with the new SMTP data, the new registered provider should take them. Logging the config shows even in the TransportManager, that the correct SMTP data were set, right before sending the mail, but the mail was sent with the first set config.
I found this thread and tried the linked solution, but with the same result: How to set dynamic SMTP details laravel
So I need a way to override the Services / ServiceProvider / SMTP config. Even if the Supervisor restarts the queue, there is a chance that multiple emails with different configs should be send at the same time.
In Laravel 5.4+, as I see that the Mailer Class is a singleton that hold a MailTransport Class, which is responsible for the config of SMTP mail and is a singleton,too; I just have to override the config using the following approach:
First, I setup a trait so I can just turn this feature on some Mails:
trait MailSenderChangeable
{
/**
* #param array $settings
*/
public function changeMailSender($settings)
{
$mailTransport = app()->make('mailer')->getSwiftMailer()->getTransport();
if ($mailTransport instanceof \Swift_SmtpTransport) {
/** #var \Swift_SmtpTransport $mailTransport */
$mailTransport->setUsername($settings['email']);
$mailTransport->setPassword($settings['password']);
}
}
}
Then, in the build() method of your mail class, you can utilize the above trait and call:
$this->changeMailSender([
'email'=>$this->company->email,
'password'=>$this->company->email_password,
]);
Boom, let the Laravel do the rest.
After a lot of researching I stumbled upon the different queue commands. I tried queue:listen (which is not described in the Laravel 5.4 docs) instead of queue:work and the problems are gone.
Of course, this doesn't really explain the described behavior, but fortunately it doesn't matter, because I can live with this solution/workaround.
Another strange behavior is, that from time to time the queue worker throws an exception because the database was locked. No idea, when or why this happened.
This post explained a little bit, why things can happen: What is the difference between queue:work --daemon and queue:listen
In a nutshell, queue:listen solved my problem and another very strange db lock problem as well.

Resources