(ASP.NET) ASP.NET (2007 год)

Наблюдение за Web-приложениями

Нет необходимости говорить о важности наблюдения за событиями Web-приложений. Они предоставлены сами себе годами и открыты безбрежному интернету. Что случилось с ним, почему вдруг все стало тормозить? Инструменты для ответа на эти вопросы надо готовить еще на этапе девелопмента.

Я оснащал практически все свои приложения инструментами для мониторинга. Например электронный магазин, систему моделирования событий в огранизационных структурах, сайт знакомств, сайт учебного заведения, интернет-банк, музыкальный сайт и многие другие свои Web-приложения. И на этой страничке я попробую систематизировать некоторые свои мозгозаключения по этому вопросу.


Прежде всего, существует ПЯТЬ технологий наблюдения за Web-приложениями. Вы их видите на рисунке:



Теперь рассмотрим подробнее каждую из указанных технологий (хотя в принципе существуют и другие технологии, но в силу их нестабильности на практике и сложной интерпретации полученных результатов - я не буду их тут рассматривать). Заметьте, что у нас не идет тут речь о поведении ОТДЕЛЬНОЙ странички, которое можно наблюдать по NET->All или по Trace - мы наблюдаем за работой всего нашего Web-приложения в целом.


Итак, начнем с записи собственных событий в My.Log

Все Web-приложения имееют сложную логику перехода внутренних состояний. Например тут показан небольшой фрагмент логики состояний сайта знакомств, а тут рассказано о значительно большей машине состояний.

Трассировка, которую мы пишем в My.Log обрабатывается по умолчанию классом Microsoft.VisualBasic.Logging.AspLog и именно его вывод мы наблюдаем в окне OUTPUT в студии.

MS поставляет несколько стандартных классов, которые слушают поток данных, который формирует приложение при записи в My.Log. Эти провайдеры перечислены ниже и назначение их должно быть понятно из сделанных мною комментариев. Кроме, конечно, первого провайдера. Это нестандартный провайдер и мы его сделаем сейчас сами. Вы можете его сгрузить также готовый ОТСЮДА.

00001:     <system.diagnostics>
00002:         <sources>
00003:             <source name="DefaultSource" switchName="DefaultSwitch">
00004:                 <listeners>
00005:                     <add name="SQLLog" />       
00006:                     <add name="EventLog" />
00007:                     <add name="Delimited" />
00008:                     <add name="XmlWriter" />
00009:                     <add name="FileLog"/>
00010:                     <add name="Console" />
00011:                 </listeners>
00012:             </source>
00013:         </sources>
00014:         <switches>
00015:             <add name="DefaultSwitch" value="Information" />
00016:         </switches>
00017:         <sharedListeners>
00018:             <!-- собственный SQL-журнал <type name>, <assembly name>, <version number>, <culture>, <strong name>-->          
00019:             <add name="SQLLog" type="MyLogToSQLFromWeb.MyLogListener, MyLogToSQLFromWeb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e989b5738bc16ea9" />
00020:             <!--в окно OUTPUT-->
00021:             <add name="AspLog"  type="Microsoft.VisualBasic.Logging.AspLog, Microsoft.VisualBasic, Version=2.0.0.1, Culture=neutral, PublicKeyToken=6315780d5082271f"  />
00022:             <!--В системный журнал-->
00023:             <add name="EventLog" type="System.Diagnostics.EventLogTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"     initializeData="Votpusk_EventLogWriter"/>
00024:             <!--в указанный файл в текстовом формате -->
00025:             <add name="Delimited" type="System.Diagnostics.DelimitedListTraceListener,  System, Version=2.0.0.0,  Culture=neutral, PublicKeyToken=b77a5c561934e089" initializeData="c:\temp\Votpusk_DelimitedWriter.txt" delimiter=";" traceOutputOptions="DateTime" />
00026:             <!--в указанный файл в формате XML-->
00027:             <add name="XmlWriter" type="System.Diagnostics.XmlWriterTraceListener, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" initializeData="c:\temp\Votpusk_XmlWriter.xml" />
00028:             <!--в  C:\Documents and Settings\Administrator\Application Data\Microsoft Corporation\Microsoft (R) Visual Studio (R) 2005\8.0.50727.762 (кстати в этом же каталоге находится лог девелопментского сервера) -->
00029:             <add name="FileLog"  type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"  initializeData="FileLogWriter" BaseFileName="1.txt" />
00030:             <!-- ? -->
00031:             <add name="Console" type="System.Diagnostics.ConsoleTraceListener, System, Version=2.0.0.0,  Culture=neutral, PublicKeyToken=b77a5c561934e089" initializeData="true" />
00032:         </sharedListeners>
00033:     </system.diagnostics>

