65.9K
CodeProject 正在变化。 阅读更多。
Home

IIS Web 日志命中计数器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2016 年 9 月 9 日

CPOL

3分钟阅读

viewsIcon

23966

downloadIcon

730

使用 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 日:初始版本
IIS Web 日志访问计数器 - CodeProject - 代码之家
© . All rights reserved.