Skip to content

Commit 0f35064

Browse files
authored
Merge pull request #1 from mathom/master
Repo sync
2 parents 5a13ad0 + 97ccaf5 commit 0f35064

20 files changed

+1524
-3
lines changed

.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
*.py[co]
2+
3+
# Packages
4+
*.egg
5+
*.egg-info
6+
dist
7+
build
8+
eggs
9+
parts
10+
var
11+
sdist
12+
develop-eggs
13+
.installed.cfg
14+
15+
# Installer logs
16+
pip-log.txt
17+
18+
# Unit test / coverage reports
19+
.coverage
20+
.tox
21+
22+
#Translations
23+
*.mo
24+
25+
#Mr Developer
26+
.mr.developer.cfg
27+
28+
# virutalenvs
29+
.venv
30+
31+
# debug stuff
32+
test.py

LICENSE.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO

README.md

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,99 @@
1-
stackify-api-python
2-
===================
3-
41
Stackify API for Python
2+
=======
3+
4+
[Stackify](https://stackify.com) support for Python programs.
5+
6+
```python
7+
import stackify
8+
9+
logger = stackify.getLogger()
10+
11+
try:
12+
"Make it so, #" + 1
13+
except:
14+
logger.exception("Can't add strings and numbers")
15+
```
16+
17+
## Installation
18+
stackify-python can be installed through pip:
19+
```bash
20+
$ pip install -U stackify
21+
```
22+
23+
You can also check out the repository and install with setuptools:
24+
```bash
25+
$ ./setup.py install
26+
```
27+
28+
## Configuration
29+
Your Stackify setup information can be provided via environment variables. For example:
30+
```bash
31+
export STACKIFY_APPLICATION=MyApp
32+
export STACKIFY_ENVIRONMENT=Dev
33+
export STACKIFY_API_KEY=******
34+
```
35+
36+
These options can also be provided in your code:
37+
```python
38+
import stackify
39+
40+
logger = stackify.getLogger(application="MyApp", environment="Dev", api_key=******)
41+
logger.warning('Something happened')
42+
```
43+
44+
## Usage
45+
46+
stackify-python handles uploads in batches of 100 messages at a time on another thread.
47+
When your program exits, it will shut the thread down and upload the remaining messages.
48+
49+
Stackify can store extra data along with your log message:
50+
```python
51+
import stackify
52+
53+
logger = stackify.getLogger()
54+
55+
try:
56+
user_string = raw_input("Enter a number: ")
57+
print("You entered", int(user_string))
58+
except ValueError:
59+
logger.exception('Bad input', extra={'user entered': user_string})
60+
```
61+
62+
You can also name your logger instead of using the automatically generated one:
63+
```python
64+
import stackify
65+
66+
logger = stackify.getLogger('mymodule.myfile')
67+
```
68+
69+
## Internal Logger
70+
71+
This library has an internal logger it uses for debugging and messaging.
72+
For example, if you want to enable debug messages:
73+
```python
74+
import logging
75+
76+
logging.getLogger('stackify').setLevel(logging.DEBUG)
77+
```
78+
79+
By default, it will enable the default logging settings via `logging.basicConfig()`
80+
and print `WARNING` level messages and above. If you wish to set everything up yourself,
81+
just pass `basic_config=False` in `getLogger`:
82+
```python
83+
import stackify
84+
85+
logger = stackify.getLogger(basic_config=False)
86+
```
87+
88+
## Testing
89+
Run the test suite with setuptools:
90+
```bash
91+
$ ./setup.py test
92+
```
93+
94+
You can obtain a coverage report with nose:
95+
```bash
96+
$ ./setup nosetests --with-coverage --cover-package=stackify
97+
```
98+
You might need to install the `nose` and `coverage` packages.
99+

setup.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python
2+
from setuptools import setup
3+
import re
4+
import ast
5+
6+
try:
7+
from pypandoc import convert
8+
read_md = lambda f: convert(f, 'rst')
9+
except ImportError:
10+
print('warning: pypandoc module not found, could not convert Markdown to RST')
11+
read_md = lambda f: open(f).read()
12+
13+
version_re = re.compile(r'__version__\s+=\s+(.*)')
14+
15+
with open('stackify/__init__.py') as f:
16+
f = f.read()
17+
version = ast.literal_eval(version_re.search(f).group(1))
18+
19+
setup(
20+
name = 'stackify',
21+
version = version,
22+
author = 'Matthew Thompson',
23+
author_email = 'chameleonator@gmail.com',
24+
packages = ['stackify'],
25+
url = 'https://github.com/stackify/stackify-api-python',
26+
license = open('LICENSE.txt').readline(),
27+
description = 'Stackify API for Python',
28+
long_description = read_md('README.md'),
29+
download_url = 'https://github.com/stackify/stackify-api-python/tarball/0.0.1',
30+
keywords = ['logging', 'stackify', 'exception'],
31+
classifiers=["Programming Language :: Python"],
32+
install_requires = [
33+
'retrying>=1.2.3',
34+
'requests>=2.4.1'
35+
],
36+
test_suite = 'tests',
37+
tests_requires = [
38+
'mock>=1.0.1',
39+
'nose==1.3.4'
40+
]
41+
)
42+

stackify/__init__.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
"""
2+
Stackify Python API
3+
"""
4+
5+
__version__ = '0.0.1'
6+
7+
8+
API_URL = 'https://api.stackify.com'
9+
10+
READ_TIMEOUT = 5000
11+
12+
MAX_BATCH = 100
13+
14+
QUEUE_SIZE = 1000
15+
16+
import logging
17+
import inspect
18+
import atexit
19+
20+
DEFAULT_LEVEL = logging.ERROR
21+
22+
LOGGING_LEVELS = {
23+
logging.CRITICAL: 'CRITICAL',
24+
logging.ERROR: 'ERROR',
25+
logging.WARNING: 'WARNING',
26+
logging.INFO: 'INFO',
27+
logging.DEBUG: 'DEBUG',
28+
logging.NOTSET: 'NOTSET'
29+
}
30+
31+
32+
class NullHandler(logging.Handler):
33+
def emit(self, record):
34+
pass
35+
36+
logging.getLogger(__name__).addHandler(NullHandler())
37+
38+
39+
from stackify.application import ApiConfiguration
40+
from stackify.http import HTTPClient
41+
42+
from stackify.handler import StackifyHandler
43+
44+
45+
def getLogger(name=None, auto_shutdown=True, basic_config=True, **kwargs):
46+
'''Get a logger and attach a StackifyHandler if needed.
47+
48+
You can pass this function keyword arguments for Stackify configuration.
49+
If they are omitted you can specify them through environment variables:
50+
* STACKIFY_API_KEY
51+
* STACKIFY_APPLICATION
52+
* STACKIFY_ENVIRONMENT
53+
* STACKIFY_API_URL
54+
55+
Args:
56+
name: The name of the logger (or None to automatically make one)
57+
auto_shutdown: Register an atexit hook to shut down logging
58+
basic_config: Set up with logging.basicConfig() for regular logging
59+
60+
Optional Args:
61+
api_key: Your Stackify API key
62+
application: The name of your Stackify application
63+
environment: The Stackfiy environment to log to
64+
api_url: An optional API url if required
65+
66+
Returns:
67+
A logger instance with Stackify handler and listener attached.
68+
'''
69+
if basic_config:
70+
logging.basicConfig()
71+
72+
if not name:
73+
name = getCallerName(2)
74+
75+
logger = logging.getLogger(name)
76+
77+
if not [isinstance(x, StackifyHandler) for x in logger.handlers]:
78+
internal_logger = logging.getLogger(__name__)
79+
internal_logger.debug('Creating handler for logger %s', name)
80+
handler = StackifyHandler(**kwargs)
81+
logger.addHandler(handler)
82+
83+
if auto_shutdown:
84+
internal_logger.debug('Registering atexit callback')
85+
atexit.register(stopLogging, logger)
86+
87+
if logger.getEffectiveLevel() == logging.NOTSET:
88+
logger.setLevel(DEFAULT_LEVEL)
89+
90+
handler.listener.start()
91+
92+
return logger
93+
94+
95+
def stopLogging(logger):
96+
'''Stop logging on the Stackify handler.
97+
98+
Shut down the StackifyHandler on a given logger. This will block
99+
and wait for the queue to finish uploading.
100+
'''
101+
internal_logger = logging.getLogger(__name__)
102+
internal_logger.debug('Shutting down all handlers')
103+
for handler in getHandlers(logger):
104+
handler.listener.stop()
105+
106+
107+
def getCallerName(levels=1):
108+
'''Gets the name of the module calling this function'''
109+
try:
110+
frame = inspect.stack()[levels]
111+
module = inspect.getmodule(frame[0])
112+
name = module.__name__
113+
except IndexError:
114+
name = 'stackify-python-unknown'
115+
return name
116+
117+
118+
def getHandlers(logger):
119+
'''Return the StackifyHandlers on a given logger'''
120+
return [x for x in logger.handlers if isinstance(x, StackifyHandler)]

stackify/application.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import socket
2+
import os
3+
4+
from stackify import API_URL
5+
from stackify.formats import JSONObject
6+
7+
8+
class EnvironmentDetail(JSONObject):
9+
def __init__(self, api_config):
10+
self.deviceName = socket.gethostname()
11+
self.appLocation = os.getcwd()
12+
self.configuredAppName = api_config.application
13+
self.configuredEnvironmentName = api_config.environment
14+
15+
16+
class ApiConfiguration:
17+
def __init__(self, api_key, application, environment, api_url=API_URL):
18+
self.api_key = api_key
19+
self.api_url = api_url
20+
self.application = application
21+
self.environment = environment
22+
23+
24+
def arg_or_env(name, args, default=None):
25+
env_name = 'STACKIFY_{0}'.format(name.upper())
26+
try:
27+
value = args.get(name)
28+
if not value:
29+
value = os.environ[env_name]
30+
return value
31+
except KeyError:
32+
if default:
33+
return default
34+
else:
35+
raise NameError('You must specify the keyword argument {0} or '
36+
'environment variable {1}'.format(name, env_name))
37+
38+
39+
def get_configuration(**kwargs):
40+
return ApiConfiguration(
41+
application=arg_or_env('application', kwargs),
42+
environment=arg_or_env('environment', kwargs),
43+
api_key=arg_or_env('api_key', kwargs),
44+
api_url=arg_or_env('api_url', kwargs, API_URL))

0 commit comments

Comments
 (0)