Можно подумать - зачем он нужен? Ведь уже есть целая куча провайдеров. В том-то и дело, что нужен. Неужели мы снизойдем в боевом приложении до того, что будем писать в NTFS? А события сайта, лежащего на SHARED-хостинге нас не интересуют? А как выводить все это на форму из длинного-предлинного текстового файла? Получается, MS не предусмотрела ГЛАВНОГО провайдера - для записи сообщений о переходе состояний в приложении в SQL. Вот такие-то дела... Но ничего - это мы сейчас легко исправим.


Собственный провайдер записи в SQL не получается из соображений безопасности делать прямо в самом проекте - нужна сборка со строгим именем. Поэтому мы делаем отдельный проект класса, подпишем в нем сборку и поставим в нашем сайте ссылку на эту сборку со строгим именем. А сам по себе смысловой класс этой сборки получается безобразно простой, зато исключительно полезный:

00001: Imports Microsoft.VisualBasic
00002: 
00003: Public Class MyLogListener
00004:     Inherits System.Diagnostics.TraceListener
00005: 
00006:     Dim CN As New System.Data.SqlClient.SqlConnection
00007:     Dim CMD As New System.Data.SqlClient.SqlCommand
00008:     ''' <summary>
00009:     ''' В этот метод приходит префикс каждого сообщения, типа - DefaultSource Information: 0 : 
00010:     ''' </summary>
00011:     <System.Security.Permissions.HostProtection(Synchronization:=True)> _
00012:    Public Overloads Overrides Sub Write(ByVal message As String)
00013:         'игнорируем его
00014:     End Sub
00015: 
00016:     ''' <summary>
00017:     ''' после этого сюда приходит уже само смысловое сообщение
00018:     ''' </summary>
00019:     <System.Security.Permissions.HostProtection(Synchronization:=True)> _
00020:    Public Overloads Overrides Sub WriteLine(ByVal message As String)
00021:         CMD.Parameters("txt").Value = message
00022:         CMD.ExecuteNonQuery()
00023:     End Sub
00024: 
00025:     Public Sub New()
00026:         MyBase.New()
00027:         CN.ConnectionString = My.Settings.SQLLogConnectionString
00028:         CN.Open()
00029:         CMD.CommandType = CommandType.Text
00030:         CMD.CommandText = My.Settings.CMD
00031:         CMD.Connection = CN
00032:         CMD.Parameters.Add("txt", SqlDbType.NVarChar)
00033:     End Sub
00034: End Class

Как вы видите, у этой сборки существует также Application.setting, который на форме выглядит вот так, а в сыром виде вот так:

00001: <?xml version="1.0" encoding="utf-8" ?>
00002: <configuration>
00003:     <configSections>
00004:         <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
00005:             <section name="MyLogToSQLFromWeb.My.MySettings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
00006:         </sectionGroup>
00007:     </configSections>
00008:     <connectionStrings>
00009:         <add name="MyLogToSQLFromWeb.My.MySettings.SQLLogConnectionString"
00010:             connectionString="server=localhost;Initial Catalog=vOtpusk;User ID=VotpuskLogin;Password=Otpusk" />
00011:     </connectionStrings>
00012:     <applicationSettings>
00013:         <MyLogToSQLFromWeb.My.MySettings>
00014:             <setting name="CMD" serializeAs="String">
00015:                 <value>INSERT [MyLog]([datetime],[txt]) VALUES (GetDate(), @txt)</value>
00016:             </setting>
00017:         </MyLogToSQLFromWeb.My.MySettings>
00018:     </applicationSettings>
00019: </configuration>

Для этой сборки требуется вот такая табла:

00001: CREATE TABLE [dbo].[MyLog](
00002:     [i] [int] IDENTITY(1,1) NOT NULL,
00003:     [datetime] [datetime] NOT NULL,
00004:     [txt] [nvarchar](max) NULL
00005: ) ON [PRIMARY]

