TripPin 第 8 部分 - 添加诊断
注意
此内容当前引用了 Visual Studio 中用于诊断的旧实现中的内容。 内容将在不久的将来更新,以涵盖 Visual Studio Code 中新的 Power Query SDK。
本教程分为多个部分,介绍如何针对 Power Query 创建新数据源扩展。 本教程按顺序进行,每一课都建立在前几课创建的连接器的基础上,逐步为连接器添加新功能。
在本课中,你将:
- 了解 Diagnostics.Trace 函数
- 使用诊断帮助程序函数添加跟踪信息,帮助调试连接器
启用诊断
Power Query 用户可以通过选中“选项 | 诊断”下的复选框来启用跟踪日志记录。
启用后,任何后续查询都会导致 M 引擎向位于固定用户目录下的日志文件发送跟踪信息。
在 Power Query SDK 中运行 M 查询时,会在项目级别启用跟踪功能。 在项目属性页上,有三个与跟踪相关的设置:
- 清除日志 - 设置为
true
,运行查询时将重置/清除日志。 我们建议将其设置为true
。 - 显示引擎跟踪 - 此设置可控制 M 引擎内置跟踪的输出。 这些跟踪只对 Power Query 团队成员有用,因此通常希望将其设置为
false
。 - 显示用户跟踪 - 此设置控制连接器输出的跟踪信息。 需要将其设置为
true
.
启用后,你将开始在“日志”选项卡下的“M 查询输出”窗口中看到日志条目。
Diagnostics.Trace
Diagnostics.Trace 函数用于将消息写入 M 引擎的跟踪日志中。
Diagnostics.Trace = (traceLevel as number, message as text, value as any, optional delayed as nullable logical as any) => ...
重要
M 是一种纯函数语言,用于惰性计算。 使用 Diagnostics.Trace
时,请记住,只有在实际计算函数所属的表达式时,才会调用该函数。 本教程稍后将举例说明。
traceLevel
参数可设置为下列值之一(按降序排列)。
TraceLevel.Critical
TraceLevel.Error
TraceLevel.Warning
TraceLevel.Information
TraceLevel.Verbose
启用跟踪后,用户可以选择希望查看的最大消息级别。 此级别及其下的所有跟踪消息都将输出到日志中。 例如,如果用户选择了“警告”级别,跟踪消息 TraceLevel.Warning
、TraceLevel.Error
和 TraceLevel.Critical
将显示在日志中。
参数 message
是要输出到跟踪文件的实际文本。 除非在文本中明确包含 value
参数,否则文本将不包含此参数。
参数 value
是函数将返回的内容。 当 delayed
参数设置为 true
时,value
将是零参数函数,返回要计算的实际值。 当 delayed
设置为 false
, value
为实际值。 下面举例说明其工作原理。
使用诊断。 TripPin 连接器中的跟踪
若要了解使用 Diagnostics.Trace 的实际示例以及该 delayed
参数的影响,请更新 TripPin 连接器的 GetSchemaForEntity
函数以封装 error
异常:
GetSchemaForEntity = (entity as text) as type =>
try
SchemaTable{[Entity=entity]}[Type]
otherwise
let
message = Text.Format("Couldn't find entity: '#{0}'", {entity})
in
Diagnostics.Trace(TraceLevel.Error, message, () => error message, true);
通过向 GetEntity
函数传递无效的实体名称,可以在评估过程中强制出错(用于测试目的!)。 此时,可更改 TripPinNavTable
函数中的 withData
行,将 [Name]
替换为 "DoesNotExist"
。
TripPinNavTable = (url as text) as table =>
let
// Use our schema table as the source of top level items in the navigation tree
entities = Table.SelectColumns(SchemaTable, {"Entity"}),
rename = Table.RenameColumns(entities, {{"Entity", "Name"}}),
// Add Data as a calculated column
withData = Table.AddColumn(rename, "Data", each GetEntity(url, "DoesNotExist"), type table),
// Add ItemKind and ItemName as fixed text values
withItemKind = Table.AddColumn(withData, "ItemKind", each "Table", type text),
withItemName = Table.AddColumn(withItemKind, "ItemName", each "Table", type text),
// Indicate that the node should not be expandable
withIsLeaf = Table.AddColumn(withItemName, "IsLeaf", each true, type logical),
// Generate the nav table
navTable = Table.ToNavigationTable(withIsLeaf, {"Name"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf")
in
navTable;
为项目启用跟踪功能,然后运行测试查询。 在 Errors
选项卡上,应能看到引发的错误文本:
此外,在 Log
选项卡上,应会看到相同的消息。 如果对 message
和 value
参数使用不同的值,则这些信息会有所不同。
另请注意,日志消息的 Action
字段包含扩展的名称(数据源类型),在本例中为 Engine/Extension/TripPin
。 这样,当涉及多个查询和/或启用系统(糅合引擎)跟踪时,可以更轻松地查找与扩展相关的消息。
延迟计算
作为 delayed
参数工作原理的示例,你将进行一些修改并再次运行查询。
首先,将 delayed
值设置为 false
,但将 value
参数保持不变:
Diagnostics.Trace(TraceLevel.Error, message, () => error message, false);
运行查询时,将收到错误“我们无法将‘函数’类型的值转换为‘类型’类型”,而不是你提出的实际错误。 这是因为现在调用返回一个 function
值,而不是值本身。
接下来,从 value
参数中删除函数:
Diagnostics.Trace(TraceLevel.Error, message, error message, false);
运行查询时,会收到正确的错误消息,但如果查看“日志”选项卡,则不会显示任何消息。 这是因为在调用 Diagnostics.Trace
的过程中,error
最终会被提出/计算,因此消息实际上并没有输出。
现在您已经了解
delayed
参数的影响,请确保在继续之前将连接器重置回工作状态。
Diagnostics.pqm 中的诊断帮助程序函数
此项目中包括的 Diagnostics.pqm 文件包含许多帮助程序函数,可使跟踪变得更容易。 如上一教程所示,可以在项目中包括此文件(记得将“生成操作”设置为“编译”),然后将其加载到连接器文件中。 现在,连接器文件的底部应类似于下面的代码片段。 你可以自由探索该模块提供的各种功能,但在本示例中,将仅使用 Diagnostics.LogValue
和 Diagnostics.LogFailure
函数。
// Diagnostics module contains multiple functions. We can take the ones we need.
Diagnostics = Extension.LoadFunction("Diagnostics.pqm");
Diagnostics.LogValue = Diagnostics[LogValue];
Diagnostics.LogFailure = Diagnostics[LogFailure];
Diagnostics.LogValue
函数 Diagnostics.LogValue
与 Diagnostics.Trace
非常类似,可用于输出要计算的值。
Diagnostics.LogValue = (prefix as text, value as any) as any => ...
参数 prefix
会被添加到日志消息中。 你可以用它来确定哪个调用输出了信息。 参数 value
是函数的返回值,也会以 M 值的文本形式写入跟踪中。 例如,如果 value
与具有列 A 和 B 的 table
相等,则日志将包含等效 #table
表示形式:#table({"A", "B"}, {{"row1 A", "row1 B"}, {"row2 A", row2 B"}})
注意
将 M 值序列化为文本可能是一项高消耗操作。 请注意输出到跟踪中值的潜在大小。
注意
大多数 Power Query 环境会将跟踪消息截断为最大长度。
例如,你将更新 TripPin.Feed
函数以跟踪传递到函数中的 url
和 schema
参数。
TripPin.Feed = (url as text, optional schema as type) as table =>
let
_url = Diagnostics.LogValue("Accessing url", url),
_schema = Diagnostics.LogValue("Schema type", schema),
//result = GetAllPagesByNextLink(url, schema)
result = GetAllPagesByNextLink(_url, _schema)
in
result;
必须在调用 GetAllPagesByNextLink
时使用新的 _url
和 _schema
值。 如果使用原始函数参数,则永远不会实际计算 Diagnostics.LogValue
调用,从而导致没有信息写入到跟踪。 函数式编程很有趣!
运行查询时,现在应该会在日志中看到新消息。
访问 URL:
架构类型:
你会看到 schema
参数 type
的序列化版本,而不是在对类型值执行简单 Text.FromValue
时得到的结果(结果为“类型”)。
Diagnostics.LogFailure
该 Diagnostics.LogFailure
函数可用于封装函数调用,并且仅在函数调用失败(即返回 error
)时才会写入跟踪。
Diagnostics.LogFailure = (text as text, function as function) as any => ...
在内部,Diagnostics.LogFailure
向 function
调用中添加 try
运算符。 如果调用失败,则 text
值将写入跟踪,然后再返回原始 error
值。 如果 function
调用成功,则返回结果而不向跟踪写入任何内容。 由于 M 错误不包含完整堆栈跟踪(也就是说,通常只能看到错误消息),因此在需要确定错误发生在何处时,这一点非常有用。
作为(差)示例,修改 TripPinNavTable
函数的 withData
行,可再次强制出错:
withData = Table.AddColumn(rename, "Data", each Diagnostics.LogFailure("Error in GetEntity", () => GetEntity(url, "DoesNotExist")), type table),
在跟踪中,您可以找到产生的错误信息,其中包含你的 text
,以及原始错误信息。
在继续下一教程之前,请务必将函数重置为工作状态。
结束语
本节简短(但非常重要!)的课程介绍了如何使用诊断帮助程序函数记录到 Power Query 跟踪文件。 如果使用得当,则这些函数对调试连接器中的问题很有用。
注意
作为连接器开发人员,有责任确保不会将敏感信息或个人身份信息 (PII) 记录到诊断日志中。 还必须注意不要输出过多的跟踪信息,因为这可能会对性能产生负面影响。