Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lumen 8 DB:listen 重复记录 sql 日志 #57

Open
wilbur-yu opened this issue Nov 27, 2020 · 3 comments
Open

Lumen 8 DB:listen 重复记录 sql 日志 #57

wilbur-yu opened this issue Nov 27, 2020 · 3 comments

Comments

@wilbur-yu
Copy link
Contributor

wilbur-yu commented Nov 27, 2020

QueryLoggerServiceProvider.php

DB::listen(function (QueryExecuted $query) {
	if ($query->time < $this->app['config']->get('logging.query.slower_than', 0)) {
		return;
	}
	
	$sqlWithPlaceholders = str_replace(['%', '?'], ['%%', '%s'], $query->sql);
	
	$bindings = $query->connection->prepareBindings($query->bindings);
	$pdo      = $query->connection->getPdo();
	$realSql  = $sqlWithPlaceholders;
	$duration = $this->formatDuration($query->time / 1000);
	
	if (count($bindings) > 0) {
		$realSql = vsprintf($sqlWithPlaceholders, array_map([$pdo, 'quote'], $bindings));
	}
	
	Log::debug(sprintf('[%s] [%s] %s | %s: %s', $query->connection->getDatabaseName(), $duration, $realSql,
		request()->method(), request()->getRequestUri()));
});

日志

[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [500μs] select * from `products` where `products`.`id` = '4' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
[2020-11-27 23:18:23] local.DEBUG: [test] [390μs] select * from `product_skus` where `product_skus`.`id` = '2' limit 1 | POST: /v1/orders  
@huang-yi
Copy link
Owner

我本地尝试了一下,并没有复现你描述的情况。你需要检查一下你的代码里面是否有导致重复调用上述DB::listen()的情况。

@wilbur-yu
Copy link
Contributor Author

wilbur-yu commented Dec 3, 2020

检查了下代码, 也没有重复定义或者重复调用的地方.
该监听我是定义在了一个 ServiceProvider 中.
app/Providers/QueryLoggerServiceProvider.php

<?php
declare(strict_types = 1);

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class QueryLoggerServiceProvider extends ServiceProvider
{
	/**
	 * Bootstrap the application services.
	 */
	public function boot(): void
	{
		if (!$this->app['config']->get('logging.query.enabled', false)) {
			return;
		}
		
		DB::listen(function (QueryExecuted $query) {
			if ($query->time < $this->app['config']->get('logging.query.slower_than', 0)) {
				return;
			}
			
			$sqlWithPlaceholders = str_replace(['%', '?'], ['%%', '%s'], $query->sql);
			
			$bindings = $query->connection->prepareBindings($query->bindings);
			$pdo      = $query->connection->getPdo();
			$realSql  = $sqlWithPlaceholders;
			$duration = $this->formatDuration($query->time / 1000);
			
			if (count($bindings) > 0) {
				$realSql = vsprintf($sqlWithPlaceholders, array_map([$pdo, 'quote'], $bindings));
			}
			
			Log::debug(sprintf('[%s] [%s] %s | %s: %s', $query->connection->getDatabaseName(), $duration, $realSql,
				request()->method(), request()->getRequestUri()));
		});
	}
	
	/**
	 * Register the application services.
	 */
	public function register(): void
	{
	}
	
	/**
	 * Format duration.
	 *
	 * @param  float  $seconds
	 *
	 * @return string
	 */
	private function formatDuration(float $seconds): string
	{
		if ($seconds < 0.001) {
			return round($seconds * 1000000).'μs';
		}
		
		if ($seconds < 1) {
			return round($seconds * 1000, 2).'ms';
		}
		
		return round($seconds, 2).'s';
	}
}

然后在 bootstrap/app.php 中注册

<?php

require_once __DIR__.'/../vendor/autoload.php';

(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
	dirname(__DIR__)
))->bootstrap();

date_default_timezone_set(env('APP_TIMEZONE', 'Asia/Shanghai'));

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/

$app = new Laravel\Lumen\Application(
	dirname(__DIR__)
);

$app->withFacades();

$app->withEloquent();

