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,4 +9,15 @@ Para funcionar é necessário primeiro instalar o pacote da distribuição e só | ||
9 | 9 | ||
10 | <pre> | 10 | <pre> |
11 | virtualenv --system-site-packages -p /usr/bin/python2.7 cocar-agente | 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 | </pre> | 23 | </pre> |
13 | \ No newline at end of file | 24 | \ No newline at end of file |
cocar/__init__.py
1 | +#!/bin/env python | ||
2 | +# -*- coding: utf-8 -*- | ||
1 | __author__ = 'eduardo' | 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 | \ No newline at end of file | 34 | \ No newline at end of file |
cocar/host.py
@@ -1,57 +0,0 @@ | @@ -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 | \ No newline at end of file | 0 | \ No newline at end of file |
@@ -0,0 +1 @@ | @@ -0,0 +1 @@ | ||
1 | +__author__ = 'eduardo' |
@@ -0,0 +1,20 @@ | @@ -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 | \ No newline at end of file | 21 | \ No newline at end of file |
@@ -0,0 +1,37 @@ | @@ -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 | \ No newline at end of file | 38 | \ No newline at end of file |
@@ -0,0 +1,45 @@ | @@ -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 | \ No newline at end of file | 46 | \ No newline at end of file |
@@ -0,0 +1,25 @@ | @@ -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 | \ No newline at end of file | 26 | \ No newline at end of file |
cocar/query.py
@@ -3,7 +3,7 @@ | @@ -3,7 +3,7 @@ | ||
3 | # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ | 3 | # Inspired by the code in http://www.copyandwaste.com/posts/view/multiprocessing-snmp-with-python/ |
4 | __author__ = 'eduardo' | 4 | __author__ = 'eduardo' |
5 | 5 | ||
6 | -from host import SnmpSession | 6 | +from session import SnmpSession |
7 | from multiprocessing import Process, Queue, current_process | 7 | from multiprocessing import Process, Queue, current_process |
8 | 8 | ||
9 | 9 |
@@ -0,0 +1,116 @@ | @@ -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 | \ No newline at end of file | 117 | \ No newline at end of file |
cocar/tests/test_discover.py
@@ -3,7 +3,12 @@ | @@ -3,7 +3,12 @@ | ||
3 | __author__ = 'eduardo' | 3 | __author__ = 'eduardo' |
4 | 4 | ||
5 | import unittest | 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 | class TestDiscover(unittest.TestCase): | 14 | class TestDiscover(unittest.TestCase): |
@@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase): | @@ -15,31 +20,98 @@ class TestDiscover(unittest.TestCase): | ||
15 | """ | 20 | """ |
16 | Parâmetros iniciais | 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 | def test_active(self): | 35 | def test_active(self): |
20 | """ | 36 | """ |
21 | Teste que verifica se o ativo de rede está ativo | 37 | Teste que verifica se o ativo de rede está ativo |
22 | """ | 38 | """ |
23 | - session = SnmpSession() | 39 | + session = SnmpSession(DestHost=self.activeip) |
24 | result = session.query() | 40 | result = session.query() |
25 | print(result.query[0]) | 41 | print(result.query[0]) |
26 | self.assertIsNotNone(result.query[0]) | 42 | self.assertIsNotNone(result.query[0]) |
27 | 43 | ||
28 | def test_inactive(self): | 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 | result = session.query() | 49 | result = session.query() |
34 | print(result.query[0]) | 50 | print(result.query[0]) |
35 | self.assertIsNone(result.query[0]) | 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 | def tearDown(self): | 113 | def tearDown(self): |
43 | """ | 114 | """ |
44 | Apaga dados inicias | 115 | Apaga dados inicias |
45 | - """ | ||
46 | \ No newline at end of file | 116 | \ No newline at end of file |
117 | + """ | ||
118 | + #os.rmdir(self.data_dir) | ||
47 | \ No newline at end of file | 119 | \ No newline at end of file |
@@ -0,0 +1,42 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 33 | \ No newline at end of file |