То, что получится в итоге работы этой сборки в SQL, вы видите на этом скрине, ну а готовый класс трассировки вы можете сгрузить ОТСЮДА, только не забудьте настроить в ней коннект к своей базе


Ну теперь нам ничего не стоит вывести события нашего приложения и на форме. Для этого сделаем прямо в дизайн тайме вот такую простенькую форму без программного кода:

00001: <%@ Page Language="VB" MasterPageFile="~/M1.master" AutoEventWireup="false" CodeFile="MyLog.aspx.vb" Inherits="MyLog" title="Untitled Page" %>
00002: <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
00003:     <asp:SqlDataSource ID="GetFromMyLog" runat="server" ConnectionString="<%$ ConnectionStrings:Votpusk %>"
00004:         SelectCommand="select * from dbo.MyLog order by i desc"></asp:SqlDataSource>
00005:     <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False"
00006:         DataSourceID="GetFromMyLog" PageSize="20" Width="100%">
00007:         <Columns>
00008:             <asp:BoundField DataField="i" HeaderText="i" InsertVisible="False" ReadOnly="True"
00009:                 SortExpression="i" />
00010:             <asp:BoundField DataField="datetime" HeaderText="datetime" SortExpression="datetime" />
00011:             <asp:BoundField DataField="txt" HeaderText="txt" SortExpression="txt" />
00012:         </Columns>
00013:         <RowStyle Font-Size="Small" />
00014:     </asp:GridView>
00015: </asp:Content>

Как вы понимаете, все перечисленные провайдеры в реальной работе не нужны. Для девелопмента достаточно провайдера AspLog, ну а когда сайт ляжет на хостинг - мы будем смотреть события в нем с помощью описанного тут провайдера - SQLLog.




Теперь рассмотрим мониторинг WMI-событий сайта. Это тоже важные события и там отражаются весьма важные аспекты работы приложения, начиная от рестарта AppDomain, кончая всевозможными сбоями.

Для мониторинга WMI-событий MS к счастью предусмотрела все. Надо знать, что во-первых существует множество провайдеров, которые вписывают данные о событиях в различные накопители данных:
SimpleMailWebEventProvider This provider sends e-mail for event notifications.
TemplatedMailWebEventProvider This provider uses templates to define and format e-mail messages sent for event notifications.
SqlWebEventProvider This provider logs event details to a SQL Server database. If you use this provider, you should encrypt the connection string in your Web.config file by using the Aspnet_regiis.exe tool.
EventLogWebEventProvider This provider logs events to the Windows application event log.
TraceWebEventProvider This provider logs events as ASP.NET trace messages.
WmiWebEventProvider This provider maps ASP.NET health monitoring events to Windows Management Instrumentation (WMI) events.

Классы этих провайдеров расположены в пространстве имен System.Web.Management и, при желании, вы можете их посмотреть подробнее. Но мы подойдем к этому вопросу с практической стороны и воспользуемся единственным на мой взгляд разумным провайдером - SqlWebEventProvide.

Во-вторых надо понимать, что в принципе существуют различные классы событий, которые можно учитывать.


Но мы подойдем к этому вопросу с практической стороны и учтем ВСЕ события приложения (хотя по умолчанию включен лишь мониторинг событий All Errors и Failure Audit, которые и записываются в журнал системы). Итого, воспользовавшись готовыми наработками MS, сформируем примерно вот такую секцию в конфиге:


00001:         <healthMonitoring enabled="true">
00002:             <providers>
00003:                 <clear/>
00004:                 <add connectionStringName="Votpusk" maxEventDetailsLength="1073741823" buffer="true" bufferMode="Notification" name="MySqlWebEventProvider" 
00005:                 type="System.Web.Management.SqlWebEventProvider,System.Web,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"/>
00006:             </providers>
00007:             <rules>
00008:                 <clear/>
00009:                 <add name="Application Lifetime Events Rule" eventName="All Events" provider="MySqlWebEventProvider" profile="Critical"/>
00010:             </rules>
00011:         </healthMonitoring>

Этот конфиг переопределяет конфиг, заданный по умолчанию в Web-конфиге кампутера.


В предыдущем случае нам пришлось создавать базу самим, но сейчас мы пользуемся готовыми наработками MS и просто вызовем класс, создающий таблицу aspnet_WebEvent_Events. Сделать это можно двумя способами:

