Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
razonyang committed Aug 14, 2019
0 parents commit 90d1c08
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
@@ -0,0 +1,7 @@
# composer
composer.phar
composer.lock
vendor/

# phpunit
.phpunit.result.cache
14 changes: 14 additions & 0 deletions .scrutinizer.yml
@@ -0,0 +1,14 @@
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run

checks:
php: true
tools:
php_code_coverage:
enabled: true
external_code_coverage:
timeout: 600
25 changes: 25 additions & 0 deletions .travis.yml
@@ -0,0 +1,25 @@
language: php

php:
- 7.2
- 7.3

# faster builds on new travis setup not using sudo
sudo: false

# cache vendor dirs
cache:
directories:
- $HOME/.composer/cache

install:
- travis_retry composer self-update && composer --version
- export PATH="$HOME/.composer/vendor/bin:$PATH"
- travis_retry composer install --prefer-dist --no-interaction

script:
- |
vendor/bin/phpunit $PHPUNIT_FLAGS --coverage-clover=coverage.clover
after_script:
- wget -c https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
3 changes: 3 additions & 0 deletions CHANGELOG.md
@@ -0,0 +1,3 @@
v1.0.0
------
First release.
29 changes: 29 additions & 0 deletions LICENSE
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2019, Razon Yang
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
62 changes: 62 additions & 0 deletions README.md
@@ -0,0 +1,62 @@
Yii2 enhanced database log target
=================================

[![Build Status](https://travis-ci.org/razonyang/yii2-log-target-db.svg?branch=master)](https://travis-ci.org/razonyang/yii2-log-target-db)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/razonyang/yii2-log-target-db/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/razonyang/yii2-log-target-db/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/razonyang/yii2-log-target-db/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/razonyang/yii2-log-target-db/?branch=master)
[![Latest Stable Version](https://img.shields.io/packagist/v/razonyang/yii2-log-target-db.svg)](https://packagist.org/packages/razonyang/yii2-log-target-db)
[![Total Downloads](https://img.shields.io/packagist/dt/razonyang/yii2-log-target-db.svg)](https://packagist.org/packages/razonyang/yii2-log-target-db)
[![LICENSE](https://img.shields.io/github/license/razonyang/yii2-jsend)](LICENSE)

Because the built-in database log target can not figure out the context of same request, especially in the case of concurrency,
so that the log is very confusing, it is hard to diagnose errors.

According this problem, what this extension do is that record the request ID via `dechex($_SERVER['REQUEST_TIME_FLOAT'] * 1000000)`.

Installation
------------

```
composer require razonyang/yii2-log-target-db
```

Usage
-----

```php
return [
// console configuration
'controllerMap' => [
'migrate' => [
'migrationPath' => [
// ...
'@yii/log/migrations/',
],
'migrationNamespaces' => [
// ...
'RazonYang\Yii2\Log\Db\Migration',
],
],
],

// common/web/console configuration
'components' => [
'log' => [
'targets' => [
'db' => [
'class' => \RazonYang\Yii2\Log\Db\Target::class,
'levels' => ['error', 'warning'],
'db' => 'db',
'logTable' => '{{%log}}',
],
],
],
],
];
```

then:

```shell
$ yii migrate
```
35 changes: 35 additions & 0 deletions composer.json
@@ -0,0 +1,35 @@
{
"name": "razonyang/yii2-log-target-db",
"description": "Yii2 enhanced database log target",
"type": "yii2-extension",
"keywords": ["yii2", "log", "db target"],
"license": "BSD-3-Clause",
"authors": [
{
"name": "Razon Yang",
"email": "razonyang@gmail.com"
}
],
"require": {
"yiisoft/yii2": "~2.0.13"
},
"require-dev": {
"phpunit/phpunit": "^8"
},
"autoload": {
"psr-4": {
"RazonYang\\Yii2\\Log\\Db\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"RazonYang\\Yii2\\Log\\Db\\Tests\\": "tests"
}
},
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}
30 changes: 30 additions & 0 deletions phpunit.xml.dist
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
verbose="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true">
<php>
<ini name="error_reporting" value="-1" />
</php>

<testsuites>
<testsuite name="Application Unit Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>
67 changes: 67 additions & 0 deletions src/Migration/M190814022727RequestId.php
@@ -0,0 +1,67 @@
<?php
namespace RazonYang\Yii2\Log\Db\Migration;

use yii\db\Migration;
use RazonYang\Yii2\Log\Db\Target;
use Yii;

/**
* Class M190814022727RequestId
*/
class M190814022727RequestId extends Migration
{
/**
* @var DbTarget[] Targets to create log table for
*/
private $dbTargets = [];

/**
* @throws InvalidConfigException
* @return DbTarget[]
*/
protected function getDbTargets()
{
if ($this->dbTargets === []) {
$log = Yii::$app->getLog();

$usedTargets = [];
foreach ($log->targets as $target) {
if ($target instanceof Target) {
$currentTarget = [
$target->db,
$target->logTable,
];
if (!in_array($currentTarget, $usedTargets, true)) {
// do not create same table twice
$usedTargets[] = $currentTarget;
$this->dbTargets[] = $target;
}
}
}

if ($this->dbTargets === []) {
throw new InvalidConfigException('You should configure "log" component to use one or more database targets before executing this migration.');
}
}

return $this->dbTargets;
}

public function up()
{
foreach ($this->getDbTargets() as $target) {
$this->db = $target->db;
$this->addColumn($target->logTable, 'request_id', $this->char(13)->notNull()->defaultValue('')->after('id'));
$this->createIndex('idx_request_id', $target->logTable, 'request_id');
}
}

public function down()
{
foreach ($this->getDbTargets() as $target) {
$this->db = $target->db;

$this->dropColumn($target->logTable, 'request_id');
}
}
}
55 changes: 55 additions & 0 deletions src/Target.php
@@ -0,0 +1,55 @@
<?php
namespace RazonYang\Yii2\Log\Db;

use yii\log\DbTarget as BaseTarget;
use Yii;

class Target extends BaseTarget
{
public function export()
{
if ($this->db->getTransaction()) {
// create new database connection, if there is an open transaction
// to ensure insert statement is not affected by a rollback
$this->db = clone $this->db;
}

$tableName = $this->db->quoteTableName($this->logTable);
$sql = "INSERT INTO $tableName ([[request_id]], [[level]], [[category]], [[log_time]], [[prefix]], [[message]])
VALUES (:request_id, :level, :category, :log_time, :prefix, :message)";
$command = $this->db->createCommand($sql);
foreach ($this->messages as $message) {
list($text, $level, $category, $timestamp) = $message;
if (!is_string($text)) {
// exceptions may not be serializable if in the call stack somewhere is a Closure
if ($text instanceof \Throwable || $text instanceof \Exception) {
$text = (string) $text;
} else {
$text = VarDumper::export($text);
}
}
if ($command->bindValues([
':request_id' => $this->getRequestId(),
':level' => $level,
':category' => $category,
':log_time' => $timestamp,
':prefix' => $this->getMessagePrefix($message),
':message' => $text,
])->execute() > 0) {
continue;
}
throw new LogRuntimeException('Unable to export log through database!');
}
}

protected $requestId;

protected function getRequestId()
{
if ($this->requestId === null) {
$this->requestId = dechex($_SERVER['REQUEST_TIME_FLOAT'] * 1000000);
}

return $this->requestId;
}
}

0 comments on commit 90d1c08

Please sign in to comment.