Problem/Motivation

Contrib/custom database drivers are not in the Drupal\Core\Database namespace, and in this case Log::findCaller will not report the caller correctly, but just the first function/method coming after the first call to the database connection, or worse after a vendor namespaced method if the driver implements methods there.

Incurred into this while experimenting a db driver https://github.com/mondrake/drudbal in conjuction with #2605284: Testing framework does not work with contributed database drivers, and getting failure for the LoggingTest.

See the trace below: in this case the identified 'caller' is always 'query' since it's the first method coming after either not namespaced or not into Drupal\Core\Database namespace.

    [0] => Drupal\Core\Database\Log -> findCaller
    [1] => Drupal\Core\Database\Log -> log
    [2] => Drupal\Core\Database\Statement -> execute
    [3] => Doctrine\DBAL\Statement -> execute
    [4] => Drupal\Driver\Database\dbal\Connection -> query
    [5] =>  -> db_query
    [6] => Drupal\drudbal\Tests\LoggingTest -> testEnableLogging
    [7] => Drupal\simpletest\TestBase -> run
    [8] =>  -> _simpletest_batch_operation
    [9] =>  -> _batch_process
    [10] =>  -> _batch_do
    [11] =>  -> _batch_page
    [12] => Drupal\system\Controller\BatchController -> batchPage
    [13] =>  -> call_user_func_array
    [14] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> Drupal\Core\EventSubscriber\{closure}
    [15] => Drupal\Core\Render\Renderer -> executeInRenderContext
    [16] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> wrapControllerExecutionInRenderContext
    [17] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> Drupal\Core\EventSubscriber\{closure}
    [18] =>  -> call_user_func_array
    [19] => Symfony\Component\HttpKernel\HttpKernel -> handleRaw
    [20] => Symfony\Component\HttpKernel\HttpKernel -> handle
    [21] => Drupal\Core\StackMiddleware\Session -> handle
    [22] => Drupal\Core\StackMiddleware\KernelPreHandle -> handle
    [23] => Drupal\page_cache\StackMiddleware\PageCache -> pass
    [24] => Drupal\page_cache\StackMiddleware\PageCache -> handle
    [25] => Drupal\Core\StackMiddleware\ReverseProxyMiddleware -> handle
    [26] => Drupal\Core\StackMiddleware\NegotiationMiddleware -> handle
    [27] => Stack\StackedHttpKernel -> handle
    [28] => Drupal\Core\DrupalKernel -> handle

Proposed resolution

I think we can safely assume in D8 any logged query will always pass by the Connection object. Get its class name, and drop any stack entry with classes that are not the same as the connection class, drop any entry of the connection class, and then process the remaining stack trace as before.

I think we can safely assume in D8 any logged query will always pass through methods in the namespace of the Connection object.

  • Get the connection class namespace,
  • Drop any stack entry with methods from classes that are not within that namespace,
  • Drop any stack entry with methods from classes that are within that namespace,
  • then process the remaining stack trace as before.

This will get to:

    [0] =>  -> db_query
    [1] => Drupal\drudbal\Tests\LoggingTest -> testEnableLogging
    [2] => Drupal\simpletest\TestBase -> run
    [3] =>  -> _simpletest_batch_operation
    [4] =>  -> _batch_process
    [5] =>  -> _batch_do
    [6] =>  -> _batch_page
    [7] => Drupal\system\Controller\BatchController -> batchPage
    [8] =>  -> call_user_func_array
    [9] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> Drupal\Core\EventSubscriber\{closure}
    [10] => Drupal\Core\Render\Renderer -> executeInRenderContext
    [11] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> wrapControllerExecutionInRenderContext
    [12] => Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber -> Drupal\Core\EventSubscriber\{closure}
    [13] =>  -> call_user_func_array
    [14] => Symfony\Component\HttpKernel\HttpKernel -> handleRaw
    [15] => Symfony\Component\HttpKernel\HttpKernel -> handle
    [16] => Drupal\Core\StackMiddleware\Session -> handle
    [17] => Drupal\Core\StackMiddleware\KernelPreHandle -> handle
    [18] => Drupal\page_cache\StackMiddleware\PageCache -> pass
    [19] => Drupal\page_cache\StackMiddleware\PageCache -> handle
    [20] => Drupal\Core\StackMiddleware\ReverseProxyMiddleware -> handle
    [21] => Drupal\Core\StackMiddleware\NegotiationMiddleware -> handle
    [22] => Stack\StackedHttpKernel -> handle
    [23] => Drupal\Core\DrupalKernel -> handle

