webandco / neos-dev-tools
Neos 开发工具
Requires
- neos/flow: *
- symfony/stopwatch: >=6.0
README
该包提供 Neos 开发的工具
安装
使用 composer 安装该包。建议只在开发环境中使用该包。
composer require webandco/neos-dev-tools --dev
工具
nodePublished 文件工具
该工具在 Neos 中发布内容时创建一个文件。这样做的原因是,例如,在文件更改时使用文件(例如在 gulp watch 中用于 browser-sync 和重新加载前端)。
配置
Webandco:
DevTools:
nodePublished:
use: true
file: '%FLOW_PATH_ROOT%.WebandcoNeosDevToolsLastPublished'
Webandco.DevTools.nodePublished.use
true
启用写入 nodePublished 文件,否则 false
Webandco.DevTools.nodePublished.file
nodePublished 文件的路径
FLOW 命令行 BASH 自动完成
要获取 FLOW 命令行的自动完成,您需要在 BASH 中加载脚本。
source Scripts/flow.complete.sh
这可能在 Beach 或自定义 docker 容器等服务器环境中很有用。
计时器
有时确定操作耗时多少毫秒以确定性能问题非常有用。该包使用 Symfony 的 Stopwatch 来实现此目的。
Stopwatch 类被扩展以提供更多功能。
实例化
有两种方法可以实例化计时器。直接使用 new Stopwatch()
或通过提供的 StopwatchFactory
。
如果在不同代码部分需要多个计时器,则工厂可能相关。使用工厂,您可以创建所有使用计时器的最终日志输出。
用法
计时器可以使用(Symfony's Stopwatch)https://symfony.com.cn/doc/current/components/stopwatch.html)。
还有两个附加功能:重新启动计时器和估计。
重新启动
给定一个计时器 $s = new Stopwatch();
,您可以通过使用以下方法避免连续调用 ->start('critical_section')
和 ->stop('critical_section')
:
$s->start('init');
....
$s->restart('workspace_live');
if ($workspace->getName() == 'live') {
$s->restart('critical_section_1');
...
$s->restart('critical_section_2');
...
$s->restart('nodes');
foreach($nodes as $node){
...
$s->lap('nodes');
}
}
$s->stop();
ETA 和格式化持续时间
假设您在循环中处理一组节点,并且您知道这需要很长时间。为了获取估计的耗时,您可以使用以下方法:
$s = new Stopwatch();
...
$nodes = ....
$nodeCount = count($nodes);
$s->restart('nodes');
foreach($nodes as $node){
$c = $stopwatch->countLaps('nodes');
if($c % 10 == 0){ // print ETA on every 10'th node
$this->systemLogger->debug("Duration: ".Stopwatch::format($s->getEvent('nodes')->getDuration())." ETA: ".Stopwatch::format($s->eta('nodes', $nodeCount)));
}
... // process a node
$s->lap('nodes');
}
$s->stop('nodes');
计时器持续时间
可以使用 $s->getDuration()
获取整个计时器的持续时间(以毫秒为单位),或对于单个事件 $s->getEvent('someeventname')->getDuration()
。
计时器元数据
您还可以向计时器添加元数据,稍后可以用于此。
public function processNode(Node $node){
$s = new Stopwatch();
$s->setMetadata('nodeIdentifier', $node->getIdentifier());
$s->setMetadata('nodeType', $node->getNodeType()->getName());
...
$s->restart('ciritical_section');
$s->stop();
// Log the stopwatch, e.g. using wLog($s); - see below
}
计时器信号
当开始/停止/openSection/stopSection 时,Stopwatch 会发出信号。这些信号可用于创建计时器调用的树。
计时器树
提供了一个 StopwatchTree,它接收 Stopwatch 发出的信号并创建类似于调用树的层次结构。
该树可以提供有关方法中哪些部分运行缓慢的洞察。
例如,在 Neos 4.3 中,neos 后端发出的第一个初始请求之一是 /neos/schema/node-type?version=...
。该请求依赖于配置的 nodetypes 数量,并且运行时间主要取决于 Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php::generateConstraints()
。
可以使用 StopwatchTree 获取对方法的更好视图
....
/**
* @Flow\Inject
* @var StopwatchFactoryInterface
*/
protected $stopwatchFactory;
/**
* @Flow\Inject
* @var StopwatchTreeInterface
*/
protected $stopwatchTree;
....
/**
* Generate the list of allowed sub-node-types per parent-node-type and child-node-name.
*
* @return array constraints
*/
protected function generateConstraints()
{
$s = $this->stopwatchFactory->create();
//$s = new Stopwatch();
$s->start('generateConstraints');
$constraints = [];
$nodeTypes = $this->nodeTypeManager->getNodeTypes(true);
$s->restart('outer-nodeTypes');
/** @var NodeType $nodeType */
foreach ($nodeTypes as $nodeTypeName => $nodeType) {
$constraints[$nodeTypeName] = [
'nodeTypes' => [],
'childNodes' => []
];
$s->start('inner-nodeTypes');
foreach ($nodeTypes as $innerNodeTypeName => $innerNodeType) {
$s->start('allowsChildNodeType');
if ($nodeType->allowsChildNodeType($innerNodeType)) {
$constraints[$nodeTypeName]['nodeTypes'][$innerNodeTypeName] = true;
}
$s->stop('allowsChildNodeType');
$s->lap('inner-nodeTypes');
}
$s->restart('inner-autocreatedChildNodes');
foreach ($nodeType->getAutoCreatedChildNodes() as $key => $_x) {
$s->start('inner-nodeTypes-2');
foreach ($nodeTypes as $innerNodeTypeName => $innerNodeType) {
$s->start('allowsGrandchildNodeType');
if ($nodeType->allowsGrandchildNodeType($key, $innerNodeType)) {
$constraints[$nodeTypeName]['childNodes'][$key]['nodeTypes'][$innerNodeTypeName] = true;
}
$s->stop('allowsGrandchildNodeType');
$s->lap('inner-nodeTypes-2');
}
$s->stop('inner-nodeTypes-2');
$s->lap('inner-autocreatedChildNodes');
}
$s->stop('inner-autocreatedChildNodes');
$s->lap('outer-nodeTypes');
}
$s->stop('outer-nodeTypes');
wLog($s, "\n".$this->stopwatchTree->getTreeString());
return $constraints;
}
最终的 wLog()
记录信号本身和调用树
20-04-21 10:56:28 26 DEBUG Webandco\DevTools\Domain\Model\Dto\Stopwatch 00:01:07.763
Events:
generateConstraints 00:00:00.128 : 126.00 MiB
outer-nodeTypes 00:00:23.012 : 244.02 MiB
inner-nodeTypes 00:00:14.205 : 244.02 MiB
allowsChildNodeType 00:00:07.032 : 244.02 MiB
inner-autocreatedChildNodes 00:00:08.525 : 244.02 MiB
inner-nodeTypes-2 00:00:08.442 : 242.02 MiB
allowsGrandchildNodeType 00:00:06.419 : 242.02 MiB
generateConstraints : 00:00:00.128
outer-nodeTypes : 00:00:23.012 (laps 546)
inner-nodeTypes : 00:00:14.205 (laps 297570)
allowsChildNodeType : 00:00:07.032 (laps 297025)
inner-autocreatedChildNodes : 00:00:08.525 (laps 688)
inner-nodeTypes-2 : 00:00:08.442 (laps 78078)
allowsGrandchildNodeType : 00:00:06.419 (laps 77935)
请注意,这种对 Stopwatch 和信号的过度使用会将运行时间加倍。方法 getConstraints()
约需 23 秒,分为 14 秒用于 inner-nodeTypes
和 8 秒用于 inner-autocreatedChildNodes
。
在没有 Stopwatch 的情况下,该方法的测试项目中的耗时约为 6 秒。
性能
通过一些测试,似乎过度使用Stopwatch会将不带信号的原始运行时间加倍。使用信号似乎将原始运行时间增加三倍,与不带计时器相比。
日志记录
此包还提供了一种快速而简单的日志记录到SystemLogger的方法。请注意,在生产环境中不应使用此方法!
目标是快速将日志记录到系统日志,并具有类似于浏览器中已知的console.log
的便利性。
在Package.php中,在全局命名空间中声明了一个名为wLog()
的函数。此函数使用了LogService。
在包加载后,您可以使用wLog()
函数,而无需通过简单地写入
wLog("Something to log", 4711, null, true);
在您的代码中的某个地方注入系统Logger,即可实现这一点
20-04-10 13:41:11 2865 DEBUG Something to log here 4711 NULL true
您也可以使用以下语法写入多行
wLog("First line")->wLog("Second line");
配置
请参阅Configuration/Settings.yaml。
- enabled:启用或禁用向SystemLogger记录日志。全局方法
wLog()
始终创建。 - pretty:如果提供复杂的对象作为参数,则使用
json_encode
记录对象。如果pretty
为true
,则对这些参数使用JSON_PRETTY_PRINT
选项。 - color:如果设置为
true
,则每个新的日志消息都将使用新颜色打印。因此,您将获得彩色系统日志,并且可能更容易发现某些问题或模式。颜色也可以使用以下任何名称固定:none, bold, dark, italic, underline, blink, concealed, black, red, green, yellow, blue, magenta, cyan, white, bg_black, bg_red, bg_green, bg_yellow, bg_blue, bg_magenta, bg_cyan, bg_white
。 - level:用于记录的日志级别。可以是以下任何一个:
emergency, alert, critical, error, warning, notice, info, debug
- renderer:对于自定义对象,您可以提供一个自定义渲染器,该渲染器实现了LogRendererInterface.php
- signal:如果启用,则使用DevTools日志功能(即
wLog()
)记录发出的信号及其对应的槽。
覆盖配置
您可以使用以下方法覆盖配置
wLog()->pretty(true)->color('red')->level('critical')->wLog("Something gone wrong, see this Exception", $e);
启用记录调用深度,由count(debug_backtrace(false))
给出
wLog("log message")->withCallDepth();
彩色日志消息
写入系统日志的消息由bash颜色格式化。可以记录加粗、斜体、闪烁、下划线或有背景颜色的消息。
wLog("very important")->color('red')->background('green')->italic()->bold()->blink()->underline();
条件日志记录
为了减少日志消息的数量,可以添加一个条件
wLog("problem node", $node)->condition($node->getName() == 'node-og6r6je5wpwnd');
计时
为了确定一个方法运行了多长时间以及它被调用了多少次
function someMethod(){
$wLog = wLog(__METHOD__, __LINE__, $interestingArgument)->withTiming(__METHOD__, __LINE__);
....
}
示例输出
22-03-22 15:14:50 28 DEBUG [⏰ 0.0244s = 1.477% of 1.653s] Webco\Test\Service\TestService_Original::someMethod 371
十六进制转储
一个修改版的https://stackoverflow.com/a/34279537 允许以类似十六进制转储的格式记录一个字符串
wLog()->hexDump($fusionValueWithCacheMarkers);
输出如下
默认情况下,二进制在控制台中是反转的。在此示例中,可以查看融合缓存标记。
十六进制转储有以下配置选项
- highlightBinary:如何突出显示二进制。默认是使用“颜色”
invert
- lineSeparator:要使用的行分隔符。默认为"\n"
- bytesPerLine:每行应显示多少字节。默认为48
- paddingCharacter:用于二进制的字符。默认为
.
调用者
默认情况下,将确定并写入日志的wLog()
的调用者
22-03-22 16:05:06 35 DEBUG Webco\Test\Service\TestService:testMethod:433 some log message
确定调用者大约需要0.2毫秒。这可以通过在Settings.yaml中的Webandco.DevTools.log.caller.enable
来禁用。
自定义日志渲染器
复杂数据对象使用json_encode
进行渲染。通常这不会暴露所需的信息,因此您可以创建一个自定义渲染器来将复杂数据对象转换为更简单的日志消息部分。例如,请参阅ThrowableLogRenderer.php和Settings.yaml。
Eel 辅助工具
添加了 Eel 辅助工具以通过融合使用 wLog
。辅助工具提供两种方法
wLog()
,它仅将参数转发到实际的function wLog()
rwLog()
,它使用第一个参数作为返回值。其他参数再次转发到实际的function wLog()
例如
@process.log = ${value + Webandco.DevTools.rwLog(Date.format('now', 'Y-m-d H:i:s'), this)}
将当前日期和时间添加到当前融合值,并在日志中写入融合对象。还提供了一个AbstractFusionObjectRenderer
来格式化给定的AbstractFusionObject
。
确定调用者
由于flow创建代理类,因此从堆栈跟踪中获取方法的调用者更复杂。
BacktraceService::getCaller()
方法返回给定方法和类的调用者文件、行、类和方法,并使用Neos\Flow\Error\Debugger
。
一行中混合日志格式
可以编写具有变化格式的日志行
wLog()->bold(LogService::FORMAT_ON)->wLog('This is bold')->bold(LogService::FORMAT_OFF)
->italic(LogService::FORMAT_ON)->wLog('This is italic text')->italic(LogService::FORMAT_OFF);
此行生成一个看起来像的单一日志行:这是粗体 这是斜体
强制日志消息
默认情况下,日志在LogService的__destruct()
期间写入。如果您注入LogService,可以调用eol()
来强制写入日志消息
wLog('Whats wrong with the node:', $nodeData)->eol();
LogService 注入
为了避免全局的wLog()
方法,您也可以注入LogService
/**
* @Flow\Inject
* @var LogService
*/
protected $logService;
注入的LogService然后可以用于通过eol()
方法写入日志消息
...
// first log line
$this->logService->wLog('Some log message')->eol();
...
// second log line
$this->logService->wLog('Another log message')->eol();
....
信号日志
对于调试某些请求或实现功能,了解哪些信号被发出以及谁捕获了它们可能很有用。
这可以通过在Settings.yaml中的Webco.DevTools.log.signal.enabled
启用。可以指定正则表达式以仅显示匹配的信号,例如/.*nodePropertyChanged$/i
。
在 CLI 的情况下将日志写入 stdout
在 Settings.yaml 中,您可以根据 php sapi 名称设置配置记录器
Webandco:
DevTools:
log:
sapiLogger:
cli: 'webcoDevStdoutLogger'
在上面的配置中,webcoDevStdoutLogger
用于在 CLI 命令的情况下将日志写入 stdout。
FusionRenderingAspect
通过配置
Webandco:
DevTools:
fusion:
enableRenderStats: false
启用了一个方面,该方面收集关于渲染融合对象的统计数据。结果通过wLog()
写入系统日志。这在确定应该缓存哪些融合原型时很有用。通过wLog()
打印的列表按最不相关原型到最相关原型的顺序排列,如果缓存将受益最大。