Теперь, после запуска приложения, нам остается только наслаждаться в профайлере работой healthMonitoring, ну или смотреть результаты работы уже в базе.

Но чтобы реально воспользоваться результатами этой работы - сделано слишком мало. Нужна ФОРМА, на которой мы увидим результат healthMonitoring. Я сделаю эту форму для разнообразия на DetailsView. Она будет состоять из вот такой разметки:


00001: <%@ Page Language="VB" MasterPageFile="~/M1.master" AutoEventWireup="false" CodeFile="Monitor.aspx.vb" Inherits="Monitor" %>
00002: <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
00003:         <asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" AutoGenerateRows="False"
00004:             DataKeyNames="EventId" DataMember="DefaultView" DataSourceID="healthMonitoring_WebEvents"
00005:             Height="50px" Width="100%" HorizontalAlign="Left">
00006:             <Fields>
00007:                 <asp:BoundField DataField="Details" SortExpression="Details" HtmlEncode="False" ShowHeader="False" />
00008:             </Fields>
00009:             <RowStyle HorizontalAlign="Left" VerticalAlign="Top" Font-Size="Small" />
00010:         </asp:DetailsView>
00011:         <asp:SqlDataSource ID="healthMonitoring_WebEvents" runat="server" ConnectionString="<%$ ConnectionStrings:Votpusk %>"
00012:             SelectCommand="select EventId, Details from  aspnet_WebEvent_Events order by EventTime DESC">
00013:         </asp:SqlDataSource>
00014: </asp:Content>
И вот такого кода:

00001: Partial Class Monitor
00002:     Inherits System.Web.UI.Page
00003: 
00004:     Protected Sub DetailsView1_ItemCreated(ByVal sender As Object, ByVal e As System.EventArgs) Handles DetailsView1.ItemCreated
00005:         If CType(sender, System.Web.UI.WebControls.DetailsView).DataItem IsNot Nothing Then
00006:             CType(CType(sender, System.Web.UI.WebControls.DetailsView).DataItem, System.Data.DataRowView)(1) = _ 
00007:             CType(CType(sender, System.Web.UI.WebControls.DetailsView).DataItem, System.Data.DataRowView)(1).ToString.Replace(Convert.ToChar(10), "<br>")
00008:         End If
00009:     End Sub
00010: 
00011: End Class

Как видите, тут вся хитрость в том, что нам надо заменить знак перевода строки ctrl-c на <br> и вывести все это на форму без HtmlEncode. Эту коррекцию, как вы видите, я сделал прямо на ходу по живым данным. Естественно, как и в предыдущем случае, наблюдение за приложением можно в любой момент отключить - закоментив пару строк в конфиге.




Теперь пойдем дальше и посмотрим как можно наблюдать за приложением с помощью счетчиков производительности. В принципе существует три пути использования счетчиков производительности:

Теперь посмотрим как с помощью VS2005 правильно создать в своей проге объекты счетчиков. Прежде всего надо асилить, что счетчики бывают с двухуровневой или трехуровневой системой именования. Соотвественно, обьект для обращения к ним будет в первом случае выглядеть так:

Dim Performance_2 As System.Diagnostics.PerformanceCounter = New System.Diagnostics.PerformanceCounter("ASP.NET", "Application Restarts")

а во втором вот так:
Dim Performance_3 As System.Diagnostics.PerformanceCounter = New System.Diagnostics.PerformanceCounter("ASP.NET Applications", "Anonymous Requests", "_LM_W3SVC_2_Root_services")

Таким образом обновляемое значение такого счетчика, выведенное на отладочной панели мастер-формы, может выглядеть например вот так. Что касается конкретно наборов счетчиков, полезных для оптимизации WEB-приложений, то я не буду сейчас останавливаться на этом, а посвящу этому отдельную заметку.




Теперь рассмотрим следующий аспект наблюдения за Web-приложениями. Основное воздействие, которым они подвергаются, происходит из Интернета. Поэтому важно четко фиксировать, во-первых, что именно делают с приложением, а во-вторых, кто именно это делает. Лучше всего для этого подходит класс, размещенный прямо в ASP2-конвеере и унаследованный от IHttpModule. Это весьма полезный интерфейс и на моем сайте уже описано несколько его применений. Другое полезное применение этого класса - URL rewriting - сокрытие реальных имен страничек и структуры сайта от посторонних. По этому же интерфейсу подключаются все основные авторизационные модули ASP2, определенные в Web-конфиге машины.

