IIS Web 日志命中计数器





5.00/5 (4投票s)
使用 IIS 日志统计通过 IIS 运行的网站的访问量
引言
IIS 在其日志文件中保留了详细的网站访问历史记录。有时,可能需要统计通过 IIS 运行的网站的访问量。
背景
一个实际的应用场景是,当需要停用一台服务器时,但我们需要查看哪些网站仍在被用户使用,以便在新的服务器上托管它们并继续业务。
IIS 在日志文件中保留了网站访问历史记录。它们通常位于 "\inetpub\logs\LogFiles"。一个典型的文件夹快照是
在这里,每个文件夹对应于 IIS 中的特定节点。例如,上面的快照对应于以下 IIS 结构。
要确定哪个日志文件夹属于哪个网站,您可以在 IIS 网站属性中找到网站 ID,如下所示。此处“AdvancedSearch
”网站的网站 ID 为 3
,因此文件夹“W3SVC3”包含此网站(以及任何子网站)的日志文件。
一个典型的日志文件结构如下所示
请注意,页面访问序列为 default.aspx->Display.aspx>MoreInfo.aspx。
然后稍后启动了一个新会话,并且访问序列为 default.aspx,然后发生了重新加载(或回发)(从底部数第二行)。但在中间,还有其他行,这些行实际上不是访问,而是加载相关的样式表和脚本。所以我们必须将它们排除在计数之外。
Using the Code
代码很简单。主要是一些 string
操作,以及一些特定的检查。
程序界面如下所示。它有一个用于定位 log 文件夹的文件夹浏览对话框。作为一项附加功能,有一个日期检查;如果提供了日期,则会考虑该日期(<=
)之前的所有访问。
它将计数保存在一个 dictionary
对象中
Dictionary<string, int> SiteAndCount = new Dictionary<string, int>();
需要计数的网站(和子网站)在配置文件中添加。 config 文件也有其他键,可以通过名称理解。所以基本上,在统计完网站的访问量 (Books
, PearlSBuck
, SidneySheldon
, SatyajitRoy
, HumayunAhmed
) 之后,输出将提供在名为 "Hit_Stats.txt" 的文本文件中,该文件将位于与可执行文件相同的目录中。从日志文件的元数据中可以看出,网站访问量将位于标题 "cs-uri-stem
" 下(请参阅上面的日志文件结构快照)。
<add key ="APP_TITLE" value="Web Log Counter"/>
<add key ="STAT_FILE_NAME" value="Hit_Stats.txt"/>
<add key ="SITE_NAMES" value="Books, PearlSBuck, SidneySheldon, SatyajitRoy"/>
<add key ="URL_HEADER" value="cs-uri-stem"/>
每天都会创建一个单独的日志文件,所以我们需要浏览并解析所有网站的日志文件。这在以下代码中完成
foreach (string LogFile in Files)
{
StreamReader SReader = new StreamReader(LogFile);
StatusLabel.Text = "Parsing file: " + LogFile;
FileCountLabel.Text = "Processing file: " + count++.ToString() +
" of " + Files.Count().ToString();
Application.DoEvents(); // Refresh the labels.
ParseFile(SReader, ConfigurationManager.AppSettings["URL_HEADER"]);
SReader.Close();
}
所有行都在一个 WHILE
循环中读取和处理。然后丢弃这些行,直到到达最后一个元数据(包含 "#fields
")(请参阅上面的文件结构快照)。
while (Line != null)
{
do
Line = SReader.ReadLine();
while (Line != null && Line.Substring(0, 7) != "#Fields"); // Read through
// the #-ed lines, these are meta info.
此行用于确定访问计数器的索引。首先它分割该行,然后检查是否已确定索引。如果没有,则循环直到找到索引,然后设置索引并退出以继续下一步。
Strings = Line.Split(' ');
if (UrlIndex == -1) // UrlIndex = -1 means, the index was not obtained.
// Generally after the first finding it will be something else.
for (int i = 0; i < Strings.GetUpperBound(0); i++)
{
if (Strings[i].Equals(UrlHeader))
{
UrlIndex = i - 1; // This line might be like '#Fields:
// date time s-ip cs-method cs-uri-stem cs-uri-query s-port'
break; // Subsequent lines will not have the '#Fields' attribute.
// Hence reduce the index by 1.
}
}
下一段代码开始处理行,直到到达下一个元数据(请参阅上面的文件结构快照)。首先,它检查是否要进行日期检查。如果是,则检查该日期(<=
)之前的网站访问量。否则,它开始检查访问量,而不考虑日期。
Line = SReader.ReadLine(); // Read the line next to the #Fields line,
// these subsequent lines actually contain the site hits.
while (Line != null && !Line.Substring(0, 1).Equals("#")) // Parse all the lines until
// the next meta-data starts (#), or end of file is reached (NULL).
{
bool SiteHitFound = false;
Strings = Line.Split(' ');
if (!CheckFindAllHits.Checked) // If date check was intended.
{
var Regex = new Regex(@"\d{4}-\d{2}-\d{2}", RegexOptions.Compiled);
IsSuccess = Regex.Match(Strings[0]).Success; // Check for a valid date format.
if (IsSuccess)
HitDate = Convert.ToDateTime(Strings[0]);
if (HitDate <= Convert.ToDateTime(HitsBeforeThisDate.Text)) // If the log date
// is over the check date, then no need to proceed with this line,
CheckSiteHit(ref CurrentSite, Strings, UrlIndex, ref LastSite, ref SiteHitFound);
}
else
CheckSiteHit(ref CurrentSite, Strings, UrlIndex, ref LastSite, ref SiteHitFound);
Line = SReader.ReadLine(); // Proceed with the next line in the log file.
}
“CheckSiteHit
”方法实际上执行计数。它浏览列出的网站进行检查,并查看是否有访问发生。它还跟踪遇到的最后一个网站。这是为了忽略相同的连续网站,因为实际上那是一个单一的访问,而随后的访问是加载 CSS、JS、图像等,或者是回发 (POST
)。这样做的原因已经在上面解释过了(请参阅上面的文件结构快照)。然后,如果它找到一个访问,则将其输入到字典中(如果它已经存在,则增加计数器)。
private void CheckSiteHit(ref string CurrentSite,
string[] Strings, int UrlIndex, ref string LastSite, ref bool SiteHitFound)
{
foreach (string Site in Sites)
{ // Check if any of the sites to be counted matches the URI string.
if (Strings[UrlIndex].IndexOf(Site, StringComparison.CurrentCultureIgnoreCase) > -1)
{
if (!LastSite.Equals(Site)) // There might be consecutive site listings
// whereas the later ones usually contain
// CSS, JS, image etc.
{
SiteHitFound = true;
CurrentSite = LastSite = Site; // Hence, only if it is a new site,
// then add it to counter.
break; // Proceed with the next line.
}
}
}
if (SiteHitFound)
{
int value;
if (!SiteAndCount.TryGetValue(CurrentSite.ToUpper(), out value))
SiteAndCount.Add(CurrentSite.ToUpper(), 1); // If the site is not found
// in the dictionary then
// add it and start the counter.
else
SiteAndCount[CurrentSite.ToUpper()] = value + 1; // Else increase the count of
// the site.
}
}
最后,它将 dictionary
输出到状态文件。
StreamWriter SWriter = new StreamWriter(ConfigurationManager.AppSettings["STAT_FILE_NAME"]);
foreach (KeyValuePair<string, int> entry in SiteAndCount)
SWriter.WriteLine(entry.Key.ToString() + ":\t" + entry.Value.ToString());
StatusLabel.Text = "Finished parsing.";
FileCountLabel.Text = "Processed file: " + (--count).ToString() +
" of " + Files.Count().ToString();
SWriter.Close();
MessageBox.Show("Finished parsing. Please see the stats in file: " +
ConfigurationManager.AppSettings["STAT_FILE_NAME"]);
就这样! :)
历史
- 2016 年 9 月 9 日:初始版本