Кнопки


Яндекс цитирования


Главная » Документы » SQUID-STAT in DB

Как заносить стастику Squid в базу данных


Привет !!!

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

У меня в сквиде используется NTLM аутентификация, но подробно на этом вопросе я подробно останавливаться не буду, так как подобных док в интернете кучи, остановлюсь на вопросе занесения данных в базу...

Когда мне потребовалось реализовать статистику я написал небольшой скриптик складывающий данные в MySQL, скрипт я написал на Python (кстати, до сих пор на нем пишу). Все анализаторы лога делятся на 2 вида:

1. Прямые - те когда любая информация поступающая в лог, сразу заносится в базу
2. Циклическая - когда раз в некоторое время запускается анализатор и парсит лог сквида.

Ессно я пошел по первому пути.

Формат базы для MySQL:

CREATE TABLE `traf` (
  `ip` decimal(10,0) default NULL,
  `username` varchar(35) default NULL,
  `hostname` varchar(40) default NULL,
  `dt` datetime default NULL,
  `size` bigint(20) default NULL,
  `out` bigint(20) default NULL,
  `query` bigint(20) default NULL,
  KEY `ip_idx` (`ip`,`dt`),
  KEY `dt_idx` (`dt`),
  KEY `username` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

1. Вырубаем сквида
2. Заходим в /var/log/squid и киляем access.log
3. Там же делаем mkfifo -m 0666 access.log
4. Запускаем скриптик: /usr/bin/python insert.log.py /var/log/squid/access.log /home/squid.log &
    папка /home/squid.log/ должна быть создана, сюда в текстовом виде складываются логи сквида. Они могу пригодится для чего-то еще, в крайнем случае их можно удалять раз в день :) Я жму раром и в архив, чтобы (если что) предъявить :)
5. Запускаем сквид - пробуем ходить, смотрим что в логах.
6. Если будет все нормально, а логи и база чистые - не торопитесь, кругом кеши и все такое, попробуйте побольше страничек пооткрывать и вообче подольше побродить.

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

Теперь сам скрипт:

#!/usr/bin/python
import sys,string,time,fileinput,_mysql,socket
db = _mysql.connect(db='traf',host='127.0.0.1',user='traf',passwd='пароля')# Если 1, до добавляется к трафику пользователя, если 0 - просто к трафику
ACTION_LIST={
 'TCP_HIT':0,
 'TCP_MISS':1,
 'TCP_REFRESH_HIT':0,
 'TCP_REFRESH_FAIL_HIT':0,
 'TCP_REFRESH_MISS':1,
 'TCP_NEGATIVE_HIT':1,
 'TCP_MEM_HIT':1,
 'TCP_CLIENT_REFRESH':1,
 'TCP_CLIENT_REFRESH_MISS':1,
 'TCP_IMS_HIT':0,
 'TCP_IMS_MISS':1,
 'TCP_SWAPFAIL_MISS':1,
 'TCP_DENIED':0,
 'UDP_HIT':0,
 'UDP_HIT_OBJ':1,
 'UDP_MISS':1,
 'UDP_DENIED':0,
 'UDP_INVALID':0,
 'UDP_RELOADING':1,
 'NONE':0,
 'ERR_READ_TIMEOUT':0,
 'ERR_LIFETIME_EXP':1,
 'ERR_NO_CLIENTS_BIG_OBJ':1,
 'ERR_READ_ERROR':0,
 'ERR_CLIENT_ABORT':1,
 'ERR_CONNECT_FAIL':0,
 'ERR_INVALID_REQ':1,
 'ERR_UNSUP_REQ':0,
 'ERR_INVALID_URL':0,
 'ERR_NO_FDS':0,
 'ERR_DNS_FAIL':0,
 'ERR_NOT_IMPLEMENTED':0,
 'ERR_CANNOT_FETCH':1,
 'ERR_NO_RELAY':1,
 'ERR_DISK_IO':0,
 'ERR_ZERO_SIZE_OBJECT':0,
 'ERR_FTP_DISABLED':0,
 'ERR_PROXY_DENIED':0,
}
if len(sys.argv)<2:
    raise NameError('Command string is necompatable,')
filelog=sys.argv[1] # Это лог файл от сквида или fifo от него же
outlog=sys.argv[2]  # Это директория, куда складываем дополнительные
sel = 0
def ip2i(s):
    ''' Переводит IP адрес в число '''
    x = 0
    for i in s.split('.'):
        x = ( x << 8 ) + int(i)
    return str(x)
