first of all, your $config array must contain the following elements: 
$config = [
    //others
    'modules' => [
        'debug' => [
            'class' => 'yii\debug\Module',
        ], 
        'gii'   => [
            'class' => 'yii\gii\Module',
        ];
    ],
    //others
]
 explain the inheritance relationship here: 
class yii\base\Application extends yii\base\Module
class yii\base\Module extends yii\di\ServiceLocator
class yii\di\ServiceLocator extends yii\base\Component
class yii\base\Component extends yii\base\Object
< hr >
 -  yiibaseApplication::__construct () method comments 
public function __construct($config = [])
{
    Yii::$app = $this;
    //\yii\base\ApplicationYii::$app->loadedModules
    static::setInstance($this);
    $this->state = self::STATE_BEGIN;
    // :
    $this->preInit($config);
    //
    $this->registerErrorHandler($config);
    //  __construct
    // Component__construct
    //  `yii\base\Object__construct($config)`
    Component::__construct($config);
}
 in the above method  Component::__construct ($config)  will call  yii\ base\ Object::__construct ()  method 
 -  yiibaseObject::__construct () method comments 
public function __construct($config = [])
{
    if (!empty($config)) {
        // Object
        // Object
        // yii\web\Application
        Yii::configure($this, $config);
    }
    $this->init();
}
< hr >
 1. The following is just to explain where  'components' = > [' log' = > [.]]  comes from. If you don't care, you can go straight to the second step. 
 -  first look at  $this- > preInit ($config); , that is, yii\ base\ Application::preInit (& $config) 
public function preInit(&$config)
{
    //others...
    
    // merge core components with custom components
    // 
    foreach ($this->coreComponents() as $id => $component) {
        if (!isset($config['components'][$id])) {
            // 
            $config['components'][$id] = $component;
        } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) {
            //  'class'class
            $config['components'][$id]['class'] = $component['class'];
        }
    }
}
/**
 * Returns the configuration of core application components.
 * 
 * @see set()
 */
public function coreComponents()
{
    return [
        // 
        'log' => ['class' => 'yii\log\Dispatcher'],
        
        //others...
    ];
}
 -  after  $this- > preInit ($config); , we get $config 
$config = [
    'modules' => [
        'debug' => [
            'class' => 'yii\debug\Module',
        ], 
        'gii'   => [
            'class' => 'yii\gii\Module',
        ];
    ],
    'components' => [
        'log'   => [
            'class' => 'yii\\log\\Dispatcher',
        ],
        
        //others...
    ]
    //others...
]
< hr >
< hr >
 the above is just to explain  'components' = > [' log' = > [.]]  where it comes from 
 2. The key point is here 
 - 
 yii\ base\ Object::__construct ($config = [])  Yii::configure ($this, $config); )
public static function configure($object, $properties)
{
    // 
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }
    return $object;
}
 -  here we need to cooperate with  yii\ base\ Object::__set ($name, $value) 
/**
 * 
 *
 * Do not call this method directly as it is a PHP magic method that
 * will be implicitly called when executing `$object->property = $value;`.
 * PHP `$object->property = $value;` 
 */
public function __set($name, $value)
{
    // setter
    // phpsetproperty()  setProperty()
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        // setter
        $this->$setter($value);
    } elseif (method_exists($this, 'get' . $name)) {
        // gettersetter 
        throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
    } else {
        throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
    }
}
  $object  in the current context, we can think of it as  yii\ base\ Application   $app  
.
$app->modules =  [
    'debug' => [
        'class' => 'yii\debug\Module',
    ], 
    'gii'   => [
        'class' => 'yii\gii\Module',
    ];
]
 the  yii\ base\ Module::setModules ($modules)  method will be called here 
public function setModules($modules)
{
    foreach ($modules as $id => $module) {
        $this->_modules[$id] = $module;
    }
}
  so you have  
 in question.
["_modules":"yii\base\Module":private]=>
    array(3) {
        ["debug"]=> object(yii\debug\Module)-sharp33 (28) {
            ......
        }
        ["gii"]=> object(yii\gii\Module)-sharp113 (21) {
            ......
        }
        ...
    }
 -  the same thing, when traversing to: 
$app->components =  [
    'log'   => [
        'class' => 'yii\\log\\Dispatcher',
    ],
]
 -  the  yii\ di\ ServiceLocator::setComponents ($components) method
 will be called here.
public function setComponents($components)
{
    foreach ($components as $id => $component) {
        $this->set($id, $component);
    }
}
public function set($id, $definition)
{
    // others ...
    if (is_object($definition) || is_callable($definition, true)) {
        // an object, a class name, or a PHP callable
        $this->_definitions[$id] = $definition;
    } elseif (is_array($definition)) {
        //  class 
        // a configuration array
        if (isset($definition['class'])) {
            //  $_definitions 
            $this->_definitions[$id] = $definition;
        } else {
            throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element.");
        }
    } else {
        throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition));
    }
}
  so you have  
 in question.
["_definitions":"yii\di\ServiceLocator":private]=>
        array(24) {
            ["errorHandler"]=> .....
            ["request"]=> ......
            ["log"]=> ......
            ......
        }