Laravel slack log but with additional fields and configuration - php

In Laravel 5.6 I'm trying to make proper slack logs and I did:
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'slack'],
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'TEST',
'icon' => ':boom:',
'level' => 'info',
],
It works but I want to specify additional fields and maybe customize it a little if it match some other conditions.
I was looking at SlackWebhookHandler.php monolog file but not all parameters work in this configuration..
For example emoji and username doesn't work - I don't know if slack already has even options for changing bot username.
Other example is that in this file something it's called useAttachment and here it's just attachment - where the names are stored..?
Back to topic I did:
Log::info('added test',['test'=>'test']);
And it works, but for slack I want to send additional field, in every request for example:
'added test',['test'=>'test', 'more' => 'test2']
How I'm able to accomplish it? I need to connect to Log Class and slack driver in some way but I don't have idea how to do this?

I debugged myself to SlackRecord::getSlackData, there you see how he handles attachments and add's additional data to the record.
For me it totally fitted to set 'context' => true in logging.php for the Slack Channel and define a Processor which just add's the Data I need to the record
class SlackProcessor {
/**
* #param array $record
* #return array
*/
public function __invoke(array $record) {
$record["context"]["Env"] = env("LOG_SLACK_USERNAME", "localhost");
$record["context"]["Full URL"] = Request::fullUrl();
$record["extra"]["Request Data"] = Request::all();
return $record;
}
}
So maybe you could just debug again to getSlackData and see why he jumps over the attachment part you need.

I was able to get closer to solution but still not at all:
On logging.php now I have
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'tap' => [App\Logging\SlackLogger::class],
'username' => 'BOT',
'attachment' => false,
'emoji' => ':boom:',
'level' => 'info',
],
I created App/Logging/SlackLogger.php:
namespace App\Logging;
use Monolog\Logger;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\JsonFormatter;
class SlackLogger
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
$dateFormat = "Y-m-d H:i:s";
$checkLocal = env('APP_ENV');
foreach ($logger->getHandlers() as $handler) {
if ($handler instanceof SlackWebhookHandler) {
$output = "[$checkLocal]: %datetime% > %level_name% - %message% `%context% %extra%` :poop: \n";
$formatter = new LineFormatter($output, $dateFormat);
$handler->setFormatter($formatter);
$handler->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'test';
return $record;
});
}
}
}
}
And It works only if I don't try to make custom attachment on slack.. When I'm trying to do:
$handler->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'test';
$record['attachments'] = [
'color' => "#36a64f",
"title" => "Slack API Documentation",
"text" => "Optional text that appears within the attachment"
];
return $record;
});
the $record losts 'attachments' array.. I was checking it in SlackWebhookHandler in write function because at this pushProcessor at return it still exists, but not sending to slack. I know that can be related to $handler->setFormatter($formatter); but I if I remove It, the problem still exists - so I still don't know how to solve it.

Related

There is nothing happening when store data for Laravel API

I making laravel API where i can store a new data where the value in body raw json. but when i try to send request using post, i got nothing but the status is 200 OK. when i chek my mysql there is no data inputed.
So, what should i do?
mysql data
Laravel Controller, and API,
// function in controller
use App\Models\ChartAge;
class ChartController extends Controller
{
public function saveChart(Request $request)
{
$data = $request->validate([
'entity' => 'required|string|max:10',
'code' => 'required|string|max:10',
'year' => 'required|int|max:10',
'under_age_15' => 'required|string|max:50',
'age_15_64' => 'required|string|max:50',
'age_65_over' => 'required|string|max:50',
]);
$values = ChartAge::create($request);
return response()->json(
[
'status' => true,
'message' => "the videos has been favorites",
'data' => $values,
],
201
);
}
}
//in api.php
Route::post("charts", [ChartController::class, 'saveChart']);
and here is when i tried to send request using postman.
because there is no error, i don't know what's wrong??
First double check your ChartAge model, does it have $fillable or not?
and Edit your code:
From
$values = ChartAge::create($request);
To:
$values = ChartAge::create($request->all());
Hope this will be useful.
With validation:
$data = \Validator::make($request->all(),[
'entity' => 'required|string|max:10',
'code' => 'required|string|max:10',
'year' => 'required|int|max:10',
'under_age_15' => 'required|string|max:50',
'age_15_64' => 'required|string|max:50',
'age_65_over' => 'required|string|max:50',
]);
if($data-> fails()){
return back()->withErrors($data)->withInput();
}
$values = ChartAge::create($request->all());
Do you set fillable fields in your 'ChartAge' model?
protected $fillable = ['entity','code','year'...];
Do you try to test code with disabling validation?
Please try to put dd($request) in the first row of the controller code.
Method create expects a plain PHP array, not a Request object.

In Laravel can I set a default context for the Log facade

