Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tool udplog-send to log from the command line. #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,9 @@ def read(fname):
'python-dateutil',
],
extras_require=EXTRAS,
entry_points={
'console_scripts': [
"udplog-send = udplog.cli:send",
]
},
)
99 changes: 99 additions & 0 deletions udplog/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# -*- test-case-name: udplog.test.test_cli -*-
# Copyright (c) Rackspace US, Inc.
# See LICENSE for details.

"""
Command line script entry points.
"""

from __future__ import division, absolute_import

import socket
import sys
from os import path

import simplejson

from twisted.python import usage

from udplog import udplog

LEVEL_NAMES = {'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG'}

class SendOptions(usage.Options):
synopsis = ("udplog-send --category=category [options] message ...\n"
"udplog-send --extra=json [options]")
optParameters = [
['category', 'c', 'udplog_unknown',
"The type of log event"],
['level', 'l', 'INFO',
"The log level"],
['appname', 'a', 'udplog-send',
"The name of the application emitting the log event"],
['extra', 'e', None,
"A JSON object (dictionary) of event fields to be merged into the "
"emitted event"],
['udplog-host', 'h', udplog.DEFAULT_HOST,
"The hostname of the UDPLog server"],
['udplog-port', 'p', udplog.DEFAULT_PORT,
"The portname of the UDPLog server"],
]

def parseArgs(self, *args):
self['message'] = (" ".join(args)).decode('utf-8')


def postOptions(self):
if self['extra']:
try:
self['extra'] = simplejson.loads(self['extra'])
except Exception, exc:
raise usage.UsageError("Could not parse extra fields as a "
"JSON object: %s" % (exc,))
else:
self['extra'] = {}

if self['extra'].get('category'):
self['category'] = self['extra']['category']

if self['extra'].get('message'):
self['message'] = self['extra']['message']

if self['extra'].get('logLevel'):
self['level'] = self['extra']['logLevel']

if not self['category']:
raise usage.UsageError("No log category provided")

if not self['message']:
raise usage.UsageError("No log message provided")

self['level'] = self['level'].upper()
if self['level'] not in LEVEL_NAMES:
raise usage.UsageError("Log level must be one of " +
", ".join(LEVEL_NAMES))

def send(options=None):
config = SendOptions()
try:
config.parseOptions(options)
except usage.UsageError, exc:
name = path.basename(sys.argv[0])
print "%s: %s" % (name, exc)
print "%s: Try --help for usage details." % (name,)
return 1

eventDict = {
'message': config['message'],
'logLevel': config['level'],
'appname': config['appname'],
}

eventDict.update(config['extra'])

eventDict.setdefault('hostname', socket.gethostname())

logger = udplog.UDPLogger()
logger.log(config['category'], eventDict)

return 0
100 changes: 100 additions & 0 deletions udplog/test/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright (c) Rackspace US, Inc.
# See LICENSE for details.
"""
Tests for L{udplog.cli}.
"""

from __future__ import division, absolute_import

from twisted.python import usage
from twisted.trial.unittest import TestCase

from udplog.cli import SendOptions

class SendOptionsTests(TestCase):
"""
Tests for L{udplog.cli.SendOptions}.
"""

def test_message(self):
"""
Non-option arguments are the log message.
"""
options = ["foo"]
config = SendOptions()
config.parseOptions(options)
self.assertEqual('foo', config['message'])


def test_messageMultiple(self):
"""
Multiple non-option arguments are joined by spaces.
"""
options = ["foo", "bar"]
config = SendOptions()
config.parseOptions(options)
self.assertEqual('foo bar', config['message'])


def test_messageEmpty(self):
"""
An empty log message raises an exception.
"""
options = [""]
config = SendOptions()
exc = self.assertRaises(usage.UsageError, config.parseOptions,
options)
self.assertEqual("No log message provided", str(exc))


def test_messageMissing(self):
"""
Not providing a message raises an exception.
"""
options = []
config = SendOptions()
exc = self.assertRaises(usage.UsageError, config.parseOptions,
options)
self.assertEqual("No log message provided", str(exc))


def test_category(self):
"""
The log category is parsed.
"""
options = ["--category=foo", "bar"]
config = SendOptions()
config.parseOptions(options)
self.assertEqual("foo", config['category'])


def test_categoryEmpty(self):
"""
An empty log category raises an exception.
"""
options = ["--category="]
config = SendOptions()
exc = self.assertRaises(usage.UsageError, config.parseOptions,
options)
self.assertEqual("No log category provided", str(exc))


def test_extra(self):
"""
Extra fields as JSON are merged in.
"""
options = ["""--extra={"foo": "baz"}""", "bar"]
config = SendOptions()
config.parseOptions(options)
self.assertEqual("baz", config['extra']['foo'])


def test_extraMessage(self):
"""
The log message may also be provided in the extra fields.
"""
options = ["""--extra={"message": "bar"}"""]
config = SendOptions()
config.parseOptions(options)
self.assertEqual("bar", config['message'])