I want to be able to add a unique id (Uid) to my logging.
In Example 1: Which is depended on config/logging.php and ProcessorTap files below is not working as expected. The logging is configured to use stdout which refers to the ProcessorTap class that is suppose to add a Uid, when the log statement is created (in accordance with UidProcessor)
Example 2: Which uses purely Mono classes works as expected.
Why isnt Example 1 adding the Uid to the logs, when laravel ("laravel/framework": "5.7.*") should be using Monolog classes as well ?
Example 1: When this api is invoked, the output for Log::info('test') does not include UiD
Route::get('/test', function () {
Log::info('test'); //output = [2020-03-24 04:51:16] local.INFO: test
});
config/logging.php:
'default' => env('LOG_CHANNEL', 'stdout'), //.env LOG_CHANNEL=stdout
'stdout' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'with' => [
'stream' => 'php://stdout',
],
'tap' => [
ProcessorTap::class,
],
]
ProcessorTap:
use Monolog\Processor\UidProcessor;
class ProcessorTap
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
$logger->pushProcessor(new UidProcessor());
}
}
Example 2: Working correctly the Uid (a484a6729e14996c0af1)
is added to the log for $logger->info('test')
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
Route::get('/test', function () {
$logger = new Logger('main');
$logger->pushProcessor(new UidProcessor(20));
$logger->info('test'); // output = [2020-03-24 04:57:26] main.INFO: test [] {"uid":"a484a6729e14996c0af1"}
});
This might be a laravel (5.7)/mono version specific issue, but I was able to resolve the via iterating via the handlers and calling pushProcessor
use Monolog\Processor\UidProcessor;
class ProcessorTap
{
/**
* Customize the given logger instance.
*
* #param \Illuminate\Log\Logger $logger
* #return void
*/
public function __invoke($logger)
{
collect($logger->getHandlers())->each(function ($handler) {
$handler->pushProcessor(new UidProcessor());
});
}
}
Related
I have a project with multiple migration files, according to different modules of the project. I want to pass a key (associative array) and run all the files on that index.
//EXAMPLE
$files = [
'blog' => [
//array of file names
],
'storage' => [
//array of file names
],
'sales' => [
//array of file names
],
]
runAll($files['sales'])
I've read the docs but it just allow specified class name in terminal.
Edit: Just checked again and it either allow class name but namespace on migrate command.
For those coming here wondering how to run one specific migration file in Codeigniter 4:
Unfortunately, at the time of writing this post, I couldn't find a direct command/way to handle this task.
Luckily, you can have access to the methods available within the MigrationRunner class in your own source code.
Usage Example Here
Even better, you have the ability to create your own custom commands.
With that in mind, I created a custom command to support running a single migration file.
Custom Command
Step 1:
Generate the basic command file by running the command below in your terminal:
php spark make:command MigrateFile --command migrate:file --group Database --suffix Command
This will create/generate a command file in the path: APPPATH\Commands\MigrateFileCommand.php
Step 2:
Edit this new command file (app/Commands/MigrateFileCommand.php) to something similar to the source code below:
<?php
namespace App\Commands;
use CodeIgniter\CLI\BaseCommand;
use CodeIgniter\CLI\CLI;
use Config\Services;
class MigrateFileCommand extends BaseCommand
{
/**
* The Command's Group
*
* #var string
*/
protected $group = 'Database';
/**
* The Command's Name
*
* #var string
*/
protected $name = 'migrate:file';
/**
* The Command's Description
*
* #var string
*/
protected $description = 'Migrates a single migration file.';
/**
* The Command's Usage
*
* #var string
*/
protected $usage = 'migrate:file [arguments] [options]';
/**
* The Command's Arguments
*
* #var array
*/
protected $arguments = [
'name' => 'The valid migration file path beginning from the ROOTPATH. For example: php spark migrate:file "app\Database\Migrations\2022-02-16-101819_AddBlogMigration.php"'
];
/**
* The Command's Options
*
* #var array
*/
protected $options = [
'--namespace' => 'Set migration namespace. Default: "App".',
'--dbgroup' => 'Set database group. Default: "default".',
];
/**
* Actually execute a command.
*
* #param array $params
*/
public function run(array $params)
{
CLI::write("Running migration...", 'yellow');
$message = "";
$paramsSize = count($params);
if (!$paramsSize) {
$message = 'Too few arguments passed. Missing "migration file path."';
} else if ($paramsSize > 1) {
$message = 'Too many arguments passed.';
}
if ($paramsSize !== 1) {
CLI::write(sprintf('Invalid Params: %s', $message), 'red');
CLI::newLine();
$this->showHelp();
return;
}
$runner = Services::migrations();
$namespace = ($params['namespace'] ?? CLI::getOption('namespace')) ?: "App";
$dbgroup = ($params['dbgroup'] ?? CLI::getOption('dbgroup')) ?: "default";
try {
if (!$runner->force(ROOTPATH . $params[0], $namespace, $dbgroup)) {
CLI::error(lang('Migrations.generalFault'), 'light_gray', 'red'); // #codeCoverageIgnore
}
$messages = $runner->getCliMessages();
foreach ($messages as $message) {
CLI::write($message);
}
CLI::write('Done migration.', 'green');
// #codeCoverageIgnoreStart
} catch (\Throwable $e) {
$this->showError($e);
// #codeCoverageIgnoreEnd
}
}
}
The source code above is essentially making use of the force(...) method to execute a single migration file.
Step 3:
Now moving forward, you can easily run a single migration file using the command below in your terminal.
php spark migrate:file "app\Database\Migrations\2022-02-16-101819_AddBlogMigration.php"
Sample Output:
CodeIgniter v4.1.4 Command Line Tool - Server Time: 2022-02-16 13:09:34 UTC+01:00
Running migration...
Running: (App) 2022-02-16-101819_App\Database\Migrations\AddBlogMigration
Done migration.
If in case your migration file resides in a different namespace other than App, for example in a different module ('Modules\Sales'), you can pass an option defining the specific namespace.
The command also supports passing a different database group other than 'default'. I.e:
php spark migrate:file "app\Database\Migrations\2022-02-16-101819_AddBlogMigration.php" --namespace "Modules\Sales" --dbgroup "tests"
You can view the documentation of the new command by running the command below:
php spark help migrate:file
Sample Output:
CodeIgniter v4.1.4 Command Line Tool - Server Time: 2022-02-16 15:16:31 UTC+01:00
Usage:
migrate:file [arguments] [options]
Description:
Migrates a single migration file.
Arguments:
name The valid migration file path beginning from the ROOTPATH. For example: php spark migrate:file "app\Database\Migrations\2022-02-16-101819_AddBlogMigration.php"
Options:
--namespace Set migration namespace. Default: "App".
--dbgroup Set database group. Default: "default".
Bonus Tip
If for some reason you wish to run the new command from within your own code or Controller, this is possible by using:
echo command('migrate:file "app\Database\Migrations\2022-02-16-101819_AddBlogMigration.php"');
You can migrate a single file regardless of order or batches using the method force(string $path, string $namespace, ?string $group = null) available within the MigrationRunner class.
force($path, $namespace, $group)
This forces a single file to migrate regardless of order or batches.
Method “up” or “down” is detected based on whether it has already been
migrated.
Note:
This method is recommended only for testing and could cause data
consistency issues.
So in your case, you would just run a for loop through the files as you pass the expected parameters in the force(...) method. I.e:
Assuming the array keys of your $files array represent the various 'modules' of the project:
$migrate = \Config\Services::migrations();
$psr4 = config(\Config\Autoload::class)->psr4;
foreach ($files as $module => $filenames) {
$namespace = "Modules\\" . ucwords($module);
foreach ($filenames as $filename) {
try {
$migrate->force($psr4[$namespace] . "\\" . $filename, $namespace);
} catch (\Throwable $e) {
// Do something with the error here...
}
}
}
NOTES:
The above solution assumes that you already mapped your $psr4 namespaces in your application modules to their respective locations on the file system: I.e:
File: app/Config/Autoload.php
// ...
public $psr4 = [
APP_NAMESPACE => APPPATH, // For custom app namespace
'Config' => APPPATH . 'Config',
'Modules\Blog' => ROOTPATH . 'module/blog',
'Modules\Storage' => ROOTPATH . 'module/storage',
'Modules\Sales' => ROOTPATH . 'module/sales',
];
// ...
let's do it like me,
see codes step by step
this my codes modules
in app/config/autoload.php
<?php
namespace Config;
use CodeIgniter\Config\AutoloadConfig;
/**
* -------------------------------------------------------------------
* AUTOLOADER CONFIGURATION
* -------------------------------------------------------------------
*
* This file defines the namespaces and class maps so the Autoloader
* can find the files as needed.
*
* NOTE: If you use an identical key in $psr4 or $classmap, then
* the values in this file will overwrite the framework's values.
*/
class Autoload extends AutoloadConfig
{
/**
* -------------------------------------------------------------------
* Namespaces
* -------------------------------------------------------------------
* This maps the locations of any namespaces in your application to
* their location on the file system. These are used by the autoloader
* to locate files the first time they have been instantiated.
*
* The '/app' and '/system' directories are already mapped for you.
* you may change the name of the 'App' namespace if you wish,
* but this should be done prior to creating any namespaced classes,
* else you will need to modify all of those classes for this to work.
*
* Prototype:
*```
* $psr4 = [
* 'CodeIgniter' => SYSTEMPATH,
* 'App' => APPPATH
* ];
*```
*
* #var array<string, string>
*/
public $psr4 = [
APP_NAMESPACE => APPPATH, // For custom app namespace
'Config' => APPPATH . 'Config',
'Myth\Auth' => APPPATH .'ThirdParty/myth-auth/src',
'Modules\Shared' => ROOTPATH . 'module/shared',
'Modules\Common' => ROOTPATH . 'module/common',
'Modules\Auth' => ROOTPATH . 'module/auth',
'Modules\Home' => ROOTPATH . 'module/home',
'Modules\Payment' => ROOTPATH . 'module/payment',
'Modules\App' => ROOTPATH . 'module/app',
];
/**
* -------------------------------------------------------------------
* Class Map
* -------------------------------------------------------------------
* The class map provides a map of class names and their exact
* location on the drive. Classes loaded in this manner will have
* slightly faster performance because they will not have to be
* searched for within one or more directories as they would if they
* were being autoloaded through a namespace.
*
* Prototype:
*```
* $classmap = [
* 'MyClass' => '/path/to/class/file.php'
* ];
*```
*
* #var array<string, string>
*/
public $classmap = [];
/**
* -------------------------------------------------------------------
* Files
* -------------------------------------------------------------------
* The files array provides a list of paths to __non-class__ files
* that will be autoloaded. This can be useful for bootstrap operations
* or for loading functions.
*
* Prototype:
* ```
* $files = [
* '/path/to/my/file.php',
* ];
* ```
*
* #var array<int, string>
*/
public $files = [];
}
this is my migration
path file =Modules\Common\Database\Migrations\Settting
<?php namespace Modules\Common\Database\Migrations;
use CodeIgniter\Database\Migration;
class Setting extends Migration
{
public function up()
{
//
/*
* Setting
*/
$this->forge->addField([
'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
'key' => ['type' => 'varchar', 'constraint' => 255],
'value' => ['type' => 'varchar', 'constraint' => 255],
'description' => ['type' => 'varchar', 'constraint' => 300],
'status' => ['type' => 'tinyint', 'constraint' => 1, 'null' => 0, 'default' => 1],
'created_at' => ['type' => 'datetime', 'null' => true],
'updated_at' => ['type' => 'datetime', 'null' => true],
'deleted_at' => ['type' => 'datetime', 'null' => true],
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey('key');
$this->forge->createTable('setting', true);
}
//--------------------------------------------------------------------
public function down()
{
// drop constraints first to prevent errors
$this->forge->dropTable('setting', true);
}
}
to call it
php spark migrate setting -n 'Module\Common'
for more info go there
https://codeigniter.com/user_guide/dbmgmt/migration.html
So I use a Service Class (extends from TYPO3\CMS\Core\Authentication\AuthenticationService) to authenticate our Frontend Users using OAuth2. These Services are automatically instantiated and called via Typos own Middleware: FrontendUserAuthenticator.
In this class I used to save data from the authentication result to $GLOBALS['TSFE']->fe_user using setKey('ses', 'key', 'data'), which seems is not possible anymore since v10. How would I go about still doing this?
The documentation is sparse
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Deprecation-85878-EidUtilityAndVariousTSFEMethods.html
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Context/Index.html
I've tried the following:
constructor injecting the TSFE using DI
class FrontendOAuthService extends AuthenticationService
{
public function __construct(TypoScriptFrontendController $TSFE) {
=> LogicException: TypoScriptFrontendController was tried to be injected before initial creation
changing the Middlewares order to have it instantiate before the Auth Middleware
(packages/extension_name/Configuration/RequestMiddlewares.php)
return [
'frontend' => [
'typo3/cms-frontend/tsfe' => [
'disabled' => true,
],
'vendor/extension_name/frontend-oauth' => [
'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
'before' => [
'typo3/cms-frontend/authentication',
],
'after' => [
'typo3/cms-frontend/eid',
'typo3/cms-frontend/page-argument-validator',
],
],
],
];
=> UnexpectedValueException: Your dependencies have cycles. That will not work out.
instantiating the TSFE myself
/** #var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var DealerService $dealerService */
$lang = $site->getDefaultLanguage();
$siteLanguage = $objectManager->get(SiteLanguage::class, $lang->getLanguageId(), $lang->getLocale(), $lang->getBase(), []);
/** #var TypoScriptFrontendController $TSFE */
$TSFE = $objectManager->get(
TypoScriptFrontendController::class,
GeneralUtility::makeInstance(Context::class),
$site,
$siteLanguage,
GeneralUtility::_GP('no_cache'),
GeneralUtility::_GP('cHash')
);
=> the $TSFE->fe_user is an emptystring ("")
using the UserAspect
/** #var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
$feUser = $context->getAspect('frontend.user');
$feUser->set...
=> Aspects are read-only
adding vars to the user data in the getUser method of the AuthenticationService
(packages/extension_name/Classes/Service/FrontendOAuthService.php)
public function getUser()
{
$user = allBusinessCodeHere();
$user['my_own_key'] = 'myData';
return $user;
=> is not propagated to the UserAspect(frontend.user) nor the $TSFE->fe_user
I'm out of ideas guys.
I had a similar problem when i wanted to use redirects with record links.
I ended up disabling the original redirect middleware and adding my own with a mocked version of tsfe.
The extension can be found here:
https://github.com/BenjaminBeck/bdm_middleware_redirect_with_tsfe
Late to the party, but I had the same issue and was able to solve it:
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.html states:
Storing session data from a Frontend User Session / Anonymous session
is now triggered within the Frontend User
(frontend-user-authenticator) Middleware, at a later point - once the
page was generated. Up until TYPO3 v9, this was part of the
RequestHandler logic right after content was put together. This was
due to legacy reasons of the previous hook execution order. Migration
Consider using a PSR-15 middleware instead of using a hook, or
explicitly call storeSessionData() within the PHP hook if necessary.
In my MyAuthenticationService extends AbstractAuthenticationService in method getUser() I set $_SESSION['myvendor/myextension/accessToken'] to the token received by the external oauth service. In my SaveSessionMiddleware I save this token to the FrontendUserAuthentication object using setKey() which by then is available:
EXT:myextension/Configuration/RequestMiddlewares.php
return [
'frontend' => [
'myvendor/myextension/save-session-middleware' => [
'target' => \MyVendor\MyExtension\Middleware\SaveSessionMiddleware::class,
'after' => [
'typo3/cms-frontend/authentication',
],
]
]
];
EXT:myextension/Classes/Middleware/SaveSessionMiddleware.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class SaveSessionMiddleware implements MiddlewareInterface {
/**
* #param ServerRequestInterface $request
* #param RequestHandlerInterface $handler
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!empty($_SESSION['myvendor/myextension/accessToken'])) {
$this->getFrontendUserAuthentication()->setKey(
'ses',
'myvendor/myextension/accessToken',
$_SESSION['myvendor/myextension/accessToken']);
unset($_SESSION['myvendor/myextension/accessToken']);
}
return $handler->handle($request);
}
private function getFrontendUserAuthentication(): FrontendUserAuthentication {
return $GLOBALS['TSFE']->fe_user;
}
}
Im using Queue::before in AppServiceProvider.php and set logging.channels.single.path value every time when job started:
config(['logging.channels.single.path' => storage_path('logs/accounts/'.$command->acc->login.'.log')]);
When I running 1 job all ok - logs in the right place.
When running 2 or more it writing logs to different files - one account can write to another accounts logfile. Why is it happening? It looks like it is caching the config variable.
Queue on horizon redis. One job after done dispatching another same job with the same $acc instance.
Queue::before(function (JobProcessing $event) {
$job = $event->job->payload();
$command = unserialize($job['data']['command']);
Added ^^^ from where $command going.
Customization is now done through invoking a custom formatter for Monolog.
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/accounts/laravel.log'),
'level' => 'debug',
],
]
In your custom formatter, you can manipulate the Monolog logger however you wish:
<?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) {
$login = $command->acc->login;
$handler->setFilenameFormat("{filename}-$login-{date}", 'Y-m-d');
}
}
}
}
See: https://laravel.com/docs/5.6/logging#advanced-monolog-channel-customization
https://github.com/Seldaek/monolog/blob/master/src/Monolog/Handler/RotatingFileHandler.php
The configuration values work globaly for all sessions, like global variables (
See the exampe here https://laravel.io/forum/how-can-i-set-global-dynamic-variables-in-laravel)
You set the value in the config file always to the last login. Therefore all new logs go in the new named config file.
I am working on a project which has Laravel 5.3 version and using Laravel Infyom Generator, somehow it generated all these traits and other test files such as (ApiTest, RepositoryTest, ...etc). when I try to run PHPUNIT I am getting this error Can someone help me to find out why I am getting this error?
PHP Fatal error: Trait 'MakeCustomerTrait' not found in C:\Users\ahmed\dev\gamla\tests\CustomerApiTest.php on line 8
Fatal error: Trait 'MakeCustomerTrait' not found in C:\Users\ahmed\dev\gamla\tests\CustomerApiTest.php on line 8
I want to start making new tests for my project do I need to delete these files? because it keeps giving me that error?
screenshot of CustomerApiTest code:
MakeCustomerTrait
<?php
use Faker\Factory as Faker;
use App\Models\Customer;
use App\Repositories\CustomerRepository;
trait MakeCustomerTrait
{
/**
* Create fake instance of Customer and save it in database
*
* #param array $customerFields
* #return Customer
*/
public function makeCustomer($customerFields = [])
{
/** #var CustomerRepository $customerRepo */
$customerRepo = App::make(CustomerRepository::class);
$theme = $this->fakeCustomerData($customerFields);
return $customerRepo->create($theme);
}
/**
* Get fake instance of Customer
*
* #param array $customerFields
* #return Customer
*/
public function fakeCustomer($customerFields = [])
{
return new Customer($this->fakeCustomerData($customerFields));
}
/**
* Get fake data of Customer
*
* #param array $postFields
* #return array
*/
public function fakeCustomerData($customerFields = [])
{
$fake = Faker::create();
return array_merge([
'name' => $fake->word,
'address_street' => $fake->word,
'address_zip' => $fake->word,
'address_city' => $fake->word,
'address_country' => $fake->word,
'shipping_address_street' => $fake->word,
'shipping_address_zip' => $fake->word,
'shipping_address_city' => $fake->word,
'shipping_address_country' => $fake->word,
'contact_person_id' => $fake->randomDigitNotNull,
'created_at' => $fake->word,
'updated_at' => $fake->word
], $customerFields);
}
}
Actually what is happening is here is autoloader is unable to resolve the class in your code. Laravel uses PSR-4 standards to autoload classes which needs fully qualified name spaces for the class which also represents the path of the file which holds the class.
See it this way that if you want to load a class inside your Laravel app directory at the following address :
app/repositories/users/UserRoleRepository.php
You will specify the namespace of your class like App\repositories\users with class name UserRoleRepository so the autoloader can load your class. Otherwise you have to include the file of your class manually.
You can register custom autoloads in your composer.json and by running the following command composer dump-autoload.
You can find more about it over internet like this
Hope it would help.
I assumed that by default the Log::info calls wouldn't log in production, but they are still coming in.
Im setting production using my .env file
APP_ENV=production
APP_DEBUG=false
Ive tried these commands as well, but no luck
composer dump-autoload
php artisan cache:clear
php artisan optimize
Am i missing something?
For anyone still finding this thread (8 years later):
Configure your log channels in config/logging.php file
Set "level" parameter for your log channel to a .env variable
Example:
'channels' => [
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Lumen Log',
'emoji' => ':boom:',
'level' => env('LOG_LEVEL', 'error'),
]
]
Now you can set the LOG_LEVEL variable in your .env file for each environment
Well, I think that it's too late to search for all the Log::info() and do the proposed answer by #jon__o
if (App::environment('local', 'staging')) {
Log::info($error);
}
But you can still do something. You can override the default Laravel logger instance with your own implementation.
Go to your ApplicationServiceProvider and override the log instance with a custom one:
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->registerLogger();
}
/**
* Register the logger instance in the container.
*
* #return MyCustomWriter
*/
protected function registerLogger()
{
$this->app->instance('log', $log = new MyCustomWriter(
new Monolog($this->app->environment()), $app['events'])
);
$log->dontLogInfoOnEnvironmnets(['production', 'staging', 'other']);
return $log;
}
Now you can create your custom writer by just extending the Laravel's Writer and overriding the info() method.
class MyCustomWriter extends \Illuminate\Log\Writer
{
protected $dontInfoOn = [];
/**
* Log an informational message to the logs.
*
* #param string $message
* #param array $context
* #return void
*/
public function info($message, array $context = [])
{
// Since we are providing the app environment to the Monolog instance in out ApplicationServiceProvider
// we can get the environment from the Monolog getName() method
if(!in_array($this->monolog->getName(), $this->dontInfoOn)) {
return parent::info($message, $context);
}
}
/**
* Don't log info() on the supplied environments .
*
* #param array $environments
* #return void
*/
public function dontLogInfoOnEnvironmnets(array $environments)
{
$this->dontInfoOn = $environments;
}
}
This way, you can still keep you Log::info on testing environments without checking every time.
Only the displaying of errors will be suppressed when your application is not in debug mode. The Log::info() function will always log when called.
The simple solution is for you to wrap that Log::info() function in something like this:
if (App::environment('local', 'staging')) {
Log::info($error);
}
Be sure to include the App facade use App; at the top of your file. Alternatively you can use the app() helper to get the environment: $environment = app()->environment();.