I'm using the Log:: facade a lot and have a helper class called LogHelper which provide me with a static method LogHelper::context() which include many key values I need to track the requests. But having to type it every time for each usage make it error prune and fill not so efficient.
I'm looking for a way to inject the values by default, and allow me to overwrite them if needed specifically.
At the moment this is how I use it,
Log::debug('Request Started', LogHelper::context());
what I'm looking for is to inject the context by default
Log::debug('Request Started');
and have the option to overwrite it, if need it:
Log::debug('Request Started', ['more' => 'context'] + LogHelper::context());
PS, the LogHelper::context() return a simple key => value array which include some staff i need to debug requests, and the reason it do not use the values directly in the message is because i log to graylog as structured data, and this way i can filter by any key.
I have solved this issue by using the tap functionality and $logger->withContext() (note: the latter was added in Laravel 8.49).
You want to create a new class which contains your context logic. I've created an extra Logging folder in app/ in which my logging customizations sit.
app/Logging/WithAuthContext.php:
<?php
namespace App\Logging;
use Illuminate\Log\Logger;
class WithAuthContext
{
public function __invoke(Logger $logger)
{
$logger->withContext([
'ip' => request()?->ip(),
'ua' => request()?->userAgent(),
]);
}
}
Depending on which logging channel(s) you use, you will have to add the class to each one you want to add context to. So in app/config/logging.php:
<?php
use App\Logging\WithAuthContext;
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
// ...
'channels' => [
// ...
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'tap' => [WithAuthContext::class],
],
// ...
],
];
There is a way, but it is not pretty. You can create a custom monolog logger driver. The process is described at https://laravel.com/docs/8.x/logging#creating-monolog-handler-channels.
Here's a possible implementation:
class ContextEnrichingLogger extends \Monolog\Handler\AbstractHandler {
private $logger;
public function __construct($level = Monolog\Logger::DEBUG, bool $bubble = true, $underlyingLogger = 'single') {
$this->logger = Log::driver($underlyingLogger);
}
public function handle(array $record) {
$record['context'] += LogHelper::context();
return $this->logger->handle($record);
}
}
Then register this as a custom logger in your config/logging.php:
return [
'default' => 'enriched',
//...
'channels' => [
// ...
'enriched' => [
'driver' => 'monolog',
'handler' => ContextEnrichingLogger::class,
'level' => env('APP_LOG_LEVEL', 'debug'),
"with" => [
"underlyingLogger" => env('LOG_CHANNEL', 'single')
]
]
]
];
I haven't tested this particular one but this is how I've defined other custom loggers.
Note, this is probably also achievable via a custom formatter though I think it's probably the same trouble.

Laravel: Adding log channels outside logging.php configuration (plugin development)

I'm working on a laravel plugin/package.
We want to define our own loggers outside of the main project configuration (config/logger.php)
I have tried the following in the ServiceProvider register() function.
Based on the MonoLogger test code.
$partLogger = new Logger('vendor_part-log');
$partLogHandler = new StreamHandler(storage_path('logs/vendor/part-log.log', Logger::DEBUG));
$partLogger->pushHandler($partLogHandler);
// MonoLog Registry
Registry::addLogger($partLogger, 'vendor_part-log');
Sadly this doesn't work inside Laravel.
I also can't get the other existing loggers from Registry::
So the problem is that the new channel won't register.
Is there a different Registry in use inside Laravel or do I need an entirely different solution to achieve this?
While we'd still like a more automated solution, we've compromised and are using a static function as source of the configuration.
In config/logging.php:
At the top replace return [... with $config = [....
And at the bottom add the following lines:
$config['channels'] = array_merge(
$config['channels'],
\FooVendor\Bar\Classes\Logs::getLogs(),
);
return $config;
And create the mentioned class with the following function:
/**
* Configs to be merged in config/logging.php
* #return array
*/
public static function getLogs()
{
return [
'foo_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/partlogger.log'),
'level' => 'debug',
],
'foo_second_partlogger' => [
'driver' => 'single',
'path' => storage_path('logs/foo/second_partlogger.log'),
'level' => 'debug',
],
];
}

Custom (dynamic) log file names with laravel5.6