Remaining tasks

Review patch.

User interface changes

None

API changes

None

Data model changes

None

CommentFileSizeAuthor
#92 2867788-92.patch14.31 KBmrinalini9
#89 2867788-89.patch2.13 KBmrinalini9
#79 2867788-79.patch14.31 KBmondrake
#79 interdiff_77-79.txt527 bytesmondrake
#77 2867788-77.patch14.33 KBmondrake
#71 interdiff_62-71.txt1.42 KBmondrake
#71 2867788-71.patch10.69 KBmondrake
#62 2867788-62.patch11.45 KBmondrake
#62 interdiff_58-62.txt8 KBmondrake
#58 interdiff_53-58.txt4.16 KBmondrake
#58 2867788-58.patch11.92 KBmondrake
#53 2867788-53.patch11.59 KBmondrake
#51 2867788-51.patch11.58 KBmondrake
#51 interdiff_45-51.txt4.92 KBmondrake
#46 interdiff-2867788-45-46.txt2.15 KBdaffie
#46 2867788-46.patch10.67 KBdaffie
#45 2867788-45.patch11.34 KBmondrake
#45 interdiff_43-45.txt7.08 KBmondrake
#43 interdiff_41-43.txt720 bytesmondrake
#43 2867788-43.patch10.28 KBmondrake
#41 interdiff_40-41.txt2.1 KBmondrake
#41 2867788-41.patch10.28 KBmondrake
#40 2867788-40.patch9.97 KBmondrake
#40 interdiff_38-40.txt743 bytesmondrake
#38 interdiff_36-38.txt1.43 KBmondrake
#38 2867788-38.patch9.9 KBmondrake
#36 2867788-36.patch9.25 KBmondrake
#36 interdiff_29-36.txt4.36 KBmondrake
#29 interdiff_22-29.txt2.29 KBmondrake
#29 2867788-29.patch10.05 KBmondrake
#27 2867788-22_0.patch9.03 KBmondrake
#25 2867788-25.patch8.97 KBmondrake
#25 interdiff-22_25.patch631 bytesmondrake
#22 interdiff_20-22.txt554 bytesmondrake
#22 2867788-22.patch9.03 KBmondrake
#20 interdiff_18-20.txt1.18 KBmondrake
#20 2867788-20.patch9.01 KBmondrake
#18 2867788-18.patch11.5 KBmondrake
#18 interdiff_17-18.txt2.51 KBmondrake
#17 interdiff_15-17.txt3.17 KBmondrake
#17 2867788-17.patch9.56 KBmondrake
#15 interdiff_13-15.txt2.85 KBmondrake
#15 2867788-15.patch8.94 KBmondrake
#13 2867788-13.patch8.53 KBmondrake
#13 interdiff_12-13.txt4.66 KBmondrake
#12 2867788-12.patch8.47 KBmondrake
#12 interdiff_11-12.txt5.87 KBmondrake
#11 interdiff_8-11.txt2.39 KBmondrake
#11 2867788-11.patch6.8 KBmondrake
#8 interdiff_6-8.txt8.42 KBmondrake
#8 2867788-8.patch6.19 KBmondrake
#6 2867788-6.patch5.13 KBdawehner
#3 2867788-2.patch1.02 KBmondrake

Comments

mondrake created an issue. See original summary.

mondrake’s picture

Issue summary: View changes
mondrake’s picture

