Commit 45d477ecea855227b17ea3b81b94bd5f2b456b06
Committed by
Eduardo Santos
Exists in
master
Primeira versão do módulo capaz de realizar a descoberta da rede
Showing
15 changed files
with
449 additions
and
65 deletions
Show diff stats
.gitignore
README.md
| ... | ... | @@ -9,4 +9,15 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só |
| 9 | 9 | |
| 10 | 10 | <pre> |
| 11 | 11 | virtualenv --system-site-packages -p /usr/bin/python2.7 cocar-agente |
| 12 | +</pre> | |
| 13 | + | |
| 14 | +Dependência de um módulo externo chamado pylanos | |
| 15 | + | |
| 16 | +<pre> | |
| 17 | +cd /home/eduardo/srv/cocar-agente/src | |
| 18 | +cd cocar-agente/cocar | |
| 19 | +mkdir lib | |
| 20 | +cd lib | |
| 21 | +wget https://github.com/c0r3dump3d/pylanos/raw/master/PyLanOS.py | |
| 22 | +mv PyLanOS.py pylanos.py | |
| 12 | 23 | </pre> |
| 13 | 24 | \ No newline at end of file | ... | ... |
cocar/__init__.py
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 1 | 3 | __author__ = 'eduardo' |
| 4 | +import os | |
| 5 | + | |
| 6 | +import ConfigParser | |
| 7 | +import logging | |
| 8 | +import logging.config | |
| 9 | + | |
| 10 | +config = ConfigParser.ConfigParser() | |
| 11 | +here = os.path.abspath(os.path.dirname(__file__)) | |
| 12 | +config_file = os.path.join(here, '../development.ini') | |
| 13 | +config.read(config_file) | |
| 14 | + | |
| 15 | +# Logging | |
| 16 | +logging.config.fileConfig(config_file) | |
| 17 | + | |
| 18 | +class Cocar(object): | |
| 19 | + """ | |
| 20 | + Classe global com as configurações | |
| 21 | + """ | |
| 22 | + | |
| 23 | + def __init__(self): | |
| 24 | + """ | |
| 25 | + Parâmetro construtor | |
| 26 | + """ | |
| 27 | + cocar_data_dir = config.get('cocar', 'data_dir') | |
| 28 | + | |
| 29 | + if os.path.isdir(cocar_data_dir): | |
| 30 | + self.cocar_data_dir = cocar_data_dir | |
| 31 | + else: | |
| 32 | + os.mkdir(cocar_data_dir) | |
| 33 | + self.cocar_data_dir = cocar_data_dir | |
| 2 | 34 | \ No newline at end of file | ... | ... |
cocar/host.py
| ... | ... | @@ -1,57 +0,0 @@ |
| 1 | -#!/bin/env python | |
| 2 | -# -*- coding: utf-8 -*- | |
| 3 | -# Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ | |
| 4 | -__author__ = 'eduardo' | |
| 5 | - | |
| 6 | -import netsnmp | |
| 7 | - | |
| 8 | - | |
| 9 | - | |
| 10 | -class Host(object): | |
| 11 | - """ | |
| 12 | - Creates a host record | |
| 13 | - """ | |
| 14 | - | |
| 15 | - def __init__(self, | |
| 16 | - hostname=None, | |
| 17 | - query=None): | |
| 18 | - self.hostname = hostname | |
| 19 | - self.query = query | |
| 20 | - | |
| 21 | - | |
| 22 | -class SnmpSession(): | |
| 23 | - """A SNMP Session""" | |
| 24 | - def __init__(self, | |
| 25 | - oid=".1.3.6.1.2.1.1.1.0", | |
| 26 | - iid=None, | |
| 27 | - Version=2, | |
| 28 | - DestHost="localhost", | |
| 29 | - Community="public", | |
| 30 | - Verbose=True, | |
| 31 | - ): | |
| 32 | - self.oid = oid | |
| 33 | - self.Version = Version | |
| 34 | - self.DestHost = DestHost | |
| 35 | - self.Community = Community | |
| 36 | - self.Verbose = Verbose | |
| 37 | - self.var = netsnmp.Varbind(oid, iid) | |
| 38 | - self.hostrec = Host() | |
| 39 | - self.hostrec.hostname = self.DestHost | |
| 40 | - | |
| 41 | - def query(self): | |
| 42 | - """Creates SNMP query | |
| 43 | - | |
| 44 | - Fills out a Host Object and returns result | |
| 45 | - """ | |
| 46 | - try: | |
| 47 | - result = netsnmp.snmpget(self.var, | |
| 48 | - Version=self.Version, | |
| 49 | - DestHost=self.DestHost, | |
| 50 | - Community=self.Community) | |
| 51 | - self.hostrec.query = result | |
| 52 | - except Exception, err: | |
| 53 | - if self.Verbose: | |
| 54 | - print err | |
| 55 | - self.hostrec.query = None | |
| 56 | - finally: | |
| 57 | - return self.hostrec | |
| 58 | 0 | \ No newline at end of file |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +__author__ = 'eduardo' | ... | ... |
| ... | ... | @@ -0,0 +1,20 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +__author__ = 'eduardo' | |
| 4 | + | |
| 5 | +from .host import Host | |
| 6 | + | |
| 7 | + | |
| 8 | +class Computer(Host): | |
| 9 | + """ | |
| 10 | + Ativo de rede identificado como estação de trabalho | |
| 11 | + """ | |
| 12 | + def __init__(self, | |
| 13 | + so | |
| 14 | + ): | |
| 15 | + """ | |
| 16 | + Classe que identifica uma estação de trabalho | |
| 17 | + :param so: Sistema Operacional encontrado | |
| 18 | + """ | |
| 19 | + Host.__init__(self) | |
| 20 | + self.so = so | |
| 0 | 21 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,37 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +__author__ = 'eduardo' | |
| 4 | +from netaddr import IPAddress | |
| 5 | + | |
| 6 | + | |
| 7 | +class Host(object): | |
| 8 | + """ | |
| 9 | + Classe que define um ativo de rede | |
| 10 | + """ | |
| 11 | + def __init__(self, | |
| 12 | + ip_address, | |
| 13 | + mac_address, | |
| 14 | + network, | |
| 15 | + hostname=None, | |
| 16 | + inclusion_date=None, | |
| 17 | + scantime=None, | |
| 18 | + open_ports=[]): | |
| 19 | + """ | |
| 20 | + Método construtor do ativo de rede | |
| 21 | + | |
| 22 | + :param ip_address: Endereço Ip | |
| 23 | + :param mac_address: MAC | |
| 24 | + :param network: Endereço da rede onde o ativo foi encontrado | |
| 25 | + :param hostname: Nome do host | |
| 26 | + :param inclusion_date: Data de coleta | |
| 27 | + :param scantime: Tempo levado na execução | |
| 28 | + :param open_ports: Portas abertas | |
| 29 | + :return: | |
| 30 | + """ | |
| 31 | + self.ip_address = IPAddress(ip_address) | |
| 32 | + self.mac_address = mac_address | |
| 33 | + self.network = network | |
| 34 | + self.hostname = hostname | |
| 35 | + self.inclusion_date = inclusion_date | |
| 36 | + self.scantime = scantime | |
| 37 | + self.open_ports = open_ports | |
| 0 | 38 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,45 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +__author__ = 'eduardo' | |
| 4 | +import os.path | |
| 5 | +from .. import Cocar | |
| 6 | +from netaddr import IPNetwork, IPSet | |
| 7 | + | |
| 8 | + | |
| 9 | +class Network(Cocar): | |
| 10 | + """ | |
| 11 | + Rede onde a busca será realizada | |
| 12 | + """ | |
| 13 | + def __init__(self, | |
| 14 | + network_ip, | |
| 15 | + netmask=None, | |
| 16 | + prefixlen=None, | |
| 17 | + name=None | |
| 18 | + ): | |
| 19 | + """ | |
| 20 | + :param network_ip: Ip da rede | |
| 21 | + :param netmask: Máscara da rede | |
| 22 | + :param cidr: CIDR para calcular a máscara da rede | |
| 23 | + :param name: Nome da rede | |
| 24 | + """ | |
| 25 | + Cocar.__init__(self) | |
| 26 | + self.network_ip = IPNetwork(network_ip) | |
| 27 | + self.netmask = netmask | |
| 28 | + self.prefixlen = prefixlen | |
| 29 | + self.name = name | |
| 30 | + self.network_dir = self.cocar_data_dir + "/" + str(self.network_ip.ip) | |
| 31 | + # Cria diretório se não existir | |
| 32 | + if not os.path.isdir(self.network_dir): | |
| 33 | + os.mkdir(self.network_dir) | |
| 34 | + | |
| 35 | + if self.netmask is None: | |
| 36 | + self.netmask = self.network_ip.netmask | |
| 37 | + if self.prefixlen is None: | |
| 38 | + self.prefixlen = self.network_ip.prefixlen | |
| 39 | + | |
| 40 | + def ip_list(self): | |
| 41 | + """ | |
| 42 | + Método que encontra a lista de IP's da subrede | |
| 43 | + :return: Conjunto de IP's para realizar a interação | |
| 44 | + """ | |
| 45 | + return IPSet(self.network_ip) | |
| 0 | 46 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,25 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +__author__ = 'eduardo' | |
| 4 | + | |
| 5 | +from .host import Host | |
| 6 | + | |
| 7 | + | |
| 8 | +class Printer(Host): | |
| 9 | + """ | |
| 10 | + Classe que identifica uma impressora | |
| 11 | + """ | |
| 12 | + def __init__(self, | |
| 13 | + counter, | |
| 14 | + model=None, | |
| 15 | + serial=None | |
| 16 | + ): | |
| 17 | + """ | |
| 18 | + :param counter: Contador da impressora | |
| 19 | + :param model: Modelo da impressora | |
| 20 | + :param serial: Número de série da impressora | |
| 21 | + """ | |
| 22 | + Host.__init__(self) | |
| 23 | + self.counter = counter | |
| 24 | + self.model = model | |
| 25 | + self.serial = serial | |
| 0 | 26 | \ No newline at end of file | ... | ... |
cocar/query.py
| ... | ... | @@ -3,7 +3,7 @@ |
| 3 | 3 | # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ |
| 4 | 4 | __author__ = 'eduardo' |
| 5 | 5 | |
| 6 | -from host import SnmpSession | |
| 6 | +from session import SnmpSession | |
| 7 | 7 | from multiprocessing import Process, Queue, current_process |
| 8 | 8 | |
| 9 | 9 | ... | ... |
| ... | ... | @@ -0,0 +1,116 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +# Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ | |
| 4 | +__author__ = 'eduardo' | |
| 5 | + | |
| 6 | +import netsnmp | |
| 7 | +import subprocess | |
| 8 | +import logging | |
| 9 | +from . import Cocar | |
| 10 | + | |
| 11 | +log = logging.getLogger() | |
| 12 | + | |
| 13 | + | |
| 14 | +class Host(object): | |
| 15 | + """ | |
| 16 | + Creates a host record | |
| 17 | + """ | |
| 18 | + | |
| 19 | + def __init__(self, | |
| 20 | + hostname=None, | |
| 21 | + query=None): | |
| 22 | + self.hostname = hostname | |
| 23 | + self.query = query | |
| 24 | + | |
| 25 | + | |
| 26 | +class SnmpSession(Cocar): | |
| 27 | + """A SNMP Session""" | |
| 28 | + def __init__(self, | |
| 29 | + oid=".1.3.6.1.2.1.1.1.0", | |
| 30 | + iid=None, | |
| 31 | + Version=2, | |
| 32 | + DestHost="localhost", | |
| 33 | + Community="public", | |
| 34 | + Verbose=True, | |
| 35 | + ): | |
| 36 | + Cocar.__init__(self) | |
| 37 | + self.oid = oid | |
| 38 | + self.Version = Version | |
| 39 | + self.DestHost = DestHost | |
| 40 | + self.Community = Community | |
| 41 | + self.Verbose = Verbose | |
| 42 | + self.var = netsnmp.Varbind(oid, iid) | |
| 43 | + self.hostrec = Host() | |
| 44 | + self.hostrec.hostname = self.DestHost | |
| 45 | + | |
| 46 | + def query(self): | |
| 47 | + """Creates SNMP query | |
| 48 | + | |
| 49 | + Fills out a Host Object and returns result | |
| 50 | + """ | |
| 51 | + try: | |
| 52 | + result = netsnmp.snmpget(self.var, | |
| 53 | + Version=self.Version, | |
| 54 | + DestHost=self.DestHost, | |
| 55 | + Community=self.Community) | |
| 56 | + self.hostrec.query = result | |
| 57 | + except Exception, err: | |
| 58 | + if self.Verbose: | |
| 59 | + print err | |
| 60 | + self.hostrec.query = None | |
| 61 | + finally: | |
| 62 | + return self.hostrec | |
| 63 | + | |
| 64 | + | |
| 65 | +class NmapSession(Cocar): | |
| 66 | + """ | |
| 67 | + Realiza busca Nmap num ativo de rede | |
| 68 | + Inspirado em https://github.com/c0r3dump3d/pylanos | |
| 69 | + """ | |
| 70 | + def __init__(self, | |
| 71 | + host, | |
| 72 | + full=False, | |
| 73 | + outfile=None | |
| 74 | + ): | |
| 75 | + """ | |
| 76 | + Parâmetros obrigatórios | |
| 77 | + """ | |
| 78 | + Cocar.__init__(self) | |
| 79 | + self.host = host | |
| 80 | + self.full = full | |
| 81 | + if outfile is not None: | |
| 82 | + self.outfile = outfile | |
| 83 | + else: | |
| 84 | + self.outfile = self.cocar_data_dir + "/" + str(self.host) + ".xml" | |
| 85 | + | |
| 86 | + def scan(self): | |
| 87 | + """ | |
| 88 | + Realiza busca Nmap | |
| 89 | + :return: | |
| 90 | + """ | |
| 91 | + try: | |
| 92 | + if self.full: | |
| 93 | + scanv = subprocess.Popen(["nmap", | |
| 94 | + "-PR", | |
| 95 | + "-sV", | |
| 96 | + str(self.host), | |
| 97 | + "-oX", | |
| 98 | + self.outfile], | |
| 99 | + stdout=subprocess.PIPE, | |
| 100 | + stderr=subprocess.PIPE).communicate()[0] | |
| 101 | + else: | |
| 102 | + scanv = subprocess.Popen(["nmap", | |
| 103 | + "-PE", | |
| 104 | + "-PP", | |
| 105 | + "-PS21,22,23,25,80,443,3306,3389,8080", | |
| 106 | + "-sV", | |
| 107 | + str(self.host), | |
| 108 | + "-oX", | |
| 109 | + self.outfile], | |
| 110 | + stdout=subprocess.PIPE, | |
| 111 | + stderr=subprocess.PIPE).communicate()[0] | |
| 112 | + except OSError: | |
| 113 | + log.error("Install nmap: sudo apt-get install nmap") | |
| 114 | + return False | |
| 115 | + | |
| 116 | + return True | |
| 0 | 117 | \ No newline at end of file | ... | ... |
cocar/tests/test_discover.py
| ... | ... | @@ -3,7 +3,12 @@ |
| 3 | 3 | __author__ = 'eduardo' |
| 4 | 4 | |
| 5 | 5 | import unittest |
| 6 | -from ..host import Host, SnmpSession | |
| 6 | +import os | |
| 7 | +import os.path | |
| 8 | +from ..session import Host, SnmpSession, NmapSession | |
| 9 | +from .. import Cocar | |
| 10 | +from ..model import network | |
| 11 | +from .. import utils | |
| 7 | 12 | |
| 8 | 13 | |
| 9 | 14 | class TestDiscover(unittest.TestCase): |
| ... | ... | @@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase): |
| 15 | 20 | """ |
| 16 | 21 | Parâmetros iniciais |
| 17 | 22 | """ |
| 23 | + self.activeip = '127.0.0.1' | |
| 24 | + self.inactiveip = '127.1.1.1' | |
| 25 | + self.localhost = '127.0.0.1' | |
| 26 | + cocar = Cocar() | |
| 27 | + self.data_dir = cocar.cocar_data_dir | |
| 28 | + | |
| 29 | + local_network = utils.get_local_network() | |
| 30 | + self.network = network.Network( | |
| 31 | + network_ip=str(local_network.cidr), | |
| 32 | + name='Rede de teste' | |
| 33 | + ) | |
| 18 | 34 | |
| 19 | 35 | def test_active(self): |
| 20 | 36 | """ |
| 21 | 37 | Teste que verifica se o ativo de rede está ativo |
| 22 | 38 | """ |
| 23 | - session = SnmpSession() | |
| 39 | + session = SnmpSession(DestHost=self.activeip) | |
| 24 | 40 | result = session.query() |
| 25 | 41 | print(result.query[0]) |
| 26 | 42 | self.assertIsNotNone(result.query[0]) |
| 27 | 43 | |
| 28 | 44 | def test_inactive(self): |
| 29 | 45 | """ |
| 30 | - Teste que identifica que um nó inativo | |
| 46 | + Teste que identifica que um ativo de rede está inativo | |
| 31 | 47 | """ |
| 32 | - session = SnmpSession(DestHost="192.168.0.201") | |
| 48 | + session = SnmpSession(DestHost=self.inactiveip) | |
| 33 | 49 | result = session.query() |
| 34 | 50 | print(result.query[0]) |
| 35 | 51 | self.assertIsNone(result.query[0]) |
| 36 | 52 | |
| 37 | - def test_identify(self): | |
| 53 | + def test_scan(self): | |
| 54 | + """ | |
| 55 | + Teste que realiza o scan em todas as informações do ativo | |
| 56 | + """ | |
| 57 | + session = NmapSession(self.localhost) | |
| 58 | + result = session.scan() | |
| 59 | + assert result | |
| 60 | + | |
| 61 | + # Tenta achar o arquivo | |
| 62 | + outfile = self.data_dir + "/" + self.localhost + ".xml" | |
| 63 | + assert (os.path.isfile(outfile)) | |
| 64 | + | |
| 65 | + def test_scan_rede_full(self): | |
| 38 | 66 | """ |
| 39 | - Teste que identifica qual é o ativo | |
| 67 | + Realiza busca em todos os IP's da rede e grava resultados num arquivo específico | |
| 40 | 68 | """ |
| 69 | + ip_list = self.network.ip_list() | |
| 70 | + i = 0 | |
| 71 | + for ip in ip_list: | |
| 72 | + outfile = self.network.network_dir + "/" + str(ip) + ".xml" | |
| 73 | + #print(outfile) | |
| 74 | + session = NmapSession(ip, outfile=outfile) | |
| 75 | + session.scan() | |
| 76 | + i += 1 | |
| 77 | + if i > 10: | |
| 78 | + break | |
| 79 | + | |
| 80 | + # List all IP's from directory | |
| 81 | + onlyfiles = [ f for f in os.listdir(self.network.network_dir) if os.path.isfile(os.path.join(self.network.network_dir, f)) ] | |
| 82 | + | |
| 83 | + # Funciona se encontrar pelo menos um arquivo | |
| 84 | + self.assertGreater(len(onlyfiles), 0) | |
| 85 | + | |
| 86 | + # Apaga diretório | |
| 87 | + os.rmdir(self.network.network_dir) | |
| 88 | + | |
| 89 | + def test_scan_rede(self): | |
| 90 | + """ | |
| 91 | + Realiza busca rápida em todos os IP's da rede e grava resultados num arquivo específico | |
| 92 | + """ | |
| 93 | + ip_list = self.network.ip_list() | |
| 94 | + i = 0 | |
| 95 | + for ip in ip_list: | |
| 96 | + outfile = self.network.network_dir + "/" + str(ip) + ".xml" | |
| 97 | + #print(outfile) | |
| 98 | + session = NmapSession(ip, outfile=outfile, full=False) | |
| 99 | + session.scan() | |
| 100 | + i += 1 | |
| 101 | + if i > 10: | |
| 102 | + break | |
| 103 | + | |
| 104 | + # List all IP's from directory | |
| 105 | + onlyfiles = [ f for f in os.listdir(self.network.network_dir) if os.path.isfile(os.path.join(self.network.network_dir, f)) ] | |
| 106 | + | |
| 107 | + # Funciona se encontrar pelo menos um arquivo | |
| 108 | + self.assertGreater(len(onlyfiles), 0) | |
| 109 | + | |
| 110 | + # Apaga diretório | |
| 111 | + os.rmdir(self.network.network_dir) | |
| 41 | 112 | |
| 42 | 113 | def tearDown(self): |
| 43 | 114 | """ |
| 44 | 115 | Apaga dados inicias |
| 45 | - """ | |
| 46 | 116 | \ No newline at end of file |
| 117 | + """ | |
| 118 | + #os.rmdir(self.data_dir) | |
| 47 | 119 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,42 @@ |
| 1 | +#!/bin/env python | |
| 2 | +# -*- coding: utf-8 -*- | |
| 3 | +__author__ = 'eduardo' | |
| 4 | + | |
| 5 | +import netifaces | |
| 6 | +import netaddr | |
| 7 | +import socket | |
| 8 | +from pprint import pformat | |
| 9 | + | |
| 10 | + | |
| 11 | +def get_local_network(myiface='eth0'): | |
| 12 | + """ | |
| 13 | + Função que encontra a rede local. | |
| 14 | + Fonte: http://stackoverflow.com/questions/3755863/trying-to-use-my-subnet-address-in-python-code | |
| 15 | + | |
| 16 | + :param myiface: Interface local a ser utilizada na busca | |
| 17 | + :return: IPNetwork instance | |
| 18 | + """ | |
| 19 | + ifaces = netifaces.interfaces() | |
| 20 | + # => ['lo', 'eth0', 'eth1'] | |
| 21 | + | |
| 22 | + addrs = netifaces.ifaddresses(myiface) | |
| 23 | + # {2: [{'addr': '192.168.1.150', | |
| 24 | + # 'broadcast': '192.168.1.255', | |
| 25 | + # 'netmask': '255.255.255.0'}], | |
| 26 | + # 10: [{'addr': 'fe80::21a:4bff:fe54:a246%eth0', | |
| 27 | + # 'netmask': 'ffff:ffff:ffff:ffff::'}], | |
| 28 | + # 17: [{'addr': '00:1a:4b:54:a2:46', 'broadcast': 'ff:ff:ff:ff:ff:ff'}]} | |
| 29 | + | |
| 30 | + # Get ipv4 stuff | |
| 31 | + ipinfo = addrs[socket.AF_INET][0] | |
| 32 | + address = ipinfo['addr'] | |
| 33 | + netmask = ipinfo['netmask'] | |
| 34 | + | |
| 35 | + # Create ip object and get | |
| 36 | + cidr = netaddr.IPNetwork('%s/%s' % (address, netmask)) | |
| 37 | + # => IPNetwork('192.168.1.150/24') | |
| 38 | + | |
| 39 | + #network = cidr.network | |
| 40 | + # => IPAddress('192.168.1.0') | |
| 41 | + | |
| 42 | + return cidr | ... | ... |
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +[cocar] | |
| 2 | +data_dir = /srv/cocar-agente/cocar_data | |
| 3 | + | |
| 4 | +# Begin logging configuration | |
| 5 | +[loggers] | |
| 6 | +keys = root, cocar | |
| 7 | + | |
| 8 | +[handlers] | |
| 9 | +keys = console | |
| 10 | + | |
| 11 | +[formatters] | |
| 12 | +keys = generic | |
| 13 | + | |
| 14 | +[logger_root] | |
| 15 | +level = DEBUG | |
| 16 | +handlers = console | |
| 17 | + | |
| 18 | +[logger_cocar] | |
| 19 | +level = DEBUG | |
| 20 | +handlers = | |
| 21 | +qualname = lbgenerator | |
| 22 | + | |
| 23 | +[handler_console] | |
| 24 | +class = StreamHandler | |
| 25 | +args = (sys.stderr,) | |
| 26 | +level = DEBUG | |
| 27 | +formatter = generic | |
| 28 | + | |
| 29 | +[formatter_generic] | |
| 30 | +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s | |
| 31 | +datefmt = %H:%M:%S | |
| 32 | +# End logging configuration | |
| 0 | 33 | \ No newline at end of file | ... | ... |