With laravel 5.5 we had access to configureMonologUsing() method in $app which made it possible for things like this in bootstrap/app.php:
$app->configureMonologUsing(function (Monolog\Logger $monolog) {
$processUser = posix_getpwuid(posix_geteuid());
$processName= $processUser['name'];
$filename = storage_path('logs/laravel-' . php_sapi_name() . '-' . $processName . '.log');
$handler = new Monolog\Handler\RotatingFileHandler($filename);
$monolog->pushHandler($handler);
});
Doing this is useful when your app may be called from different contexts (eg CLI/HTTP) with different users (which is desirable) and file rotation. Doing this prevents write errors in case the log file was created by the HTTP user before the CLI one tries to add something in it and vice-versa.
Handling this is otherwise tricky or insecure as it involves to be able to set write perms on files that may not exist yet.
In addition, it's quite handy to have logs separated by contexts as they usually have little in common and it makes it easier to search among them.
Unfortunately, this way of doing things is not possible anymore with laravel 5.6 and I could not (yet) find a way to transparently do so for all file based logging.
Thanks
Customisation is now done through invoking a custom formatter for Monolog.
Here is an example using daily rotating filenames (as I do).
This can be setup in config/logging.php, note the non-default tap parameter:
'channels' => [
'daily' => [
'driver' => 'daily',
'tap' => [App\Logging\CustomFilenames::class],
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
]
In your custom formatter, you can manipulate the Monolog logger however you wish, similar to configureMonologUsing():
app\Logging\CustomFilenames.php
<?php
namespace App\Logging;
use Monolog\Handler\RotatingFileHandler;
class CustomFilenames
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
foreach ($logger->getHandlers() as $handler) {
if ($handler instanceof RotatingFileHandler) {
$sapi = php_sapi_name();
$handler->setFilenameFormat("{filename}-$sapi-{date}", 'Y-m-d');
}
}
}
}
One way to restore your original behaviour is to remove the {date} component from the handler's filenameFormat. A better way might be to manipulate the appropriate handler for the single driver.
See: https://laravel.com/docs/5.6/logging#advanced-monolog-channel-customization
Solution:
step1: create a channel inside the config/logging.php file
example :
'channels' => [
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'web' => [
'driver' => 'single',
'path' => storage_path('logs/web/web.log'),
],
]
Step2: Now set dyanamic path from controller like this
config(['logging.channels.web.path' => storage_path('logs/web/'.time().'.log')]);
Step3 : now generate your log
Log::channel('web')->info("your message goes here");
Enjoy :)

Changing display logic for a field in sugarcrm

I have the following situation: Contacts without a first or last name, in fact, they only have a email address.
I can work with these contacts fine, but when I use the listview anywhere (for instance to show all contacts from a company) there now is no way to click through to the contact (normally you would click on the name).
I'm looking for a way to solve this, for instance by showing a clickable text like 'name not known', but can't figure out how to do this. I've been looking at the manual and in the files in the modules directory and the sugarfields dir, but can't quite figure it out.
The closest I got was in /sugarcrm/modules/Contacts/metadata/listviewdefs.php
where this piece of code resides:
$listViewDefs['Contacts'] = array(
'NAME' => array(
'width' => '20%',
'label' => 'LBL_LIST_NAME',
'link' => true,
'contextMenu' => array('objectType' => 'sugarPerson',
'metaData' => array('contact_id' => '{$ID}',
'module' => 'Contacts',
'return_action' => 'ListView',
'contact_name' => '{$FULL_NAME}',
'parent_id' => '{$ACCOUNT_ID}',
'parent_name' => '{$ACCOUNT_NAME}',
'return_module' => 'Contacts',
'return_action' => 'ListView',
'parent_type' => 'Account',
'notes_parent_type' => 'Account')
),
'orderBy' => 'name',
'default' => true,
'related_fields' => array('first_name', 'last_name', 'salutation', 'account_name', 'account_id'),
),
Somewhere there has to be a function that joins the first and lastname together...
Edit: I found a solution:
The actual concatenation function is in /sugarcrm/include/SugarObjects/templates/person/person.php and is called _create_proper_name_field()
I can modify the output for my specific case by adding something like this to the end of the function:
if (empty(trim($full_name))){
$full_name = 'Name unknown';
}
However, I would rather have a upgrade safe solution, so that will be the next challenge.
Don't edit the core because the next upgrade will break your SugarCRM instance. Use logic hooks to be upgrade safe:
create a file 'logic_hooks.php' in /custom/modules/Contacts/
In that file, add the followin code:
<?php
$hook_array['before_save'][] = Array(1,'logic_fill_name','custom/modules/Contacts/logic_hooks/logics.php','ContactLogics','logic_fill_name');
After you have done this. create the file 'logics.php' in /custom/modules/Contacts/logic_hooks.
In the logics.php file, add something like:
<?php
require_once 'include/SugarQuery/SugarQuery.php';
/**
* Class ContactLogics
*/
class ContactLogics {
/**
* #param $bean
* #param $event
* #param $arguments
*/
public function logic_fill_name($bean, $event, $arguments) {
if (empty(trim($bean->first_name)) && empty(trim($bean->last_name))){
$bean->last_name = 'Name unknown';
}
}
}
Now some explanation. When you edited a recordview and pressed the save button, the logic hook 'before_save' will be triggered. This code will change the full name to 'Name unknown' when the full name is empty. When the 'before_save' is executed, the actual save will take place.

Categories