/*
|--------------------------------------------------------------------------
| Register Container Bindings
|--------------------------------------------------------------------------
|
| Now we will register a few bindings in the service container. We will
| register the exception handler and the console kernel. You may add
| your own bindings here if you like or you can make another file.
|
*/

$app->singleton(
	Illuminate\Contracts\Debug\ExceptionHandler::class,
	App\Exceptions\Handler::class
);

$app->singleton(
	Illuminate\Contracts\Console\Kernel::class,
	App\Console\Kernel::class
);

/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/
$app->configure('app');
$app->configure('auth');
$app->configure('broadcasting');
$app->configure('cache');
$app->configure('database');
$app->configure('filesystems');
$app->configure('logging');
$app->configure('queue');
$app->configure('services');
$app->configure('views');
$app->configure('wechat');
$app->configure('enum');
$app->configure('jwt');
$app->configure('cors');
$app->configure('trustedproxy');
$app->configure('horizon');

$app->alias('cache', Illuminate\Cache\CacheManager::class);

/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/

$app->middleware([
	App\Http\Middleware\TrustProxiesMiddleware::class,
	Fruitcake\Cors\HandleCors::class,
	App\Http\Middleware\AcceptHeaderMiddleware::class,
	//	App\Http\Middleware\EtagMiddleware::class,
	//	App\Http\Middleware\IdDecryptMiddleware::class,
]);

$app->routeMiddleware([
	'auth' => App\Http\Middleware\AuthenticateMiddleware::class,
	//	'permission' => Spatie\Permission\Middlewares\PermissionMiddleware::class,
	//	'role'       => Spatie\Permission\Middlewares\RoleMiddleware::class,
]);

/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Service providers are
| totally optional, so you are not required to uncomment this line.
|
*/

/**
 * Application Service Providers
 */
$app->register(App\Providers\AppServiceProvider::class);
$app->register(App\Providers\AuthServiceProvider::class);
$app->register(App\Providers\EventServiceProvider::class);
$app->register(App\Providers\FormRequestServiceProvider::class);
//$app->register(App\Providers\WechatNotificationChannelServiceProvider::class);
$app->register(App\Providers\CustomHorizonServiceProvider::class);
$app->register(App\Providers\HorizonServiceProvider::class);

/**
 * Package Service Providers
 */
$app->register(Illuminate\Redis\RedisServiceProvider::class);
$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
$app->register(Fruitcake\Cors\CorsServiceProvider::class);
$app->register(Overtrue\LaravelWeChat\ServiceProvider::class);
$app->register(Bavix\Wallet\WalletServiceProvider::class);

/**
 * Dev
 */
if($app->environment() === 'local'){
	$app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);
}
$app->register(App\Providers\QueryLoggerServiceProvider::class);

$app->register(HuangYi\Shadowfax\ShadowfaxServiceProvider::class);


/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/

$app->router->group([
	'namespace' => 'App\Http\Controllers',
], function ($router) {
	require __DIR__.'/../routes/web.php';
});

return $app;

@dafa168
Copy link

dafa168 commented Feb 14, 2022

建议放到AppServiceProvider中。

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->debug();
    }

    public function debug()
    {
        //TODO:开发模式下 - DEBUG SQL
        $debug = env('APP_DEBUG');
        if ($debug) {
            /* 开启mongodb的日志监听 */
            $connections = config('database.connections');
            if ($connections) {
                foreach ($connections as $k => $item) {
                    if (isset($item['driver']) && ($item['driver'] === 'mongodb')) {
                        DB::connection($k)->enableQueryLog();
                    }
                }
            }

            //use Illuminate\Support\Str;
            DB::listen(function ($query) {
                $sql = $query->sql;
                $bindings = [];
                if ($query->bindings) {
                    foreach ($query->bindings as $v) {
                        if (is_numeric($v)) {
                            $bindings[] = $v;
                        } else {
                            $bindings[] = '"' . (string)$v . '"';
                        }
                    }
                }
                $execute = Str::replaceArray('?', $bindings, $sql);
                Log::channel('sql')->info(' SQL :' . $execute, ['time' => $query->time ?? 0, 'connectionName' => $query->connectionName ?? '']);
            });

        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants