logtopg 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ recursive-include logtopg *.sql
logtopg-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: logtopg
3
+ Version: 1.0.0
4
+ Summary: Python logging handler that stores logs in postgresql
5
+ Home-page: https://github.com/216software/logtopg/
6
+ Author: 216 Software, LLC
7
+ Author-email: info@216software.com
8
+ License: BSD License
9
+ Platform: UNKNOWN
10
+
11
+ UNKNOWN
12
+
@@ -0,0 +1,129 @@
1
+ +++++++++++++++++
2
+ Log to PostgreSQL
3
+ +++++++++++++++++
4
+
5
+ .. image:: https://travis-ci.org/216software/logtopg.svg?branch=master
6
+ :target: https://travis-ci.org/216software/logtopg
7
+
8
+ .. image:: https://circleci.com/gh/216software/logtopg.png?circle-token=389fee16249541b4b1df6e8a7f8edb1401be66de
9
+ :target:https://circleci.com/gh/216software/logtopg
10
+
11
+ Install
12
+ =======
13
+
14
+ Grab the code with pip::
15
+
16
+ $ pip install logtopg
17
+
18
+ But you also have to install the ltree contrib module into your
19
+ database::
20
+
21
+ $ sudo -u postgres psql -c "create extension ltree;"
22
+
23
+ Try it out
24
+ ==========
25
+
26
+ The code in `docs/example.py`_ shows how to set up your logging configs
27
+ with this handler.
28
+
29
+ .. _`docs/example.py`: https://github.com/216software/logtopg/blob/master/docs/example.py
30
+
31
+ .. include:: docs/example.py
32
+ :number-lines:
33
+ :code: python
34
+
35
+
36
+ Contribute to logtopg
37
+ =====================
38
+
39
+ Get a copy of the code::
40
+
41
+ $ git clone --origin github https://github.com/216software/logtopg.git
42
+
43
+ Install it like this::
44
+
45
+ $ cd logtopg
46
+ $ pip install -e .
47
+
48
+ Create test user and test database::
49
+
50
+ $ sudo -u postgres createuser logtopg
51
+ $ sudo -u postgres createdb --owner logtopg logtopg_tests
52
+ $ sudo -u postgres psql -c "create extension ltree;" -d logtopg_tests
53
+
54
+ Then run the tests like this::
55
+
56
+ $ python setup.py --quiet test
57
+ .....
58
+ ----------------------------------------------------------------------
59
+ Ran 5 tests in 0.379s
60
+
61
+ OK
62
+
63
+ Hopefully it works!
64
+
65
+
66
+ Stuff to do
67
+ ===========
68
+
69
+ * Fill out classifiers in setup.py.
70
+
71
+ * Somehow block updates to the table. Maybe a trigger is the right
72
+ way. Maybe there's a much simpler trick that I'm not aware of.
73
+
74
+ * Create a few views for typical queries.
75
+
76
+ * Test performance with many connected processes and tons of logging
77
+ messages. Make sure that logging doesn't compete with real
78
+ application work for database resources. Is there a way to say
79
+ something like
80
+
81
+ "Hey postgresql, take your time with this stuff, and deal with
82
+ other stuff first!"
83
+
84
+ In other words, a "nice" command for queries.
85
+
86
+ * Allow people to easily write their own SQL to create the logging
87
+ table and to insert records to it. The queries could be returned
88
+ from properties, so people would just need to subclass the PGHandler
89
+ and then redefine those properties.
90
+
91
+ * Write some documentation:
92
+
93
+ * installation
94
+ * typical queries
95
+ * tweak log table columns or indexes
96
+ * discuss performance issues
97
+
98
+ * Set up a readthedocs page for logtopg for that documentation.
99
+
100
+ * Experiment with what happens when the emit(...) function call takes
101
+ a long time. For example, say somebody is logging to a PG server
102
+ across the internet, will calls to log.debug(...) slow down the
103
+ local app? I imagine so.
104
+
105
+ * I just found out that the ltree column type (that I use for logger
106
+ names) can not handle logger names like "dazzle.insert-stuff". That
107
+ dash in there is invalid syntax.
108
+
109
+ I hope there is a way to raise an exception as soon as somebody uses
110
+ an invalid logger name.
111
+
112
+ Or, maybe I need to convert the invalid name to a valid name, by
113
+ maybe substituting any of a set of characters with something else.
114
+
115
+ * Set up table partitioning so that when there are millions or logs,
116
+ they are dealt with sanely.
117
+
118
+ This is a query that shows logs by day and log level::
119
+
120
+ select to_char(date_trunc('day', inserted), 'YYYY-MM-DD'),
121
+ log_level, count(*)
122
+
123
+ from dazzlelogs
124
+
125
+ group by 1, 2
126
+
127
+ order by 1, 2;
128
+
129
+ .. vim: set syntax=rst:
@@ -0,0 +1,265 @@
1
+ # vim: set expandtab ts=4 sw=4 filetype=python fileencoding=utf8:
2
+
3
+
4
+ import logging
5
+ import os
6
+ import subprocess
7
+ import textwrap
8
+ import traceback
9
+ import warnings
10
+
11
+ import psutil
12
+
13
+ import pkg_resources
14
+ import psycopg2
15
+ from psycopg2.extensions import adapt
16
+
17
+ from logtopg.version import __version__
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+ class PGHandler(logging.Handler):
22
+
23
+ def __init__(self, log_table_name,
24
+ database,
25
+ user=None,
26
+ password=None,
27
+ host=None,
28
+ port=5432):
29
+
30
+ logging.Handler.__init__(self)
31
+
32
+ self.log_table_name = log_table_name
33
+
34
+ self.database = database
35
+ self.host = host
36
+ self.user = user
37
+ self.password = password
38
+ self.port = port
39
+
40
+ self.pgconn = None
41
+ self.create_table_sql = None
42
+ self.insert_row_sql = None
43
+
44
+ def check_if_log_table_exists(self):
45
+
46
+ while (True):
47
+ pgconn = self.get_pgconn()
48
+
49
+ cursor = pgconn.cursor()
50
+
51
+ try:
52
+ cursor.execute("""
53
+ SELECT %s::regclass;
54
+ """, [self.log_table_name])
55
+ except psycopg2.ProgrammingError as e:
56
+ return False
57
+ except InterfaceError as ie:
58
+ self.pgconn = None
59
+ continue
60
+
61
+ return True
62
+
63
+ def maybe_create_table(self):
64
+
65
+ if not self.check_if_log_table_exists():
66
+
67
+ create_table_sql = self.get_create_table_sql()
68
+
69
+ out = run_sql_commands(create_table_sql, self.user, self.password,
70
+ self.host, self.port, self.database)
71
+
72
+ log.info("Created log table {0}.".format(self.log_table_name))
73
+
74
+ def get_pgconn(self):
75
+
76
+ if not self.pgconn:
77
+ self.make_pgconn()
78
+
79
+ return self.pgconn
80
+
81
+ def make_pgconn(self):
82
+
83
+ self.pgconn = psycopg2.connect(
84
+ database=self.database,
85
+ host=self.host,
86
+ user=self.user,
87
+ password=self.password,
88
+ port=self.port)
89
+
90
+ self.pgconn.autocommit = True
91
+
92
+ log.info("Just made an autocommitting database connection: {0}.".format(
93
+ self.pgconn))
94
+
95
+ def get_create_table_sql(self):
96
+
97
+ if not self.create_table_sql:
98
+
99
+ s = \
100
+ pkg_resources.resource_string(
101
+ "logtopg", "createtable.sql")\
102
+ .decode("utf-8")\
103
+ .format(self.log_table_name)
104
+
105
+ self.create_table_sql = s.encode("utf-8")
106
+
107
+ return self.create_table_sql
108
+
109
+ def get_insert_row_sql(self):
110
+
111
+ """
112
+ Cache the insert query (with placeholder parameters) in memory
113
+ so that every log.... call doesn't do file IO.
114
+ """
115
+
116
+ if not self.insert_row_sql:
117
+
118
+ self.insert_row_sql = \
119
+ pkg_resources.resource_string(
120
+ "logtopg", "insertrow.sql")\
121
+ .decode("utf-8")\
122
+ .format(self.log_table_name)
123
+
124
+ return self.insert_row_sql
125
+
126
+ def build_d(self, record_dict):
127
+
128
+ d = record_dict
129
+
130
+ # Insert process info
131
+ d['cmd_line'] = " ".join(psutil.Process(os.getpid()).cmdline())
132
+
133
+ # Catch messages that can't be adapted as-is, and convert it to
134
+ # strings
135
+ try:
136
+ d["msg"] = adapt(record_dict["msg"])
137
+
138
+ except Exception as ex:
139
+ d["msg"] = str(record_dict["msg"])
140
+
141
+ return d
142
+
143
+ def emit(self, record):
144
+
145
+ self.format(record)
146
+
147
+ if record.exc_info:
148
+ record.exc_text = logging._defaultFormatter.formatException(record.exc_info)
149
+
150
+ else:
151
+ record.exc_text = ""
152
+
153
+ if isinstance(record.msg, Exception):
154
+ record.msg = str(record.msg)
155
+
156
+ pgconn = self.get_pgconn()
157
+
158
+ self.maybe_create_table()
159
+
160
+ cursor = pgconn.cursor()
161
+
162
+ cursor.execute(
163
+ self.get_insert_row_sql(),
164
+ self.build_d(record.__dict__))
165
+
166
+
167
+ example_dict_config = dict({
168
+
169
+ "loggers": {
170
+ "logtopg": {
171
+ # "handlers": ["pg", "console"],
172
+ "handlers": ["console"],
173
+ "level": "DEBUG",
174
+ }
175
+ },
176
+
177
+ 'handlers': {
178
+ 'pg': {
179
+ 'class': 'logtopg.PGHandler',
180
+ 'level': 'DEBUG',
181
+ 'log_table_name': 'logtopg_logs',
182
+
183
+ "database":"logtopg",
184
+ "host":"localhost",
185
+ "user":"logtopg",
186
+ "password":"l0gt0pg",
187
+ },
188
+
189
+ "console": {
190
+ "class": "logging.StreamHandler",
191
+ "level": "DEBUG",
192
+ "formatter": "consolefmt",
193
+ },
194
+
195
+ },
196
+
197
+ "formatters": {
198
+ "consolefmt":{
199
+ "format": '%(asctime)-22s [%(process)d] %(name)-30s %(lineno)-5d %(levelname)-8s %(message)s',
200
+ },
201
+ },
202
+
203
+ # Any handlers attached to root get log messages from EVERYTHING,
204
+ # like third-party modules, etc.
205
+ 'root': {
206
+ 'handlers': ["pg"],
207
+ 'level': 'DEBUG',
208
+ },
209
+
210
+ 'version': 1,
211
+
212
+ # This is important! Without it, any log instances created before
213
+ # you run logging.config.dictConfig(...) will be disabled, which
214
+ # means all the global log objects in all the various imported files
215
+ # won't do anything.
216
+ 'disable_existing_loggers': False,
217
+ })
218
+
219
+ def run_sql_commands(sql_text, user, password, host, port, database):
220
+
221
+ """
222
+ Run a whole bunch of SQL commands. This is nice when you have a
223
+ script with more than one statement in it.
224
+
225
+ Don't pass me the path to a SQL script file! Instead, give me the
226
+ sql text after you read it in from a file.
227
+ """
228
+
229
+ env = os.environ.copy()
230
+
231
+ if password:
232
+ env['PGPASSWORD'] = password
233
+
234
+ # Feed the sql_text to psql's stdin.
235
+ # http://stackoverflow.com/questions/163542/python-how-do-i-pass-a-string-into-subprocess-popen-using-the-stdin-argument
236
+
237
+ stuff = [
238
+ "psql",
239
+ "--quiet",
240
+ "--no-psqlrc",
241
+ "-d",
242
+ database,
243
+ "--single-transaction",
244
+ ]
245
+
246
+ if user:
247
+ stuff.append("-U")
248
+ stuff.append(user)
249
+
250
+ if host:
251
+ stuff.append("-h")
252
+ stuff.append(host)
253
+
254
+ if port:
255
+ stuff.append("-p")
256
+ stuff.append(str(port))
257
+
258
+ p = subprocess.Popen(
259
+ stuff,
260
+ stdin=subprocess.PIPE,
261
+ env=env)
262
+
263
+ out = p.communicate(input=sql_text)
264
+
265
+ return out
@@ -0,0 +1,40 @@
1
+ create table if not exists {0} (
2
+
3
+ log_id int generated
4
+ by default as identity primary key,
5
+
6
+ created timestamptz,
7
+
8
+ process_id int,
9
+ process_name text,
10
+
11
+ logger_name ltree,
12
+
13
+ path_name text,
14
+ module text,
15
+ file_name text,
16
+
17
+ function_name text,
18
+
19
+ line_number int,
20
+
21
+ log_level text,
22
+ log_level_number int,
23
+
24
+ cmd_line text,
25
+
26
+ message text,
27
+
28
+ exc_info text,
29
+ thread_id bigint,
30
+ thread_name text,
31
+ inserted timestamptz not null default now()
32
+ );
33
+
34
+ create index on {0} (created);
35
+ create index on {0} (inserted);
36
+ create index on {0} (logger_name);
37
+ create index on {0} (process_id);
38
+
39
+ CREATE EXTENSION IF NOT EXISTS pg_trgm;
40
+ CREATE INDEX idx_logs_cmdline ON {0} USING GIN (cmd_line gin_trgm_ops);
@@ -0,0 +1,36 @@
1
+ insert into {0} (
2
+ created,
3
+ process_id,
4
+ process_name,
5
+ logger_name,
6
+ path_name,
7
+ module,
8
+ file_name,
9
+ function_name,
10
+ line_number,
11
+ log_level,
12
+ log_level_number,
13
+ message,
14
+ exc_info,
15
+ cmd_line,
16
+ thread_id,
17
+ thread_name
18
+ ) values (
19
+ to_timestamp(%(created)s),
20
+ %(process)s,
21
+ %(processName)s,
22
+ %(name)s,
23
+ %(pathname)s,
24
+ %(module)s,
25
+ %(filename)s,
26
+ %(funcName)s,
27
+ %(lineno)s,
28
+ %(levelname)s,
29
+ %(levelno)s,
30
+ %(message)s,
31
+ %(exc_text)s,
32
+ %(cmd_line)s,
33
+ %(thread)s,
34
+ %(threadName)s
35
+ );
36
+
File without changes
@@ -0,0 +1,278 @@
1
+ # vim: set expandtab ts=4 sw=4 filetype=python fileencoding=utf8:
2
+
3
+ import logging
4
+ import logging.config
5
+ import os
6
+ import unittest
7
+
8
+ import logtopg
9
+ import psycopg2
10
+
11
+ testing_dict_config = dict({
12
+
13
+ "loggers": {
14
+ "logtopg": {
15
+ "handlers": ["pg"],
16
+ "level": "DEBUG",
17
+ }
18
+ },
19
+
20
+ 'handlers': {
21
+ 'pg': {
22
+ 'class': 'logtopg.PGHandler',
23
+ 'level': 'DEBUG',
24
+ 'log_table_name': 'logtopg_tests',
25
+ "database":"logtopg_tests",
26
+ },
27
+
28
+ "console": {
29
+ "class": "logging.StreamHandler",
30
+ "level": "DEBUG",
31
+ },
32
+
33
+ },
34
+
35
+ # 'root': {
36
+ # 'handlers': ["console"],
37
+ # 'level': 'DEBUG'},
38
+
39
+ 'version': 1,
40
+
41
+ # This is important! Without it, any log instances created before
42
+ # you run logging.config.dictConfig(...) will be disabled.
43
+ 'disable_existing_loggers': False,
44
+ })
45
+
46
+ class Test1(unittest.TestCase):
47
+
48
+ """
49
+ This depends on a real postgresql database. I'll create a table and
50
+ then drop it.
51
+ """
52
+
53
+ d = testing_dict_config
54
+ log_table_name = d["handlers"]["pg"]["log_table_name"]
55
+ database = d["handlers"]["pg"]["database"]
56
+ user = d["handlers"]["pg"].get("user")
57
+ password = d["handlers"]["pg"].get("password")
58
+ host = d["handlers"]["pg"].get("host")
59
+
60
+ db_credentials = dict(
61
+ user=user,
62
+ password=password,
63
+ host=host,
64
+ database=database,
65
+ )
66
+
67
+ def setUp(self):
68
+
69
+ logging.config.dictConfig(self.d)
70
+
71
+ self.log = logging.getLogger("logtopg.tests")
72
+
73
+ self.ltpg = logtopg.PGHandler(
74
+ self.log_table_name,
75
+ self.user,
76
+ self.password,
77
+ self.host,
78
+ self.database)
79
+
80
+ # Make a separate database connection to check results in
81
+ # database.
82
+ self.test_pgconn = psycopg2.connect(**self.db_credentials)
83
+
84
+ def test_1(self):
85
+
86
+ """
87
+ Verify we only read sql files once each.
88
+ """
89
+
90
+ self.assertTrue(self.ltpg.create_table_sql is None)
91
+
92
+ s1 = self.ltpg.get_create_table_sql()
93
+
94
+ self.assertTrue(isinstance(self.ltpg.create_table_sql, bytes))
95
+
96
+ s2 = self.ltpg.get_create_table_sql()
97
+
98
+ self.assertTrue(s1 is s2)
99
+
100
+ self.ltpg.get_insert_row_sql()
101
+
102
+
103
+ def test_2(self):
104
+
105
+ """
106
+ Verify we make only one database connection in an instance.
107
+ """
108
+
109
+ ltpg = logtopg.PGHandler(
110
+ self.log_table_name,
111
+ **self.db_credentials)
112
+
113
+ self.assertTrue(ltpg.pgconn is None)
114
+
115
+ conn1 = ltpg.get_pgconn()
116
+
117
+ self.assertTrue(ltpg.pgconn)
118
+
119
+ conn2 = ltpg.get_pgconn()
120
+
121
+ self.assertTrue(conn1 is conn2)
122
+
123
+
124
+ def test_3(self):
125
+
126
+ """
127
+ Verify we can create the log table.
128
+ """
129
+
130
+ ltpg = logtopg.PGHandler(
131
+ self.log_table_name,
132
+ **self.db_credentials)
133
+
134
+ ltpg.maybe_create_table()
135
+
136
+ # Now, verify the table exists.
137
+ cursor = ltpg.pgconn.cursor()
138
+
139
+ cursor.execute("""
140
+ select exists(
141
+ select *
142
+ from information_schema.tables
143
+ where table_name = %s)
144
+ """, [self.log_table_name])
145
+
146
+ row = cursor.fetchone()
147
+
148
+ self.assertTrue(row[0], )
149
+
150
+ # Subsequent calls to maybe_create_table should be harmless and
151
+ # nearly instantaneous.
152
+ ltpg.maybe_create_table()
153
+ ltpg.maybe_create_table()
154
+ ltpg.maybe_create_table()
155
+
156
+ ltpg.pgconn.rollback()
157
+
158
+
159
+ def test_4(self):
160
+
161
+ """
162
+ Verify log messages are stored in the database.
163
+ """
164
+
165
+ logging.config.dictConfig(self.d)
166
+
167
+ log1 = logging.getLogger("logtopg.tests")
168
+ log2 = logging.getLogger("logtopg.tests")
169
+ log3 = logging.getLogger("logtopg.tests")
170
+ log4 = logging.getLogger("logtopg.tests")
171
+
172
+ log = logging.getLogger("logtopg.tests")
173
+
174
+ log.debug("debug!")
175
+ log.info("info!")
176
+ log.warning("warning!")
177
+ log.error("error!")
178
+ log.critical("critical!")
179
+
180
+ # Now check that those logs are actually in the database.
181
+ cursor = self.test_pgconn.cursor()
182
+
183
+ cursor.execute(
184
+ """
185
+ select message
186
+ from {}
187
+ where process_id = %s
188
+ """.format(self.log_table_name), [os.getpid()])
189
+
190
+ counted_rows = cursor.rowcount
191
+
192
+ self.test_pgconn.rollback()
193
+
194
+ # There should be 7 logs in the database with this process's ID.
195
+ # Those 7 are the five above and the two connection logs.
196
+ self.assertEqual(counted_rows, 7)
197
+
198
+
199
+ def test_5(self):
200
+
201
+ """
202
+ Verify different logger instances use a single database
203
+ connection.
204
+ """
205
+
206
+ logging.config.dictConfig(self.d)
207
+
208
+ log1 = logging.getLogger("logtopg.tests.a")
209
+ log1.debug("trying this guy out")
210
+
211
+ log2 = logging.getLogger("logtopg.tests.b")
212
+ log2.debug("trying this guy out")
213
+
214
+ def test_6(self):
215
+
216
+ """
217
+ Log an exception to the database.
218
+ """
219
+
220
+ logging.config.dictConfig(self.d)
221
+ log = logging.getLogger("logtopg.tests.tests_6")
222
+
223
+ try:
224
+
225
+ 1/0
226
+
227
+ except Exception as ex:
228
+
229
+ log.exception(ex)
230
+
231
+ log.debug(AttributeError("This is a bogus exception"))
232
+
233
+ def test_7(self):
234
+
235
+ """
236
+ Log something that can't be adapted to the database.
237
+ """
238
+
239
+ logging.config.dictConfig(self.d)
240
+ log = logging.getLogger("logtopg.tests.tests_7")
241
+
242
+ class Unadaptable(object):
243
+ pass
244
+
245
+ u = Unadaptable()
246
+
247
+ log.debug("u is a {0}.".format(u))
248
+ log.debug(u)
249
+ log.debug(dict(u=u))
250
+
251
+ def tearDown(self):
252
+
253
+ self.test_pgconn.rollback()
254
+
255
+ cursor = self.test_pgconn.cursor()
256
+
257
+ cursor.execute(
258
+ "drop table if exists {0}".format(
259
+ Test1.log_table_name))
260
+
261
+ self.test_pgconn.commit()
262
+
263
+
264
+ def tearDownModule():
265
+
266
+ pgconn = psycopg2.connect(**Test1.db_credentials)
267
+
268
+ cursor = pgconn.cursor()
269
+
270
+ cursor.execute(
271
+ "drop table if exists {0}".format(
272
+ Test1.log_table_name))
273
+
274
+ pgconn.commit()
275
+
276
+
277
+ if __name__ == "__main__":
278
+ unittest.main()
@@ -0,0 +1,25 @@
1
+ # vim: set expandtab ts=4 sw=4 filetype=python fileencoding=utf8:
2
+
3
+ """
4
+ Do not do anything in this file except define the __version__ variable!
5
+
6
+ The setup.py script reads this version from here during install.
7
+
8
+ In the past, I've defined the version in the setup.py file (A), or
9
+ defined it in the top of the project, like in logtopg/__init__.py (B).
10
+
11
+ Choice A is bad because it isn't easy to fire up a python session and
12
+ then do::
13
+
14
+ >>> import logtopg
15
+ >>> logtopg.__version__ # doctest: +SKIP
16
+
17
+ to look up the version.
18
+
19
+ And choice B is bad because the logtopg/__init__.py file might blow up
20
+ during install because it tries to import a some third-party module that
21
+ hasn't been imported yet.
22
+
23
+ """
24
+
25
+ __version__ = "1.0.0"
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.1
2
+ Name: logtopg
3
+ Version: 1.0.0
4
+ Summary: Python logging handler that stores logs in postgresql
5
+ Home-page: https://github.com/216software/logtopg/
6
+ Author: 216 Software, LLC
7
+ Author-email: info@216software.com
8
+ License: BSD License
9
+ Platform: UNKNOWN
10
+
11
+ UNKNOWN
12
+
@@ -0,0 +1,14 @@
1
+ MANIFEST.in
2
+ README.rst
3
+ setup.py
4
+ logtopg/__init__.py
5
+ logtopg/createtable.sql
6
+ logtopg/insertrow.sql
7
+ logtopg/version.py
8
+ logtopg.egg-info/PKG-INFO
9
+ logtopg.egg-info/SOURCES.txt
10
+ logtopg.egg-info/dependency_links.txt
11
+ logtopg.egg-info/requires.txt
12
+ logtopg.egg-info/top_level.txt
13
+ logtopg/tests/__init__.py
14
+ logtopg/tests/test_logtopg.py
@@ -0,0 +1 @@
1
+ psycopg2
@@ -0,0 +1 @@
1
+ logtopg
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
logtopg-1.0.0/setup.py ADDED
@@ -0,0 +1,33 @@
1
+ # vim: set expandtab ts=4 sw=4 filetype=python fileencoding=utf8:
2
+
3
+ import sys
4
+
5
+ if sys.version_info < (2, 7):
6
+ raise Exception("sorry, this needs at least python 2.7!")
7
+
8
+ # Read __version__ from version.py
9
+ with open("logtopg/version.py") as f:
10
+ exec(f.read())
11
+
12
+ from setuptools import find_packages, setup
13
+
14
+ setup(
15
+ # name="LogToPG",
16
+ name="logtopg",
17
+ version=__version__,
18
+ description="Python logging handler that stores logs in postgresql",
19
+ url="https://github.com/216software/logtopg/",
20
+ packages=find_packages(),
21
+
22
+ author="216 Software, LLC",
23
+ author_email="info@216software.com",
24
+ license="BSD License",
25
+ include_package_data=True,
26
+
27
+ install_requires=[
28
+ 'psycopg2',
29
+ ],
30
+
31
+ test_suite="nose.collector",
32
+ use_2to3=True,
33
+ )