Status: Active » Needs review
StatusFileSize
new1.02 KB

Status: Needs review » Needs work

The last submitted patch, 3: 2867788-2.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review

Failure in #4 is a DrupalCI failure.

dawehner’s picture

StatusFileSize
new5.13 KB

I want to support you doing your research. Here is for example a test for your fix, even it doesn't run yet. Do you have an idea by any change?

Status: Needs review » Needs work

The last submitted patch, 6: 2867788-6.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new6.19 KB
new8.42 KB

Thanks a lot @dawehner!

Let's see this, I do not think we need a separate test class here, LoggingTest is there already. The test passes locally with the contrib driver on, but should fail on d.o. because the debug trace data hardcodes the contrib driver's Connection class, whereas on d.o. it will be searching for mysql/pgsql/sqlite core's Connection classes. Maybe we can just check the current active connection class before running the test and just update the sample in the array?

The last submitted patch, 6: 2867788-6.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 8: 2867788-8.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new6.8 KB
new2.39 KB

So #6 and #8 were aborting because of infinite loops while removing from the $stack array. Fixed here, and fixed my point in #8 by implementing a protected method to return the connection's class name, and stubbing it in the test.

mondrake’s picture

StatusFileSize
new5.87 KB
new8.47 KB

Using a mocked Log class in the test, we can also introduce a protected method to get the debug backtrace and stub it in the test, thus avoiding to change the signature of the findCaller method (I can't see other use cases for passing a different backtrace to it, other than testing). Also added a dataprovider to the test to test the (unfathomable) case of no methods from the Connection class being present in the trace (which caused CI errors in #6 an #8). Finally, small adjustment to findCaller docs to make it up-to-date.

mondrake’s picture

Issue summary: View changes
StatusFileSize
new4.66 KB
new8.53 KB

My original assumption

I think we can safely assume in D8 any logged query will always pass by the Connection object.

is incorrect. Sqlite fails because the core Database opens a Statement class directly, without passing through the Connection class. So here I am 'widening' the approach to check for methods in the Connection class namespace instead.

dawehner’s picture

@mondrake
Good point. Its certainly worth trying out all database engines.

+++ b/core/lib/Drupal/Core/Database/Log.php
@@ -142,7 +143,18 @@ public function log(StatementInterface $statement, $args, $time) {
+    while (count($stack) > 2 && strpos((!empty($stack[0]['class']) ? $stack[0]['class'] : ''), $connection_namespace) === FALSE) {
+      array_shift($stack);
+    }
+    while (count($stack) > 2 && strpos((!empty($stack[0]['class']) ? $stack[0]['class'] : ''), $connection_namespace) === 0) {
+      array_shift($stack);
+    }

Can't you combine those two while statements into one using in_array(, , TRUE)?

mondrake’s picture

StatusFileSize
new8.94 KB
new2.85 KB

@dawehner thanks. Actually I think we can just incorporate the logic in the main for loop, and avoid array_shift calls.

larowlan’s picture

  1. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +143,27 @@ public function log(StatementInterface $statement, $args, $time) {
    +    $connection_namespace = $this->getConnectionNamespace();
    

    Should we statically cache this as a property on the class? Reflection isn't cheap - how frequently will findCaller be called? Does the namespace ever change?

  2. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +143,27 @@ public function log(StatementInterface $statement, $args, $time) {
         for ($i = 0, $stack_count = count($stack); $i < $stack_count; ++$i) {
    

    In my opinion this whole chunk would be more readable if it used

    
    foreach ($stack as $ix => $entry)
    
    

    instead of for

    I realise its an existing issue, but if we're touching it, we should leave it in a happier place

  3. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +143,27 @@ public function log(StatementInterface $statement, $args, $time) {
           if (empty($stack[$i]['class'])) {
             $stack[$i]['class'] = '';
           }
    

    There is some ugmo existing code here. Can we touch it up while we're here?

    $stack[$i] += ['class' => ''];
    

    would move three lines to one.

  4. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +143,27 @@ public function log(StatementInterface $statement, $args, $time) {
    +      if (($is_driver_method === TRUE || $skipped_driver_methods ===  FALSE) && $i < $stack_count - 2) {
    

    why not

    if (($is_driver_method || !$skipped_driver_methods) ... 
  5. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +143,27 @@ public function log(StatementInterface $statement, $args, $time) {
    +        if ($is_driver_method === TRUE) {
    

    why not

    if ($is_driver_method)
    

    this is definitely a boolean

  6. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -164,4 +178,24 @@ public function findCaller() {
    +    return debug_backtrace();
    

    nice work

  7. +++ b/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php
    @@ -135,4 +137,180 @@ public function testGetLoggingWrongKey() {
    +      // Test that if the driver Connection is in the stack trace, the first
    +      // non-database entry is returned.
    +      [
    ...
    +      // Test that if the driver Connection is not in the stack trace, the very
    +      // last backtrace entry is still returned.
    

    nit: personally I prefer named test cases (use names in array keys instead of integers).

    The need for comments to describe the cases, illustrates that they would be useful.

mondrake’s picture

StatusFileSize
new9.56 KB
new3.17 KB

Thanks @larowlan!

#16:
1. Better not use Reflection at all, I am adding a getter to the Connection class instead.
2. and 3. Fully agree but I am quite sure if we change here it will be pushed back. How about a follow-up, or extending the scope of #2088271: Undefined index: args in DatabaseLog->findCaller()?
4. and 5. OK done
6. :)
7. OK done

mondrake’s picture

StatusFileSize
new2.51 KB
new11.5 KB

__NAMESPACE__ constant is inherited by extending classes still with the abstract class namespace, contrib will fail. Here making getNamespace abstract in the base Connection, and implementing in each driver.

Status: Needs review » Needs work

The last submitted patch, 18: 2867788-18.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new9.01 KB
new1.18 KB

Adding an abstract method to the Connection class is probably controversial. Just doing sth simpler.

Status: Needs review » Needs work

The last submitted patch, 20: 2867788-20.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new9.03 KB
new554 bytes

Sorry for the noise.

So here instead of using Reflection to get the namespace of the driver's Connection class, which is expensive as pointed out by @larowlan in #15.1, we get the class name through get_class, and just skip the part of the name that comes after the last backslash (included). Please comment if it still makes sense to cache it.

larowlan’s picture

Looking good to me, can you comment about the cleanup in #2088271: Undefined index: args in DatabaseLog->findCaller()?

mondrake’s picture

@larowlan re

can you comment about the cleanup in #2088271: Undefined index: args in DatabaseLog->findCaller()

That one is about ensuring an 'args' key is defined for the stack trace entry, very much like #16.1 is about ensuring that a 'class' key exists. We could just have a protected method to array_merge a set of default keys into the actual stack trace entry. In that issue we are missing a test, though; with the test added in this patch we could then also add other sample backtrace arrays with the case of missing 'class' and 'args'.

mondrake’s picture

StatusFileSize
new631 bytes
new8.97 KB

Re #22, actually there is no purpose even to use get_class: the namespace is always specified in the connection options and we can retrieve it via Connection::getConnectionOptions()['namespace'].

The last submitted patch, 25: interdiff-22_25.patch, failed testing. View results

mondrake’s picture

StatusFileSize
new9.03 KB

Actually no, sorry - the 'namespace' connection key can be missing with core drivers. So #22 still stands as the patch to review, unless we want to replicate here the piece of code from Database::openConnection that resolves the driver class

    if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
      $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
    }
    else {
      // Fallback for Drupal 7 settings.php.
      $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
    }

but I do not think it makes sense here.

Reuploading patch in #22 to be clean.

The last submitted patch, 25: 2867788-25.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

StatusFileSize
new10.05 KB
new2.29 KB

Trying again along the lines of #25. This time we are resolving the namespace in Database::openConnection in case it's missing, and passing it over to the connection options when instatiating the connection. This patch collides with #2605284: Testing framework does not work with contributed database drivers, though.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mondrake’s picture

mondrake’s picture

Title: Log::findCaller fails to report the correct caller function with non-core drivers. » [PP-1] Log::findCaller fails to report the correct caller function with non-core drivers.
Status: Needs review » Postponed

Postponing on #2605284: Testing framework does not work with contributed database drivers that has a different way to get the Connection's namespace.

mondrake’s picture

Title: [PP-1] Log::findCaller fails to report the correct caller function with non-core drivers. » Log::findCaller fails to report the correct caller function with non-core drivers.
Status: Postponed » Needs work
daffie’s picture

Issue tags: +Needs reroll
mondrake’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
StatusFileSize
new4.36 KB
new9.25 KB

Rerolled + adjustments after #2605284: Testing framework does not work with contributed database drivers went in + moved the test debug_bactrace stack to the test dataprovider.

Status: Needs review » Needs work

The last submitted patch, 36: 2867788-36.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new9.9 KB
new1.43 KB

Argh I did not realize that in the end in #2605284: Testing framework does not work with contributed database drivers we set Database::getDatabaseDriverNamespace visibility to protected.

I think it's better to change it to public rather then duplicating code or using reflection here.

Status: Needs review » Needs work

The last submitted patch, 38: 2867788-38.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new743 bytes
new9.97 KB

Database::getConnectionInfo returns an array with multiple targets so we take the default one.

mondrake’s picture

StatusFileSize
new10.28 KB
new2.1 KB

Let's reduce unnecessary calls - here the namespace will be only determined once.

Status: Needs review » Needs work

The last submitted patch, 41: 2867788-41.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new10.28 KB
new720 bytes

Fix test

daffie’s picture

Status: Needs review » Needs work

The patch looks good, but I do have some remarks:

  1. +++ b/core/lib/Drupal/Core/Database/Database.php
    @@ -503,7 +503,7 @@ public static function getConnectionInfoAsUrl($key = 'default') {
    -  protected static function getDatabaseDriverNamespace(array $connection_info) {
    +  public static function getDatabaseDriverNamespace(array $connection_info) {
    

    Changing this method from protected to public does warrend the creation of a change record.

  2. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +150,26 @@ public function log(StatementInterface $statement, $args, $time) {
    +    $stack = $this->getDebugBacktrace();
    
    @@ -164,4 +184,31 @@ public function findCaller() {
    +  /**
    +   * Gets the debug backtrace.
    +   *
    +   * @return array[]
    +   *   The debug backtrace.
    +   */
    +  protected function getDebugBacktrace() {
    +    return debug_backtrace();
    +  }
    

    I think that moving the calling of debug_backtrace() to a separate method makes things only more complicated and unnecessary. If you do not agree, please explain why you think so.

  3. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +150,26 @@ public function log(StatementInterface $statement, $args, $time) {
    +    $skipped_driver_methods = FALSE;
    

    Can you add some documentation about why/what this variable is for/what it does.

  4. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +150,26 @@ public function log(StatementInterface $statement, $args, $time) {
    +      $is_driver_method = (strpos($stack[$i]['class'], $this->getConnectionNamespace()) === 0);
    

    Why do you compare the result of strpos() with === 0 and not with === FALSE?

  5. +++ b/core/lib/Drupal/Core/Database/Log.php
    @@ -142,14 +150,26 @@ public function log(StatementInterface $statement, $args, $time) {
    +      if (($is_driver_method || !$skipped_driver_methods) && $i < $stack_count - 2) {
    

    Why do you add the part $i < $stack_count - 2? It is properly necessary, but I am missing the point. Sorry.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new7.08 KB
new11.34 KB

Thanks @daffie

Replies to #44.2/3/4/5 can be found in the comments in this patch.

#44.1 I haven't tackled yet, let's see feedback from maintainers if change of visibility is OK or not.

daffie’s picture

StatusFileSize
new10.67 KB
new2.15 KB

Just testing if a simpler solution would also work.

Status: Needs review » Needs work

The last submitted patch, 46: 2867788-46.patch, failed testing. View results

daffie’s picture

  1. +++ b/core/lib/Drupal/Core/Database/Log.php
          // The function called is a method from the database driver if the class
          // name begins with the driver's namespace.
          $is_driver_method = (strpos($stack[$i]['class'], $this->getDriverNamespace()) === 0);
    
          // Skip all stack entries before and including methods called from the
          // database driver namespace. In case no database driver methods are
          // found, at least ensure the last two stack entries are left, so that
          // the very first function call in the stack can be returned as the
          // caller.
          if (($is_driver_method || !$skipped_driver_methods) && $i < $stack_count - 2) {
            if ($is_driver_method) {
              $skipped_driver_methods = TRUE;
            }
            continue;
          }
          if (strpos($stack[$i]['class'], __NAMESPACE__) === FALSE && strpos($stack[$i + 1]['function'], 'db_') === FALSE && !empty($stack[$i]['file'])) {
            $stack[$i] += ['file' => '?', 'line' => '?', 'args' => []];
            return [
              'file' => $stack[$i]['file'],
              'line' => $stack[$i]['line'],
              'function' => $stack[$i + 1]['function'],
              'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL,
              'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL,
              'args' => $stack[$i + 1]['args'],
            ];
          }
    

    Please do not see my following remarks as personal. Your work @mondrake on this and other issues is great. with that said:
    I get bad feeling of codesmell when I look at the above code. I find it diffecult to read and to really see want is happening.
    I am unable to test this code. Therefor am I asking you @mondrake if it is possible to change it to something like:

          if (strpos($stack[$i]['class'], $this->getDriverNamespace()) === FALSE  && !empty($stack[$i]['file'])) {
            $stack[$i] += ['file' => '?', 'line' => '?', 'args' => []];
            return [
              'file' => $stack[$i]['file'],
              'line' => $stack[$i]['line'],
              'function' => $stack[$i + 2]['function'],
              'class' => isset($stack[$i + 2]['class']) ? $stack[$i + 1]['class'] : NULL,
              'type' => isset($stack[$i + 2]['type']) ? $stack[$i + 1]['type'] : NULL,
              'args' => $stack[$i + 1]['args'],
            ];
          }
          if (strpos($stack[$i]['class'], __NAMESPACE__) === FALSE && strpos($stack[$i + 1]['function'], 'db_') === FALSE && !empty($stack[$i]['file'])) {
            $stack[$i] += ['file' => '?', 'line' => '?', 'args' => []];
            return [
              'file' => $stack[$i]['file'],
              'line' => $stack[$i]['line'],
              'function' => $stack[$i + 1]['function'],
              'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL,
              'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL,
              'args' => $stack[$i + 1]['args'],
            ];
          }
    

    If there is no other solution I will give your patch RTBC.

  2. Maybe this is a stupid question, but does the this patch also fixes the problem if the database query is called from a "db_"-function for a 3rd party database driver?
mondrake’s picture

@daffie I never take feedback in issue queues as personal... and there are no such things as stupid questions :)

Thanks for your input. I agree that's difficult to read. I was thinking to change the approach and instead of looping backwards through the debug stack, start from the very last entry (which is the first call made), and stop when you find a db_* function or a method from the Database or database driver namespace. That should be more easily readable.

The db_* functions are wrappers to methods in classes of the Database or database driver namespace, regardless if the driver is core or contrib.

I do not have time right now to look at it, will do later if noone beats me at it :)

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new4.92 KB
new11.58 KB

This does #49 and should be more readable.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mondrake’s picture

StatusFileSize
new11.59 KB

Reroll.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

bohart’s picture

+1 to RTBC.

The latest patch #53 was tested and added to Oracle Database Driver into installation instruction as a required one for proper work.

Related issue: #3107885: Fix Database\LoggingTest

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@bohart: Thank you for testing the patch with your Oracle database driver.

The patch loks good to me. All my remarks are addressed.
The patch works for the Oacle database driver.
There are tests added.
The readability of the code from the findCaller method has been improved.
For me it is RTBC.

mondrake’s picture

Version: 8.9.x-dev » 9.0.x-dev
Status: Reviewed & tested by the community » Needs work

Needs a reroll after commit of #3109097: Drupal 9 does not handle db_* functions any more, and moving to 9.0.x since db_ functions are no longer to be supported. If it will have to be backported to 8.x, the patch should include the db_ check again.

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new11.92 KB
new4.16 KB

Taking the opportunity to use the null coalesce operator in PHP 7+. I believe we need a new sample backtrace in the test, the current one is still representing a call to db_query.

Interdiff is complicated since it's intertwined wit the reroll.

daffie’s picture

@mondrake: I think that something went wrong with your interdiff file.

mondrake’s picture

@daffie yes, it's difficult to get a clean interdiff when rerolling. Better look at the patch itself.

Status: Needs review » Needs work

The last submitted patch, 58: 2867788-58.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new8 KB
new11.45 KB

Here's with a re-sampled backtrace from running LoggingTest::testEnableLogging on a contrib driver, this time with no longer a db_query call in the stack.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

The db_query parts have been removed.
It all looks good again.
Back to RTBC.

daffie’s picture

beakerboy’s picture

I'd be happy to test this on 8.8.x with SQL Server. I need to set up a travis-ci environment for 8.9.x and 9.0.x. #53 worked for me on 8.8.x another RTBC.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 62: 2867788-62.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

random test failure

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
+++ b/core/lib/Drupal/Core/Database/Database.php
@@ -503,7 +503,7 @@ public static function getConnectionInfoAsUrl($key = 'default') {
-  protected static function getDatabaseDriverNamespace(array $connection_info) {
+  public static function getDatabaseDriverNamespace(array $connection_info) {

So this change is interesting. Here's the code of the method:

  protected static function getDatabaseDriverNamespace(array $connection_info) {
    if (isset($connection_info['namespace'])) {
      return $connection_info['namespace'];
    }
    // Fallback for Drupal 7 settings.php.
    return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
  }

I think in Drupal 9 this method is not even necessary. I've opened #3112476: Always set $info['namespace'] on database connection info. I'm not sure about making a method public that's not really necessary.

mondrake’s picture

@alexpott #3112476: Always set $info['namespace'] on database connection info would defer all this to 9.1+, whereas this issue is for 9.0 with potential backport to 8.x - to allow contrib drivers to be compatible with these versions.

alexpott’s picture

@mondrake i don't think this really increases compatibility or not. Also looking at the code we can do this without affecting the public API.

+++ b/core/lib/Drupal/Core/Database/Log.php
@@ -141,26 +150,65 @@ public function log(StatementInterface $statement, $args, $time) {
+      $connection_info = Database::getConnectionInfo($this->connectionKey);
+      if ($connection_info && isset($connection_info['default'])) {
+        $this->driverNamespace = Database::getDatabaseDriverNamespace($connection_info['default']);
+      }

This could be done this...

$connection = Database::getConnection('default', $this->connectionKey);
$this->driverNamespace = (new \ReflectionObject($connection))->getNamespaceName();

Note that exceptions will be thrown in the connection key and default target don't exist which imo is the correct behaviour because we can't log on a non-existent connection.

mondrake’s picture

StatusFileSize
new10.69 KB
new1.42 KB

#70: @larowlan had some concerns on using reflection back in #16, but I think this can work. IMHO, using reflection in runtime code means something is wrong with the public API, but OK, when #3112476: Always set $info['namespace'] on database connection info this all may become unnecessary.

alexpott’s picture

Well this is in-between runtime and debug code. It's only on during views ui query capture in core. Which is not the 99.999999% use of core db code.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

The changes look good to me and it addresses the points made by @alexpott.
Back to RTBC.

beakerboy’s picture

#71 does not apply to 8.8.x. If someone could backport the patch, I can test on my sqlsrv github repo.

alexpott’s picture

Version: 9.0.x-dev » 8.9.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)
Issue tags: +Needs reroll

Committed 97bac4f and pushed to 9.0.x. Thanks!

If we want to fix this in Drupal 8.x then we need a new patch for 8.9.x

  • alexpott committed 97bac4f on 9.0.x
    Issue #2867788 by mondrake, daffie, dawehner, alexpott, larowlan: Log::...
mondrake’s picture

Status: Patch (to be ported) » Needs review
Issue tags: -Needs reroll
StatusFileSize
new14.33 KB

Here's a patch for D8.9. Rerolled #71, and added back checks for db_* functions and a test for that, taking from #53.

Status: Needs review » Needs work

The last submitted patch, 77: 2867788-77.patch, failed testing. View results

mondrake’s picture

Status: Needs work » Needs review
StatusFileSize
new527 bytes
new14.31 KB
mondrake’s picture

beakerboy’s picture

Status: Needs review » Reviewed & tested by the community

Works for sql server
#79 Works for SQL Server...I have reviewed the code and it meets my expectations and appears to be well formatted. I have installed the patch on my testing environment and testing failures now pass.

daffie’s picture

@Beakerboy: Thank you for reviewing the patch from this issue. Because of your short comment: "Works for sql server", I was wondering if you followed the reviewing guidelines set in https://www.drupal.org/patch/review? Let me point out that I am myself not always following those guidelines as good as should.

alexpott’s picture

Status: Reviewed & tested by the community » Postponed

I think we need to do #3112476: Always set $info['namespace'] on database connection info first. If we can get the default namespace setting in $info back in 8.x then we don't need the additional protected methods for finding a driver namespace or mocking out debug_backtrace() - which leads to less API via inheritance to support. Which is a good thing - especially as our test coverage involves mocking out this API.

beakerboy’s picture

@alexpott, #3112476: Always set $info['namespace'] on database connection info has been RTBC for 3 weeks and it is holding up this issue. Do you have any comments?

beakerboy’s picture

@mondrake, Patch #71 needs to be rerolled

mondrake’s picture

Status: Postponed » Patch (to be ported)
Issue tags: +Needs reroll

Blocker is in now

mondrake’s picture

Actually, dunno... more than a reroll here we need a totally new patch if we want to port this to D8. Maybe we could close this as a backport and open a D8 issue only.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

mrinalini9’s picture

StatusFileSize
new2.13 KB

Rerolled patch for 9.1.x, please review.

mondrake’s picture

Version: 9.1.x-dev » 8.8.x-dev

@mrinalini9 this is already committed to D9. Needs a patch for D8.

mrinalini9’s picture

Assigned: Unassigned » mrinalini9
mrinalini9’s picture

Assigned: mrinalini9 » Unassigned
StatusFileSize
new14.31 KB

Hi @mondrake, I have rerolled patch for 8.8.x, please review.

sokru’s picture

Status: Patch (to be ported) » Needs review
Issue tags: -Needs reroll

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

beakerboy’s picture

Status: Needs review » Reviewed & tested by the community

@mrinalini9 Is your patch any different than #79? They both fix the bug, so I don't see why one of them cannot be used.

catch’s picture

Title: Log::findCaller fails to report the correct caller function with non-core drivers. » [backport] Log::findCaller fails to report the correct caller function with non-core drivers.
Status: Reviewed & tested by the community » Needs review

Re-titling to make it clear this is about the 8.9 backport. What is the difference between the #95 and #79 patches?

mondrake’s picture

Title: [backport] Log::findCaller fails to report the correct caller function with non-core drivers. » Log::findCaller fails to report the correct caller function with non-core drivers.
Status: Needs review » Fixed

I am afraid this backport has stalled. I'm marking the issue fixed not to drag it forward. I'd rather suggest opening a backport-specific issue it this has to be a thing.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.