К конвееру ASP2 наш класс мы подключим следующим образом:

        <httpModules>
            <add name="MapURL" type="MapURL"/>
        </httpModules>
а текст класса разместим непосредственно в папке App_Code.

Как видите, здесь нет необходимости в сборке со строго подписанным именем. Сам по себе текст класса может выглядеть например вот так:


00001: ''' <summary>
00002: ''' класс реврайтинга URL
00003: ''' </summary>
00004: Public Class MapURL
00005:     Implements IHttpModule
00006:     Dim Dump As New RequestDump
00007: 
00008:     Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
00009:         AddHandler context.BeginRequest, AddressOf Me.BeginRequest
00010:     End Sub
00011: 
00012:     Public Sub BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
00013:         Dim App As HttpApplication = CType(sender, HttpApplication)
00014:         If App.Request.RawUrl.Contains(".aspx") And App.Request.HttpMethod = "POST" Then
00015:             Dump.DumpRequestKey(App.Request)
00016:             Dump.DumpPostData(App.Request)
00017:             Dump.SetDumpLenth(32765) 'максимальная длина, обрабатываемая таким провайдером. Больше только в базу
00018:             My.Log.WriteEntry(Dump.ToString)
00019:             Dump.Clear()
00020:         ElseIf App.Request.RawUrl.Contains("/Login/") And App.Request.HttpMethod = "POST" Then
00021:             'это собственно редирект (переделанный из POST в GET) на сокрытую страничку
00022:             App.Response.Redirect("~/X.aspx?Login=" & App.Request.Form("Login") & "&Pass=" & App.Request.Form("Pass") & "&Key=" & App.Request.Form("Key"))
00023:         End If
00024:     End Sub
00025: 
00026:     Public Sub Dispose() Implements IHttpModule.Dispose
00027:     End Sub
00028: 
00029: End Class

Как видите, тут результат пишется прямо в провайдер AspLog, но нам ничего не мешает писать в наш провайдер SQLLog, описанный выше - тогда результирующая длина дампа уже не будет ограничена. Полный результат формируемого дампа можно посмотреть здесь (для реквестов с моего локального кампа).

Теперь я покажу вам свой класс, который формирует такой замечательный и исчерпывающий дамп:

00001: Imports Microsoft.VisualBasic
00002: 
00003: ''' <summary>
00004: ''' Класс формирования шестнадцатиричного дампа
00005: ''' </summary>
00006: Public Class RequestDump
00007: 
00008:     Dim Buf1(100000) As Byte  'значение буфера по умолчанию
00009:     Public DumpStringBuilder As StringBuilder
00010:     Dim ANSI As New System.Text.ASCIIEncoding
00011:     ''' <summary>
00012:     ''' Без заголовка и с размером буфера по умолчнию
00013:     ''' </summary>
00014: 
00015:     Public Sub New()
00016:         DumpStringBuilder = New StringBuilder
00017:     End Sub
00018: 
00019:     ''' <summary>
00020:     ''' Задается заголовок распечатки
00021:     ''' </summary>
00022:     Public Sub New(ByVal DumpTitle As String)
00023:         DumpStringBuilder = New StringBuilder(DumpTitle)
00024:     End Sub
00025: 
00026:     ''' <summary>
00027:     ''' Задается размер буфера
00028:     ''' </summary>
00029:     Public Sub New(ByVal BuferSize As Integer)
00030:         ReDim Buf1(BuferSize)
00031:         DumpStringBuilder = New StringBuilder
00032:     End Sub
00033: 
00034:     ''' <summary>
00035:     ''' Задается размер буфера и заголовок дампа
00036:     ''' </summary>
00037:     Public Sub New(ByVal DumpTitle As String, ByVal BuferSize As Integer)
00038:         ReDim Buf1(BuferSize)
00039:         DumpStringBuilder = New StringBuilder(DumpTitle)
00040:     End Sub
00041: 
00042:     ''' <summary>
00043:     ''' Задается размер буфера и заголовок дампа
00044:     ''' </summary>
00045:     Public Sub New(ByVal BuferSize As Integer, ByVal DumpTitle As String)
00046:         ReDim Buf1(BuferSize)
00047:         DumpStringBuilder = New StringBuilder(DumpTitle)
00048:     End Sub
00049:     ''' <summary>
00050:     ''' Отсюда забирать результат
00051:     ''' </summary>
00052:     Public Overrides Function ToString() As String
00053:         Return DumpStringBuilder.ToString
00054:     End Function
00055: 
00056:     ''' <summary>
00057:     ''' Очистка класса для повторного использования
00058:     ''' </summary>
00059:     Public Sub Clear()
00060:         DumpStringBuilder.Length = 0
00061:         Array.Clear(Buf1, 0, Buf1.Length - 1)
00062:     End Sub
00063: 
00064:     ''' <summary>
00065:     ''' Ограничить результирующую длину распечатки
00066:     ''' </summary>
00067:     Public Sub SetDumpLenth(ByVal MaxOutLen As Integer)
00068:         DumpStringBuilder.Length = MaxOutLen
00069:     End Sub
00070: 
00071:     ''' <summary>
00072:     ''' Зафиксировать в дамп все параметры переданного реквеста кроме данных POST
00073:     ''' </summary>
00074:     Public Sub DumpRequestKey(ByVal Request As System.Web.HttpRequest)
00075:         DumpStringBuilder.AppendLine()
00076:         DumpStringBuilder.AppendLine("****** Request: (" & Now.ToString & ") " & Request.RawUrl)
00077:         DumpStringBuilder.AppendLine()
00078:         For Each One As String In Request.Params.AllKeys
00079:             DumpStringBuilder.AppendLine(One & " : " & Request.Params(One))
00080:         Next
00081:         DumpStringBuilder.AppendLine()
00082:     End Sub
00083: 
00084:     ''' <summary>
00085:     ''' Зафиксировать в дамп данные POST, передаваемые в реквесте
00086:     ''' </summary>
00087:     Public Sub DumpPostData(ByVal Request As System.Web.HttpRequest)
00088:         DumpStringBuilder.AppendLine()
00089:         DumpStringBuilder.AppendLine("****** Postdata:")
00090:         DumpStringBuilder.AppendLine()
00091:         If Request.TotalBytes < Buf1.Length Then
00092:             Buf1 = Request.BinaryRead(Request.TotalBytes)
00093:             DumHexBytes(Buf1)
00094:         Else
00095:             DumpStringBuilder.AppendLine("Ошибка. Превышен размер буфера.")
00096:         End If
00097:         DumpStringBuilder.AppendLine()
00098:     End Sub
00099: 
00100:     ''' <summary>
00101:     ''' Форматированная распечатка заданного извне байтового массива в виде стандартного шестнадцатиричного дампа
00102:     ''' </summary>
00103:     Public Sub DumHexBytes(ByVal Buffer() As Byte)
00104:         Dim I As Integer
00105:         DumpStringBuilder.AppendFormat("{0:X5}(x): {1:D5}(d): ", I, I)
00106:         For I = 0 To Buffer.Length - 1
00107:             DumpStringBuilder.Append(Buffer(I).ToString("X") & " ")
00108:             If ((I + 1) Mod 32 = 0) And I > 0 Then
00109:                 DumpStringBuilder.Append("    " & ANSI.GetString(Buffer, I - 31, 32))
00110:                 DumpStringBuilder.AppendLine()
00111:                 DumpStringBuilder.AppendFormat("{0:X5}(x): {1:D5}(d): ", I, I)
00112:             End If
00113:         Next
00114:         'выравнивание символьной части дампа
00115:         DumpStringBuilder.Append(New String(" ", (32 * 3 + "    ".Length) - (I Mod 32) * 3))
00116:         DumpStringBuilder.Append(ANSI.GetString(Buffer, I - (I Mod 32), I Mod 32))
00117:     End Sub
00118: 
00119: End Class

Осталось только вывести все это на форму. Это позволит на сделать вот такой код:

00001: <%@ Page Language="VB" MasterPageFile="~/M1.master" AutoEventWireup="false" CodeFile="MyLog.aspx.vb" Inherits="MyLog"%>
00002: <asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
00003:     <asp:SqlDataSource ID="GetFromMyLog" runat="server" ConnectionString="<%$ ConnectionStrings:Votpusk %>"
00004:         SelectCommand="select i, replace(txt,nchar(10),'&lt;br>') as txt from dbo.MyLog order by i desc"></asp:SqlDataSource>
00005:     <asp:GridView ID="GridView1" runat="server" AllowPaging="True" AutoGenerateColumns="False"
00006:         DataSourceID="GetFromMyLog" PageSize="20" Width="100%" DataKeyNames="i">
00007:         <Columns>
00008:             <asp:BoundField DataField="txt" HeaderText="txt" SortExpression="txt" HtmlEncode="False" />
00009:         </Columns>
00010:         <RowStyle Font-Names="Courier" />
00011:     </asp:GridView>
00012: </asp:Content>

Программный код тут не требуется. Потому что базу мы проектировали сами и сделали ее правильно (в отличие от MS) - типа Nvarchar, а не Ntext. А значит REPLACE можно выполнить прямо в команде SQL (тем более в GridView это не получится так сделать как в DetailsView). Важно чтобы шрифт тут был Courier - тк дамп строго форматирован по колонкам.




Теперь рассмотрим еще одну технологию мониторинга сайта. Понятно, что в виндузне сокрыто множество уязвимостей - доказательство тому - бесконечные еженедельные обновления, которые за много-много лет так ни к чему хорошему и не привели (кроме, конечно того, что Билл Гейтс стал самым богатым человеком планеты). Следовательно вероятность обычной вирусной атаки на сайт существует. Особенно если админ сайта не особо аккуратен и существует возможность атаки на библиотеки сайта не только из интернета, но и из локальной сети. И особенно это актуально для сайтов, которые работают с деньгами. В таком случае нам будет гораздо спокойнее, если мы уверены в НЕИЗМЕННОСТИ сайта. В отсутствии какого-нибудь вируса, прицепившегося к библиотекам сайта. В стабильности и неизменности версий сторонних библиотек, используемых сайтом. На самом деле, контрольная сумма сайта - это конечно не панацея - хороший вирус сумеет скрыть свое присутсвие, но все же примем, что 90% продекларированных проблем этим решением закрываются.

Разумеется эта технология отчасти ограничена и не подходит для сайтов, которые используют файловую систему для хранения временных файлов, но сайты постоенные по такой схеме вообще обычно напоминают проходной двор и заниматься их защитой или мониторингом вообще не имеет смысла.

Технология контроля контрольной суммы сайта заключается в подсчете контрольной суммы при загрузке сайта, вписывания ее в базу и последующей периодической перепроверке этой контрольной суммы. Странно, что такой элементарной вещи нет в IIS - зафиксировать ее при аплоаде сайта и потом например подстветить этот узел в IIS красным, если вдруг контрольная сумма сайта поменялась.

Для целей контрля неизменности сайта создается поток, который периодически пересчитывает контрольную сумму и сличает ее с базой. В случае подмены библиотеки - прекращает работу сайта. Чтобы не дублироваться, я тут повторяться не буду, ибо подобная программа уже лежит на моем сайте в виде утилиты с открытым исходным кодом.




Теперь рассмотрим технологию моделирования нагрузки на сайт, чтобы опробовать и производительность сайта и испытать все описанные выше средства мониторинга. Например, вот на этом рисунке видно как я добавлял новых пользователей в Web-проект моделирования событий в организационных структурах и, постепенно увеличивая нагрузку до 50 реквестов в секунду, наблюдал сколько одновременно добавляемых юзеров может выдержать мой сайт. Как вы видите, при такой нагрузке (из 11 тысяч 461 реквеста) - 1159 реквестов завершились неудачей (без формирования респонза сервером), а 4994 реквеста остались на момент завершения теста в очереди необслуженными.

К этому тесту на добавление юзеров я тоже пришел не сразу - протестировав все странички проекта, я выяснил, что именно создание нового пользователя является самой ресурсоемкой операцией моего приложения. В дельнейшем именно детальное изучение счетчиков позволило выявить узкие места и вынести некоторые фрагменты кода в SQL.

Впрочем в контексте данной заметки мы не будем останавливаться ни на применении указанных средств тестирования для выявления узких мест в приложении (это нехилая тема и ее опишу как нибудь позднее), ни на применении этих же средств для взлома сайтов, путем генерирования массовых реквестов на страничку логина (используя базу логинов и паролей). В контексте данной заметки эти 50 POST-реквестов в секунду нам были нужны - чтобы протестировать все описанные мною в этом топике средства мониторинга сайтов.






Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/asp2/31/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>