作者:Vishal Sood
IIS 高级日志记录可以扩展 Web 平台以支持实时分析,帮助向客户提供实时报告或与合作伙伴合作提供实时报告。 高级日志记录功能包含的一个选项可实时消耗日志条目。 此选项能聚合在每个请求期间发送给它的所有事件,日志定义属性 publishLogEvent 控制针对其他应用程序使用的情况引发实时事件。
要求
本文面向开发人员,并假定读者具备基本的本机代码编写技能。
不需要了解 IIS 管道;但最好是查看参考部分中列出的文章,详细了解所使用的方法和数据结构。
IIS 高级日志记录是 Internet Information Services (IIS) 7 的扩展,后者已不再可用。 我们建议使用 IIS 8.5 的增强型日志记录。
启用实时日志记录
实时日志记录是 IIS 高级日志记录中的每日志定义设置。 要为日志定义启用实时日志记录,请执行以下操作:
在 IIS 管理器中,打开高级日志记录功能。 单击“连接”窗格中的服务器,然后双击“主页”上的“高级日志记录”图标。
启用高级日志记录功能。 在“操作”窗格中,单击“启用高级日志记录”。
选择要为其启用实时日志记录的日志定义。
在 IIS 管理器中,在服务器、网站、目录或应用程序级别打开高级日志记录功能。
在“高级日志记录”功能页中,单击日志定义,然后在“操作”窗格中单击“编辑日志定义”。
选中“发布实时事件”复选框,为所选日志定义启用事件的实时日志记录。
编写实时使用事件的 IIS 模块,如下节所述。
编写 IIS 模块以使用实时事件
若要记录高级日志记录功能引发的实时事件,必须创建 IIS 模块。 本部分将回顾 IIS 跟踪基础结构,并在压缩的(压缩)文件夹中提供示例代码,以便创建一个可以用作引用的简单模块。
注意
示例代码仅供参考,尚未针对内存泄漏和其他问题进行测试。
IIS 跟踪基础结构
本部分介绍用于记录实时事件的一些 IIS 跟踪概念。
IGlobalTraceEventProvider::GetTraceEvent 方法
要使用高级日志记录引发的实时事件,必须为全局事件注册创建的 IIS 模块。 对于系统引发的每个事件,应调用 OnGlobalTraceEvent 方法。 注册此方法后便可以访问实时日志记录事件。 有关详细信息,请参阅 CGlobalModule::OnGlobalTraceEvent 方法。
数据结构
HTTP_TRACE_EVENT 结构
HTTP_TRACE_EVENT 结构构成了实时日志记录基础结构的主干。 实时日志记录信息以这种结构的形式传递。
struct HTTP_TRACE_EVENT{
LPCGUID pProviderGuid;
DWORD dwArea;
LPCGUID pAreaGuid;
DWORD dwEvent;
LPCWSTR pszEventName;
DWORD dwEventVersion;
DWORD dwVerbosity;
LPCGUID pActivityGuid;
LPCGUID pRelatedActivityGuid;
DWORD dwTimeStamp;
DWORD dwFlags;
DWORD cEventItems;
__field_ecount(cEventItems) HTTP_TRACE_EVENT_ITEM * pEventItems;
};
有关此结构的详细信息,请参阅 HTTP_TRACE_EVENT 结构。
HTTP_TRACE_EVENT_ITEM 结构
HTTP_TRACE_EVENT 结构包含一个或多个 HTTP_TRACE_EVENT_ITEM 结构,具体取决于生成日志的日志定义中包含的日志记录字段数。
struct HTTP_TRACE_EVENT_ITEM{
LPCWSTR pszName;
HTTP_TRACE_TYPE dwDataType;
PBYTE pbData;
DWORD cbData;
LPCWSTR pszDataDescription;
};
有关此结构的详细信息,请参阅 HTTP_TRACE_EVENT_ITEM 结构。
pProviderGuid
HTTP_TRACE_EVENT 结构包含 pProviderGuid 属性,这是一个包含提供程序标识符的 LPCGUID。 务必了解它的重要性。
如 CAnalyticsGlobalModule::OnGlobalTraceEvent 中所述,会针对系统引发的每个事件调用 OnGlobalTraceEvent。 这意味着必须从传入事件中筛选不需要的事件,以便只有感兴趣的事件(实时日志记录事件)可供使用。 可以使用 pProviderGuid 属性值 3C729B22-F9A9-4096-92A4-07E0DDF403EB 执行此操作。
//
// {3C729B22-F9A9-4096-92A4-07E0DDF403EB}
//
static const GUID _LOGGING_PUBLISHING_GUID =
{ 0x3c729b22, 0xf9a9, 0x4096, { 0x92, 0xa4, 0x7, 0xe0, 0xdd, 0xf4, 0x3, 0xeb } };
………………
………………
if ((pTraceEvent->pProviderGuid != &_LOGGING_PUBLISHING_GUID) &&
(!IsEqualGUID(*(pTraceEvent->pProviderGuid), _LOGGING_PUBLISHING_GUID)))
{
goto Finished;
}
示例代码使用此值来筛选掉不需要的事件。
代码示例
本部分显示演示本文前面介绍的实时日志记录概念的示例代码。 下载 CAnalyticsGlobalModule.zip,这是压缩(zip 格式)文件夹中示例代码的副本。
注意
示例代码仅供参考,尚未针对内存泄漏和其他问题进行测试。
CAnalyticsGlobalModule::OnGlobalTraceEvent
//
// {3C729B22-F9A9-4096-92A4-07E0DDF403EB}
//
static const GUID _LOGGING_PUBLISHING_GUID =
{ 0x3c729b22, 0xf9a9, 0x4096, { 0x92, 0xa4, 0x7, 0xe0, 0xdd, 0xf4, 0x3, 0xeb } };
//
// This call is happening on the same thread (synchronous/blocking call) as
// the call to RaiseTraceEvent, so bail a.s.a.p. if this isn't something
// we want to handle, and minimize the work we do here
//
//
GLOBAL_NOTIFICATION_STATUS
CAnalyticsGlobalModule::OnGlobalTraceEvent(
__in IGlobalTraceEventProvider * pProvider)
{
HRESULT hr = S_OK;
IHttpContext * pHttpContext = NULL;
HTTP_TRACE_EVENT * pTraceEvent = NULL;
DBG_ASSERT(pProvider != NULL
//
// We only want to handle trace events that are raised for
// logging purposes, so bail a.s.a.p. if this event isn't
// for us
//
hr = pProvider->GetTraceEvent(&pTraceEvent
if (FAILED(hr))
{
TRACEHR(hr
goto Finished;
}
if (pTraceEvent->pProviderGuid == NULL)
{
TRACEMSG(SS_DEFAULT,
TRACE_LEVEL_INFORMATION,
L"Not handling trace event - NULL value for provider GUID"
goto Finished;
}
if ((pTraceEvent->pProviderGuid != &_LOGGING_PUBLISHING_GUID) &&
(!IsEqualGUID(*(pTraceEvent->pProviderGuid), _LOGGING_PUBLISHING_GUID)))
{
goto Finished;
}
//
// We now need the HTTP context which is used to get the site info later
//
hr = pProvider->GetCurrentHttpRequestContext(&pHttpContext
if (FAILED(hr))
{
TRACEHR(hr
goto Finished;
}
ProcessLogEvent(pTraceEvent, pHttpContext
Finished:
return GL_NOTIFICATION_CONTINUE;
}
ProcessLogEvent
ProcessLogEvent 方法会将日志记录数据复制到本地数据结构中,这样以后便可以使用此方法将数据推送到 Web 服务或数据库。
注意
不应处理请求本身中的数据,因为这可能会使请求对客户端的响应速度变慢。
注意
请注意 ProcessLogEvent 的代码,事件使用的内存可能是由 AllocateRequestMemory 分配的临时内存。 要取消阻止线程,应复制数据。
void ProcessLogEvent(
__in HTTP_TRACE_EVENT * pHttpTraceEvent,
__in IHttpContext * pHttpContext)
{
HRESULT hr = S_OK;
DWORD cchName = 0;
HTTP_TRACE_EVENT * pNewHttpTraceEvent = NULL;
LPCSTR pszHostName = NULL;
pNewHttpTraceEvent = new HTTP_TRACE_EVENT;
if (pNewHttpTraceEvent == NULL)
{
goto Finished;
}
pNewHttpTraceEvent->pEventItems = new HTTP_TRACE_EVENT_ITEM[pHttpTraceEvent->cEventItems];
if (pNewHttpTraceEvent->pEventItems == NULL)
{
goto Finished;
}
ZeroMemory(pNewHttpTraceEvent->pEventItems, sizeof(HTTP_TRACE_EVENT_ITEM) * pHttpTraceEvent->cEventItems);
for (DWORD ix = 0; ix < pHttpTraceEvent->cEventItems; ix++)
{
if (pHttpTraceEvent->pEventItems[ix].pszName == NULL)
{
pNewHttpTraceEvent->pEventItems[ix].pszName = NULL;
pNewHttpTraceEvent->pEventItems[ix].cbData = 0;
pNewHttpTraceEvent->pEventItems[ix].pbData = NULL;
continue;
}
//
// Copy the name of this event item
//
cchName = wcslen(pHttpTraceEvent->pEventItems[ix].pszName);
pNewHttpTraceEvent->pEventItems[ix].pszName = new WCHAR[cchName + 1];
if (pNewHttpTraceEvent->pEventItems[ix].pszName == NULL)
{
goto Finished;
}
memcpy((VOID *)pNewHttpTraceEvent->pEventItems[ix].pszName, pHttpTraceEvent->pEventItems[ix].pszName, (cchName+1) * sizeof(WCHAR));
//
// If there's no data to copy, mark it empty
//
if ((pHttpTraceEvent->pEventItems[ix].cbData == 0) ||
(pHttpTraceEvent->pEventItems[ix].pbData == NULL))
{
pNewHttpTraceEvent->pEventItems[ix].cbData = 0;
pNewHttpTraceEvent->pEventItems[ix].pbData = NULL;
continue;
}
pNewHttpTraceEvent->pEventItems[ix].pbData = new BYTE[pHttpTraceEvent->pEventItems[ix].cbData];
if (pNewHttpTraceEvent->pEventItems[ix].pbData == NULL)
{
goto Finished;
}
memcpy(pNewHttpTraceEvent->pEventItems[ix].pbData, pHttpTraceEvent->pEventItems[ix].pbData, pHttpTraceEvent->pEventItems[ix].cbData);
pNewHttpTraceEvent->pEventItems[ix].cbData = pHttpTraceEvent->pEventItems[ix].cbData;
pNewHttpTraceEvent->pEventItems[ix].dwDataType = pHttpTraceEvent->pEventItems[ix].dwDataType;
}
//
// At this point, you've copied the event into your memory and can now process your copy, queue it, etc.
//
// WriteEventViewerLog(pHttpTraceEvent->pszEventName); // Can write to eventViewer log to verify that event is processed...
Finished:
return;
}
总结
在本演练中,我们回顾了 IIS 高级日志记录功能中的实时日志记录的工作原理,以及如何通过创建简单的 IIS 模块来实时使用日志记录数据。
参考
创建本机代码 HTTP 模块
IGlobalTraceEventProvider::GetTraceEvent 方法
HTTP_TRACE_EVENT 结构
HTTP_TRACE_EVENT_ITEM 结构
CGlobalModule::OnGlobalTraceEvent 方法
开发适用于 IIS 的本机 C\\C++ 模块
CAnalyticsGlobalModule.zip(示例代码的副本)
钱包快贷
《炉石传说》野性成长解析:德鲁伊技能卡牌效果与策略运用