def i2ip(i_ip):
    ''' Переводит число в IP адрес '''
    return '.'.join( [str( (i_ip >> (i*8)) & 0xFF ) for i in (3,2,1,0)] )
def nint(a):
    ''' Нормально округляет переменную'''
    try: result = int(a)
    except ValueError: result = -1
    return result
def nindex(g,x):
    ''' Если индекса нет - возвращает пустоту'''
    try: result = g[x]
    except IndexError: result = ''
    return result
def nact(a):
    ''' Возвращает резуьтать акции или -1, если не найдено'''
    try: result = ACTION_LIST[a]
    except KeyError: result = -1
    return result
class tm:
    'Класс вовзращает с учетом кеширования ип из имени'
    __ip2name__={}
    __elapsed__ = 100 # Время устаревания значения в кеше, в секундах
    def ip2name(self,x):
        'Переводит ip в имя'
        try: return socket.gethostbyaddr(x)[0].lower()
        except: return x
    def __getitem__(self,x):
        ct = time.time()
        if self.__ip2name__.has_key(x):
            t,name = self.__ip2name__[x]
            if t+self.__elapsed__>ct: # Неустарело
                return name
            del self.__ip2name__[x]
        name = self.ip2name(x)
        self.__ip2name__[x]=(ct,name)
        return name
ip2name = tm()
def insDB(x):
    ''' Функция автоматически обрабатывает и добавляет в базу входную строку'''
    # Раньше была ошибка при увеличении на 1 цифру, теперь не повторится..:)
    g1 = x.split('.')[0]
    g2 = x.split(' ',1)[1]
    g = g2.strip()
    g=g.split(' ')
    if len(g)==9:
        # - Получаем параметры
        timestamp=nint(g1)         # - TimeStamp - юниксовое врямя запроса
        elapsed=nint(g[0])        # - Длительность запроса
        ip=g[1]            # - IP адрес клиента
        hostname = ip2name[ip] # Получим имя хоста
        type_reply=g[2]        # - Тип/код ответа на запрос
        action=g[2].split('/')[0]    # - Тип ответа на запрос
        size=g[3]            # - Размер ответа
        method=g[4]            # - Метод запроса
        url=g[5]            # - URL запроса
        ident=g[6].replace(chr(92),chr(92)+chr(92))    # - ident пользователя
        hierarhy=g[7]            # - Тип иерархического запроса
        mime=g[8]            # - MIME типа ответа
        Eact=nact(action)        # - Если есть код ответа, то 1 или 0 (-1 если нету)
        if Eact == 1: out=size            # - Это взято из интернета
        else: out='0'            # - Это взято из кеша
        #######################
        gt=time.gmtime(timestamp)     # Получим время запроса
        t = str(gt[0])+'-'+str(gt[1])+'-'+str(gt[2])
        h = str(gt[3])
        if ip.count('.')==3:
            #print sel
            if Eact == -1:  print 'Error ',sel,' string ACTION_TYPE: ',action
            else:

                qq = "update traf set size=size+%s,out=out+%s,query=query+1 where ip=inet_aton('%s') and dt='%s %s' and username='%s' and traf.hostname='%s'" % (size,out,ip,t,h,ident,hostname)

                db.query(qq)

                if db.affected_rows()==0:

                    qq = "insert into traf (ip,username,traf.hostname,dt,size,out,query) values(inet_aton('%s'),'%s','%s','%s %s',%s,%s,1)" % (ip,ident,hostname,t,h,size,out)
                    db.query(qq)

                db.query('commit')

    else: # А сюда сваливаемся по ошибке
        print 'Error ',sel,' string log file ->',x
def xxx():
    sel=0
    CurName=''
    fl=open('/dev/null','a') # Это чтобы его с нуля закрыть можно было...:)
    for x in fileinput.input(filelog):
    gm=time.gmtime(nint(x.split('.')[0]))
    LogName=str(gm[0])+'_'+string.zfill(gm[1],2)+'_'+string.zfill(gm[2],2)+'.access.log'
    if LogName <> CurName:
        print 'New Log Created:',outlog+'/'+LogName
        fl.close
        CurName=LogName
        fl=open(outlog+'/'+CurName,'a')
    fl.write(x)
    sel=sel+1
    try: insDB(x)
    except: print 'Error insert to db'
while 1:
    xxx()
db.close
fl.close
print 'Stop. ',sel,' string parsed.'

 Вот в принципе и все, если есть какие замечания - пишите, обязательно статью изменю :)

С Уважением, Алексей.